【问题标题】:Disable model validation for single .NET Core API action禁用单个 .NET Core API 操作的模型验证
【发布时间】:2020-07-30 20:39:31
【问题描述】:

我有一个 API 控制器,用于在我正在开发的应用程序上执行自动保存。它使用与视图相同的视图模型,其中包含许多必填字段。如果用户在保存表单时尚未完成表单,则自动保存控制器可能需要保存被认为无效的模型。默认情况下,使用 [ApiController] 属性声明的 .NET Core 控制器将自动强制验证。我知道我可以在Startup.cs 中这样禁用它:

services.Configure<ApiBehaviorOptions>(options =>
{
     options.SuppressModelStateInvalidFilter = true;
});

但这将适用于项目中的所有 API 控制器。是否可以仅对 one 控制器或操作禁用此默认验证?到目前为止,我发现的所有内容都指示我使用上面的代码,但这并没有实现我想要的。

【问题讨论】:

  • 您可以将ModelBinder 属性添加到模型中,并制作您自己想要的活页夹。
  • 或者不要将任何参数传递给您的操作并手动提取模型值。

标签: c# asp.net-core-mvc asp.net-core-3.1


【解决方案1】:

您可以覆盖默认的InvalidModelStateResponseFactory

            services.Configure<ApiBehaviorOptions>(options =>
            {
                options.InvalidModelStateResponseFactory =
                    AllowingServerSideValidationToBeDisabledInvalidModelStateResponseFactoryHelper.InvalidModelStateResponseFactory;
            });

下面的InvalidModelStateResponseFactory 在控制器操作上检查OptionalValidationAttribute,并搜索控制验证启用/禁用的表单/查询参数标志:

    // Code taken from https://github.com/dotnet/aspnetcore/blob/5747cb36f2040d12e75c4b5b3f49580ef7aac5fa/src/Mvc/Mvc.Core/src/DependencyInjection/ApiBehaviorOptionsSetup.cs#L23
    // and is modified to optionally disable validation for controller action methods decorated with OptionalValidationAttribute
    public static class AllowingServerSideValidationToBeDisabledInvalidModelStateResponseFactoryHelper
    {
        public static Func<ActionContext, IActionResult> InvalidModelStateResponseFactory => actionContext =>
        {
            var shouldEnableDataValidationarameterName = ((OptionalValidationAttribute)((ControllerActionDescriptor)actionContext.ActionDescriptor)
                .MethodInfo.GetCustomAttributes(typeof(OptionalValidationAttribute), true)
                .SingleOrDefault())?.ShouldEnableDataValidationParameterName;

            var isValidationEnabled = true;

            if (shouldEnableDataValidationarameterName != null)
            {
                var httpContextRequest = actionContext.HttpContext.Request;
                var shouldEnableDataValidationValue = httpContextRequest.Form[shouldEnableDataValidationarameterName]
                    .Union(httpContextRequest.Query[shouldEnableDataValidationarameterName]).FirstOrDefault();
                isValidationEnabled = shouldEnableDataValidationValue?.ToLower() == bool.TrueString.ToLower();
            }

            if (!isValidationEnabled)
            {
                return null;
            }

            var problemDetailsFactory = actionContext.HttpContext.RequestServices.GetRequiredService<ProblemDetailsFactory>();
            var problemDetails = problemDetailsFactory.CreateValidationProblemDetails(actionContext.HttpContext, actionContext.ModelState);
            ObjectResult result;
            if (problemDetails.Status == 400)
            {
                // For compatibility with 2.x, continue producing BadRequestObjectResult instances if the status code is 400.
                result = new BadRequestObjectResult(problemDetails);
            }
            else
            {
                result = new ObjectResult(problemDetails)
                {
                    StatusCode = problemDetails.Status,
                };
            }
            result.ContentTypes.Add("application/problem+json");
            result.ContentTypes.Add("application/problem+xml");

            return result;
        };
    }

OptionalValidationAttribute:

    [AttributeUsage(AttributeTargets.Method)]
    public class OptionalValidationAttribute : Attribute
    {
        public OptionalValidationAttribute(string shouldEnableDataValidationParameterName)
        {
            ShouldEnableDataValidationParameterName = shouldEnableDataValidationParameterName;
        }

        public string ShouldEnableDataValidationParameterName { get; }
    }

控制器动作的示例用法:

[HttpPost]
[OptionalValidation(shouldEnableDataValidationParameterName: "shouldEnableDataValidation")]
public async Task<IActionResult> Update(
    [FromForm] int id,
    [FromForm] string name,
    [FromForm] bool shouldEnableDataValidation
)
{
...
}

【讨论】:

    【解决方案2】:

    我建议您以不同的方式处理此问题:禁用模型验证将意味着此操作没有任何验证;现在不行,以后不行。仅仅因为您现在不需要验证并不意味着您以后不需要某种验证。

    如果您使用一些自定义处理来完全禁用该操作的验证,那么您所做的就是在您的应用程序中创建一个实际的异常,这将使其更加复杂。稍后查看此问题的开发人员可能不会预料到这种行为,并且可能会花费大量时间试图弄清楚为什么验证在它适用于所有其他操作时没有运行。

    因此,请考虑仅复制模型,以便每个操作都有自己的模型:操作 A 具有带有验证属性的原始模型,需要填充值。并且操作 B 具有该模型的副本,但没有任何验证属性。

    虽然这看起来很浪费,但这确实会给您带来更多好处:

    • 如果您稍后需要对操作 B 上的某些字段进行验证,您可以只添加一些验证属性。您不必完全禁用自动验证,因此如果您将单个验证属性添加到模型中,它们将继续工作。
    • 拥有单独的模型允许两个动作独立发展。已经有一个很好的指标表明,这些操作做了两件不同的事情:一件需要值,另一件不需要。因此,这些模型未来可能需要进一步分化并不是不可能的。例如,您可能只想将属性添加到一个模型而不是另一个模型。
    • 如上所述,您可以坚持默认行为并保持一致的开发体验。

    【讨论】:

    • 谢谢!这肯定是一个更清洁的解决方案,尽管现在我可能需要在我的 Models 文件夹中添加其他文件夹以保持它们井井有条。不过,我仍然想知道我最初提出的问题是否可行。
    猜你喜欢
    • 2019-03-26
    • 2019-01-12
    • 1970-01-01
    • 2015-04-16
    • 1970-01-01
    • 2021-04-23
    • 2019-03-29
    • 1970-01-01
    • 2021-01-06
    相关资源
    最近更新 更多