views:

197

answers:

1

I am trying to figure out how to use IoC in situations where the dependent classes can change based on some variable in the application (in this case, Session state). For example, each of our clients have a different database, so the connection to the database needs to be built on a value stored in their Session (particularly since some users could have multiple databases if they own multiple businesses, and would switch between databases).

Here is a generic example of how we'd currently set up this structure:

public class MyTestController : ControllerBase
{
    Repository _rep;

    public MyTest(Repository rep)
    {
        _rep = rep;
    }

    public MyTest()
    {
        string connString = String.Format("Server={0}; Database={1};"
            , SessionContainer.ServerName, SessionContainer.DatabaseName;
        var dc = new DataContext(connString);
        _rep = new Repository(dc);
    }

    public int SampleFn()
    {
        return _rep.GetCountOfEmployees();
    }
}

public class Repository
{
    DataContext _context;

    public Repository(DataContext context)
    {
        _context = context;
    }
}

Would we be able to set this up using IoC and eliminate the default c-tors? If so, how? I don't have a problem just using D.I. like this, but I'd like to explore the possibility of a StructureMap or Unity (note: we normally pass in db/server to a factory class that builds the datacontext ... above example is just for brevity).

+5  A: 

How the Repository instance is created, as well as its lifetime, is of no concern of the Controller.

When you register components in the container, you should specify the lifetime of the component. Depending on your implementation, you may simply choose to set the lifefime of the Repository to follow the session.

In any case you could use a factory to create the repository from the session, but do this from outside the Controller.

You definitely need to get rid of the default constructor.


Off the top of my head I can't remember how to do this in Unity or StructureMap, so here's a Castle Windsor example.

Define an Abstract Factory:

public interface IRepositoryFactory
{
    Repository Create();
}

and an implementation

public class MyRepositoryFactory : IRepositoryFactory
{
    private readonly HttpContextBase httpContext;

    public MyRepositoryFactory(HttpContextBase httpContext)
    {
        if (httpContext == null)
        {
            throw new ArgumentNullException("httpContext");
        }

        this.httpContext = httpContext;
    }

    #region IRepositoryFactory Members

    public Repository Create()
    {
        // return Repository created from this.httpContext
    }

    #endregion
}

Now register all the stuff

container.AddFacility<FactorySupportFacility>();
container.Register(Component.For<IRepositoryFactory>()
    .ImplementedBy<MyRepositoryFactory>()
    .LifeStyle.PerWebRequest);
container.Register(Component.For<Repository>()
    .UsingFactory((IRepositoryFactory f) => f.Create())
    .LifeStyle.PerWebRequest);

Here I've used the PerWebRequest lifestyle, but if you want to optimize you might want to create a custom PerWebSession lifestyle. This is not too hard to do in Castle, but I can't remember how hard it is in other DI Containers.

You will also need to register HttpContextBase, since MyRepositoryFactory depends on it.

Mark Seemann
Mark - can you be more specific, or provide an example? We could have multiple repository objects, all of which would need to use the same DataContext (for proper transaction processing). I have no problem registering this in the IoC container when the "DataContext" uses a connection string that is global or defined in the container, but what about when the connection string is in the user's session? I feel like I'm missing something obvious.
Jess
Added an example to my answer.
Mark Seemann
Ah ha, I think I get it now! =)I downvoted you so that I could upvote you tomorrow for double win. Great response.
Jess