0

I am struggling with wiring dependencies through autofac in my WebApi 2 project. I have a following interface and class that i'd like to inject in my GET and POST controller actions,

public interface IRepository
{
    IContext Context
        {
        get;
        }

    void SomeOperation();
}

public MyRepository : IRepository
{
    IContext _context;

    public MyRepository(IContext context)
    {
        _context = context;
    }

    public Context
    {
        get
        {
        return _context;
        }
    }

    public void SomeOperation
    {
    // Perform some operation using _context;
    }
}

I 'd like IRepository to be injected in controller like this,

public class MyController : ApiController
    {
        private readonly IRepository _repo;

        public ApplicationsController(IRepository repo)
            {
            _repo = repo;
            }

        // GET: api/v1/Contexts({contextId})
        public IHttpActionResult Get(string contextId)
            {
            _repo.SomeOperation();
            }
    }

IContext object to be injected in MyRepository has to be fetched from a factory, something like this

public class ContextFactory
{
    Hashtable contextMap;

    IContext Get(string contextId)
    {
        if contextMap.Contains(contextId)
            return contextMap[contextId].Value;
        else
            {
                IContextConfiguration configuration = ContextConfigurationFactory.Get(contextId);

                IContext context = new ConcreteContext(configuration);
                contextMap.Add[contextId, context];

                return context;
            }
    }
}

I am not sure how to wire all the classes and convert logic in factory classes by injecting relationships through Autofac so that context id passed in url is passed to ContextConfigurationFactory.Get and instantiate ConcreteContext object when not found in hash and eventually Autofac injecting right context object in MyRepository before passing it on to Get action in the controller.

Travis Illig
  • 23,195
  • 2
  • 62
  • 85
Usman Khan
  • 139
  • 2
  • 12
  • It seems like `contextId` doesn't need to be part of your DI container. That would just be a parameter your pass to the methods on the objects you resolved from your DI container. Perhaps if you gave us more information about how `contextId` and `IContext` are to be used it would be helpful. – mason Mar 12 '18 at 17:34
  • I am working on a multitenant system and each tenant will have different IContext object. IContext refers to multiple data storages. When context with certain id is requested for the first time through GET call, i'll create context object, open connections with data stores etc and cache that object so that i can inject the cached object on subsequent requests. Context Id is passed as a parameter to REST end point. – Usman Khan Mar 12 '18 at 17:37
  • That sounds incredibly dangerous. If you open database connections when they're first requested, you run the risk of leaving database connections open for too long and that will cause issues. Connections should be opened as late as possible and closed as early as possible. – mason Mar 12 '18 at 17:39
  • MyRepository implementation will perform CRUD operations by fetching connection objects contained in IContext – Usman Khan Mar 12 '18 at 17:39
  • I have yet to explore how expensive it is to establish connection with every call, it sounds inefficient. Idea is that service will open just one connection with each database and all CRUD operations will be performed on that connection and connection will be never closed. – Usman Khan Mar 12 '18 at 17:42
  • What kind of database are you dealing with? Often Connection Pooling will keep it efficient for you. Never closing a database connection is not going to work out for you. – mason Mar 12 '18 at 17:43
  • I'll be working with Azure Cosmos Db, Azure Search and Azure blob. I'll find out if connection pooling is supported with Cosmos Db. Nevertheless, how will i wire everything up? – Usman Khan Mar 12 '18 at 17:50

1 Answers1

2

Let's simplify this a bit. What you're trying to do is:

  • Get the context ID from a route parameter.
  • Use that route parameter in the factory to create a context.

The rest seems pretty much peripheral - the repository, the controller, all that. The crux of the question is that you need to get a route parameter into your factory.

Given that, let's put together some simplified code:

public class ContextFactory
{
    public IContext Get(string contextId)
    {
        return new Context(contextId);
    }
}

public interface IContext
{
    string Id { get; }
}

public class Context : IContext
{
    public Context(string id)
    {
        this.Id = id;
    }

    public string Id { get; private set; }
}

That's basically what you have:

  • An IContext interface that things need.
  • A ContextFactory that is basically responsible for building these things.
  • A Context concrete implementation of IContext that is built by the factory.

I would probably do something like this:

var builder = new ContainerBuilder();
builder.RegisterType<ContextFactory>();
builder.Register(ctx =>
{
    var routeData = HttpContext.Current.Request.RequestContext.RouteData;
    var id = routeData.Values["contextId"] as string;
    var factory = ctx.Resolve<ContextFactory>();
    return factory.Get(id);
}).As<IContext>()
.InstancePerLifetimeScope();

Now when you resolve IContext it will use your factory, get the current context ID from route data, and pass it through the factory.

I will leave the following for you to look into:

  • What happens if the route parameter isn't there? (Autofac won't let you return null.)
  • What happens if the route parameter has invalid data?
  • The route parameter is pretty hackable, is this a security risk?

...and so on.

Travis Illig
  • 23,195
  • 2
  • 62
  • 85
  • Everything is working as expected! Thank you Travis. The only issue remaining is that RequestContext.RouteData is not containing route information. Moreover, can you please elaborate a bit why InstancePerLifetimeScope is appropriate here? – Usman Khan Mar 14 '18 at 19:54
  • If the RouteData isn't there you'll need to look around in your app for a better way to get that. The idea still holds - get there parameter _however you want_ and use it. This question is more about Autofac than the WebAPI and routing part. On lifetime scope, see this: http://autofac.readthedocs.io/en/latest/faq/per-request-scope.html – Travis Illig Mar 14 '18 at 20:10