Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Resolving Autofac components in background Tasks in ASP.NET

Using Autofac in ASP.NET along with the ContainerDisposalModule, how can i support fire and forget calls that have component dependencies that need to be resolved? The problem I'm running into is that the ASP.NET request completes and disposes the lifetime scope of the request before the Task is ran, so any components that need to be resolved in the new thread fail with the message "Instances cannot be resolved and nested lifetimes cannot be created from this LifetimeScope as it has already been disposed". What's the best way to support fire and forget call using Autofac in ASP.NET? I don't want to delay the request to perform certain tasks which can be done on background threads.

like image 372
user1591480 Avatar asked Aug 10 '12 23:08

user1591480


1 Answers

Answer posted by Alex adapted to current Autofac and MVC versions:

  • Use InstancePerRequest for a database context
  • Add ILifetimeScope as dependency to get to the container
  • SingleInstance ensures it's the root lifetime scope
  • Use HostingEnvironment.QueueBackgroundWorkItem to reliably run something in the background
  • Use MatchingScopeLifetimeTags.RequestLifetimeScopeTag to avoid having to know the tagname autofac uses for PerRequest lifetimes

https://groups.google.com/forum/#!topic/autofac/gJYDDls981A https://groups.google.com/forum/#!topic/autofac/yGQWjVbPYGM

Gist: https://gist.github.com/janv8000/35e6250c8efc00288d21

Global.asax.cs:

protected void Application_Start() {
  //Other registrations
  builder.RegisterType<ListingService>();
  builder.RegisterType<WebsiteContext>().As<IWebsiteContext>().InstancePerRequest();  //WebsiteContext is a EF DbContext
  builder.RegisterType<AsyncRunner>().As<IAsyncRunner>().SingleInstance();
}

AsyncRunner.cs

public interface IAsyncRunner
{
    void Run<T>(Action<T> action);
}

public class AsyncRunner : IAsyncRunner
{
    public ILifetimeScope LifetimeScope { get; set; }

    public AsyncRunner(ILifetimeScope lifetimeScope)
    {
        Guard.NotNull(() => lifetimeScope, lifetimeScope);
        LifetimeScope = lifetimeScope;
    }

    public void Run<T>(Action<T> action)
    {
        HostingEnvironment.QueueBackgroundWorkItem(ct =>
        {
            // Create a nested container which will use the same dependency
            // registrations as set for HTTP request scopes.
            using (var container = LifetimeScope.BeginLifetimeScope(MatchingScopeLifetimeTags.RequestLifetimeScopeTag))
            {
                var service = container.Resolve<T>();
                action(service);
            }
        });
    }
}

Controller

public Controller(IAsyncRunner asyncRunner)
{
  Guard.NotNull(() => asyncRunner, asyncRunner);
  AsyncRunner = asyncRunner;
}

public ActionResult Index()
{
  //Snip
  AsyncRunner.Run<ListingService>(listingService => listingService.RenderListing(listingGenerationArguments, Thread.CurrentThread.CurrentCulture));
  //Snip
}

ListingService

public class ListingService : IListingService
{
  public ListingService(IWebsiteContext context)
  {
    Guard.NotNull(() => context, context);
    Context = context;
  }
}
like image 186
janv8000 Avatar answered Oct 09 '22 03:10

janv8000