2

I am using Castle Windsor for a DI container for Web API 2 and some of my controllers have EF DbContext dependencies, which I am injecting using the following CW installers (this part works fine):

public class ApiControllerInstaller : IWindsorInstaller
{
    public void Install(IWindsorContainer container, IConfigurationStore store)
    {
        container.Register(Classes.FromThisAssembly().BasedOn<ApiController>().LifestylePerWebRequest());
    }
}

public class MyEntitiesInstaller : IWindsorInstaller
{
    public void Install(IWindsorContainer container, IConfigurationStore store)
    {
        container.Register(Component.For<MyEntities>().LifestylePerWebRequest());
    }
}

I also have global logging in place with the following message handler:

public class LoggingHandler : DelegatingHandler
{
    private readonly IWindsorContainer _container;

    public LoggingHandler(IWindsorContainer container)
    {
        _container = container;
    }

    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
                                                                 CancellationToken cancellationToken)
    {
        // Logic here to parse the request, send off the request, and then parse the response for logging info

        // Log to database
        using (MyEntities myEntities = _container.Resolve<MyEntities>())
        {
            // Log to database using MyEntities (DbContext)
        }

        return response;
    }
}

... which gets set in WebApiConfig.cs like this:

    public static void Register(HttpConfiguration config, IWindsorContainer container)
    {
        // Unrelated code

        config.MessageHandlers.Add(new LoggingHandler(container));
    }

The problem is that I get an exception in the LoggingHandler when trying to access MyEntities (which extends EF DbContext) because it says it has already been disposed. I think that this happens because the MyEntitiesInstaller registers it as LifestylePerWebRequest() and by this point the request has already completed. I don't think it's a good idea to globally change the lifestyle of the DbContext because for api controller purposes we do want it to be an instance per web request. But how do I prevent it from being disposed before the logging happens (or even better, use a separate instance for the logging)?

mayabelle
  • 9,804
  • 9
  • 36
  • 59
  • I've never used Castle Windsor, but doesn't it support "named" configurations of things? My instinct would be to create an alternate, named configuration for DbContext, and then configure your attribute to use it. – Casey Mar 11 '15 at 14:20
  • I'm not sure registering your `MyEntities` is a smart idea. They should be short lived for the lifetime of the query, not the entire web request. I would keep it simple and simply use `using (var entityContext = new MyEntities())` instead. There's no need to [*"Inject all the things"*](http://s.quickmeme.com/img/fe/fe05577986250c85a9df4c46dbf46afb33332155da3e0f590cfd99bf5259b1dd.jpg). – Yuval Itzchakov Mar 11 '15 at 14:24
  • @emodendroket Thank you! It looks like named instances is what I need - I'm new to DI containers and wasn't aware of this feature. I'll try to see if using a separate named instance would work. – mayabelle Mar 11 '15 at 14:43
  • @YuvalItzchakov For my purposes I need the context for the entire web request anyway, so the end result is the same. Also, the code is not testable without injection. I disagree that it's a bad idea to inject DbContext, as long as it's disposed at the end of the request. I think that discussion is outside the scope of this question, though. – mayabelle Mar 11 '15 at 14:45
  • You're right. I'm wondering though, what would you need to test your `DbContext`? Or are you talking about injecting a dummy context? Also, [this](http://stackoverflow.com/questions/15654536/using-entity-framework-with-castle-windsor) might help you. – Yuval Itzchakov Mar 11 '15 at 14:46
  • @YuvalItzchakov I disagree... even setting testing aside one of the biggest advantages of using a DI container is that it also centralizes lifetime management. EF 7 will support an in-memory provider which will make it much easier to use these for testing without creating a goofy wrapper around your context which exposes more or less the same methods. – Casey Mar 11 '15 at 14:50
  • @mayabelle Are you calling `await base.SendAsync()` before you start your `using` scope? – Yuval Itzchakov Mar 11 '15 at 14:52
  • Yes, I'm talking about injecting a mocked or dummy context. I don't think I need my own repository/UnitOfWork implementations since EF already has its own which is sufficient for me. – mayabelle Mar 11 '15 at 14:53
  • Yes, after parsing the request, I call `HttpResponseMessage response = await base.SendAsync(request, cancellationToken);`, then parse the response (my logging logs both request and response), and then return the response. – mayabelle Mar 11 '15 at 14:54
  • Are you using your `DbContext` (Specifically `MyEntities`) inside your controller too? or only inside `LoggingHandler`? – Yuval Itzchakov Mar 11 '15 at 14:58
  • 1
    Yes, of course I am using it inside the controller, otherwise I wouldn't need to inject it there at all. :) I just didn't post that part because it was working and wasn't directly relevant to the issue I was having... sorry if that wasn't clear. I didn't want to use the same instance for both, though. The named instances seems to have solved this problem. The controllers use the instance per web request and the logging handler uses a transient instance that I resolve only when I need it and dispose immediately afterward. – mayabelle Mar 11 '15 at 15:01
  • Great solution! Kudos. – Yuval Itzchakov Mar 11 '15 at 18:39

1 Answers1

2

Thanks to @emodendroket's comment for pointing me in the right direction. I was able to get this working by using a different named instance for the DbContext that I need outside of the web request, using a transient lifestyle (and manually disposing immediately after use).

Installer:

public class MyEntitiesInstaller : IWindsorInstaller
{
    public void Install(IWindsorContainer container, IConfigurationStore store)
    {
        container.Register(Component.For<MyEntities>().LifestylePerWebRequest().IsDefault(),
                           Component.For<MyEntities>().Named("MyEntitiesTransient").LifestyleTransient());
    }
}

Usage by logging handler:

protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
                                                             CancellationToken cancellationToken)
{
    // Logic here to parse the request, send off the request, and then parse the response for logging info

    // Log to database
    using (MyEntities myEntities = _container.Resolve<MyEntities>("MyEntitiesTransient"))
    {
        // Log to database using MyEntities (DbContext)

        _container.Release(myEntities);
    }

    return response;
}

The controllers continue to use the unnamed/default instance that has the per-web-request lifestyle.

mayabelle
  • 9,804
  • 9
  • 36
  • 59