Skip to content

Commit

Permalink
Merge pull request #2151 from abpframework/maiming/AbpDateTimeModelBi…
Browse files Browse the repository at this point in the history
…nder

Implement AbpDateTimeModelBinderProvider & AbpDateTimeModelBinder
  • Loading branch information
hikalkan authored Nov 25, 2019
2 parents 0c82368 + 357be9a commit cfb714e
Show file tree
Hide file tree
Showing 5 changed files with 259 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using Volo.Abp.AspNetCore.Mvc.Conventions;
using Volo.Abp.AspNetCore.Mvc.ExceptionHandling;
using Volo.Abp.AspNetCore.Mvc.Features;
using Volo.Abp.AspNetCore.Mvc.ModelBinding;
using Volo.Abp.AspNetCore.Mvc.Uow;
using Volo.Abp.AspNetCore.Mvc.Validation;

Expand Down Expand Up @@ -35,7 +36,7 @@ private static void AddFilters(MvcOptions options)

private static void AddModelBinders(MvcOptions options)
{
//options.ModelBinderProviders.Add(new AbpDateTimeModelBinderProvider());
options.ModelBinderProviders.Insert(0, new AbpDateTimeModelBinderProvider());
}

private static void AddMetadataProviders(MvcOptions options, IServiceCollection services)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Volo.Abp.Timing;

namespace Volo.Abp.AspNetCore.Mvc.ModelBinding
{
public class AbpDateTimeModelBinder : IModelBinder
{
private readonly Type _type;
private readonly SimpleTypeModelBinder _simpleTypeModelBinder;
private readonly IClock _clock;

public AbpDateTimeModelBinder(ModelBinderProviderContext context)
{
_type = context.Metadata.ModelType;
_clock = context.Services.GetRequiredService<IClock>();
_simpleTypeModelBinder = new SimpleTypeModelBinder(context.Metadata.ModelType,
context.Services.GetRequiredService<ILoggerFactory>());
}

public async Task BindModelAsync(ModelBindingContext bindingContext)
{
await _simpleTypeModelBinder.BindModelAsync(bindingContext);

if (!bindingContext.Result.IsModelSet)
{
return;
}

if (_type == typeof(DateTime))
{
var dateTime = (DateTime) bindingContext.Result.Model;
bindingContext.Result = ModelBindingResult.Success(_clock.Normalize(dateTime));
}
else
{
var dateTime = (DateTime?) bindingContext.Result.Model;
if (dateTime != null)
{
bindingContext.Result = ModelBindingResult.Success(_clock.Normalize(dateTime.Value));
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using System;
using System.Linq;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
using Volo.Abp.Timing;

namespace Volo.Abp.AspNetCore.Mvc.ModelBinding
{
public class AbpDateTimeModelBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context.Metadata.ModelType != typeof(DateTime) &&
context.Metadata.ModelType != typeof(DateTime?))
{
return null;
}

if (context.Metadata.ContainerType == null)
{
if (context.Metadata is DefaultModelMetadata defaultModelMetadata &&
defaultModelMetadata.Attributes.Attributes.All(x => x.GetType() != typeof(DisableDateTimeNormalizationAttribute)))
{
return new AbpDateTimeModelBinder(context);
}
}
else
{
var dateNormalizationDisabledForClass =
context.Metadata.ContainerType.IsDefined(typeof(DisableDateTimeNormalizationAttribute), true);

var dateNormalizationDisabledForProperty = context.Metadata.ContainerType
.GetProperty(context.Metadata.PropertyName)
?.IsDefined(typeof(DisableDateTimeNormalizationAttribute), true);

if (!dateNormalizationDisabledForClass &&
dateNormalizationDisabledForProperty != null &&
!dateNormalizationDisabledForProperty.Value)
{
return new AbpDateTimeModelBinder(context);
}
}

return null;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
using System;
using Microsoft.AspNetCore.Mvc;
using Volo.Abp.Timing;

namespace Volo.Abp.AspNetCore.Mvc.ModelBinding
{
[Route("api/model-Binding-test")]
public class ModelBindingController : AbpController
{
[HttpGet("DateTimeKind")]
public string DateTimeKind(DateTime input)
{
return input.Kind.ToString().ToLower();
}

[HttpGet("NullableDateTimeKind")]
public string NullableDateTimeKind(DateTime? input)
{
return input.Value.Kind.ToString().ToLower();
}

[HttpGet("DisableDateTimeNormalizationDateTimeKind")]
public string DisableDateTimeNormalizationDateTimeKind([DisableDateTimeNormalization]DateTime input)
{
return input.Kind.ToString().ToLower();
}

[HttpGet("DisableDateTimeNormalizationNullableDateTimeKind")]
public string DisableDateTimeNormalizationNullableDateTimeKind([DisableDateTimeNormalization]DateTime? input)
{
return input.Value.Kind.ToString().ToLower();
}

[HttpGet("ComplexTypeDateTimeKind")]
public string ComplexTypeDateTimeKind(GetDateTimeKindModel input)
{
return input.Time1.Kind.ToString().ToLower() + "_" +
input.Time2.Kind.ToString().ToLower() + "_" +
input.Time3.Value.Kind.ToString().ToLower() + "_" +
input.InnerModel.Time4.Kind.ToString().ToLower();
}
}

public class GetDateTimeKindModel
{
[DisableDateTimeNormalization]
public DateTime Time1 { get; set; }

public DateTime Time2 { get; set; }

public DateTime? Time3 { get; set; }

public GetDateTimeKindInnerModel InnerModel { get; set; }

[DisableDateTimeNormalization]
public class GetDateTimeKindInnerModel
{
public DateTime Time4 { get; set; }
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
using System;
using System.Net;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Shouldly;
using Volo.Abp.Timing;
using Xunit;

namespace Volo.Abp.AspNetCore.Mvc.ModelBinding
{
public abstract class ModelBindingController_Tests : AspNetCoreMvcTestBase
{
protected DateTimeKind DateTimeKind { get; set; }

protected override void ConfigureServices(HostBuilderContext context, IServiceCollection services)
{
services.Configure<AbpClockOptions>(x => x.Kind = DateTimeKind);
}

[Fact]
public async Task DateTimeKind_Test()
{
var response = await Client.GetAsync("/api/model-Binding-test/DateTimeKind?input=2010-01-01T00:00:00Z");

response.StatusCode.ShouldBe(HttpStatusCode.OK);
var resultAsString = await response.Content.ReadAsStringAsync();
resultAsString.ShouldBe(DateTimeKind.ToString().ToLower());
}

[Fact]
public async Task NullableDateTimeKind_Test()
{
var response =
await Client.GetAsync("/api/model-Binding-test/NullableDateTimeKind?input=2010-01-01T00:00:00Z");

response.StatusCode.ShouldBe(HttpStatusCode.OK);
var resultAsString = await response.Content.ReadAsStringAsync();
resultAsString.ShouldBe(DateTimeKind.ToString().ToLower());
}

[Fact]
public async Task DisableDateTimeNormalizationDateTimeKind_Test()
{
var response =
await Client.GetAsync(
"/api/model-Binding-test/DisableDateTimeNormalizationDateTimeKind?input=2010-01-01T00:00:00Z");

response.StatusCode.ShouldBe(HttpStatusCode.OK);
var resultAsString = await response.Content.ReadAsStringAsync();
//Time parameter(2010-01-01T00:00:00Z) with time zone information, so the default Kind is Local.
resultAsString.ShouldBe(DateTimeKind.Local.ToString().ToLower());
}

[Fact]
public async Task DisableDateTimeNormalizationNullableDateTimeKind_Test()
{
var response =
await Client.GetAsync(
"/api/model-Binding-test/DisableDateTimeNormalizationNullableDateTimeKind?input=2010-01-01T00:00:00Z");

response.StatusCode.ShouldBe(HttpStatusCode.OK);
var resultAsString = await response.Content.ReadAsStringAsync();
//Time parameter(2010-01-01T00:00:00Z) with time zone information, so the default Kind is Local.
resultAsString.ShouldBe(DateTimeKind.Local.ToString().ToLower());
}

[Fact]
public async Task ComplexTypeDateTimeKind_Test()
{
var response = await Client.GetAsync("/api/model-Binding-test/ComplexTypeDateTimeKind?" +
"Time1=2010-01-01T00:00:00Z&" +
"Time2=2010-01-01T00:00:00Z&" +
"Time3=2010-01-01T00:00:00Z&" +
"InnerModel.Time4=2010-01-01T00:00:00Z");

response.StatusCode.ShouldBe(HttpStatusCode.OK);
var resultAsString = await response.Content.ReadAsStringAsync();
//Time parameter(2010-01-01T00:00:00Z) with time zone information, so the default Kind is Local.
resultAsString.ShouldBe(
$"local_{DateTimeKind.ToString().ToLower()}_{DateTimeKind.ToString().ToLower()}_local");
}
}

public class ModelBindingController_Utc_Tests : ModelBindingController_Tests
{
public ModelBindingController_Utc_Tests()
{
DateTimeKind = DateTimeKind.Utc;
}
}

public class ModelBindingController_Local_Tests : ModelBindingController_Tests
{
public ModelBindingController_Local_Tests()
{
DateTimeKind = DateTimeKind.Local;
}
}
}

0 comments on commit cfb714e

Please sign in to comment.