使 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.

目前活页夹假定您是从查询字符串进行绑定。我还没有研究使它更通用有多容易。