我正在使用具有属性路由的ASP.NET Web API 2,但我似乎无法使用媒体类型application / vnd.company [.version] .param [json]获得版本控制.
我收到以下错误:
The given key was not present in the dictionary.
它来源于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”)]的所有控制器,显然会抛出异常.
只是为了确保选择了正确的控制器
我不知道这是否是一个错误,但是使用路由[RoutePrefix(“api / v1 / people”)]到版本API让我很伤心.
注意:没有属性路由,这很有效.
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";
}
}
解决方法
感谢您分享您的代码.我已经修改了您的版本控制器选择器,如下所示,并尝试了一些情况,它似乎工作正常.您可以尝试如下更新您的控制器选择器,看看它是否有效?
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,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);
}