使用媒体类型对ASP.NET Web API 2进行版本控制

我正在使用ASP.NET Web API 2进行属性路由,但我似乎无法使用媒体类型application/vnd.company[.version].param[+json]进行版本控制。

在此处输入图像描述

我收到以下错误:

给定的密​​钥不在字典中。

它源于在FindActionMatchRequiredRouteAndQueryParameters()方法中测试键_actionParameterNames[descriptor]

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

来源: ApiControllerActionSelector.cs

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

 [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() { } } 

您不能使用自定义ApiVersioningSelector : DefaultHttpControllerSelector因为它将测试具有相同[RoutePrefix("api/people")]所有控制器的密钥,显然会抛出exception。

在此处输入图像描述在此处输入图像描述

只是为了确保选择了正确的控制器

在此处输入图像描述

我不知道这是不是一个错误,但使用路由[RoutePrefix("api/v1/people")] to version API让我感到难过。

注意:没有属性路由,这很好用。

UPDATE

 public class ApiVersioningSelector : DefaultHttpControllerSelector { private HttpConfiguration _HttpConfiguration; public ApiVersioningSelector(HttpConfiguration httpConfiguration) : base(httpConfiguration) { _HttpConfiguration = httpConfiguration; } public override HttpControllerDescriptor SelectController(HttpRequestMessage request) { IDictionary 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\.([az]+)\.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"; } } 

感谢您分享您的代码。 我已经修改了你的版本控制器选择器,如下所示并尝试了一些场景,它似乎运行良好。 您可以尝试更新下面的控制器选择器,看看它是否有效?

  public override HttpControllerDescriptor SelectController(HttpRequestMessage request) { HttpControllerDescriptor controllerDescriptor = null; // get list of all controllers provided by the default selector IDictionary controllers = GetControllerMapping(); IHttpRouteData routeData = request.GetRouteData(); if (routeData == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } //check if this route is actually an attribute route IEnumerable attributeSubRoutes = routeData.GetSubRoutes(); var apiVersion = GetVersionFromMediaType(request); if (attributeSubRoutes == null) { string controllerName = GetRouteVariable(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 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(IHttpRouteData routeData, string name) { object result = null; if (routeData.Values.TryGetValue(name, out result)) { return (T)result; } return default(T); }