12

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

在此处输入图像描述

我收到以下错误:

字典中不存在给定的键。

这源于测试方法中的_actionParameterNames[descriptor]关键FindActionMatchRequiredRouteAndQueryParameters()

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")]显然会抛出异常。

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

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

在此处输入图像描述

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

注意:这在没有属性路由的情况下效果很好。

更新

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

    }
4

1 回答 1

18

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

    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);
    }
于 2013-11-09T19:57:40.080 回答