Search code examples
c#autofac

Registering multiple named types using reflection in Autofac


I have about 100 repository classes that all implement the same interface. They all have a common dependency.

I have registered two versions of this common dependency with different names.

I want to register all of my repository classes a second time using the named dependency.

This is basically what my original registration looks like:

builder.RegisterAssemblyTypes(typeof(Repository<>).Assembly)
    .WithParameter(
        new ResolvedParameter(
            (pi, ctx) => pi.ParameterType == typeof(IClientContext),
            (pi, ctx) => ctx.ResolveNamed<IClientContext>(RegistrationKeys.Published)))
    .AsImplementedInterfaces();

So if I want to register all of the same types a second time, using a different key, I would need to do something like this:

builder.RegisterAssemblyTypes(typeof(Repository<>).Assembly)
    .WithParameter(
        new ResolvedParameter(
            (pi, ctx) => pi.ParameterType == typeof(IClientContext),
            (pi, ctx) => ctx.ResolveNamed<IClientContext>(RegistrationKeys.Unpublished)))
    .Named(RegistrationKeys.Unpublished)
    .AsImplementedInterfaces();

However this won't work, because the Named method requires the type of the registration to be specified, but it should be dynamic based on an array of resolved types from the RegisterAssemblyTypes call.

How can I do this cleanly without having to add hundreds of lines of code to my application?


Solution

  • You may be able to write your own extension method to accomplish this.

    You might have noticed that there is an overload of .Named that takes a function:

    builder.RegisterAssemblyTypes(typeof(AComponent).GetTypeInfo().Assembly)
           .Named(t => t.Name, typeof(object));
    

    That function takes in the Type being registered from the assembly and uses that type to generate the name on the service. Unfortunately, that overload also only takes a specific fixed type as the service type, so this is registering everything as Named<object>(...) which isn't what you want.

    BUT!

    If you look at how that's implemented...

    public static IRegistrationBuilder<TLimit, TScanningActivatorData, TRegistrationStyle>
      Named<TLimit, TScanningActivatorData, TRegistrationStyle>(
        this IRegistrationBuilder<TLimit, TScanningActivatorData, TRegistrationStyle> registration,
        Func<Type, string> serviceNameMapping,
        Type serviceType)
      where TScanningActivatorData : ScanningActivatorData
    {
      return registration.As(t => new KeyedService(serviceNameMapping(t), serviceType));
    }
    

    ...you can see that basically it's taking the type being registered and passing it to a function you provide to generate that name. You could change it to not take in a specific service type and just register it as itself.

    public static IRegistrationBuilder<TLimit, TScanningActivatorData, TRegistrationStyle>
      NamedSelf<TLimit, TScanningActivatorData, TRegistrationStyle>(
        this IRegistrationBuilder<TLimit, TScanningActivatorData, TRegistrationStyle> registration,
        Func<Type, string> serviceNameMapping)
      where TScanningActivatorData : ScanningActivatorData
    {
      return registration.As(t => new KeyedService(serviceNameMapping(t), t));
    }
    

    Now use your custom NamedSelf extension.

    builder.RegisterAssemblyTypes(typeof(Repository<>).Assembly)
        .WithParameter(
            new ResolvedParameter(
                (pi, ctx) => pi.ParameterType == typeof(IClientContext),
                (pi, ctx) => ctx.ResolveNamed<IClientContext>(RegistrationKeys.Unpublished)))
        .NamedSelf(t => RegistrationKeys.Unpublished)
        .AsImplementedInterfaces();
    

    You can, of course, update that however you want. Maybe you don't want it to be a function and just take a string - easy enough. Maybe you want it to be registered as a named interface - you could have the function generate the whole KeyedService rather than embedding that in the extension.

    Point being, you can use the existing extensions as inspiration for writing your own custom extensions that do what you want and avoid having to manually register things.

    [Disclaimer: I didn't run all this through a compiler other than my mind so there may be a typo that stops a copy/paste from compiling. You'll also want to check for null arguments and all that. Follow the links to see the actual, original source. Hopefully this at least unblocks you.]