Valid routes not discovered by MVC.ApiExplorer

痴心易碎 提交于 2019-12-04 02:45:05
Kiran Challa

I believe what you are seeing is a known bug with ApiExplorer. What's happening is that the ApiExplorer goes through each route in the route collection and checks if the controller and its actions can be resolved.

In this case, for example, the action "GetById" can be explored by both the above routes, which ApiExplorer incorrectly assumes to be causing a conflict due to ambiguous matching and it tries to filter out duplicate actions, which in this case is causing all the actions to be filtered/removed. Since this bug is in ApiExplorer(which is part of main WebAPI core), i am afraid we cannot fix it anytime soon.

While this bug is not fixed by ASP.NET Web API team, I'm using my own dumb fix.

My extension method for IApiExplorer is doing the same things as original ApiDescriptions implementation in ApiExplorer class, but instead of removing duplicate actions for different routes, it just returns actions with distinct ID (HTTP method + route). So it returns all declared actions, regardless of the routes count.

And, yes, it shamelessly uses reflection to call the private method.

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);
    }
}

It's easy to use:

var apiDescriptions = apiExplorer.GetAllApiDescriptions(httpConfig);

HttpConfiguration parameter added for testability. If you don't care about it, remove the parameter and just use GlobalConfiguration.HttpConfiguration in the extension method directly.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!