__Scenario__:
Implementing a custom IHttpControllerSelector to support namespace based versioning of controllers which are attribute routed.
__Background__:
Since DefaultHttpControllerSelector filters out duplicate controllers (controllers with same name), I am creating a custom IHttpControllerSelector(modified MikeWasson’s original selector) which considers namespace information too.
__Issue__:
My workaround works, but the following experience(highlighed comments) is not desired as a user needs to know the internal guts of attribute routing. I believe the following behavior should be improved irrespective of whether the bug(#1249) is fixed in DefaultHttpControllerSelector or not.
```
public HttpControllerDescriptor SelectController(HttpRequestMessage request)
{
IHttpRouteData routeData = request.GetRouteData();
if (routeData == null)
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
//--------------------------------------Start--------------------------
// For attribute routes
string subRoutesKey = "MS_SubRoutes";
if (routeData.Values.ContainsKey(subRoutesKey))
{
// RouteCollectionRouteData is internal
IEnumerable<IHttpRouteData> attributedRoutesData = routeData.Values[subRoutesKey] as IEnumerable<IHttpRouteData>;
if (attributedRoutesData != null && attributedRoutesData.Count() > 0)
{
IHttpRouteData subRouteData = attributedRoutesData.FirstOrDefault();
if (subRouteData != null)
{
ReflectedHttpActionDescriptor[] actions = (ReflectedHttpActionDescriptor[])subRouteData.Route.DataTokens["actions"];
return actions[0].ControllerDescriptor;
}
}
}
//--------------------------------------End--------------------------
//for non-attributed routes
string controllerName = GetRouteVariable<string>(routeData, ControllerKey);
if (controllerName == null)
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
// Check if the current request is for a versioned controller or not based
// on the presence of the 'version' key in route data.
string key = null;
string versionName = GetRouteVariable<string>(routeData, VersionKey);
if(!string.IsNullOrEmpty(versionName))
{
// Find a matching controller.
key = String.Format(CultureInfo.InvariantCulture, "{0}.{1}", versionName, controllerName);
}
else
{
key = controllerName;
}
HttpControllerDescriptor controllerDescriptor;
if (_controllers.Value.TryGetValue(key, out controllerDescriptor))
{
return controllerDescriptor;
}
else if (_duplicates.Contains(key))
{
throw new HttpResponseException(
request.CreateErrorResponse(HttpStatusCode.InternalServerError,
"Multiple controllers were found that match this request."));
}
else
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
}
```
Comments: There is no much sense in implementing the scenario above. One can simply attribute route the path right above the controller. So namespace is not a really useful one. Lets see if we have another interesting scenario to address here.
Implementing a custom IHttpControllerSelector to support namespace based versioning of controllers which are attribute routed.
__Background__:
Since DefaultHttpControllerSelector filters out duplicate controllers (controllers with same name), I am creating a custom IHttpControllerSelector(modified MikeWasson’s original selector) which considers namespace information too.
__Issue__:
My workaround works, but the following experience(highlighed comments) is not desired as a user needs to know the internal guts of attribute routing. I believe the following behavior should be improved irrespective of whether the bug(#1249) is fixed in DefaultHttpControllerSelector or not.
```
public HttpControllerDescriptor SelectController(HttpRequestMessage request)
{
IHttpRouteData routeData = request.GetRouteData();
if (routeData == null)
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
//--------------------------------------Start--------------------------
// For attribute routes
string subRoutesKey = "MS_SubRoutes";
if (routeData.Values.ContainsKey(subRoutesKey))
{
// RouteCollectionRouteData is internal
IEnumerable<IHttpRouteData> attributedRoutesData = routeData.Values[subRoutesKey] as IEnumerable<IHttpRouteData>;
if (attributedRoutesData != null && attributedRoutesData.Count() > 0)
{
IHttpRouteData subRouteData = attributedRoutesData.FirstOrDefault();
if (subRouteData != null)
{
ReflectedHttpActionDescriptor[] actions = (ReflectedHttpActionDescriptor[])subRouteData.Route.DataTokens["actions"];
return actions[0].ControllerDescriptor;
}
}
}
//--------------------------------------End--------------------------
//for non-attributed routes
string controllerName = GetRouteVariable<string>(routeData, ControllerKey);
if (controllerName == null)
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
// Check if the current request is for a versioned controller or not based
// on the presence of the 'version' key in route data.
string key = null;
string versionName = GetRouteVariable<string>(routeData, VersionKey);
if(!string.IsNullOrEmpty(versionName))
{
// Find a matching controller.
key = String.Format(CultureInfo.InvariantCulture, "{0}.{1}", versionName, controllerName);
}
else
{
key = controllerName;
}
HttpControllerDescriptor controllerDescriptor;
if (_controllers.Value.TryGetValue(key, out controllerDescriptor))
{
return controllerDescriptor;
}
else if (_duplicates.Contains(key))
{
throw new HttpResponseException(
request.CreateErrorResponse(HttpStatusCode.InternalServerError,
"Multiple controllers were found that match this request."));
}
else
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
}
```
Comments: There is no much sense in implementing the scenario above. One can simply attribute route the path right above the controller. So namespace is not a really useful one. Lets see if we have another interesting scenario to address here.