使用自定义DirectRouteProvider手动创建路由别名会导致"找到与请求匹配的多个操作"错误

时间:2014-11-07 16:08:14

标签: .net asp.net-web-api

我正在使用ASP.NET WebApi框架创建服务。我想支持基于URI扩展的内容协商,所以我在服务初始化代码中添加了以下内容:

public static class WebApiConfig
{
  public static void Register(HttpConfiguration config)
  {
    config.Formatters.JsonFormatter.AddUriPathExtensionMapping("json", "application/json");
    config.Formatters.XmlFormatter.AddUriPathExtensionMapping("xml", "application/xml");
  }
}

为此,我需要为每个控制器操作创建两个路由(我只使用基于属性的路由):

[Route("item/{id}/details")]
[Route("item/{id}/details.{ext}")]
[HttpGet]
public ItemDetail[] GetItemDetails(int id)
{
  return itemsService.GetItemDetails(id);
}

[Route("item/{name}")]
[Route("item/{name}.{ext}")]
[HttpPost]
public int CreateItem(string name)
{
  return itemsService.Create(name);
}

这很丑陋并且使代码不必要地长,因此我研究了一种在创建正常路由时自动添加带有扩展的路由的方法。我想出了IDirectRouteProvider的自定义实现,我可以在注册路由属性时使用它:

config.MapHttpAttributeRoutes(new AutomaticExtensionRouteProvider());

自定义路由提供程序如下所示:

public class AutomaticExtensionRouteProvider : DefaultDirectRouteProvider
{
    protected override IReadOnlyList<RouteEntry> GetActionDirectRoutes(
      HttpActionDescriptor actionDescriptor,
      IReadOnlyList<IDirectRouteFactory> factories,
      IInlineConstraintResolver constraintResolver)
    {
        var result = base.GetActionDirectRoutes(actionDescriptor, factories, constraintResolver);
        var list = new List<RouteEntry>(result);
        foreach(var route in result.Where(r => !r.Route.RouteTemplate.EndsWith(".{ext}")))
        {
            var newTemplate = route.Route.RouteTemplate + ".{ext}";
            if (!result.Any(r => r.Route.RouteTemplate == newTemplate))
            {
                var entry = new RouteEntry(null, new HttpRoute(newTemplate,
                    new HttpRouteValueDictionary(route.Route.Defaults),
                    new HttpRouteValueDictionary(route.Route.Constraints),
                    new HttpRouteValueDictionary(route.Route.DataTokens)));
                list.Add(entry);
            }
        }
        return list.AsReadOnly();
    }
}

问题在于这种方法主要是有效......但我发现它在一种情况下失败了:当路线的最后一部分是无约束参数时 。因此,对于前一个示例的控制器方法,GetItemDetails有效,但CreateItem方法不起作用,并抛出以下内容:

System.InvalidOperationException: Multiple actions were found that match the request: 
CreateItem on type FooBar.Api.Controllers.ItemsController
CreateItem on type FooBar.Api.Controllers.ItemsController
   at System.Web.Http.Controllers.ApiControllerActionSelector.ActionSelectorCacheItem.SelectAction(HttpControllerContext controllerContext)
   at System.Web.Http.Controllers.ApiControllerActionSelector.SelectAction(HttpControllerContext controllerContext)

我意识到可能导致错误的原因:随机字符串匹配{name}{name}.{ext}模式,因此WebApi引擎在尝试选择适当的路由时会阻塞。 但是,为什么它在显式指定属性中的两个路由时实际上有效?据我所知,我在AutomaticExtensionRouteProvider类中创建的路由与路由相同是使用显式属性创建的(调试似乎也证实了这一点)。

那么,发生了什么?任何帮助都感激不尽。谢谢!

1 个答案:

答案 0 :(得分:2)

好的,最后我自己搞清楚了。

事实证明路由分配了数字优先级,WebApi路由引擎用它来决定在发生冲突时实际使用哪条路由。为同一操作自动创建的路由始终具有不同的优先级,但我手动创建的路由具有与现有路由相同的优先级!

因此,解决方案是在GetActionDirectRoutes语句之后立即将以下内容添加到new RouteEntry

entry.Route.DataTokens["precedence"] = 
    ((decimal)route.Route.DataTokens["precedence"]) - 0.1M;