Thursday, May 11, 2023

AutoFac

I have been pushing for adoption of Dependency Injection at work for a while now and I'm finally getting some traction. Of course, it's not because it's the right thing to do, but because it enables us to do something we want to do. I can live with that.

I wrote a blog entry about MEF a while back (I just checked and it was four years ago!). My boss came across a container called AutoFac which I did not know about so I've been looking at it. I am particularly interested in how the different types of registration affect the timing of the constructors.

Create a new C# .Net WPF project (I used Framework 4.5.1 but it should work with Core too). Call it DemoAutoFac. Use NuGet to add the AutoFac package.


Change App.xaml.cs to look like this.

using System.Windows;
 
namespace DemoAutoFac
{
    public partial class App : Application
    {
        public static Autofac.IContainer Container { get; set; }
    }
}

and change MainWindow.xaml.cs to look like this.

using Autofac;
using System;
using System.Windows;
 
namespace DemoAutoFac
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            Console.WriteLine("-------------------------");
            Console.WriteLine("MainWindow");
            CreateAutoFacContainerByType();
            WriteDate();
            WriteDate();
 
            Console.WriteLine("");
            CreateAutoFacContainerByInstance();
            WriteDate();
            WriteDate();
        }
 
        public void CreateAutoFacContainerByType()
        {
            Console.WriteLine("CreateAutoFacContainerByType");
            ContainerBuilder builder = new ContainerBuilder();
            builder.RegisterType<ConsoleOutput>().As<IOutput>();
            builder.RegisterType<TodayWriter>().As<IDateWriter>();
            DemoAutoFac.App.Container = builder.Build();
            Console.WriteLine("Exit CreateAutoFacContainerByType");
        }
 
        public void CreateAutoFacContainerByInstance()
        {
            Console.WriteLine("CreateAutoFacContainerByInstance");
            ContainerBuilder builder = new ContainerBuilder();
            builder.RegisterInstance<ConsoleOutput>(new ConsoleOutput()).As<IOutput>();
            builder.RegisterInstance<TodayWriter>(new TodayWriter()).As<IDateWriter>();
            DemoAutoFac.App.Container = builder.Build();
            Console.WriteLine("Exit CreateAutoFacContainerByInstance");
        }
 
        public void WriteDate()
        {
            DemoAutoFac.App.Container.Resolve<IDateWriter>().WriteDate();
        }
    }
 
    public interface IOutput
    {
        void Write(string content);
    }
 
    public class ConsoleOutput : IOutput
    {
        public ConsoleOutput()
        {
            Console.WriteLine("ctor ConsoleOutput");
        }
 
        public void Write(string content)
        {
            Console.WriteLine("ConsoleOutput.Write");
            Console.WriteLine(content);
        }
    }
 
    public interface IDateWriter
    {
        void WriteDate();
    }
 
    public class TodayWriter : IDateWriter
    {
        public TodayWriter()
        {
            Console.WriteLine("ctor TodayWriter");
        }
 
        public void WriteDate()
        {
            IOutput output = DemoAutoFac.App.Container.Resolve<IOutput>();
            output.Write(DateTime.Today.ToShortDateString());
        }
    }
}
 
The Autofac container is hosted by App so it is reachable from anywhere. Alternatively it could be injected via a property in each class. That's a bit more work. If you run the application the Immediate Window output looks like this...



You can see when we use RegisterType, the constructors are not called until the class is used. However, the constructor is called EACH TIME we call WriteDate. We could use a Singleton pattern, but we would have to use that for each registered class and it will not work with a strict singleton pattern. You'd have to have a non-singleton wrapper around a singleton object because AutoFac wants to call a constructor.

The output after the blank line shows the results of RegisterInstance. The constructors are called as I register the instances, but they are only ever called once. The TodayWriter.WriteDate method can access the container to fetch a reference to the correct IOutput class. 

I prefer the second method. You can avoid delays while registering instances by implementing just-in-time initialization. 

No comments:

Post a Comment