Home > Blockchain >  Resolve service by short class name while using RegisterAssemblyTypes
Resolve service by short class name while using RegisterAssemblyTypes

Time:05-12

I would like to fetch component by short class name. Example:

public interface ITransactionDef{}
public class Transaction1 : ITransactionDef {}

I will have hundreds of transaction classes. So I used this to register all ITransactionDef-s:

builder.RegisterAssemblyTypes(typeof(MyType).Assembly)
    .AssignableTo<ITransactionDef>().As<ITransactionDef>()
    .AsSelf().InstancePerLifetimeScope();

However in my controller I have this:

public IActionResult GetTransaction([FromQuery]GetTransactionRequestDto actionRequestDto) 
{
    ITransactionDef trx = (ITransactionDef)serviceProvider
        .GetServices(typeof(ITransactionDef))
        .FirstOrDefault(s => s.GetType().Name == actionRequestDto.TransactionId);
    ...
}

I don't want this since it will construct all transactions on every request. I thought about three solutions:

  1. Receive full class name in actionRequestDto.TransactionId, get type and use that type instead of typeof(ITransactionDef). I would like to skip this. It seems a bit strange to have full class name somewhere in front end.
  2. Use named component, however I didn't find a way to register component by its short class name. Also, I need to do this inside RegisterAssemblyTypes.
  3. Using TransactionDefRepository and register components with it:
builder.Register(c => new TransactionDefRepository());

builder.RegisterAssemblyTypes(typeof(MyType).Assembly).AssignableTo<ITransactionDef>().
            OnActivating(e => {
                TransactionDefRepository repository =
                    e.Context.Resolve<TransactionDefRepository>();
                ITransactionDef transactionDef = ((ITransactionDef)e.Instance);
                repository.Register(transactionDef);
            })
            .AsSelf().InstancePerLifetimeScope();

And then use it in controller method:

Type trxType = trxRepository.GetTransactionType(actionRequestDto.TransactionId);
ITransactionDef trx = (ITransactionDef)serviceProvider.GetServices(trxType)

This won't work since OnActivating is called when component is asked for, so TransactionDefRepository is empty.

How can I implement this? Preferably without solution 1.

CodePudding user response:

When you say "short class name" I assume what you mean is Type.Name (SomeTransaction) rather than Type.FullName (MyNamespace.SomeTransaction).

You won't see anything specific to Autofac to help you here because types do need to be qualified. If you think about it, you might have FirstNamespace.SomeType and SecondNamespace.SomeType so the Type.Name is going to be the same thing. (Actually, you can technically have the same Type.FullName in two different assemblies loaded at the same time so you get into Type.AssemblyQualifiedName at that point, but let's not get in the weeds there.)

Thus, if you want to solve it, likely you'll have to write something on your own.

However, there's one way I can you could do it using just Autofac, but it won't be available from an IServiceProvider, you'd have to use Autofac: metadata

When you register types, you can register with metadata. Add a SimpleName metadata value at registration time.

builder
  .RegisterAssemblyTypes(typeof(MyType).Assembly)
  .AssignableTo<ITransactionDef>()
  .WithMetadata("SimpleName", t => t.Name)
  .As<ITransactionDef>()
  .InstancePerLifetimeScope();

Note I switched it to register them as ITransactionDef this comes in handy because you can inject the types into your controller without resolving them by using the Lazy<T> relationship, and you can query metadata using the Meta<T> relationship. They're combinable, so:

public class MyController
{
  private readonly IEnumerable<Meta<Lazy<ITransactionDef>>> _transactionDefs;

  public MyController(IEnumerable<Meta<Lazy<ITransactionDef>>> transactionDefs)
  {
    this._transactionDefs = transactionDefs;
  }

  public string SomeAction(RequestObject request)
  {
    var simpleName = request.GetTheSimpleName();

    // Find the right Meta<T> from the list
    var meta = this._transactionDefs.First(m => m.Metadata["SimpleName"].Equals(simpleName));
    // Get the Lazy<T> out of the Meta<T>
    var lazy = meta.Value;
    // Get the T out of the Lazy<T>
    var transactionDef = lazy.Value;

    // Now you have the right thing resolved
    transactionDef.DoWork(request);
  }
}

You can tighten that up into a one-liner LINQ query but I broke it down so you can see what's going on.

Anyway, that's one way to do it. You could technically wrap that in a factory if you wanted and then inject the factory.

public class DefFactory
{
  private readonly IEnumerable<Meta<Lazy<ITransactionDef>>> _transactionDefs;

  public DefFactory(IEnumerable<Meta<Lazy<ITransactionDef>>> transactionDefs)
  {
    this._transactionDefs = transactionDefs;
  }

  public ITransactionDef GetDef(string simpleName)
  {
    return this._transactionDefs
      .First(m => m.Metadata["SimpleName"].Equals(simpleName))
      .Value
      .Value;
  }
}

You can expand on the concept however you want. But, anyway, this is probably the easiest thing to do to get what you want.

  • Related