Asp.net 核心 Web api 在 fromquery 参数中传递 null 时抛出错误 [英] Asp.net core web api throws error while passing null in the fromquery param

查看:90
本文介绍了Asp.net 核心 Web api 在 fromquery 参数中传递 null 时抛出错误的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在调用具有空值的 asp.net 核心 Web API 方法时遇到问题,请检查以下 API 方法

I am facing an issue while calling asp.net core web API method with null value please check the below API method

    [HttpGet]
    [ProducesResponseType(StatusCodes.Status200OK)]
    [ProducesResponseType(StatusCodes.Status404NotFound)]
    [ProducesResponseType(StatusCodes.Status400BadRequest)]
    [ProducesResponseType(StatusCodes.Status500InternalServerError)]
    [AllowAnonymous]
    public ActionResult<List<int?>> Test([FromQuery] List<int?> userIds = null)
    {
        try
        {
            return Ok(userIds);
        }
        catch (Exception ex)
        {
            return HandleException(ex);
        }
    }

我像下面这样调用这个方法

and I am calling this method like below

https://localhost:44349/api/v1/Sessions/Test?userIds=null&userIds=1&userIds=2

https://localhost:44349/api/v1/Sessions/Test?userIds=null&userIds=1&userIds=2

我收到以下错误

{类型":https://tools.ietf.org/html/rfc7231#section-6.5.1",标题":发生了一个或多个验证错误."、状态":400,traceId":00-1c1d75974b9c4c489b3cca6b17f005ec-2aaa26d807bc3e42-00",错误":{用户 ID":[值 'null' 无效."]}

{ "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1", "title": "One or more validation errors occurred.", "status": 400, "traceId": "00-1c1d75974b9c4c489b3cca6b17f005ec-2aaa26d807bc3e42-00", "errors": { "userIds": [ "The value 'null' is not valid." ] }

如何使 asp.net 核心 Web API 接受来自查询的空值.

how to make asp.net core web API to accept null valeus in from query.

推荐答案

在评论中撰写时,您需要问题中的 URL 才能按原样工作.然后,我们必须对 asp.net Core 绑定查询参数的方式进行一些更改,这可以通过实现自定义模型绑定器来实现.

As you write in a comment, you'll need the URL from your question to work as-is. Then we'll have to do some changes in how asp.net Core binds query parameters, and that may be accomplished by implementing a custom model binder.

请注意,这是一个针对特定问题的简单活页夹.有关更通用的解决方案,请查看例如来源Microsoft.AspNetCore.Mvc.ModelBinding.Binders.CollectionModelBinder

Please note that this is a simple binder targeting a specific problem. For more generic solutions, have a look at the source for e.g. Microsoft.AspNetCore.Mvc.ModelBinding.Binders.CollectionModelBinder

public class CustomUserIdsBinder : IModelBinder
{
    private const string NullValue = "null";

    public Task BindModelAsync(ModelBindingContext bindingContext)
    {
        if (bindingContext == null)
        {
            throw new ArgumentNullException(nameof(bindingContext));
        }

        var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
        if (valueProviderResult == ValueProviderResult.None)
        {
            return Task.CompletedTask;
        }

        var result = new List<int?>();
        foreach (var currentValue in valueProviderResult)
        {
            // remove this code block if you want to filter out null-values
            if (string.IsNullOrEmpty(currentValue)
                || NullValue.Equals(currentValue, StringComparison.OrdinalIgnoreCase))
            {
                result.Add(null);
                continue;
            }

            if (int.TryParse(currentValue, out var currentIntValue))
            {
                result.Add(currentIntValue);
            }
        }

        bindingContext.Result = ModelBindingResult.Success(result);
        return Task.CompletedTask;
    }
}

要验证我们到目前为止所做的工作,请像这样更改控制器方法的签名:

To verify what we've done so far, change the signature for your controller method like this:

public ActionResult<List<int?>> Test(
    [FromQuery][ModelBinder(BinderType = typeof(CustomUserIdsBinder))]
    List<int?> userIds = null)

您不想像上面那样重复注释所有控制器方法,所以让我们将此绑定器应用于所有控制器.首先,一个活页夹提供者:

You don't want to repeat annotating all controller methods like above, so let's apply this binder to all controllers. First, a binder provider:

public class CustomUserIdsBinderProvider : IModelBinderProvider
{
    public IModelBinder GetBinder(ModelBinderProviderContext context)
    {
        if (context == null)
        {
            throw new ArgumentNullException(nameof(context));
        }
        return context.Metadata.ModelType == typeof(List<int?>) 
            ? new BinderTypeModelBinder(typeof(CustomUserIdsBinder)) 
            : null;
    }
}

与其在 index = 0 处插入新的 binder provider(在许多示例中都已完成),让我们使用此扩展方法为新的 binder provider 找到合适的位置:

Instead of inserting the new binder provider at index = 0 (which is done in many examples), let's find a proper position for the new binder provider using this extension method:

public static class BinderProviderExtensions
{
    public static void UseCustomUserIdsBinderProvider(this MvcOptions options)
    {
        var collectionBinderProvider = options.ModelBinderProviders
            .FirstOrDefault(x => x.GetType() == typeof(CollectionModelBinderProvider));

        if (collectionBinderProvider == null)
        {
            return;
        }

        // indexToPutNewBinderProvider = 15 in my test-app
        var indexToPutNewBinderProvider = options.ModelBinderProviders.IndexOf(collectionBinderProvider);
        options.ModelBinderProviders.Insert(indexToPutNewBinderProvider, new CustomUserIdsBinderProvider());
    }
}

然后像这样更改 Startup#ConfigureServices:

Then change Startup#ConfigureServices like this:

services.AddControllers(options => options.UseCustomUserIdsBinderProvider());

通过上述更改,您现在可以使用原始控制器,并且将应用上述绑定器.

With the above changes, you can now use your original controller, and the above binder will be applied.

最后,编写上述代码时使用的端点测试:

Finally, end-point tests used when writing the above code:

public class ControllerWithCustomBindingTests : IClassFixture<WebApplicationFactory<Startup>>
{
    private const string TestUrl = "/api/v1/Sessions/Test?userIds=null&userIds=1&userIds=2";
    private readonly WebApplicationFactory<Startup> _webApplicationFactory;

    public ControllerWithCustomBindingTests(WebApplicationFactory<Startup> factory) => _webApplicationFactory = factory;

    [Theory]
    [InlineData(TestUrl)]
    public async Task SessionTest_UrlWithNull_ReceiveOk(string url) => 
        Assert.Equal(HttpStatusCode.OK, (await _webApplicationFactory.CreateClient().GetAsync(url)).StatusCode);

    [Theory]
    [InlineData(TestUrl)]
    public async Task SessionTest_UrlWithNull_ReceiveListOfThreeItems(string url)
    {
        var items = await
            (await _webApplicationFactory.CreateClient().GetAsync(url))
            .Content.ReadFromJsonAsync<IEnumerable<int?>>();

        Assert.Equal(3, items?.Count());
    }
}

开发/测试期间使用的环境:asp.net Core 5、Kestrel、XUnit、Rider.

Environment used during devel/testing: asp.net Core 5, Kestrel, XUnit, Rider.

这篇关于Asp.net 核心 Web api 在 fromquery 参数中传递 null 时抛出错误的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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