Autofac IComponentContext.Resolve With Parameters

3.9k views Asked by At

How would one provide an IComponentContext func to a type Registration where the parameters could be either types (to be resolved) or parameters passed in?

So if I have a func I'd like to use for registration such as this:

    public static ClassTarget Resolver(ClassArg1 arg1, ClassArg2 arg2)
    {
        // Do something fancier than this...
        return new ClassTarget(arg1, arg2);
    }

How would I register that for ClassTarget?

A full example, with the hack I'm currently using and alternate registration that work (as one would expect):

public class ClassArg1 { }

public class ClassArg2 { }

public class ClassTarget
{
    public ClassTarget(ClassArg1 arg1, ClassArg2 arg2) {  }
}

public static class ResolveFuncTest
{
    public static ClassTarget Resolver(ClassArg1 arg1, ClassArg2 arg2)
    {
        // Do something fancier than this...
        return new ClassTarget(arg1, arg2);
    }

    private static T GetArgValue<T>(IComponentContext componentContext, IEnumerable<Parameter> parameters)
    {
        if (parameters != null)
        {
            var param = parameters.OfType<TypedParameter>().FirstOrDefault(p => p.Type == typeof(T));
            if (param != null)
            {
                return (T)param.Value;
            }
        }

        return componentContext.Resolve<T>();
    }

    public static void Test()
    {
        var builder = new ContainerBuilder();

        // The first argument will be resolved as normal
        builder.RegisterType<ClassArg1>().AsSelf().SingleInstance();

        // Works - just a typical type registration without the Func used
        //builder.RegisterType<ClassTarget>().AsSelf().SingleInstance();
        // Works - but only if we know how to resolve the arguments as types or parameters
        //builder.Register((c, p) => Resolver(c.Resolve<ClassArg1>(), p.TypedAs<ClassArg2>())).AsSelf().SingleInstance();
        // Works - smells though!
        builder.Register((c, p) => Resolver(GetArgValue<ClassArg1>(c,p), GetArgValue<ClassArg2>(c, p))).AsSelf().SingleInstance();

        // Build/scope
        var context = builder.Build();
        var scope = context.BeginLifetimeScope();

        // The second argument is passed as an instance/parameter at resolve time
        scope.Resolve<ClassTarget>(new TypedParameter(typeof(ClassArg2), new ClassArg2()));
    }
}

Clearly I misunderstand something core here, since I'm stumbling over myself to do the parameter resolution that Autofac normally does so seamlessly! Is there just another overload to Resolve I'm missing in the documentation?

1

There are 1 answers

5
Travis Illig On BEST ANSWER

If you have to do your initialization entirely inside that Resolver function, you are probably stuck using the mechanism you have now. That is, if you have a specific function and, for whatever reason, you have to both instantiate the ClassTarget object and you have to initialize it there, you're stuck.

If you can refactor a bit, you can use the delegate factories feature of Autofac to your advantage.

Here's an example of what the code could look like if you refactored a bit and used the delegate factory feature:

public class ClassArg1 { }

public class ClassArg2 { }

public class ClassTarget
{
  // Create a delegate factory with the set of parameters you require
  // during the Resolve operation - things that won't be auto-filled by Autofac.
  public delegate ClassTarget Factory(ClassArg2 arg2);

  // The constructor can have all the required parameters. Make sure the
  // names here match the names in the delegate factory.
  public ClassTarget(ClassArg1 arg1, ClassArg2 arg2)  { }

  // Just something to show the initalization working.
  public bool IsInitialized { get; set; }
}

public static class ResolveFuncTest
{
  public static void Initialize(ClassTarget target)
  {
    // Instead of newing up the ClassTarget here, let Autofac do that
    // through the delegate factory and *only* do initialization here -
    // the "something fancier" you previously alluded to.
    target.IsInitialized = true;
  }

  public static void Test()
  {
    // Register the argument that gets populated by Autofac.
    var builder = new ContainerBuilder();
    builder.RegisterType<ClassArg1>().AsSelf().SingleInstance();

    // Register the ClassTarget and Autofac will see the factory delegate.
    builder.RegisterType<ClassTarget>().OnActivated(args => Initialize(args.Instance));

    var context = builder.Build();
    using(var scope = context.BeginLifetimeScope())
    {
      // Resolve a factory delegate rather than resolving the class directly.
      var factory = scope.Resolve<ClassTarget.Factory>();
      var classTarget = factory(new ClassArg2());

      // Do whatever you need.
      Console.WriteLine("ClassTarget is initialized? {0}", classTarget.IsInitialized);
    }
  }
}

I'm guessing this is a little closer to what you were hoping to achieve.