ASP.NET Core: 防止个别操作自动返回HTTP 400响应

7
我很喜欢ASP.NET Core 2.1中新增的自动HTTP 400响应功能,对于大多数情况,它的效果非常好。
然而,在一个操作中,在验证负载之前,我需要进行一些预处理。我有一个自定义验证器,需要模型中的两个值来执行验证。其中一个值在路径中,所以我想从路径设置该值到模型中,然后进行验证。
我不想关闭所有操作的此功能:
public void ConfigureServices(IServiceCollection services)
{
    services.Configure<ApiBehaviorOptions>(options =>
    {
        options.SuppressModelStateInvalidFilter = true;
    });
}

有没有办法只针对某个操作关闭它?

编辑:

我尝试修改InvalidModelStateResponseFactory,但它并没有解决我的问题,因为我仍然需要进入控制器操作:

services.Configure<ApiBehaviorOptions>(options =>
{
    options.InvalidModelStateResponseFactory = actionContext =>
    {
        var ignore = actionContext.ActionDescriptor.FilterDescriptors.Any(fd => fd.Filter is SuppressModelStateInvalidFilterAttribute);
        if (ignore)
        {
            // Can only return IActionResult so doesn't enter the controller action.
        }

        return new BadRequestObjectResult(actionContext.ModelState);
    };
});

[AttributeUsage(AttributeTargets.Method)]
public class SuppressModelStateInvalidFilterAttribute : FormatFilterAttribute
{
}

编辑:

以下是我在asp.net core仓库上报告的问题链接,如果我有任何进展,可以查看 - https://github.com/aspnet/Mvc/issues/8575


如果您可以接受添加第三方库,那么有一个名为Fluent Validation的工具可帮助验证复杂模型类型,并支持“预验证”逻辑。它可能会有所帮助。这是文档链接:https://fluentvalidation.net/start#using-prevalidate - Gerald Chifanzwa
5个回答

12

更新: 您只需在Startup.cs的ConfigureServices中使用以下代码:

services.Configure<ApiBehaviorOptions>(apiBehaviorOptions => {
    apiBehaviorOptions.SuppressModelStateInvalidFilter = true;
});
基于 Simon Vane 的回答,我不得不修改 ASP.Net Core 2.2 的属性如下所示:
基于Simon Vane的回答,我必须修改ASP.Net Core 2.2的属性,如下所示:
/// <summary>
/// Suppresses the default ApiController behaviour of automatically creating error 400 responses
/// </summary>
[AttributeUsage(AttributeTargets.Method)]
public class SuppressModelStateInvalidFilterAttribute : Attribute, IActionModelConvention {
    private static readonly Type ModelStateInvalidFilterFactory = typeof(ModelStateInvalidFilter).Assembly.GetType("Microsoft.AspNetCore.Mvc.Infrastructure.ModelStateInvalidFilterFactory");

    public void Apply(ActionModel action) {
        for (var i = 0; i < action.Filters.Count; i++) {
            if (action.Filters[i] is ModelStateInvalidFilter || action.Filters[i].GetType() == ModelStateInvalidFilterFactory) {
                action.Filters.RemoveAt(i);
                break;
            }
        }
    }
}

5
我收到了来自微软的回复-https://github.com/aspnet/Mvc/issues/8575 下面的方法非常有效。
[AttributeUsage(AttributeTargets.Method)]
public class SuppressModelStateInvalidFilterAttribute : Attribute, IActionModelConvention
{
    public void Apply(ActionModel action)
    {
        for (var i = 0; i < action.Filters.Count; i++)
        {
            if (action.Filters[i] is ModelStateInvalidFilter)
            {
                action.Filters.RemoveAt(i);
                break;
            }
        }
    }
}

在我的控制器中,我可以在重新验证模型之前对模型进行更改(请注意ModelState.Clear(),TryValidateModel会添加到现有的模型状态):
if (model == null)
{
    return BadRequest(ModelState);
}

model.Property = valueFromPath;

ModelState.Clear();
if (TryValidateModel(model) == false)
{
    return BadRequest(ModelState);
}

2
它应该是 action.Filters[i] 是 ModelStateInvalidFilterFactory,但它是 internal。/. - Yoda

2
你可以使用 ApiBehaviorOptions.InvalidModelStateResponseFactory 属性来处理特定情况,基于 actionContext 的详细信息:
services.Configure<ApiBehaviorOptions>(options =>
{
    options.InvalidModelStateResponseFactory = actionContext => 
    {
        // Do what you need here for specific cases with `actionContext` 
        // I believe you can cehck the action attributes 
        // if you'd like to make mark / handle specific cases by action attributes. 

        return new BadRequestObjectResult(context.ModelState);
    }
});

非常感谢您的建议,但是它并没有解决我的问题。我需要进入控制器操作。为了提供一些额外的信息,我已经更新了我的问题。 - Simon Vane

2
我遇到了类似的问题,并想出了这个解决方案。
public class SuppressModelStateInvalidFilterAttribute : ActionFilterAttribute
{
    public SuppressModelStateInvalidFilterAttribute()
    {
        Order = -2500;
    }

    public override Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
    {
        context.ModelState.Clear();
        return next.Invoke();
    }
}

1

1
我正在实现一个自定义验证器。在验证之前,我只需要从路径中获取一个值并将其设置到模型上。我认为你的自定义绑定建议可能是可行的。我会尝试一下。谢谢。 - Simon Vane

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接