4

I have a controller that accepts some dependency as a constructor argument:

public class AccountsController : ApiController
{
    public AccountsController(IAccountsService accountService)
    {
        this.accountService = accountService;
    }
    // actions
}

public interface IAccountsService
{
    IEnumerable<AccountDto> GetAccounts(string userName);
}

To resolve this dependency I use Unity.WebApi package:

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        // other configuration
        config.DependencyResolver = new UnityDependencyResolver(myContainer);
    }
}

I have two different implementations of IAccountsService and I'd like to expose both of them using the same controller class. From the routing perspective, I'd like to use different controller-level paths and the same underlying path structure for actions and parameters.

My way is to inherit two controllers from AccountsController and register them in UnityContainer to use different IAccountsService implementations.

public class Accounts1Controller : AccountsController
{
    public Accounts1Controller([Dependency("Service1")]IAccountsService accountService) :
        base(accountService) { }
}

public class Accounts2Controller : AccountsController
{
    public Accounts2Controller([Dependency("Service2")]IAccountsService accountService) :
        base(accountService) { }
}

Is there a more straightforward way to do this?

I'd prefer to make the controller(s) container-unaware and to avoid the redundant inheritance (regardless to DI framework - Unity is not a single option).

11
  • That is not very clear. Controller accepts one object as parameter and has one field to hold a reference to it. How do you envision it being able to use both implementations at the same time then? Commented Oct 10, 2016 at 8:43
  • @Andrei, the controller exposes a set of actions, that utilize IAccountsService as a data source. I'm going to make same web APIs for both implementations of IAccountsService. The set of actions is the same for both implementations. Commented Oct 10, 2016 at 8:50
  • 3
    I would suggest using the Proxy design pattern. The Proxy class would have both the implementations of IAccountsService and it resolves which one to be used. To DI framework you would give the implementation of Proxy class. Commented Oct 10, 2016 at 9:06
  • @Andrei, added a possible way, see my edits. Commented Oct 10, 2016 at 9:15
  • @stop-cran, Unity has a concept of named dependencies. This along with what L J suggested should be a good option for you Commented Oct 10, 2016 at 9:17

1 Answer 1

2

Here is one way to do it:

Let's say that the two routes are as follows:

config.Routes.MapHttpRoute(
    name: "DefaultApi",
    routeTemplate: "api/{controller}/{id}",
    defaults: new { id = RouteParameter.Optional }
);

config.Routes.MapHttpRoute(
    name: "DefaultApi2",
    routeTemplate: "api2/{controller}/{id}",
    defaults: new { id = RouteParameter.Optional }
);

You can make Unity resolve the correct service based on the Url of the request like this:

//Map IAccountsService to AccountsService1 with name Service1
container.RegisterType<IAccountsService, AccountsService1>("Service1");

//Map IAccountsService to AccountsService2 with name Service2
container.RegisterType<IAccountsService, AccountsService2>("Service2");

//At composition time, map IAccountsService to appropriate
//service based on Url
container.RegisterType<IAccountsService>(new InjectionFactory(c =>
{
    var pathAndQuery = HttpContext.Current.Request.Url.PathAndQuery;

    if(pathAndQuery.StartsWith("/api2"))
        return c.Resolve<IAccountsService>("Service2");
    else if(pathAndQuery.StartsWith("/api"))
        return c.Resolve<IAccountsService>("Service1");

    throw new Exception("Unexpected Url");
}));

UPDATE:

In the case where Self-Hosting is used, HttpContext.Current would be null.

What you can do is create a custom IHttpControllerActivator. This will allow you customize the way controllers are created in a context where the current HttpRequestMessage is available.

Here is an example of such custom IHttpControllerActivator:

public class MyControllerActivator : IHttpControllerActivator
{
    public IHttpController Create(
        HttpRequestMessage request,
        HttpControllerDescriptor controllerDescriptor,
        Type controllerType)
    {
        if (controllerType == typeof (ValuesController))
        {
            var pathAndQuery = request.RequestUri.PathAndQuery;

            IAccountsService svc;

            if (pathAndQuery.StartsWith("/api2"))
                svc = new Service2();
            else if (pathAndQuery.StartsWith("/api"))
                svc = new Service1();
            else 
                throw new Exception("Unexpected Url");

            return new ValuesController(svc);
        }

        throw new Exception("Unexpected Controller Type");
    }
}

You can get more details about this approach here.

Note that although the example I provided does not use a DI container (and thus uses Pure DI), you should be able to make it work with a container.

Don't forget to remove the code that sets the DependencyResolver since we are using a different seam to extend the framework.

Sign up to request clarification or add additional context in comments.

4 Comments

That's something I expected! Unfortunately, I've tried it, and HttpContext.Current is null every time. Perhaps, it's assigned after resolving the dependencies.
I actually tested this code before posting the answer. What version of Web API are you using? Is there anything special about your configuration?
After some investigation it turned out that the reason is using self-host - see this question - stackoverflow.com/questions/14349557/…
It works, thanks! I see the question has reduced to per-request dependency resolution. For reference, another way to do that - stackoverflow.com/questions/22483574/…

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.