-->

Exception in HttpControllerDispatcher

2019-06-01 02:57发布

问题:

In my WebApiConfig::Register(...) I replaced the HttpControllerSelector with my own controller selector. When I fire off a POST request the SelectController member is correctly called and I return a ControllerDescriptor with the correct type of my controller. But then HttpControllerDispatcher is raising an exception saying "The given was not present in the dictionary." Anyone has an idea how to debug such error?

The complete exception is message is:

The given key was not present in the dictionary.","ExceptionType":"System.Collections.Generic.KeyNotFoundException","StackTrace":"   at System.Collections.Generic.Dictionary`2.get_Item(TKey key)\r\n   
at System.Web.Http.Controllers.ApiControllerActionSelector.ActionSelectorCacheItem.FindActionMatchRequiredRouteAndQueryParameters(IEnumerable`1 candidatesFound)\r\n   
at System.Web.Http.Controllers.ApiControllerActionSelector.ActionSelectorCacheItem.FindMatchingActions(HttpControllerContext controllerContext, Boolean ignoreVerbs)\r\n   
at System.Web.Http.Controllers.ApiControllerActionSelector.ActionSelectorCacheItem.SelectAction(HttpControllerContext controllerContext)\r\n   
at System.Web.Http.Controllers.ApiControllerActionSelector.SelectAction(HttpControllerContext controllerContext)\r\n   
at System.Web.Http.ApiController.ExecuteAsync(HttpControllerContext controllerContext, CancellationToken cancellationToken)\r\n   
at System.Web.Http.Dispatcher.HttpControllerDispatcher.<SendAsync>d__1.MoveNext()"

And here is my Controller Selector:

public class Namespace_HTTP_Controller_Selector : IHttpControllerSelector
{
    private readonly HttpConfiguration _configuration;
    private readonly Lazy<Dictionary<string, HttpControllerDescriptor>> _controller;

    public Namespace_HTTP_Controller_Selector(HttpConfiguration Config)
    {
        _configuration = Config;
        _controller = new Lazy<Dictionary<string, HttpControllerDescriptor>>(Initialize_Controller_Dictionary);
    }

    public HttpControllerDescriptor SelectController(HttpRequestMessage Request)
    {
        var Route_Data = Request.GetRouteData();
        if(Route_Data == null)
        {
            throw new HttpResponseException(HttpStatusCode.NotFound);
        }

        var Controller_Name = Get_Controller_Name(Route_Data);
        if(Controller_Name ==  null)
        {
            throw new HttpResponseException(HttpStatusCode.NotFound);
        }

        var Name_Space = Get_Version(Route_Data);
        if (Name_Space == null)
        {
            throw new HttpResponseException(HttpStatusCode.NotFound);
        }

        var Controller_Key = String.Format(CultureInfo.InvariantCulture, "{0}.{1}", Name_Space, Controller_Name);

        HttpControllerDescriptor Descriptor;
        if(_controller.Value.TryGetValue(Controller_Key, out Descriptor))
        {
            return Descriptor;
        }

        throw new HttpResponseException(HttpStatusCode.NotFound);
    }

    public IDictionary<string, HttpControllerDescriptor> GetControllerMapping()
    {
        return _controller.Value;
    }


    private Dictionary<string, HttpControllerDescriptor> Initialize_Controller_Dictionary()
    {
        var Dictionary = new Dictionary<string, HttpControllerDescriptor>(StringComparer.OrdinalIgnoreCase);

        var Assemblies_Resolver = _configuration.Services.GetAssembliesResolver();
        var Controller_Resolver = _configuration.Services.GetHttpControllerTypeResolver();

        var Controller_Types = Controller_Resolver.GetControllerTypes(Assemblies_Resolver);

        foreach(var ct in Controller_Types)
        {
            var Segments = ct.Namespace.Split(Type.Delimiter);

            var Controller_Name = ct.Name.Remove(ct.Name.Length - DefaultHttpControllerSelector.ControllerSuffix.Length);

            var Controller_Key = string.Format(CultureInfo.InvariantCulture, "{0}.{1}", Segments[Segments.Length - 1], Controller_Name);


            if(Dictionary.Keys.Contains(Controller_Key) == false)
            {
                Dictionary[Controller_Key] = new HttpControllerDescriptor(_configuration, ct.Name, ct);
            }
        }

        return Dictionary;
    }

    private T Get_Route_Variable<T>(IHttpRouteData Route_Data, string Name)
    {
        object Result;

        if(Route_Data.Values.TryGetValue(Name, out Result))
        {
            return (T)Result;
        }

        return default(T);
    }


    private string Get_Controller_Name(IHttpRouteData Route_Data)
    {

        var SubRoute = Route_Data.GetSubRoutes().FirstOrDefault();

        if( SubRoute == null )
        {
            return null;
        }

        var Data_Token_Value = SubRoute.Route.DataTokens.First().Value;
        if(Data_Token_Value == null)
        {
            return null;
        }


        var Controller_Name = ((HttpActionDescriptor[])Data_Token_Value).First().ControllerDescriptor.ControllerName.Replace("Controller", string.Empty);


        return Controller_Name;
    }

    private string Get_Version(IHttpRouteData Route_Data)
    {
        var Sub_Route_Data = Route_Data.GetSubRoutes().FirstOrDefault();
        if(Sub_Route_Data== null)
        {
            return null;
        }

        return Get_Route_Variable<string>(Sub_Route_Data, "apiVersion");
    }
}

回答1:

This is because you are not setting subroute data in request before returning descriptor.

HttpControllerDescriptor Descriptor;
if(_controller.Value.TryGetValue(Controller_Key, out Descriptor))
{
    var subRoutes = Route_Data.GetSubRoutes();
    IEnumerable<IHttpRouteData> filteredSubRoutes = subRoutes.Where(attrRouteData =>
    {
        HttpControllerDescriptor currentDescriptor = ((HttpActionDescriptor[])Route_Data.Route.DataTokens["actions"]).First().ControllerDescriptor;
        return currentDescriptor != null && currentDescriptor.ControllerName.Equals(Descriptor.ControllerName, StringComparison.OrdinalIgnoreCase);
    });
    Route_Data.Values["MS_SubRoutes"] = filteredSubRoutes.ToArray();
    return Descriptor;
}

Take a look at: Versioning ASP.NET Web API 2 with Media Types



回答2:

public class CustomSelectorController : DefaultHttpControllerSelector
    {
        HttpConfiguration _config;

        public CustomSelectorController(HttpConfiguration config) : base(config)
        {
            _config = config;
        }

        public override HttpControllerDescriptor SelectController(HttpRequestMessage request)
        {
            IHttpRouteData routeData = request.GetRouteData();
            IEnumerable<IHttpRouteData> attributeSubRoutes = routeData.GetSubRoutes();

            var actions = attributeSubRoutes.LastOrDefault()?.Route?.DataTokens["actions"] as HttpActionDescriptor[];

            var controllerName = "";
            if (actions != null && actions.Length > 0)
            {
                controllerName = actions[0].ControllerDescriptor.ControllerName;
            }

            IEnumerable<string> headerValues = null;
            //Custom Header Name to be check version
            if (request.Headers.TryGetValues("Accept-Version", out headerValues))
            {
                var apiVersion = headerValues.First().ToUpper();
                if (apiVersion == "V2")
                {
                    controllerName = controllerName + apiVersion;
                }
            }

            HttpControllerDescriptor controllerDescriptor = null;

            IEnumerable<IHttpRouteData> filteredSubRoutes = attributeSubRoutes.Where(attrRouteData =>
            {
                HttpControllerDescriptor currentDescriptor = GetControllerDescriptor(attrRouteData);

                bool match = currentDescriptor.ControllerName.Equals(controllerName);

                if (match && (controllerDescriptor == null))
                {
                    controllerDescriptor = currentDescriptor;
                }

                return match;
            });

            routeData.Values["MS_SubRoutes"] = filteredSubRoutes.ToArray();
            return controllerDescriptor;
        }
        private HttpControllerDescriptor GetControllerDescriptor(IHttpRouteData routeData)
        {
            return ((HttpActionDescriptor[])routeData.Route.DataTokens["actions"]).First().ControllerDescriptor;
        }
    }