c# - 在 fromquery 参数中传递 null 时,Asp.net 核心 Web api 引发错误
问题描述
我在使用空值调用 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
我收到以下错误
{ "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1", "title": "出现一个或多个验证错误。", "status": 400, "traceId" :“00-1c1d75974b9c4c489b3cca6b17f005ec-2aaa26d807bc3e42-00”,“错误”:{“userIds”:[“值'null'无效。” ] }
如何使 asp.net 核心 Web API 接受来自查询的空值。
解决方案
当您在评论中写道时,您需要问题中的 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。
推荐阅读
- php - 如何使用 jquery ajax php 和 mysql 更新表中的数据
- javascript - React-Native Android:调整原生 UI 组件的大小会导致黑屏
- angularjs - "TypeError: i18nService.getSafeText is not a function" when rendering data for grid using angular-ui-grid
- php - SELECT LAST and FORELAST Values
- bluetooth - _serial_.bufferUntil(byte) 有什么作用,它如何与 serialEvent 协同工作?
- python - 在 Pandas 中过滤数据返回错误“方法”对象不可迭代
- html - 模糊智能手机网页视图上的部分图像
- xaml - 应用样式时不可点击的复选框
- ruby-on-rails - 向数据库添加评论
- go - 释放结构图