Access or get Autofac Container inside a static class
Asked Answered
B

2

11

I need to get or access to my IoC container in a static class. This is my (simplified) scenario:

I register dependencies for ASP .net Web Api in a Startup class (but also I do this for MVC or WCF. I have a DependecyResolver project, but for simplicity, consider the following code)

// Web Api project - Startup.cs
public void Configuration(IAppBuilder app)
{
    HttpConfiguration config = new HttpConfiguration();

    var builder = new ContainerBuilder();

    // ... Omited for clarity
    builder.RegisterAssemblyTypes(AppDomain.CurrentDomain.GetAssemblies())
        .AsClosedTypesOf(typeof(IHandle<>))
        .AsImplementedInterfaces();

    // ...
    IContainer container = builder.Build();
    config.DependencyResolver = new AutofacWebApiDependencyResolver(container);
    // ...
}

Then, in a separate class library I have my static class (again simplified for clarity):

public static class DomainEvents
{
    private static IContainer Container { get; set; }

    static DomainEvents()
    {
        //Container = I need get my Autofac container here
    }

    public static void Register<T>(Action<T> callback) where T : IDomainEvent { /* ... */ }

    public static void ClearCallbacks() { /* ... */  }

    public static void Raise<T>(T args) where T : IDomainEvent
    {
        foreach (var handler in Container.Resolve<IEnumerable<IHandle<T>>>())
        {
            handler.Handle(args);
        }
        // ...
    }
}

Any idea how can I get this?

Burrell answered 27/11, 2015 at 10:19 Comment(0)
C
10

I need to get or access to my IoC container in a static class. Any idea how can I get this?

Yes, you don't! Seriously. The pattern with the static DomainEvents class originates from Udi Dahan, but even Udi has admitted that this was a bad design. Static classes that require dependencies of their own are extremely painful to work with. They make the system hard to test and maintain.

Instead, create a IDomainEvents abstraction and inject an implementation of that abstraction into classes that require publishing events. This completely solves the your problem.

You can define your DomainEvents class as follows:

public interface IDomainEvents
{
    void Raise<T>(T args) where T : IDomainEvent;
}

// NOTE: DomainEvents depends on Autofac and should therefore be placed INSIDE
// your Composition Root.
private class AutofacDomainEvents : IDomainEvents
{
    private readonly IComponentContext context;
    public AutofacDomainEvents(IComponentContext context) {
        if (context == null) throw new ArgumentNullException("context");
        this.context = context;
    }

    public void Raise<T>(T args) where T : IDomainEvent {
        var handlers = this.context.Resolve<IEnumerable<IHandle<T>>>();
        foreach (var handler in handlers) {
            handler.Handle(args);
        }
    }
}

And you can register this class as follows:

IContainer container = null;

var builder = new ContainerBuilder();

builder.RegisterType<AutofacDomainEvents>().As<IDomainEvent>()
    .InstancePerLifetimeScope();

// Other registrations here

container = builder.Build();
Camembert answered 27/11, 2015 at 10:57 Comment(3)
And how do I resolve the IHandle types in the Raise method? Do I need to inject all the IHandle of all types in the DomainEvents ctor? thxBurrell
For the moment I solve my problem setting DomainEvents as generic class (public class DomainEvents<T> : IDomainEvents<T> where T : IDomainEvent) and injecting in ctor an IEnumerable<IHandle<T>> parameter. I don't know whether this is the best solution or not, but it works well for nowBurrell
Finally I chose this implementation.Burrell
A
8

You can create a static method inside your DomainEvents class to inject the container like this:

public static class DomainEvents
{
    public static void SetContainer(IContainer container)
    {
        Container = container;
    }

    ....
}

And then from your ASP.NET application, call this method to inject the container:

DomainEvents.SetContainer(container);

Please note that I am giving you a direct answer to your question. However, here are some issues that I see with this:

  • Static classes should not be used when the class requires dependencies. In such case, refactor to use a non-static class and use Constructor Injection to inject the dependencies that you need in the class.
  • Using the container outside of the Composition Root is called Service Location and is considered an anti-pattern.
  • Class libraries should not use the container or even have a Composition Root. Quoting from the Composition Root article that I referenced:

Only applications should have Composition Roots. Libraries and frameworks shouldn't.

Aerie answered 27/11, 2015 at 10:55 Comment(3)
This is the first solution I thought but I discarded it due to the reasons you correctly exposeBurrell
Careful. The solution won't work (at least won't be predictable). Notice that you are passing the root container to the DomainEvent class. While in MVC or thread, new child scopes will be created and those are what you need, otherwise you might have issues with DbContexts or ISessions (e.g. using an entity from a context created in the MVC's scope and then using it inside the event, which is bound to the main scope, and probably different DbContext).Springtime
While I do agree with the anti-pattern rule of thumb, I disagree with your claims to "Class libraries should not use the container or even have a Composition Root." There are exceptions to every typical scenario. 1st of all, static method calls to be trampolined via x64 thiscall, will require a layer of abstraction from its derived class members. How else would you be able to get them in without declaring them as static as well? Lastly, if your API is to be utilized for DLL injection, you need to perform these tasks within the library.Tetrad

© 2022 - 2024 — McMap. All rights reserved.