1

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.

Josh
  • 16,286
  • 25
  • 113
  • 158
  • I suppose `ResolveOptionalNamed` should work here. – Evk Oct 31 '17 at 14:48
  • `ResolveOptionalNamed` requires that `T` inherits from `class`. I cannot use an enum. – Josh Oct 31 '17 at 15:04
  • You can create your own extension which will work with structs too. If that is fine for you I can show how exactly. – Evk Oct 31 '17 at 15:17

1 Answers1

0

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);
    }
}
Josh
  • 16,286
  • 25
  • 113
  • 158