2

I'm wanting to configure registrations in a Unity container being used by ASP.NET Web API 2 based on properties of a HTTP request. For example, a request to /api/database1/values should result in a Unity container configuration with an IDbContext configured for database1, while a request to /api/database4/values will get an IDbContext configured for database4.

I've gotten so far as using UnityHierarchicalDependencyResolver as the dependency resolver, so types registered with HierarchicalLifetimeManager last only for the lifetime of the request. This works well for getting types resolved per request. But how to get them registered per request using OWIN middleware is beyond me.

In my middleware, a call to System.Web.Http.GlobalConfiguration.Configuration.DependencyResolver.GetService(typeof(IUnityContainer)) gets an instance of IUnityContainer, but it's the same container for all requests, including any registrations from previous requests.

By encapsulating UnityHierarchicalDependencyResolver with my own implementation of IDependencyResolver I can see that IDependencyResolver.BeginScope isn't called until much later in the process. So the problem would seem to be that the child container isn't created until Web API wakes up, long after my middleware calls Next(..).

Is there a way I can get the scope of my dependency resolver to start sooner? Is there some other strategy that I'm missing. In case it makes any difference, I'm hosting in IIS, but favouring the OWIN middleware approach.

Update

This isn't an answer, and it's too big for a comment, but after struggling to solve this with Unity I decided to switch to Autofac and it all just fell into place.

The Autofac OWIN packages (Autofac.Mvc5.Owin, Autofac.Owin, Autofac.WebApi2.Owin) make it dead easy to use Autofac within the OWIN pipeline and ensure appropriate lifetime management in ASP.NET MVC and Web API. This was the missing link.

I couldn't find a way to reconfigure the container per-request, but it did at least make it possible to configure a factory per-request (so yes, @Haukinger and @alltej, you were right to push in that direction.

So I register a factory like:

builder.RegisterType<DataDependencyFactory>().InstancePerRequest();

And register the create method of that factory like:

builder
  .Register(c => c.Resolve<DataDependencyFactory>().CreateDataDependency())
  .As<IDataDependency>()
  .InstancePerRequest();

Registering the factory this way is particularly useful, because downstream dependents don't need to be aware of the factory. I like this because my dependents don't need a factory, they need an instance. The container bends to the needs of my dependents, not the other way around :)

Then, in a piece of OWIN middleware, I resolve the factory, and set a property on it according to the properties of the request. Subsequent resolution of IDataDependency in an MVC or Web API controller, or anything else later in the OWIN pipeline, will get an instance configured according to the property on the factory.

Snixtor
  • 4,239
  • 2
  • 31
  • 54
  • FWIW, I feel validated that I'm not trying to do something truly ridiculous, as Autofac documentation validates the concept. http://docs.autofac.org/en/latest/faq/per-request-scope.html#implementing-custom-per-request-semantics _**you can implement a custom mechanism that provides the ability to register and resolve dependencies on a per-request basis**_. Though I'd prefer to stick with Unity, if that's an uphill battle I'd consider an alternative IoC container like Autofac. – Snixtor Aug 09 '16 at 06:06
  • Do you consider a factory for your `IDBContext` that creates the context and gets the relevant request properties as parameter? This way you only need one registration for the factory. – Haukinger Aug 09 '16 at 14:41
  • A factory would still need a per-request configuration. Which means it would need to become aware of the connection directly (not ideal), or would need some sort of connection context injected via the IoC container, which just brings me back to square one. Or is there something I'm missing? – Snixtor Aug 09 '16 at 21:15
  • Not really, you have to somehow link the connection to a db context, whether coded manually or created automagically... I'd prefer the manual way, though, to reduce dependencies on a specific container. – Haukinger Aug 10 '16 at 05:46

1 Answers1

1

Based on your api URL ("/api/database4/values"), I suggest that you create a filter attribute(e.g. DbIdFilter) so that you can reuse the filter attribute to other controller methods that follow similar url path/segment like this below:

[HttpGet]
[DbIdFilter]
[Route("{databaseId}/values")]
public IHttpActionResult GetValues()
{
    return Ok();
}


[HttpGet]
[DbIdFilter]
[Route("{databaseId}/products")]
public IHttpActionResult GetProducts()
{
    return Ok();
}

First, create the filter attribute:

public class DbIdFilterAttribute : ActionFilterAttribute
{
    private readonly string _routeDataId;
    private const string defaultRouteName = "databaseId";
    public DbIdFilterAttribute():this(defaultRouteName)
    {}

    public DbIdFilterAttribute(string routeDataId)
    {
        _routeDataId = routeDataId;
    }

    public override void OnActionExecuting(HttpActionContext actionContext)
    {

        var routeData = actionContext.Request.GetRouteData();

        var dbId = routeData.Values[_routeDataId] as string;

        //here we create the db instance at the filter level.

        DbInstanceFactory.RegisterDbInstance(dbId);


    }

}

Next, create an instance factory that will register/resolve the db instance during runtime:

public class DbInstanceFactory : IDbInstanceFactory
{
    public static IDbInstance RegisterDbInstance(string databaseId)
    {
        var factory = UnityConfig.GetConfiguredContainer().Resolve<IDbInstanceFactory>();
        return factory.CreateInstance(databaseId);
    }

    public IDbInstance CreateInstance(string databaseId)
    {
        var container = UnityConfig.GetConfiguredContainer();
        //container.RegisterType<IDbInstance, DbInstance>();

        container.RegisterType<IDbInstance, DbInstance>(new InjectionConstructor(databaseId));
        var dbInstance = container.Resolve<IDbInstance>();
        return dbInstance;
    }

    public IDbInstance GetInstance()
    {
        var container = UnityConfig.GetConfiguredContainer();
        var dbInstance = container.Resolve<IDbInstance>();
        return dbInstance;
    }
}

public interface IDbInstanceFactory
{
    IDbInstance CreateInstance(string databaseId);
    IDbInstance GetInstance();
}

Register this factory class in UnityConfig.cs (or wherever you currently register the types):

container.RegisterType<IDbInstanceFactory, DbInstanceFactory>
    (new ContainerControlledLifetimeManager());

It's registered ContainerControlledLifetimeManager since this factory does not have to be a per request.

So just a basic DbInstance class below(for clarity) that takes a parameter in the constructor (this parameter can be your connection string or a named connection):

public class DbInstance : IDbInstance
{
    public string DbId { get; }

    public DbInstance(string databaseId)
    {
        DbId = databaseId;
    }
}

public interface IDbInstance
{
    string DbId { get; }
}

In controller class, you can use it like this:

....
private IDbInstanceFactory _dbFactory;

public MyController(IDbInstanceFactory dbFactory)
{
    _dbFactory = dbFactory;
}

// Alternate, if you want to use property injection instead of constructor injection
//[Dependency]
//public IDbInstanceFactory DbFactory { get; set; }

[HttpGet]
[DbIdFilter]
[Route("{databaseId}/test")]
public IHttpActionResult Test()
{
    var db = _dbFactory.GetInstance();

    return Ok(db.DbId);
}

...
alltej
  • 6,787
  • 10
  • 46
  • 87
  • It's good in theory, but has problems. **1.** `ActionFilterAttribute.OnActionExecuting` is executed *after* controllers are constructed, so registering dependencies in it won't work. **2.** State in `ActionFilterAttribute` is not thread-safe. http://stackoverflow.com/a/8937793/855363 , https://msdn.microsoft.com/en-us/library/system.web.http.filters.actionfilterattribute(v=vs.118).aspx "Any instance members are not guaranteed to be thread safe." **3.** It's an action filter, not OWIN middleware which is what my question sought. – Snixtor Aug 10 '16 at 23:45
  • The code above was tested and it was able to register and resolve the dependency. – alltej Aug 11 '16 at 00:44
  • To be more specific, you can't register controller constructor dependencies in the action filter. It's not a show-stopper, but it's not ideal. The other issues I raised though, are show-stoppers. – Snixtor Aug 11 '16 at 01:54