0
votes

I am trying to figure out how to resolve a type by name from the container at runtime using Autofac 3.5.2. My use case is that each business partner has a custom callback strategy that require injection of different types by the container, but I don't know which partner strategy I need until runtime. So:

class PartnerAStrategy(ISomeType aSomeType, ILog someLog) : ICallbackStrategy {}

and

class PartnerBStrategy(ISomeOtherType aSomethingElse, IShoe aSneaker) : ICallbackStrategy {}

I know which strategy I need after the class that will use it has already been resolved

class PartnerSSOController {
    void PartnerSSOController(IPartnerFactory aFactory){ 
        thePartnerFactory = aFactory;
    }

    void DoLogin(){
        // 'PartnerB'
        string aPartner = GetPartnerNameFromContext();
        //get from container, not reflection
        ICallbackStratgey aStrategy = thePartnerFactory.ResolveCallback(aPartner);
        aStratgey.Execute();
    }
}

class PartnerFactory : IPartnerFactory{
    ICallbackStratgey ResolveCallback(string aPartnerName){
        string aCallbackTypeINeed = string.format("SSO.Strategies.{0}Strategy", aPartnerName);
        // need container to resolve here
    }
}

Assuming that everything has been successfully registered with the container, how would I register the callback in my Autofac SSO module? I've tried this:

aBuilder.Register(aComponentContext => {
                      IPartnerFactory aFactory = aComponentContext.Resolve<IPartnerFactory>();
                      string aTypeName = String.Format("SSO.Strategies.{0}Strategy", /** how to access partner name here? **/);
                      Type aTypeToReturn = Type.GetType(aTypeName, false, true) ?? typeof(DefaultCallbackStrategy);
                      return aComponentContext.Resolve(aTypeToReturn);
                  })
        .As<ICallbackStrategy>()

but as you can see, I can't figure out how to make the partner or type name available during callback. I would prefer to avoid registering each partner's callback specifically and providing a key name, if possible, as I like to scan the assembly for the types in my module:

 aBuilder.RegisterAssemblyTypes(typeof(CallbackBase).Assembly)
    .Where(aType => typeof(ICallbackStrategy).IsAssignableFrom(aType))
    .AsImplementedInterfaces()
    .AsSelf();
2

2 Answers

0
votes

I think what you're looking for is the Autofac metadata support, specifically the attribute metadata you can use with assembly scanning. Plenty of docs and examples here: http://autofac.readthedocs.org/en/latest/advanced/metadata.html

0
votes

I think Travis' approach with using MetaData may have worked, but here is how I solved this problem today.

My real-world scenario uses an SSOPartner object rather than the string PartnerName that I used in my original question, so be aware of that.

First, I register all of the custom partner strategies in the SSO assembly by finding each type that implements ICallbackStrategy. CallbackBase is a type that is in the target assembly:

aBuilder.RegisterAssemblyTypes(typeof(CallbackBase).Assembly)
        .Where(aType => typeof(ICallbackStrategy).IsAssignableFrom(aType))
        .AsImplementedInterfaces()
        .AsSelf();

Then I registered a Func<ISSOPartner, ICallbackStrategy> at the composition root:

aBuilder.Register<Func<ISSOPartner, ICallbackStrategy>>((aComponentContext, aPartner) => {
                                                            IComponentContext aResolvedContext = aComponentContext.Resolve<IComponentContext>();
                                                            return aSSOPartner => {
                                                                       string aType = String.Format("SSO.Strategies.{0}Strategy", aSSOPartner.Name);
                                                                       Type aCallbackType = Type.GetType(aType, false, true);
                                                                       return (ICallbackStrategy)aResolvedContext.Resolve(aCallbackType, new NamedParameter("anSSOPartner", aSSOPartner));
                                                                   };
                                                        });

As you can see, each strategy takes the SSOPartner object in the constructor as a named parameter. This helps demonstrate how an object can be resolved from the container using a mix of types, some of which are registered with the container and others which are not.

So let me update my original example:

class PartnerAStrategy(ISSOPartner anSSOPartner) : ICallbackStrategy {}

and

class PartnerBStrategy(ISSOPartner anSSOPartner, IShoe aSneaker) : ICallbackStrategy {}

aResolvedContext.Resolve will find PartnerB's IShoe at runtime by looking in the container. The container has no knowledge of ISSOPartner.

Finally, I take a dependency on the Func<ISSOPartner, ICallbackStrategy> in the location I need to use it and invoke it to call the lambda I defined at the composition root:

public class SSOController : Controller {
    private readonly Func<ISSOPartner, ICallbackStrategy> theCallbackResolverFunc;

    public SSOController(Func<ISSOPartner, ICallbackStrategy> aCallbackResolverFunc){
        theCallbackResolverFunc=aCallbackResolverFunc;
    }

    public async Task<ActionResult> DoSSO() {
        SSOPartner anSSOPartner = GetThePartner();
        ICallbackStrategy aCallback = theCallbackResolverFunc.Invoke(anSSOPartner);
        var aReturn = await aCallback.Execute();
        ...
    }
}

The SSOController is passed a reference to the Func that I registered in the composition root. I execute that Func and pass in the SSOPartner which has a property called Name which I use to resolve the ICallbackStrategy type.