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).

stop-cran
  • 4,229
  • 2
  • 30
  • 47
  • 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? – Andrei Oct 10 '16 at 08: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. – stop-cran Oct 10 '16 at 08: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. – L J Oct 10 '16 at 09:06
  • @Andrei, added a possible way, see my edits. – stop-cran Oct 10 '16 at 09:15
  • @stop-cran, Unity has a concept of named dependencies. This along with what L J suggested should be a good option for you – Andrei Oct 10 '16 at 09:17
  • @LJ I'm going to use both implementations at different addresses. How can proxy help to do that other than introducing a kind of implementation selector parameter for each action? – stop-cran Oct 10 '16 at 09:27
  • From a Web API perspective, how are you going to distinguish between requests that should be serviced by Service1 and the ones by Service2? Are you currently using ASP.NET routing to do this? – Yacoub Massad Oct 10 '16 at 09:53
  • @YacoubMassad I'm going to use different controller-level paths and the same underlying path structure for actions and parameters. – stop-cran Oct 10 '16 at 09:58
  • So you are going to create two different routes with different paths that point to the same controller, right? – Yacoub Massad Oct 10 '16 at 10:11
  • @YacoubMassad yes. And different service implementations should be passed to the controller's constructor for each of the paths. – stop-cran Oct 10 '16 at 10:15
  • @stop-cran, Create two separate interfaces that inherit from the common interface and let the implementations use those respectively. No need to over complicate things. – Nkosi Oct 10 '16 at 10:25

1 Answers1

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.

Yacoub Massad
  • 27,509
  • 2
  • 36
  • 62
  • 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. – stop-cran Oct 10 '16 at 11:02
  • I actually tested this code before posting the answer. What version of Web API are you using? Is there anything special about your configuration? – Yacoub Massad Oct 10 '16 at 11:22
  • After some investigation it turned out that the reason is using self-host - see this question - http://stackoverflow.com/questions/14349557/getting-httprequest-context-in-self-hosted-webapi – stop-cran Oct 10 '16 at 11:49
  • It works, thanks! I see the question has reduced to per-request dependency resolution. For reference, another way to do that - http://stackoverflow.com/questions/22483574/using-a-handler-in-web-api-and-having-unity-resolve-per-request – stop-cran Oct 11 '16 at 07:09