如何让 ASP.NET Core 2.0 MVC Model Binder 绑定没有 FromBody 属性的复杂类型

Sti*_*gar 5 c# asp.net asp.net-mvc asp.net-core

因此,在 ASP.NET Core MVC 中,他们决定我需要在所有复杂类型的操作参数前面输入 [FromBody],因为一些传奇的 CSRF 问题似乎没有人谈论。我觉得这很荒谬,所以有没有办法让 ASP.NET Core MVC 表现得像旧的 WebAPI,而不是到处都需要 [FromBody],而只需将 JSON 的所有内容绑定到复杂类型参数?如果我能以某种方式选择它适用的控制器集,例如以 /api 开头的控制器或用特定属性装饰的控制器,那就太好了。

Cod*_*ler 5

FromBody您可以通过实现自定义模型绑定约定来避免对每个复杂操作参数使用属性。步骤如下:

  1. 定义属性,该属性将在控制器级别指示所有操作都应使用请求正文中的默认绑定:

    [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
    public sealed class DefaultFromBodyAttribute : Attribute
    {
    }
    
    Run Code Online (Sandbox Code Playgroud)
  2. 添加自定义模型绑定约定的实现:

    public class DefaultFromBodyBindingConvention : IActionModelConvention
    {
        public void Apply(ActionModel action)
        {
            if (action == null)
            {
                throw new ArgumentNullException(nameof(action));
            }
    
            if (action.Controller.Attributes.Any(a => a is DefaultFromBodyAttribute))
            {
                foreach (var parameter in action.Parameters)
                {
                    var paramType = parameter.ParameterInfo.ParameterType;
                    var isSimpleType = paramType.IsPrimitive
                                        || paramType.IsEnum
                                        || paramType == typeof(string)
                                        || paramType == typeof(decimal);
    
                    if (!isSimpleType)
                    {
                        parameter.BindingInfo = parameter.BindingInfo ?? new BindingInfo();
                        parameter.BindingInfo.BindingSource = BindingSource.Body;
                    }
                }
            }
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)

    实施非常简单。我们检查控制器是否标记有必需的属性,检查操作参数是否复杂,如果两个条件都满足 - 我们将绑定源设置为BindingSource.Body。我们应该检查底层操作参数类型是否复杂,因为我们不希望int类型string与请求正文绑定。我从这个答案中借用了复杂类型识别的条件。

    您可以根据您的需要调整此约定的逻辑,例如,如果您想检查控制器路由而不是属性或对特定类型具有特殊条件。

  3. 在方法中注册约定Startup.ConfigureServices

    services.AddMvc(options =>
    {
        options.Conventions.Add(new DefaultFromBodyBindingConvention());
    });
    
    Run Code Online (Sandbox Code Playgroud)
  4. 使用属性标记所需的控制器DefaultFromBody

    [Route("api/[controller]")]
    [DefaultFromBody]
    public class SomeController : Controller
    
    Run Code Online (Sandbox Code Playgroud)

现在,即使您不指定FromBody属性,复杂的操作参数也会默认从主体绑定:

[HttpPost]
public void Post(SomeData value)
{
    // ...
}
Run Code Online (Sandbox Code Playgroud)

模型约定在应用程序启动期间(对于每个操作)调用一次,因此您不应该担心请求执行期间的任何性能损失。