版本的ASP.NET Web API 2媒体类型 [英] Versioning ASP.NET Web API 2 with Media Types

查看:186
本文介绍了版本的ASP.NET Web API 2媒体类型的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我使用的ASP.NET Web API 2属性的路由,但我似乎无法使用的媒体类型,以获得版本应用程序/ vnd.company [.version] .PARAM [+ JSON] 工作

我收到以下错误:


  

给定的关键是不是在词典present。


从测试的关键源于 _actionParameterNames [描述] FindActionMatchRequiredRouteAndQueryParameters()方法。

 的foreach(在candidatesFound VAR候选人)
{
        HttpActionDescriptor描述符= candidate.ActionDescriptor;
        如果(IsSubset(_actionParameterNames [描述符],candidate.CombinedParameterNames))
        {
            matches.Add(候选人);
        }
}

来源:<一个href=\"http://aspnetwebstack.$c$cplex.com/SourceControl/latest#src/System.Web.Http/Controllers/ApiControllerActionSelector.cs\">ApiControllerActionSelector.cs

进一步调试后,我已经意识到,如果你有两个控制器

  [路线preFIX(API /人)]
公共类把PeopleController:BaseApiController
{
    [路线()]
    公众的Htt presponseMessage GetPeople()
    {
    }    [路线(标识符/(编号))
    公众的Htt presponseMessage GetPersonById()
    {
    }
}[途径preFIX(API /人)]
公共类PeopleV2Controller:BaseApiController
{
    [路线()]
    公众的Htt presponseMessage GetPeople()
    {
    }    [路线(标识符/(编号))
    公众的Htt presponseMessage GetPersonById()
    {
    }
}

您不能使用自定义的<$c$c>ApiVersioningSelector :DefaultHttpControllerSelector ,因为它将测试键,如上所述,从具有所有控制器相同 [路线preFIX(API /人)] ,显然是会抛出异常。


只是为了选择合适的控制器可以肯定

我不知道这是否是一个错误,但使用的路线 [路线preFIX(API / V1 /人)]到版本API 使得我难过。

注意::此属性没有路由的伟大工程

更新

 公共类ApiVersioningSelector:DefaultHttpControllerSelector
    {
        私人HttpConfiguration _HttpConfiguration;
        公共ApiVersioningSelector(HttpConfiguration httpConfiguration)
            :基地(httpConfiguration)
        {
            _HttpConfiguration = httpConfiguration;
        }        公众覆盖HttpControllerDescriptor SelectController(HTT prequestMessage要求)
        {
            IDictionary的&LT;字符串,HttpControllerDescriptor&GT;控制器= GetControllerMapping();            VAR attributedRoutesData = request.GetRouteData()GetSubRoutes()。
            变种subRouteData = attributedRoutesData.LastOrDefault(); // LastOrDefault()将获得把PeopleController,FirstOrDefault会得到人民{}版本控制器,我们不希望            VAR行动=(ReflectedHttpActionDescriptor [])subRouteData.Route.DataTokens [行动];
            变种controllerName =行动[0] .ControllerDescriptor.ControllerName;
            //对于没有属性的路由控制器名称
            // VAR controllerName =(字符串)routeData.Values​​ [控制器];            HttpControllerDescriptor oldControllerDescriptor;
            如果(controllers.TryGetValue(controllerName,出oldControllerDescriptor))
            {
                VAR apiVersion = GetVersionFromMediaType(请求);                变种newControllerName = String.Concat(controllerName,V,apiVersion);                HttpControllerDescriptor newControllerDescriptor;
                如果(controllers.TryGetValue(newControllerName,出newControllerDescriptor))
                {
                    返回newControllerDescriptor;
                }
                返回oldControllerDescriptor;
            }
            返回null;
        }
        私人字符串GetVersionFromMediaType(HTT prequestMessage要求)
        {
            VAR acceptHeader = request.Headers.Accept;            VAR regularEx pression =新的正则表达式(@应用程序\\ /越南盾\\ .mycompany \\([A-Z] +)\\。V([0-9] +)\\ + JSON
                RegexOptions.IgnoreCase);            的foreach(在acceptHeader VAR MIME)
            {
                VAR匹配= regularEx pression.Match(mime.MediaType);
                如果(匹配!= NULL)
                {
                    返回match.Groups [2]。价值;
                }
            }
            回到1;
        }    }


解决方案

谢谢你的分享你的code。我在下面修改您的版本控制器选择喜欢并试图某些情况下,它似乎运作良好。你可以尝试像下面更新您的控制器选择,看看它是否起作用?

 公众覆盖HttpControllerDescriptor SelectController(HTT prequestMessage要求)
    {
        HttpControllerDescriptor controllerDescriptor = NULL;        //获取由默认的选择提供的所有控制器列表
        IDictionary的&LT;字符串,HttpControllerDescriptor&GT;控制器= GetControllerMapping();        IHttpRouteData的RouteData = request.GetRouteData();        如果(的RouteData == NULL)
        {
            抛出新的Htt presponseException(的HTTPStatus code.NotFound);
        }        //检查,如果这条路其实是一个属性路径
        IEnumerable的&LT; IHttpRouteData&GT; attributeSubRoutes = routeData.GetSubRoutes();        VAR apiVersion = GetVersionFromMediaType(请求);        如果(attributeSubRoutes == NULL)
        {
            字符串controllerName = GetRouteVariable&LT;串GT;(的RouteData,控制器);
            如果(controllerName == NULL)
            {
                抛出新的Htt presponseException(的HTTPStatus code.NotFound);
            }            串newControllerName = String.Concat(controllerName,V,apiVersion);            如果(controllers.TryGetValue(newControllerName,出controllerDescriptor))
            {
                返回controllerDescriptor;
            }
            其他
            {
                抛出新的Htt presponseException(的HTTPStatus code.NotFound);
            }
        }
        其他
        {
            //我们要找出所有控制器的描述控制器类型名以
            //下面的后缀(例如:CustomersV1)
            串newControllerNameSuffix = String.Concat(V形,apiVersion);            IEnumerable的&LT; IHttpRouteData&GT; filteredSubRoutes = attributeSubRoutes.Where(attrRouteData =&GT;
            {
                HttpControllerDescriptor currentDescriptor = GetControllerDescriptor(attrRouteData);                布尔匹配= currentDescriptor.ControllerName.EndsWith(newControllerNameSuffix);                如果(匹配和放大器;及(controllerDescriptor == NULL))
                {
                    controllerDescriptor = currentDescriptor;
                }                返回匹配;
            });            routeData.Values​​ [MS_SubRoutes] = filteredSubRoutes.ToArray();
        }        返回controllerDescriptor;
    }    私人HttpControllerDescriptor GetControllerDescriptor(IHttpRouteData的RouteData)
    {
        返回((HttpActionDescriptor [])routeData.Route.DataTokens [行动])(第)ControllerDescriptor。;
    }    //从路由数据的值,如果present。
    私有静态ŧGetRouteVariable&LT; T&GT;(IHttpRouteData的RouteData,字符串名称)
    {
        对象result = NULL;
        如果(routeData.Values​​.TryGetValue(姓名,出结果))
        {
            回报(T)的结果;
        }
        返回默认(T);
    }

I'm using ASP.NET Web API 2 with attribute routing but i can't seem to get the versioning using media types application/vnd.company[.version].param[+json] to work.

I get the following error:

The given key was not present in the dictionary.

which originates from testing the key _actionParameterNames[descriptor] in FindActionMatchRequiredRouteAndQueryParameters() method.

foreach (var candidate in candidatesFound)
{
        HttpActionDescriptor descriptor = candidate.ActionDescriptor;
        if (IsSubset(_actionParameterNames[descriptor], candidate.CombinedParameterNames))
        {
            matches.Add(candidate);
        }
}

Source: ApiControllerActionSelector.cs

After further debugging I've realized that if you have two controllers

[RoutePrefix("api/people")]
public class PeopleController : BaseApiController
{
    [Route("")]
    public HttpResponseMessage GetPeople()
    {
    }

    [Route("identifier/{id}")]
    public HttpResponseMessage GetPersonById()
    {
    }
}

[RoutePrefix("api/people")]
public class PeopleV2Controller : BaseApiController
{     
    [Route("")]
    public HttpResponseMessage GetPeople()
    {
    } 

    [Route("identifier/{id}")]
    public HttpResponseMessage GetPersonById()
    {
    }
}

you can't use your custom ApiVersioningSelector : DefaultHttpControllerSelector because it will test the keys,as stated above, from all controllers having the same [RoutePrefix("api/people")] and obviously an exception will be thrown.

Just to be sure the right controller was selected

I don't know if this is a bug, but using route [RoutePrefix("api/v1/people")] to version API makes me sad.

NOTE: This works great without attribute routing.

UPDATE

public class ApiVersioningSelector : DefaultHttpControllerSelector
    {
        private HttpConfiguration _HttpConfiguration;
        public ApiVersioningSelector(HttpConfiguration httpConfiguration)
            : base(httpConfiguration)
        {
            _HttpConfiguration = httpConfiguration;
        }



        public override HttpControllerDescriptor SelectController(HttpRequestMessage request)
        {
            IDictionary<string, HttpControllerDescriptor> controllers = GetControllerMapping();                                             

            var attributedRoutesData = request.GetRouteData().GetSubRoutes();
            var subRouteData = attributedRoutesData.LastOrDefault(); //LastOrDefault() will get PeopleController, FirstOrDefault will get People{version}Controller which we don't want

            var actions = (ReflectedHttpActionDescriptor[])subRouteData.Route.DataTokens["actions"];
            var controllerName = actions[0].ControllerDescriptor.ControllerName;


            //For controller name without attribute routing
            //var controllerName = (string)routeData.Values["controller"];

            HttpControllerDescriptor oldControllerDescriptor;
            if (controllers.TryGetValue(controllerName, out oldControllerDescriptor))
            {
                var apiVersion = GetVersionFromMediaType(request);

                var newControllerName = String.Concat(controllerName, "V", apiVersion);

                HttpControllerDescriptor newControllerDescriptor;
                if (controllers.TryGetValue(newControllerName, out newControllerDescriptor))
                {
                    return newControllerDescriptor;
                }
                return oldControllerDescriptor;
            }
            return null;
        }


        private string GetVersionFromMediaType(HttpRequestMessage request)
        {
            var acceptHeader = request.Headers.Accept;

            var regularExpression = new Regex(@"application\/vnd\.mycompany\.([a-z]+)\.v([0-9]+)\+json",
                RegexOptions.IgnoreCase);

            foreach (var mime in acceptHeader)
            {
                var match = regularExpression.Match(mime.MediaType);
                if (match != null)
                {
                    return match.Groups[2].Value;
                }
            }
            return "1";
        }

    }

解决方案

Thanks for the sharing your code. I have modified your version controller selector like below and tried some scenarios and it seems to work well. Can you try updating your controller selector like below and see if it works?

    public override HttpControllerDescriptor SelectController(HttpRequestMessage request)
    {
        HttpControllerDescriptor controllerDescriptor = null;

        // get list of all controllers provided by the default selector
        IDictionary<string, HttpControllerDescriptor> controllers = GetControllerMapping();

        IHttpRouteData routeData = request.GetRouteData();

        if (routeData == null)
        {
            throw new HttpResponseException(HttpStatusCode.NotFound);
        }

        //check if this route is actually an attribute route
        IEnumerable<IHttpRouteData> attributeSubRoutes = routeData.GetSubRoutes();

        var apiVersion = GetVersionFromMediaType(request);

        if (attributeSubRoutes == null)
        {
            string controllerName = GetRouteVariable<string>(routeData, "controller");
            if (controllerName == null)
            {
                throw new HttpResponseException(HttpStatusCode.NotFound);
            }

            string newControllerName = String.Concat(controllerName, "V", apiVersion);

            if (controllers.TryGetValue(newControllerName, out controllerDescriptor))
            {
                return controllerDescriptor;
            }
            else
            {
                throw new HttpResponseException(HttpStatusCode.NotFound);
            }
        }
        else
        {
            // we want to find all controller descriptors whose controller type names end with
            // the following suffix(ex: CustomersV1)
            string newControllerNameSuffix = String.Concat("V", apiVersion);

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

                bool match = currentDescriptor.ControllerName.EndsWith(newControllerNameSuffix);

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

    // Get a value from the route data, if present.
    private static T GetRouteVariable<T>(IHttpRouteData routeData, string name)
    {
        object result = null;
        if (routeData.Values.TryGetValue(name, out result))
        {
            return (T)result;
        }
        return default(T);
    }

这篇关于版本的ASP.NET Web API 2媒体类型的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆