0

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?

Ciaran Gallagher
  • 3,895
  • 9
  • 53
  • 97
  • Do you need a scope in your job runner? – ProgrammingLlama Dec 19 '17 at 09:51
  • I'm not sure, do I? All I want to do is to be able to resolve my services whenever I execute a job in this application, and terminated on job completion. My difficulty is just knowing how to set up my configuration so that it caters for both my web application but also the job runner. – Ciaran Gallagher Dec 19 '17 at 10:07
  • 3
    `InstancePerLifetimeScope()` might suffice for both since `InstancePerRequest()` is just a specific lifetime scope, and if there isn't a lifetime scope, it should use the root scope. – ProgrammingLlama Dec 19 '17 at 11:04
  • Thanks John. Can you think of any scenarios where it wouldn't suffice? – Ciaran Gallagher Dec 19 '17 at 15:52

1 Answers1

2

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);
    }
}
gerrod
  • 6,119
  • 6
  • 33
  • 45
  • Thank you. Currently all my types are registered with scope specified for each one individually, e.g ."builder.RegistertType().As().InstancePerLifetimeScope". If I call "builder.RegisterAssemblyTypes().InstancePerMatchingLifetimeScope(lifetimeScope)" will this eliminate the need to specify a scope for each type registration? – Ciaran Gallagher Dec 20 '17 at 09:20
  • 1
    Yes; but it will also register *all* assembly types. Usually you'd filter to only register the types you want in your container; something like: `builder.RegisterAssemblyTypes().Where(type => typeof(IJob).IsAssignableFrom(type)).InstancePerMatchingLifetimeScope(lifetimeScope)` – gerrod Dec 20 '17 at 22:44