Correct use of Autofac in C# console application

Static is the problem

The main issue with a console program is that the main Program class is mostly static. This is not good for unit testing and it's not good for IoC; a static class is never constructed, for example, so there is no chance for constructor injection. As a result, you end up using new in the main code base, or pull instances from the IoC container, which is a violation of the pattern (it's more of a service locator pattern at that point). We can get out of this mess by returning to practice of putting our code in instance methods, which means we need an object instance of something. But what something?

A two-class pattern

I follow a particular, lightweight pattern when writing a console app. You're welcome to follow this pattern which works pretty well for me.

The pattern involves two classes:

  1. The original Program class, which is static, very brief, and excluded from code coverage. This class acts as a "pass through" from O/S invocation to invocation of the application proper.
  2. An instanced Application class, which is fully injected and unit-testable. This is where your real code should live.

The Program Class

The O/S requires a Main entry point, and it has to be static. The Program class exists only to meet this requirement.

Keep your static program very clean; it should contain (1) the composition root and (2) a simple, "pass-through" entry point that calls the real application (which is instanced, as we will see).

None of the code in Program is worthy of unit testing, since all it does is compose the object graph (which would be different when under test anyway) and call the main entry point for the application. And by sequestering the non-unit-testable code, you can now exclude the entire class from code coverage (using the ExcludeFromCodeCoverageAttribute).

Here is an example:

[ExcludeFromCodeCoverage]
static class Program
{
    private static IContainer CompositionRoot()
    {
        var builder = new ContainerBuilder();
        builder.RegisterType<Application>();
        builder.RegisterType<EmployeeService>().As<IEmployeeService>();
        builder.RegisterType<PrintService>().As<IPrintService>();
        return builder.Build();
    }

    public static void Main()  //Main entry point
    {
        CompositionRoot().Resolve<Application>().Run();
    }
}

As you can see, extremely simple.

The Application class

Now to implement your Application class as if it were the One and Only Program. Only now, because it is instanced, you can inject dependencies per the usual pattern.

class Application
{
    protected readonly IEmployeeService _employeeService;
    protected readonly IPrintService _printService;

    public Application(IEmployeeService employeeService, IPrintService printService)
    {
        _employeeService = employeeService; //Injected
        _printService = printService; //Injected
    }

    public void Run()
    {
        var employee = _employeeService.GetEmployee();
        _printService.Print(employee);
    }
}

This approach keeps separation of concerns, avoids too much static "stuff," and lets you follow the IoC pattern without too much bother. And you'll notice-- my code example doesn't contain a single instance of the new keyword, except to instantiate a ContainerBuilder.

What if the dependencies have dependencies of their own?

Because we follow this pattern, if PrintService or EmployeeService have their own dependencies, the container will now take care of it all. You don't have to instantiate or write any code to get those services injected, as long as you register them under the appropriate interface in the composition root.

class EmployeeService : IEmployeeService
{
    protected readonly IPrintService _printService;

    public EmployeeService(IPrintService printService)
    {
        _printService = printService; //injected
    }

    public void Print(Employee employee)
    {
        _printService.Print(employee.ToString());
    }
}

This way the container takes care of everything and you don't have to write any code, just register your types and interfaces.