使 NodaTime.Instant 在 AspNetCore 中作为 query/path/form 参数需要哪些配置?
What configurations required to make NodaTime.Instant work as query/path/form parameter in AspNetCore?
我正尝试在我的 AspNetCore 项目中使用 NodaTime
。
JSON 序列化工作正常。但是我无法为 form/query/path
params.As 的模型绑定工作 我在类似的问题中看到 NodaTime
中没有 TypeConverter 实现。也许有一些解决方法?
.csproj
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp2.2</TargetFramework>
<AspNetCoreHostingModel>InProcess</AspNetCoreHostingModel>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="Microsoft.AspNetCore.Razor.Design" Version="2.2.0" PrivateAssets="All" />
<PackageReference Include="NodaTime" Version="2.4.6" />
<PackageReference Include="NodaTime.Serialization.JsonNet" Version="2.2.0" />
</ItemGroup>
</Project>
我的启动服务配置:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
.AddJsonOptions(o => o.SerializerSettings.ConfigureForNodaTime(DateTimeZoneProviders.Tzdb));
}
Api控制器重现问题:
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
// works
[HttpGet("Serialize")]
public ActionResult Get()
{
return Ok(new { Now = SystemClock.Instance.GetCurrentInstant() });
}
// error
[HttpGet("Ping")]
public ActionResult Get([FromQuery] Instant t)
{
return Ok(new { Pong = t });
}
}
URL:
http://localhost:55555/api/values/ping?t=2019-09-02T06:55:52.7077495Z
堆栈跟踪:
System.InvalidOperationException: Could not create an instance of type
'NodaTime.Instant'. Model bound complex types must not be abstract or
value types and must have a parameterless constructor. Alternatively,
give the 't' parameter a non-null default value. at
Microsoft.AspNetCore.Mvc.ModelBinding.Binders.ComplexTypeModelBinder.CreateModel(ModelBindingContext
bindingContext) at
Microsoft.AspNetCore.Mvc.ModelBinding.Binders.ComplexTypeModelBinder.BindModelCoreAsync(ModelBindingContext
bindingContext) at
Microsoft.AspNetCore.Mvc.ModelBinding.ParameterBinder.BindModelAsync(ActionContext
actionContext, IModelBinder modelBinder, IValueProvider valueProvider,
ParameterDescriptor parameter, ModelMetadata metadata, Object value)
at
Microsoft.AspNetCore.Mvc.Internal.ControllerBinderDelegateProvider.<>c__DisplayClass0_0.<g__Bind|0>d.MoveNext()
--- End of stack trace from previous location where exception was thrown --- at
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeInnerFilterAsync()
at
Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeNextResourceFilter()
at
Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Rethrow(ResourceExecutedContext
context) at
Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Next(State& next,
Scope& scope, Object& state, Boolean& isCompleted) at
Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeFilterPipelineAsync()
at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeAsync()
at Microsoft.AspNetCore.Routing.EndpointMiddleware.Invoke(HttpContext
httpContext) at
Microsoft.AspNetCore.Routing.EndpointRoutingMiddleware.Invoke(HttpContext
httpContext) at
Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext
context)
我远不是 ASP.NET 核心模型绑定器方面的专家,但我已经设法破解了 a 方法,如下所示。
我们现在在 NodaTime 3.0 中支持 TypeConverter,它存在于 3.0.0-beta01 版本中。我不 期待 从现在到 3.0 GA 版本之间的重大变化,但我不想做任何保证 :) 虽然实现代码非常稳定 - 我不会担心那方面。
要手动绑定,您可以创建一个模型绑定器提供程序和一个模型绑定器:
public class InstantModelBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context) =>
context.Metadata.ModelType == typeof(Instant) ? new InstantModelBinder(context.Metadata.ParameterName) : null;
}
public class InstantModelBinder : IModelBinder
{
private readonly string parameterName;
public InstantModelBinder(string parameterName)
{
this.parameterName = parameterName;
}
public Task BindModelAsync(ModelBindingContext bindingContext)
{
var text = bindingContext.ActionContext.HttpContext.Request.Query[parameterName];
if (text.Count != 1)
{
bindingContext.Result = ModelBindingResult.Failed();
}
else
{
var result = InstantPattern.ExtendedIso.Parse(text);
bindingContext.Result = result.Success
? ModelBindingResult.Success(result.Value)
: ModelBindingResult.Failed();
}
return Task.CompletedTask;
}
}
然后在 MVC 选项中将提供者注册为第一个提供者:
services.AddMvc(options => options.ModelBinderProviders.Insert(0, new InstantModelBinderProvider()))
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
.AddJsonOptions(o => o.SerializerSettings.ConfigureForNodaTime(DateTimeZoneProviders.Tzdb));
如果您需要为多个 Noda Time 类型执行此操作,我希望您能够使用一个提供程序来根据 ModelBinderProviderContext.Metadata.ModelType
.
目前活页夹假定您是从查询字符串进行绑定。我还没有研究使它更通用有多容易。
我正尝试在我的 AspNetCore 项目中使用 NodaTime
。
JSON 序列化工作正常。但是我无法为 form/query/path
params.As 的模型绑定工作 我在类似的问题中看到 NodaTime
中没有 TypeConverter 实现。也许有一些解决方法?
.csproj
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp2.2</TargetFramework>
<AspNetCoreHostingModel>InProcess</AspNetCoreHostingModel>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="Microsoft.AspNetCore.Razor.Design" Version="2.2.0" PrivateAssets="All" />
<PackageReference Include="NodaTime" Version="2.4.6" />
<PackageReference Include="NodaTime.Serialization.JsonNet" Version="2.2.0" />
</ItemGroup>
</Project>
我的启动服务配置:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
.AddJsonOptions(o => o.SerializerSettings.ConfigureForNodaTime(DateTimeZoneProviders.Tzdb));
}
Api控制器重现问题:
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
// works
[HttpGet("Serialize")]
public ActionResult Get()
{
return Ok(new { Now = SystemClock.Instance.GetCurrentInstant() });
}
// error
[HttpGet("Ping")]
public ActionResult Get([FromQuery] Instant t)
{
return Ok(new { Pong = t });
}
}
URL:
http://localhost:55555/api/values/ping?t=2019-09-02T06:55:52.7077495Z
堆栈跟踪:
System.InvalidOperationException: Could not create an instance of type 'NodaTime.Instant'. Model bound complex types must not be abstract or value types and must have a parameterless constructor. Alternatively, give the 't' parameter a non-null default value. at Microsoft.AspNetCore.Mvc.ModelBinding.Binders.ComplexTypeModelBinder.CreateModel(ModelBindingContext bindingContext) at Microsoft.AspNetCore.Mvc.ModelBinding.Binders.ComplexTypeModelBinder.BindModelCoreAsync(ModelBindingContext bindingContext) at Microsoft.AspNetCore.Mvc.ModelBinding.ParameterBinder.BindModelAsync(ActionContext actionContext, IModelBinder modelBinder, IValueProvider valueProvider, ParameterDescriptor parameter, ModelMetadata metadata, Object value)
at Microsoft.AspNetCore.Mvc.Internal.ControllerBinderDelegateProvider.<>c__DisplayClass0_0.<g__Bind|0>d.MoveNext() --- End of stack trace from previous location where exception was thrown --- at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeInnerFilterAsync() at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeNextResourceFilter() at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Rethrow(ResourceExecutedContext context) at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted) at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeFilterPipelineAsync() at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeAsync()
at Microsoft.AspNetCore.Routing.EndpointMiddleware.Invoke(HttpContext httpContext) at Microsoft.AspNetCore.Routing.EndpointRoutingMiddleware.Invoke(HttpContext httpContext) at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
我远不是 ASP.NET 核心模型绑定器方面的专家,但我已经设法破解了 a 方法,如下所示。
我们现在在 NodaTime 3.0 中支持 TypeConverter,它存在于 3.0.0-beta01 版本中。我不 期待 从现在到 3.0 GA 版本之间的重大变化,但我不想做任何保证 :) 虽然实现代码非常稳定 - 我不会担心那方面。
要手动绑定,您可以创建一个模型绑定器提供程序和一个模型绑定器:
public class InstantModelBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context) =>
context.Metadata.ModelType == typeof(Instant) ? new InstantModelBinder(context.Metadata.ParameterName) : null;
}
public class InstantModelBinder : IModelBinder
{
private readonly string parameterName;
public InstantModelBinder(string parameterName)
{
this.parameterName = parameterName;
}
public Task BindModelAsync(ModelBindingContext bindingContext)
{
var text = bindingContext.ActionContext.HttpContext.Request.Query[parameterName];
if (text.Count != 1)
{
bindingContext.Result = ModelBindingResult.Failed();
}
else
{
var result = InstantPattern.ExtendedIso.Parse(text);
bindingContext.Result = result.Success
? ModelBindingResult.Success(result.Value)
: ModelBindingResult.Failed();
}
return Task.CompletedTask;
}
}
然后在 MVC 选项中将提供者注册为第一个提供者:
services.AddMvc(options => options.ModelBinderProviders.Insert(0, new InstantModelBinderProvider()))
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
.AddJsonOptions(o => o.SerializerSettings.ConfigureForNodaTime(DateTimeZoneProviders.Tzdb));
如果您需要为多个 Noda Time 类型执行此操作,我希望您能够使用一个提供程序来根据 ModelBinderProviderContext.Metadata.ModelType
.
目前活页夹假定您是从查询字符串进行绑定。我还没有研究使它更通用有多容易。