Autofac scope configuration for multi-project solu

2019-08-25 19:35发布

问题:

I have an ASP.NET web application which until now has been using an Autofac configuration class which has specified an InstancePerRequest() for services in the application.

Since then, I have created a new console application in the same solution which will be responsible for running automated job processes. Since I can't use the InstancePerRequest configuration for my console application, then I need to make a change to my configuration. Ideally, I don't want to copy and paste all the configuration into my JobRunner application and use the 'InstancePerLifetimeScope()' configuration there.

Is there a better solution where I can largely use the same configuration to serve both projects? Perhaps there is a way to override the configuration for my Job Runner application but without having to specify a change in scope for every single service?

回答1:

Although InstancePerLifetimeScope and InstancePerRequest often have the same bahviour, they definitely have different intentions. I'd avoid using InstancePerLifetimeScope as a drop-in replacement, as it's easy to create unintended side-effects. For example, if you have a service that you had intended to only live for the duration of a web request, and all of a sudden it lives for the duration of you application (since it unintentionally resolved from the root scope).

This effect would be worse inside your job runner, especially if you're not creating your own lifetime scopes - in this scenario, everything will live in the root scope, which means that one job will be sharing instances of a service with every other job that has a dependency on it.

Under the covers, InstancePerRequest() actually just delegates to InstancePerMatchingLifetimeScope() with a well-known lifetime scope tag (which you can get from MatchingScopeLifetimeTags.RequestLifetimeScopeTag). So, one way you could achieve what you're asking is you could just cut-out the middle-man... for example, you could change your Autofac modules to take the lifetime scope tag as a constructor parameter:

internal class MyModule : Module
{
    private string _lifetimeScopeTag;

    public MyModule(string lifetimeScopeTag)
    {
        _lifetimeScopeTag = lifetimeScopeTag;
    }

    protected override void Load(ContainerBuilder builder)
    {
        builder.RegisterAssemblyTypes()
            // equivalent to: .InstancePerRequest()
            .InstancePerMatchingLifetimeScope(_lifetimeScopeTag);
    }    
}

Now, when you're building your container from the web, you need to supply the well-known lifetime scope tag:

internal static class WebIoC
{
    public static IContainer BuildContainer()
    {
        var lifetimeScopeTag = MatchingScopeLifetimeTags.RequestLifetimeScopeTag;

        var builder = new ContainerBuilder();
        builder.RegisterModule(new MyModule(lifetimeScopeTag));

        return builder.Build();
    }
}

For your job runner, you can now mimic this behaviour, with a lifetime scope tag of your very own!

internal static class JobRunnerIoC
{    
    public const string LifetimeScopeTag = "I_Love_Lamp";

    public static IContainer BuildContainer()
    {
        var builder = new ContainerBuilder();
        builder.RegisterModule(new MyModule(LifetimeScopeTag));

        // Don't forget to register your jobs!
        builder.RegisterType<SomeJob>().AsSelf().As<IJob>();
        builder.RegisterType<SomeOtherJob>().AsSelf().As<IJob>();

        return builder.Build();
    }
}

(I'm assuming here that each of your jobs implements an interface, let's say it looks like this):

public interface IJob
{
    Task Run();
}

Now you just need to run the jobs in their own lifetime scope, using the tag you just made up, e.g.:

public class JobRunner
{
    public static void Main(string[] args)
    {
        var container = JobRunnerIoC.BuildContainer();

        // Find out all types that are registered as an IJob
        var jobTypes = container.ComponentRegistry.Registrations
            .Where(registration => typeof(IJob).IsAssignableFrom(registration.Activator.LimitType))
            .Select(registration => registration.Activator.LimitType)
            .ToArray();

        // Run each job in its own lifetime scope
        var jobTasks = jobTypes
            .Select(async jobType => 
            {
                using (var scope = container.BeginLifetimeScope(JobRunnerIoC.LifetimeScopeTag))
                {
                    var job = scope.Resolve(jobType) as IJob;
                    await job.Run();
                }
            });

        await Task.WhenAll(jobTasks);
    }
}