【问题标题】:Custom DateTime ModelBinder is being bypassed in ASP.NET Core Controller自定义 DateTime ModelBinder 在 ASP.NET Core 控制器中被绕过
【发布时间】: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


【解决方案1】:

您需要验证模型状态的错误,以便稍后生成BadRequest,不需要_baseBinder

public class IsoDateModelBinder : IModelBinder
{
    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);

            }
            else
            {
                bindingContext.ModelState.AddModelError(bindingContext.ModelName, "Invalid date string");
            }
        }

        return Task.CompletedTask;
    }
}

【讨论】:

  • 是的! bindingContext.ModelState.AddModelError(...) 是缺失的链接。而且我不需要从SimpleTypeModelBinder 继承。谢谢@亚历山大
  • 为了将来使用,也为了拯救其他可怜的鞋底,我创建了一个库,其中包含此活页夹的实现 nuget.org/packages/Standards.AspNetCore
猜你喜欢
  • 2020-07-11
  • 1970-01-01
  • 1970-01-01
  • 2018-01-28
  • 1970-01-01
  • 2017-02-09
  • 2017-01-08
  • 1970-01-01
  • 2021-02-10
相关资源
最近更新 更多