如何更改 .NET Core 3.1 API 中的路由错误

Posted

技术标签:

【中文标题】如何更改 .NET Core 3.1 API 中的路由错误【英文标题】:How to change routing errors in .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”,对吗? 是的,您的建议似乎准确地总结了我的需要。 【参考方案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());
        )

【讨论】:

以上是关于如何更改 .NET Core 3.1 API 中的路由错误的主要内容,如果未能解决你的问题,请参考以下文章