【问题标题】:How to change routing errors in .NET Core 3.1 API如何更改 .NET Core 3.1 API 中的路由错误
【发布时间】:2020-09-23 03:17:21
【问题描述】:

我正在将一个 API 从 ASP .NET 4.7 移植到 .NET CORE 3.1,到目前为止,我已经设法完成了大部分工作,但我遇到了一个特定的错误。 我有以下端点

    [HttpGet]
    [Route("contacts/{id}/")]
    public IActionResult GetContactByID(Guid? id)
    {
        if (!id.HasValue)
            return BadParameter("id");

        //Some irrelevant logic here
        Contact foundContact = GetContactFromRepository(id);
        return Ok(foundContact);
    }

通过在旧 API 中提供无效的 Guid(“contacts/6cb735b1-b2f0-45d1-a5b5-3a161a5b8c5eTESTINVALIDGUID”) - 将到达端点并且检查 ID 是否具有值的 IF 语句将提供预期的错误响应格式。目前在 CORE 3.1 中,当在相同的请求中,我在 Postman 中得到以下响应:

"errors": {
    "id": [
        "The value '6cb735b1-b2f0-45d1-a5b5-3a1615555555asd' is not valid."
    ]
},
"type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"traceId": "|90331587-428887e84126845c."

执行永远不会到达方法。但是控制器的构造函数被调用了。

我已经覆盖了大部分错误消息,并且我同时移植了多个 API 版本(每个版本都有不同的响应)。但我无法弄清楚这个错误来自哪里(以及如何处理它,以便请求真正到达控制器方法 - 以便我可以返回正确的响应)。

【问题讨论】:

  • .Net Core 似乎无法将字符串解析为 Guid 对象,因为它是无效的,并且框架只是向您返回了 BadRequest 响应。您的代码 if(...) 的功能现在由 .Net Core Framework 完成。除非您输入有效的 Guid,否则该方法将永远不会被调用。
  • ^^ 换句话说:“这不是一个错误。这是一个功能!”但我明白,你宁愿有自己的错误响应。所以问题将是“如何进入验证过程,以便解析失败的 Guid 传递给 'ok' 和 null 而不是返回 400”,对吗?
  • 是的,您的建议似乎准确地总结了我的需要。

标签: c# rest asp.net-core asp.net-web-api


【解决方案1】:

在 ASP.NET Core 中,如果指定 Action Method 的参数类型为Guid,并期望路由获取Guid 值,则它应该是有效的Guid 值,否则路由不会工作。 .Net Core 框架不会解析无效值。

我在 .Net Core 3.1 中测试过以下代码:

    [HttpGet]
    [Route("sitemap/{id}/")]
    public IActionResult GetSitemap(Guid id)
    {
         // ToDo- Perform some operation
    }

路线:

http://localhost:6003/api/sitemap/6cb735b1-b2f0-45d1-a5b5-3a1615555555 // Works fine, Valid Request

http://localhost:6003/api/sitemap/6cb735b1-b2f0-45d1-a5b5-3a1615555555aab //Invalid Guid, Bad Request

作为传递 Guid 的替代方法,您可以将参数类型更改为字符串并在操作方法中处理类型检查:

        [HttpGet]
        [Route("sitemap/{id}/")]
        public IActionResult GetSitemap(string id)
        {
            if (id.Length == 36)
            {
                Guid value = Guid.Parse(id);
            }
            else
            {
                return BadRequest();
            }
         }

【讨论】:

  • 这在技术上是可行的,但在某种程度上它似乎是一个肮脏的解决方案。因为有了这个,我将不得不更改签名 + 为许多不同控制器中的许多方法添加额外的验证,这很容易出现人为错误。我相信会有一种更“正确”的方式在管道中的某个地方实现它一次,之后它将应用于整个 API。
  • 准确地说——“要么遵守框架规则,要么改变设计。”如果我们需要寻找好的架构和设计方法,我猜路由和调用登录需要根据框架进行重组,否则会抛出异常。
【解决方案2】:

我认为这篇Validating and passing controller-level parameters with ASP.NET MVC attribute routing 的文章可以解决您的问题。如果您可以使用非常有用的参数ActionExecutingContextActionExecutedContext 明智地覆盖OnActionExecutingOnActionExecuted 方法,那么ActionFilterAttribute 类非常好。

【讨论】:

    【解决方案3】:

    我想通了! 我对 ModelBinders 进行了更多研究,发现拥有 CustomModelBinder 可以完成工作。

    public class GuidModelBinder : IModelBinder
    {
        public Task BindModelAsync(ModelBindingContext bindingContext)
        {
            if (bindingContext == null)
            {
                throw new ArgumentNullException(nameof(bindingContext));
            }
    
            var modelName = bindingContext.ModelName;
    
            // Try to fetch the value of the argument by name
            var valueProviderResult = bindingContext.ValueProvider.GetValue(modelName);
    
            if (valueProviderResult == ValueProviderResult.None)
            {
                bindingContext.Result = ModelBindingResult.Failed();
                return Task.CompletedTask;
            }
    
    
            Guid parsedGuid;
            if (Guid.TryParse(valueProviderResult.FirstValue, out parsedGuid))
            {
                bindingContext.Result = ModelBindingResult.Success(parsedGuid);
            }
            else
            {
                bindingContext.Result = ModelBindingResult.Failed();
            }
            return Task.CompletedTask;
    
        }
    }
    

    始终返回“Task.CompletedTask”确保即使 ModelBindingResult 失败也会触发控制器操作。并且在测试时它完全按照预期工作 - 控制器方法被分配给 Guid? Id - Null 的值。

    为了不必在每个 ActionMethod 上添加 ModelBinder 属性,我创建了一个 CustomModelBinderProvider 以确保 CustomBinder 仅适用于 Nullable Guids(这是我需要的):

    public class GuidModelBinderProvider : IModelBinderProvider
    {
        public IModelBinder GetBinder(ModelBinderProviderContext context)
        {
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }
    
            if (context.Metadata.ModelType == typeof(Guid?))
            {
                return new GuidModelBinder();
            }
    
            return null;
        }
    }
    

    要添加提供程序,我将以下内容放入 Startup.cs

     services.AddControllers(op =>{
                op.ModelBinderProviders.Insert(0, new GuidModelBinderProvider());
            })
    

    【讨论】:

      猜你喜欢
      • 2020-05-20
      • 1970-01-01
      • 2020-10-06
      • 2017-10-17
      • 1970-01-01
      • 2021-03-03
      • 2022-01-09
      • 1970-01-01
      • 2020-04-06
      相关资源
      最近更新 更多