在您写评论时,您需要问题中的 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。