【发布时间】:2021-08-25 07:22:43
【问题描述】:
我正在尝试创建一个 HTTP GET Web API,它只接受 ISO-8601 格式的日期,即 YYYY-MM-DD。我有以下接受 DateTime 的简单 API 控制器,并且我正在通过属性应用自定义模型绑定器:
using System;
using System.Globalization;
using Microsoft.AspNetCore.Mvc;
namespace WebApiDates.Controllers
{
[ApiController]
[Route("[controller]")]
public class WebApiDatesController : ControllerBase
{
[HttpGet("datetime")]
public IActionResult GetDateTime([ModelBinder(BinderType = typeof(IsoDateModelBinder))] DateTime date)
{
return Ok($"Date was {date}");
}
}
自定义模型绑定器如下所示:
using System;
using System.Globalization;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
using Microsoft.Extensions.Logging;
namespace WebApiDates
{
public class IsoDateModelBinder : IModelBinder
{
private readonly IModelBinder _baseBinder;
public IsoDateModelBinder(ILoggerFactory loggerFactory)
{
_baseBinder = new SimpleTypeModelBinder(typeof(DateTime), loggerFactory);
}
public Task BindModelAsync(ModelBindingContext bindingContext)
{
var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (valueProviderResult != ValueProviderResult.None)
{
bindingContext.ModelState.SetModelValue(bindingContext.ModelName, valueProviderResult);
var valueAsString = valueProviderResult.FirstValue;
var dateTimeParsed = DateTime.TryParseExact(valueAsString, "yyyy-MM-dd", CultureInfo.InvariantCulture, DateTimeStyles.None, out var dateTimeResult);
if (dateTimeParsed)
{
bindingContext.Result = ModelBindingResult.Success(dateTimeResult);
return Task.CompletedTask;
}
}
return _baseBinder.BindModelAsync(bindingContext);
}
}
}
项目的其余部分是一个标准的 .NET 5.0 Web API 项目,使用 dotnet new webapi 创建。
为了对此进行测试,我进行了以下单元测试:
using System.Threading.Tasks;
using Xunit;
using WebApiDates;
using Microsoft.AspNetCore.Mvc.Testing;
using System.Net;
namespace WebApiDates.Tests
{
public class WebApiTests : IClassFixture<WebApplicationFactory<Startup>>
{
private readonly WebApplicationFactory<Startup> _factory;
public WebApiTests(WebApplicationFactory<Startup> factory)
{
_factory = factory;
}
[Theory]
[InlineData("2021-08-21", HttpStatusCode.OK)]
[InlineData("2021-21-08", HttpStatusCode.BadRequest)]
[InlineData("08-21-2021", HttpStatusCode.BadRequest)]
[InlineData("21-08-2021", HttpStatusCode.BadRequest)]
[InlineData("2021/08/21", HttpStatusCode.BadRequest)]
[InlineData("2021/21/08", HttpStatusCode.BadRequest)]
[InlineData("08/21/2021", HttpStatusCode.BadRequest)]
[InlineData("21/08/2021", HttpStatusCode.BadRequest)]
[InlineData("08.21.2021", HttpStatusCode.BadRequest)]
[InlineData("21.08.2021", HttpStatusCode.BadRequest)]
[InlineData("2021.08.21", HttpStatusCode.BadRequest)]
[InlineData("2021.21.08", HttpStatusCode.BadRequest)]
public async Task DateTimeTests(string date, HttpStatusCode expectedStatusCode)
{
// Arrange
using var client = _factory.CreateClient();
// Act
var response = await client.GetAsync($"/webapidates/datetime?date={date}");
// Assert
Assert.Equal(expectedStatusCode, response.StatusCode);
}
}
}
问题是只有第一个测试应该返回OK结果,而下面的测试也返回OK:
当我调试模型绑定器时,它正在正确地进行日期解析。所以似乎有些东西正在覆盖模型绑定器。
如果您想仔细查看,来源位于here。
【问题讨论】:
-
您可能缺少 IModelBinderProvider 实现吗?
-
这行
return _baseBinder.BindModelAsync(bindingContext);的目的是什么?如果您的逻辑无法解析YYYY-MM-DD,您应该只返回失败的结果,因为这听起来像是您想要实现的目标 -
@JochemVanHespen 据我了解,您要么应用属性,要么提供 IModelBinderProvider 实现docs.microsoft.com/en-us/aspnet/core/mvc/advanced/…
-
@Alexander 我有这条线,因为我从另一个答案中得到了这个:stackoverflow.com/a/47449401/6939988。取出它会导致另外 5 次测试失败。而且您不需要将结果设置为失败,因为它默认设置为失败docs.microsoft.com/en-us/dotnet/api/…
-
@marcusturewicz 你是对的,它要么是属性,要么是提供者。仔细看看你的代码,我认为亚历山大是对的,因为你失败了一个简单的模型绑定器,一些日期仍然有效。我让你测试通过,在失败的情况下抛出一个期望,并将 badrequest 更改为 internalservererror。这不是解决方案。但至少你会知道出了什么问题
标签: c# asp.net-core .net-core