1

I'm trying to use a custom controller selector for my Web API 2.2 project that uses namespace selection for versioning. The project is self hosting.

There are many articles regarding this on the internet, but most of them cover only one use case (i.e. just the conventional routing, or only attribute routing but without route prefixes, etc.).

In my case i would like to use all the cookies. In other words, what my selector has to do is support these:

namespace WebProject.Controllers.v1
{
    [RoutePrefix("accounts")]
    public class AccountsController : ApiController
    {
        [Route("")]
        public IHttpActionResult Get()
        {
            var accounts = accountList.ConvertAll(LogicMgr.ConvertModelToView);
            return Ok(accounts);
        }
    }
}

namespace WebProject.Controllers.v2
{
    [RoutePrefix("accounts")]
    public class AccountsController : ApiController
    {
        [Route("")]
        public IHttpActionResult Get()
        {
            var accounts = accountList.ConvertAll(LogicMgr.ConvertModelToView);
            return Ok(accounts);
        }
    }
}

The main part of any selector is to extract the controller name out of request. In most articles this is done through dictionary with a specific key ("controller"). But that works only with conventional template routing.

When dealing with attribute routing it is recommended to use route constraints and keep the default controller selector. Doesn't work for me.

Currently i extract the controller name like this:

static string GetControllerName(IHttpRouteData route)
{
    var controller = route.GetSubRoutes()
        .Select(s => new
        {
            Name = ((HttpActionDescriptor[])s.Route.DataTokens.Single(t => t.Value is HttpActionDescriptor[]).Value)
            .FirstOrDefault().ControllerDescriptor.ControllerName
        }).FirstOrDefault();

    return controller.Name;
}

Seems a bit hacky to me, but it works and doesn't use any magic constants (like when using "actions" as a key in dictionary for searching).

Anyway. After building the list of controllers (through resolvers), extracting controller name from request and finding the right HttpControllerDescriptor based on version, i need to return it from SelectController() method.

And this is where i get KeyNotFoundException exception. After searching i found only one mention of this (Exception in HttpControllerDispatcher)

The solution is to fill correct subroute data in the incoming request. Now i haven't found official mentions of this or any other on the internet. Only this post.

What i came up with is another hacky piece of code:

static void FillSubRoutes(IHttpRouteData route, string version)
{
    var subroutes = route.GetSubRoutes()
        .Where(s =>
            ((HttpActionDescriptor[])s.Route.DataTokens.Single(t => t.Value is HttpActionDescriptor[]).Value)
                .Any(d => d.ControllerDescriptor.ControllerType.Namespace.EndsWith(version, StringComparison.OrdinalIgnoreCase)));
    var values = route.Values.Single(v => v.Value is IHttpRouteData[]);
    route.Values[values.Key] = subroutes.ToArray();
}

Initially a route has 4 subroutes in my case (2 for each version of controller). What happens here is i search for subroutes which have controller descriptor with a specific version in it's controller's namespace at the end part. In my case it's always 2 of them (one for "GET" and one for "POST" requests, which i ommited in my previous controller example for simplicity).

So finally i get to my questions:

  1. Is there any other (preferable more official and proper) way to extract a controller name from request?
  2. Is there any other (preferable more official and proper) way to solve the KeyNotFoundException exception, which happens if i don't fill subroutes?
  3. Will all of this work when hosted under IIS?
Community
  • 1
  • 1
Kasbolat Kumakhov
  • 607
  • 1
  • 11
  • 30

0 Answers0