【问题标题】:Asp.net core web api throws error while passing null in the fromquery paramAsp.net core web api 在 fromquery 参数中传递 null 时引发错误
【发布时间】:2021-07-03 13:45:25
【问题描述】:

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

    [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);
        }
    }

我正在调用这个方法,如下所示

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

我收到以下错误

{ “类型”:“https://tools.ietf.org/html/rfc7231#section-6.5.1”, "title": "出现一个或多个验证错误。", “状态”:400, “traceId”:“00-1c1d75974b9c4c489b3cca6b17f005ec-2aaa26d807bc3e42-00”, “错误”:{ “用户 ID”:[ “值‘null’无效。” ] }

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

【问题讨论】:

  • 恐怕因为它是一个 QueryString,它试​​图用“null”而不是实际的 NULL 填充 List,这就是你得到 400 的原因。这是有原因的吗如果 ?userIds 不是查询字符串的一部分,端点应该接受 null,而不仅仅是工作?
  • 在您的 API 中使用 List 作为参数并转换为 List

标签: c# asp.net-core .net-core asp.net-core-webapi


【解决方案1】:

发生这种情况的原因是因为它试图将"null" 的字符串值填充到int? 类型中。

如果您希望 userIds 为空,请不要将它们添加到查询字符串中。

例如,当我将输入类型更改为List&lt;string&gt;,并像上面一样传递值时,请注意字符串不为空。他们是"null"

更正

我不确定省略查询参数是否会使列表为空。它可能默认为空列表。在这种情况下,您可以检查 userIds 的计数。

【讨论】:

    【解决方案2】:

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

    请注意,这是针对特定问题的简单活页夹。有关更通用的解决方案,请查看源代码,例如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;
        }
    }
    

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

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

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

    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 提供程序(在许多示例中都会这样做),而是使用这种扩展方法为新的 binder 提供程序找到一个合适的位置:

    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:

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

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

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

    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。

    【讨论】:

    • 这行得通,但在 asp.net 框架中,我们过去常常像下面 localhost:44349/api/v1/Sessions/… 那样传递数据,我们在客户端应用程序中使用了很多类似这样的调用,所以我们需要在 api 端有一个解决方法来就像 url 提到的那样,asp.net core web api 中是否存在任何配置来接受 null 作为查询参数
    • @DineshGanesan:用自定义活页夹更新了我的答案。 BR
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-01-26
    • 2021-02-03
    • 1970-01-01
    • 2020-07-10
    • 2020-06-26
    • 2019-01-29
    相关资源
    最近更新 更多