ASP.NET Core MVC混合路由/发件人模型绑定&验证 [英] ASP.NET Core MVC Mixed Route/FromBody Model Binding & Validation

查看:47
本文介绍了ASP.NET Core MVC混合路由/发件人模型绑定&验证的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用ASP.NET Core 1.1 MVC构建JSON API.给出以下模型和操作方法:

I am using ASP.NET Core 1.1 MVC to build an JSON API. Given the following model and action method:

public class TestModel
{
    public int Id { get; set; }

    [Range(100, 999)]
    public int RootId { get; set; }

    [Required, MaxLength(200)]
    public string Name { get; set; }

    public string Description { get; set; }
}

[HttpPost("/test/{rootId}/echo/{id}")]
public IActionResult TestEcho([FromBody] TestModel data)
{
    return Json(new
    {
        data.Id,
        data.RootId,
        data.Name,
        data.Description,
        Errors = ModelState.IsValid ? null : ModelState.SelectMany(x => x.Value.Errors)
    });
}

我的action方法参数上的[FromBody]导致模型从发布到端点的JSON负载中进行绑定,但是这也阻止了IdRootId属性通过route参数进行绑定

The [FromBody] on my action method parameter is causing the model to be bound from the JSON payload that is posted to the endpoint, however it also prevents the Id and RootId properties from being bound via the route parameters.

我可以将其分解为单独的模型,一个与路线绑定,一个与车身绑定,或者我也可以强迫任何客户端发送id& rootId作为有效负载的一部分,但是这两种解决方案似乎都使​​事情变得比我想要的复杂得多,并且不允许我将验证逻辑放在一个地方.有什么方法可以使这种情况在模型可以正确绑定并且我可以保持模型与模型正常工作的情况下进行.验证逻辑在一起吗?

I could break this up into to separate models, one bound from the route and one from the body or I could also force any clients to send the id & rootId as part of the payload, but both of those solutions seem to complicate things more than I'd like and don't allow me to keep the validation logic in a single place. Is there any way to get this situation working where the model can be bound properly and I can keep my model & validation logic together?

推荐答案

您可以在输入中删除[FromBody]装饰器,并让MVC绑定映射属性:

You can remove the [FromBody] decorator on your input and let MVC binding map the properties:

[HttpPost("/test/{rootId}/echo/{id}")]
public IActionResult TestEcho(TestModel data)
{
    return Json(new
    {
        data.Id,
        data.RootId,
        data.Name,
        data.Description,
        Errors = ModelState.IsValid ? null : ModelState.SelectMany(x => x.Value.Errors)
    });
}

更多信息: ASP.NET Core MVC中的模型绑定

更新

测试

更新2

@heavyd,您很正确,因为JSON数据需要[FromBody]属性来绑定模型.因此,我上面所说的将适用于表单数据,但不适用于JSON数据.

@heavyd, you are right in that JSON data requires [FromBody] attribute to bind your model. So what I said above will work on form data but not with JSON data.

或者,您可以创建一个自定义模型绑定程序,该绑定程序绑定url中的IdRootId属性,同时绑定请求主体中的其余属性.

As alternative, you can create a custom model binder that binds the Id and RootId properties from the url, whilst it binds the rest of the properties from the request body.

public class TestModelBinder : IModelBinder
{
    private BodyModelBinder defaultBinder;

    public TestModelBinder(IList<IInputFormatter> formatters, IHttpRequestStreamReaderFactory readerFactory) // : base(formatters, readerFactory)
    {
        defaultBinder = new BodyModelBinder(formatters, readerFactory);
    }

    public async Task BindModelAsync(ModelBindingContext bindingContext)
    {
        // callinng the default body binder
        await defaultBinder.BindModelAsync(bindingContext);

        if (bindingContext.Result.IsModelSet)
        {
            var data = bindingContext.Result.Model as TestModel;
            if (data != null)
            {
                var value = bindingContext.ValueProvider.GetValue("Id").FirstValue;
                int intValue = 0;
                if (int.TryParse(value, out intValue))
                {
                    // Override the Id property
                    data.Id = intValue;
                }
                value = bindingContext.ValueProvider.GetValue("RootId").FirstValue;
                if (int.TryParse(value, out intValue))
                {
                    // Override the RootId property
                    data.RootId = intValue;
                }
                bindingContext.Result = ModelBindingResult.Success(data);
            }

        }

    }
}

创建活页夹提供程序:

public class TestModelBinderProvider : IModelBinderProvider
{
    private readonly IList<IInputFormatter> formatters;
    private readonly IHttpRequestStreamReaderFactory readerFactory;

    public TestModelBinderProvider(IList<IInputFormatter> formatters, IHttpRequestStreamReaderFactory readerFactory)
    {
        this.formatters = formatters;
        this.readerFactory = readerFactory;
    }

    public IModelBinder GetBinder(ModelBinderProviderContext context)
    {
        if (context.Metadata.ModelType == typeof(TestModel))
            return new TestModelBinder(formatters, readerFactory);

        return null;
    }
}

并告诉MVC使用它:

services.AddMvc()
  .AddMvcOptions(options =>
  {
     IHttpRequestStreamReaderFactory readerFactory = services.BuildServiceProvider().GetRequiredService<IHttpRequestStreamReaderFactory>();
     options.ModelBinderProviders.Insert(0, new TestModelBinderProvider(options.InputFormatters, readerFactory));
  });

然后您的控制器具有:

[HttpPost("/test/{rootId}/echo/{id}")]
public IActionResult TestEcho(TestModel data)
{...}

测试

您可以在您的JSON中添加IdRootId,但是由于我们在模型绑定程序中覆盖它们,它们将被忽略.

You can add an Id and RootId to your JSON but they will be ignored as we are overwriting them in our model binder.

更新3

以上内容使您可以使用数据模型注释来验证IdRootId.但是我认为这可能会使其他开发人员混淆您的API代码.我建议只是简化API签名,以接受与[FromBody]一起使用的不同模型,并分离来自uri的其他两个属性.

The above allows you to use your data model annotations for validating Id and RootId. But I think it may confuse other developers who would look at your API code. I would suggest to just simplify the API signature to accept a different model to use with [FromBody] and separate the other two properties that come from the uri.

[HttpPost("/test/{rootId}/echo/{id}")]
public IActionResult TestEcho(int id, int rootId, [FromBody]TestModelNameAndAddress testModelNameAndAddress)

您可以为所有输入编写一个验证器,例如:

And you could just write a validator for all your input, like:

// This would return a list of tuples of property and error message.
var errors = validator.Validate(id, rootId, testModelNameAndAddress); 
if (errors.Count() > 0)
{
    foreach (var error in errors)
    {
        ModelState.AddModelError(error.Property, error.Message);
    }
}

这篇关于ASP.NET Core MVC混合路由/发件人模型绑定&amp;验证的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆