如何更改 .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 的文章可以解决您的问题。如果您可以使用非常有用的参数ActionExecutingContext
和ActionExecutedContext
明智地覆盖OnActionExecuting
和OnActionExecuted
方法,那么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 中的路由错误的主要内容,如果未能解决你的问题,请参考以下文章