禁用单个 .NET Core API 操作的模型验证

Mr.*_*ian 11 c# asp.net-core-mvc asp.net-core-3.1

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

services.Configure<ApiBehaviorOptions>(options =>
{
     options.SuppressModelStateInvalidFilter = true;
});
Run Code Online (Sandbox Code Playgroud)

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

Dav*_*sch 9

与 Poke 的答案类似,我建议对您不希望验证的操作使用不同的模型。然而,我不会创建模型的副本,而是从经过验证的模型派生并添加属性[ValidateNever],例如

[ValidateNever]
public class MyUnvalidatedModel : MyValidatedModel {
}
Run Code Online (Sandbox Code Playgroud)

这将使您避免大量重复,同时仍然为您提供未经验证的模型版本。


xha*_*fan 7

您可以覆盖默认值InvalidModelStateResponseFactory

            services.Configure<ApiBehaviorOptions>(options =>
            {
                options.InvalidModelStateResponseFactory =
                    AllowingServerSideValidationToBeDisabledInvalidModelStateResponseFactoryHelper.InvalidModelStateResponseFactory;
            });
Run Code Online (Sandbox Code Playgroud)

下面的代码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;
        };
    }
Run Code Online (Sandbox Code Playgroud)

OptionalValidationAttribute

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

        public string ShouldEnableDataValidationParameterName { get; }
    }
Run Code Online (Sandbox Code Playgroud)

控制器操作的用法示例:

[HttpPost]
[OptionalValidation(shouldEnableDataValidationParameterName: nameof(shouldEnableDataValidation))] // C# 11 needed to use nameof for a method parameter 
public async Task<IActionResult> Update(
    [FromForm] int id,
    [FromForm] string name,
    [FromForm] bool shouldEnableDataValidation
)
{
...
}
Run Code Online (Sandbox Code Playgroud)


pok*_*oke 3

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

\n

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

\n

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

\n

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

\n
    \n
  • 如果您稍后需要验证操作 B 上的某些字段,您只需添加一些验证属性即可。您不必完全禁用自动验证,因此如果您将单个验证属性添加到模型中,它们将继续工作。
  • \n
  • 拥有单独的模型可以让两个动作独立发展。\xe2\x80\x99s 已经很好地表明这些操作执行两种不同的操作:一个需要值,另一个不需要\xe2\x80\x99t。因此,这些模型在未来可能需要进一步分化,这并非不可能。例如,您可能只想向一个模型添加属性,而不向另一个模型添加属性。
  • \n
  • 如上所述,您可以坚持默认行为并保持一致的开发体验。
  • \n
\n