MVC.ApiExplorer未发现的有效路由

时间:2013-01-24 23:44:44

标签: asp.net-mvc asp.net-web-api asp.net-mvc-routing asp.net-mvc-apiexplorer

使用 ASP.NET Web API帮助页面和相关的 MVC.ApiExplorer 时,我有可通过http访问但ApiExplorer未发现的有效路由。仅当使用常规路由规则时才会找到这些路由。使用更具体的规则(与普通规则一起)似乎隐藏了ApiExplorer的路线。

在三个规则的示例情况下,两个路由与控制器方法上的GET和POST操作相关,不采用查询参数进行MIA。

public class SomeControllerController : ApiController
{
    [HttpPost] public HttpResponseMessage Post(PostObject value) { ... }
    [HttpGet] public IEnumerable<DisplayObject> GetAll() { ... }
    [HttpGet] public DisplayObject GetById(string id) { ... }
}

使用

的路由规则时
routes.MapHttpRoute(
    name: "ApiDefault",
    routeTemplate: "api/{controller}/{id}",
    defaults: new
              {
                  id = RouteParameter.Optional
              }
    );

Api Explorer将路由适当地发现为

  • POST:api / SomeController
  • GET:api / SomeController
  • 获取:api / SomeController / {id}

然后添加不太通用且更有意义的规则

routes.MapHttpRoute(
    name: "ApiSomeControllerDefault",
    routeTemplate: "api/somecontroller/{id}",
    defaults: new
              {
                controller = "SomeController",
                id = RouteParameter.Optional
              }
    );

routes.MapHttpRoute(
    name: "ApiDefault",
    routeTemplate: "api/{controller}/{id}",
    defaults: new
              {
                  id = RouteParameter.Optional
              }
    );

Api Explorer只返回

  • GET:api / somecontroller / {id}

是什么导致我的某些路线无法找到?

修改 Link to Issue Report on ApiExplorer project page

2 个答案:

答案 0 :(得分:5)

我相信你所看到的是ApiExplorer的一个已知错误。发生的事情是ApiExplorer遍历路由集合中的每个路由,并检查控制器及其操作是否可以解决。

在这种情况下,例如,上述两条路线都可以探索“GetById”动作,ApiExplorer错误地假定由于模糊匹配而导致冲突,并尝试过滤掉重复动作,在这种情况下导致所有操作被过滤/删除。由于这个错误发生在ApiExplorer(它是主要的WebAPI核心的一部分),我担心我们不能很快修复它。

答案 1 :(得分:5)

虽然ASP.NET Web API团队没有修复此错误,但我正在使用自己的哑修复程序。

IApiExplorer的扩展方法与ApiDescriptions类中的原始ApiExplorer实现相同,但不是删除不同路由的重复操作,而是返回具有不同ID的操作(HTTP方法+路由)。因此,无论路由计数如何,它都会返回所有已声明的操作。

而且,是的,它无耻地使用反射来调用私有方法。

public static class WebApiExtensions
{
    public static Collection<ApiDescription> GetAllApiDescriptions(this IApiExplorer apiExplorer, HttpConfiguration httpConfig)
    {
        if (!(apiExplorer is ApiExplorer))
        {
            return apiExplorer.ApiDescriptions;
        }

        IList<ApiDescription> apiDescriptions = new Collection<ApiDescription>();
        var controllerSelector = httpConfig.Services.GetHttpControllerSelector();
        var controllerMappings = controllerSelector.GetControllerMapping();

        if (controllerMappings != null)
        {
            foreach (var route in httpConfig.Routes)
            {
                typeof(ApiExplorer).GetMethod("ExploreRouteControllers",
                    bindingAttr: BindingFlags.Instance | BindingFlags.NonPublic,
                    binder: null,
                    types: new[] {typeof(IDictionary<string, HttpControllerDescriptor>), typeof(IHttpRoute), typeof(Collection<ApiDescription>)},
                    modifiers: null
                ).Invoke(apiExplorer, new object[] {controllerMappings, route, apiDescriptions});
            }

            apiDescriptions = apiDescriptions
                .GroupBy(api => api.ID.ToLower())
                .Select(g => g.First())
                .ToList();
        }

        return new Collection<ApiDescription>(apiDescriptions);
    }
}

它易于使用:

var apiDescriptions = apiExplorer.GetAllApiDescriptions(httpConfig);
为可测试性添加了

HttpConfiguration参数。如果您不关心它,请删除该参数,然后直接在扩展方法中使用GlobalConfiguration.HttpConfiguration