Search code examples
c#autofacnamed-instance

Autofac Named Instance - Define Default Instance if Not Found


I'm using Autofac to register named instances. I have to translate xml transactions into objects.

First, I have an enum.

public enum TransactionType
{
    Unknown = 0,
    [XmlNode("MyNodeA")]
    TypeA = 1,
    [XmlNode("MyNodeA")]
    TypeB = 2
}

I have a method that creates an IDictionary<string, TransactionType> using the XmlNode attribute on the enum.

Here is my autofac mapping

var mappings = TransactionTypeHelper.GetDictionary();

foreach (var mapping in mappings)
{
    builder.Register(ctx => {
                return mapping.Key;
    })
    .Named<TransactionType>(mapping.Value)
    .InstancePerLifetimeScope();
}

Then, I have a TransactionTypeFactory for getting the TransactionType based on the xml node.

public TransactionType GetTransactionType(string rootNode)
{
    return _container.Resolve<TransactionType>(rootNode?.ToLower());
}

My problem is that I want to pass through any unknown xml nodes as unknown transactions so that I can process new transactions without making any code changes. The problem is that _container.Resolve throws an error if the node passed in has not been registered.

What I want to do is make autofac return the enum default if the named instance is not found instead of throwing an error. The funny thing is, I have unit tests where this container is mocked, and they all pass, but Autofac specifically blows up on this call.


Solution

  • I know this question is rather old, but I'd like to share a solution I have learned in the meantime in the hopes it will help someone with the same issue.

    With autofac, you can register a function that can resolve using logic.

    First, you would register each named instance. In the question I was doing this with a helper and iterating through a collection, but the essence is to map each value of the enum to an instance.

    builder.Register<TransactionAClass>(ctx =>
    {
        //get any instances required by ConcreteClass from the ctx here and pass into the constructor
        return new TransactionAClass();
    })
    .Named<Interfaces.ITransactionInterface>($"{TransactionType.TypeA:f}")
    .InstancePerLifetimeScope();
    

    Once you have all your registrations for known values, then we register a resolver function.

    builder.Register<Func<TransactionType, Interfaces.ITransactionInterface>>(ctx =>
    {
        //you must resolve the context this way before being able to resolve other types
        var context = ctx.Resolve<IComponentContext>();
    
        //get the registered named instance
        return (type) =>
        { 
            var concrete = context.ResolveNamed<Interfaces.ITransactionInterface>($"{type:f}");
    
            if (concrete == null)
            {
                //return a default class or throw an exception if a valid registration is not found
                return new TransactionAClass();
            }
    
            return concrete;
        }
    });
    

    Then, you can use the resolver like this

    public class MyClass
    {
        private readonly ITransactionInterface transaction;
    
        public MyClass(Func<TransactionType, Interfaces.ITransactionInterface> transactionResolver)
        {
            transaction = transactionResolver.Invoke(TransactionType.TypeA);
        }
    }