Search code examples
c#dependency-injectionautofac

Autofac Registration & Resolution Confusion


Having trouble determining how to register two instances of a concrete class with different parameter arguments that later need to resolve to different services. In my app I am outputting data from a model to a text file:

var builder = new ContainerBuilder();
builder.RegisterType<SqlConnection>()
    .WithParameter("connectionString", ConnectionString)
    .As<IDbConnection>();

builder.Register(c => new FileStream(".\cat.txt", FileMode.Create, FileAccess.ReadWrite))
    .As<FileStream>();
// how would I also register another file stream like this so that it can be used later on?
//builder.Register(c => new FileStream(".\dog.txt", FileMode.Create, FileAccess.ReadWrite))
//   .As<FileStream>();

// and would I need to alter this, or would it be able to resolve multiple streamwriters?
builder.Register(c => new StreamWriter(c.Resolve<FileStream>()) { AutoFlush = true })
    .As<StreamWriter>();
builder.Register(c =>
{
    var csvWriter = new CsvWriter(c.Resolve<StreamWriter>(), CultureInfo.InvariantCulture);
});

var container = builder.Build();

using (var scope = container.BeginLifetimeScope())
{
    var cnn = container.Resolve<IDbConnection>();
    var catRecords = cnn.Query<CatModel>("select * from dbo.Cat")
    container.Resolve<CsvWriter>().WriteRecords(catRecords);
    // how do I get the correct resolution here?
    //var dogRecords = cnn.Query<DogModel>("select * from dbo.Dog")
    //container.Resolve<CsvWriter>().WriteRecords(dogRecords);
}

How do I register and resolve correctly to incorporate the two files correctly? Do I needed to use a delegate factory? Keyed registration? I have read through the documentation quite a lot, but I'm having trouble figuring out what fits here.


Solution

  • Keyed Services

    Implementation with keyed services

    var builder = new ContainerBuilder();
    builder.RegisterType<SqlConnection>()
        .WithParameter("connectionString", ConnectionString)
        .As<IDbConnection>();
    
    builder.Register(c => new FileStream(".\cat.txt", FileMode.Create, FileAccess.ReadWrite))
        .Keyed<FileStream>("cat");
    builder.Register(c => new FileStream(".\dog.txt", FileMode.Create, FileAccess.ReadWrite))
       .Keyed<FileStream>("dog");
    
    builder.Register(c => new StreamWriter(c.ResolveKeyed<FileStream>("cat")) { AutoFlush = true })
        .Keyed<StreamWriter>("cat");
    builder.Register(c => new StreamWriter(c.ResolveKeyed<FileStream>("dog")) { AutoFlush = true })
        .Keyed<StreamWriter>("dog");
    
    builder.Register(c =>
    {
        new CsvWriter(c.ResolveKeyed<StreamWriter>("cat"), CultureInfo.InvariantCulture);
    }).Keyed<CsvWriter>("cat");
    builder.Register(c =>
    {
        new CsvWriter(c.ResolveKeyed<StreamWriter>("dog"), CultureInfo.InvariantCulture);
    }).Keyed<CsvWriter>("dog");
    
    var container = builder.Build();
    
    using (var scope = container.BeginLifetimeScope())
    {
        var cnn = container.Resolve<IDbConnection>();
        var catRecords = cnn.Query<CatModel>("select * from dbo.Cat")
        container.ResolveKeyed<CsvWriter>("cat").WriteRecords(catRecords);
        var dogRecords = cnn.Query<DogModel>("select * from dbo.Dog")
        container.ResolveKeyed<CsvWriter>("dog").WriteRecords(dogRecords);
    }
    

    Named Services + Autofac Module

    Combining the solution above with an Autofac module to help adhere to DRY coding practices. Swapped keyed services for named services since that was more the intent. As a side note, CsvWriter (part of CsvHelper) automatically flushes the buffer when using WriteRecords. You will get better performance by setting AutoFlush = false on the StreamWriter.

    public class FlatFileModule : Module
    {
        protected readonly string _filePath;
        protected readonly string _recordType;
    
        public FlatFileModule(string FilePath, string RecordType)
        {
            _filePath = FilePath;
            _recordType = RecordType;
        }
    
        protected override void Load(ContainerBuilder builder)
        {
            builder.Register(c => 
                new StreamWriter(_filePath, false, Encoding.UTF8) 
                { 
                    AutoFlush = false 
                })
                .Named<StreamWriter>(_recordType);
    
            builder.Register(c => 
                new CsvWriter(
                   c.ResolveNamed<StreamWriter>(_dataType),
                   CultureInfo.InvariantCulture)
                )
                .Named<IWriter>(_recordType);
        }
    }
    
    ...
    
    var builder = new ContainerBuilder();
    builder.RegisterType<SqlConnection>()
        .WithParameter("connectionString", ConnectionString)
        .As<IDbConnection>();
    
    builder.RegisterModule(new FlatFileModule(@".\cat.txt", "Cat"));
    builder.RegisterModule(new FlatFileModule(@".\dog.txt", "Dog"));
    
    var container = builder.Build();
    
    using (var scope = container.BeginLifetimeScope())
    {
        var cnn = container.Resolve<IDbConnection>();
        var catRecords = cnn.Query<CatModel>("select * from dbo.Cat")
        container.ResolveNamed<IWriter>("Cat").WriteRecords(catRecords);
        var dogRecords = cnn.Query<DogModel>("select * from dbo.Dog")
        container.ResolveNamed<IWriter>("Dog").WriteRecords(dogRecords);
    }