Model绑定自定义模型绑定类型的可选数组

时间:2017-12-04 09:39:25

标签: asp.net-core asp.net-core-mvc model-binding

我坚持在ASP.NET核心控制器中绑定可选数组。该数组包含自定义类型的元素。此类型的单个元素与自定义模型绑定器绑定并在其中进行验证。

此处的示例回购:https://github.com/MarcusKohnert/OptionalArrayModelBinding

在示例测试项目中,我只有三个测试中的两个测试: https://github.com/MarcusKohnert/OptionalArrayModelBinding/blob/master/OptionalArrayModelBindingTest/TestOptionalArrayCustomModelBinder.cs

public class TestOptionalArrayCustomModelBinder
{
    private readonly TestServer server;
    private readonly HttpClient client;

    public TestOptionalArrayCustomModelBinder()
    {
        server = new TestServer(new WebHostBuilder().UseStartup<Startup>());

        client = server.CreateClient();
    }

    [Fact]
    public async Task SuccessWithoutProvidingIds()
    {
        var response = await client.GetAsync("/api/values");

        Assert.Equal(System.Net.HttpStatusCode.OK, response.StatusCode);
    }

    [Fact]
    public async Task SuccessWithValidIds()
    {
        var response = await client.GetAsync("/api/values?ids=aaa001&ids=bbb002");

        Assert.Equal(System.Net.HttpStatusCode.OK, response.StatusCode);
    }

    [Fact]
    public async Task FailureWithOneInvalidId()
    {
        var response = await client.GetAsync("/api/values?ids=xaaa001&ids=bbb002");

        Assert.Equal(System.Net.HttpStatusCode.BadRequest, response.StatusCode);
    }
}

控制器:

[Route("api/[controller]")]
public class ValuesController : Controller
{
    [HttpGet]
    public IActionResult Get(CustomIdentifier[] ids)
    {
        if (this.ModelState.IsValid == false) return this.BadRequest();

        return this.Ok(ids);
    }
}

启动:

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvc(options =>
        {
            options.ModelBinderProviders.Insert(0, new CutomIdentifierModelBinderProvider());
            //options.ModelBinderProviders.Add(new CutomIdentifierModelBinderProvider());
        });
    }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }

        app.UseMvc();
    }
}

ModelBinder的:

public class CutomIdentifierModelBinderProvider : IModelBinderProvider
{
    public IModelBinder GetBinder(ModelBinderProviderContext context)
    {
        //if (context.Metadata.ModelType.IsArray && context.Metadata.ModelType == typeof(CustomIdentifier[]))
        //{
        //    return new ArrayModelBinder<CustomIdentifier>(new CustomIdentifierModelBinder());
        //}

        if (context.Metadata.ModelType == typeof(CustomIdentifier))
        {
            return new BinderTypeModelBinder(typeof(CustomIdentifierModelBinder));
        }

        return null;
    }
}

public class CustomIdentifierModelBinder : IModelBinder
{
    public Task BindModelAsync(ModelBindingContext bindingContext)
    {
        var attemptedValue = bindingContext.ValueProvider.GetValue(bindingContext.ModelName).ToString();
        var parseResult    = CustomIdentifier.TryParse(attemptedValue);

        if (parseResult.Failed)
        {
            bindingContext.Result = ModelBindingResult.Failed();
            bindingContext.ModelState.AddModelError(bindingContext.ModelName, parseResult.Message.Message);
        }
        else
        {
            bindingContext.Model  = parseResult.Value;
            bindingContext.Result = ModelBindingResult.Success(parseResult.Value);
        }

        return Task.CompletedTask;
    }
}

T的MVC默认ArrayModelBinder正确绑定可选数组并将ModelState.IsValid设置为true。如果我使用自己的CustomIdentifierModelBinder,但ModelState.IsValid将为false。空数组无法识别为有效。

我该如何解决这个问题?提前谢谢。

2 个答案:

答案 0 :(得分:0)

你非常接近。只需为缺少参数的情况自定义内置ArrayModelBinder的行为。如果提取的值是空字符串,则只需使用空数组填充模型。在所有其他情况下,您可以调用常用ArrayModelBinder

这是一个通过所有3个测试的工作示例:

public class CutomIdentifierModelBinderProvider : IModelBinderProvider
{
    public IModelBinder GetBinder(ModelBinderProviderContext context)
    {
        if (context.Metadata.ModelType.IsArray && context.Metadata.ModelType == typeof(CustomIdentifier[]))
        {
            return new CustomArrayModelBinder<CustomIdentifier>(new CustomIdentifierModelBinder());
        }

        return null;
    }
}

public class CustomArrayModelBinder<T> : IModelBinder
{
    private readonly ArrayModelBinder<T> innerModelBinder;

    public CustomArrayModelBinder(IModelBinder elemeBinder)
    {
        innerModelBinder = new ArrayModelBinder<T>(elemeBinder);
    }

    public Task BindModelAsync(ModelBindingContext bindingContext)
    {
        var attemptedValue = bindingContext.ValueProvider.GetValue(bindingContext.ModelName).ToString();

        if (String.IsNullOrEmpty(attemptedValue))
        {
            bindingContext.Model = new T[0];
            bindingContext.Result = ModelBindingResult.Success(bindingContext.Model);
            return Task.CompletedTask;
        }

        return innerModelBinder.BindModelAsync(bindingContext);
    }
}

答案 1 :(得分:0)

解决方案是以下代码更改,反映在此提交中: https://github.com/MarcusKohnert/OptionalArrayModelBinding/commit/552f4d35d8c33c002e1aa0c05acb407f1f962102

我通过再次检查MVC的源代码找到了解决方案。 https://github.com/aspnet/Mvc/blob/35601f95b345d0ef938fb21ce1c51f5a67a1fb62/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/SimpleTypeModelBinder.cs#L37

您需要检查valueProviderResult是否为None。如果没有,则没有给出参数,并且ModelBinder正确绑定。

        var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
        if (valueProviderResult == ValueProviderResult.None)

您还可以使用自定义的ModelBinder注册提供的T的ArrayModelBinder:

        if (context.Metadata.ModelType.IsArray && context.Metadata.ModelType == typeof(CustomIdentifier[]))
        {
            return new ArrayModelBinder<CustomIdentifier>(new CustomIdentifierModelBinder());
        }