JasperFx / lamar

Fast Inversion of Control Tool and Successor to StructureMap
https://jasperfx.github.io/lamar
MIT License
572 stars 119 forks source link

Unable to override a named registration in Lamar #343

Open torfig opened 2 years ago

torfig commented 2 years ago

I'm using Lamar for DI and the IHostBuilder.OverrideServices method to introduce fakes/mocks during integration testing which works fine until I want to replace a named instance.

builder.OverrideServices(services =>
{
    services.For<IUserController>().Use(Mocks.UserController.Object);
    services.For<ICompanyRepositoryProvider>().Use(Mocks.CompanyRepositoryProvider.Object);
    services.For<IGeoCodingDataAccess>().Use(Mocks.GeoCodingDataAccess.Object);
    services.For<IDbRepository().Use(Mocks.DataAccessRepository.Object).Named("AlreadyRegisteredName");
});

As soon as I add a named instance in OverrideServices that is already registered elsewhere, I get the following error:

System.InvalidOperationException
      HResult=0x80131509
      Message=Referenced instance of Company.DbRepository.Interfaces.IDbRepository named 'AlreadyRegisteredName' does not exist
      Source=Lamar
      StackTrace:
       at Lamar.IoC.Instances.ReferencedInstance.<createPlan>d__10.MoveNext()
       at System.Linq.Enumerable.Any[TSource](IEnumerable`1 source, Func`2 predicate)
       at Lamar.IoC.Instances.Instance.CreatePlan(ServiceGraph services)
       at Lamar.IoC.Instances.ConstructorInstance.buildOutConstructorArguments(ServiceGraph services)
       at Lamar.IoC.Instances.ConstructorInstance.createPlan(ServiceGraph services)
       at Lamar.IoC.Instances.Instance.CreatePlan(ServiceGraph services)
       at Lamar.IoC.Instances.ConstructorInstance.buildOutConstructorArguments(ServiceGraph services)
       at Lamar.IoC.Instances.ConstructorInstance.createPlan(ServiceGraph services)
       at Lamar.IoC.Instances.Instance.CreatePlan(ServiceGraph services)
       at Lamar.IoC.Instances.ConstructorInstance.buildOutConstructorArguments(ServiceGraph services)
       at Lamar.IoC.Instances.ConstructorInstance.createPlan(ServiceGraph services)
       at Lamar.IoC.Instances.Instance.CreatePlan(ServiceGraph services)
       at Lamar.ServiceGraph.planResolutionStrategies()
       at Lamar.ServiceGraph.buildOutMissingResolvers()
       at LamarCodeGeneration.Util.PerfTimer.Record(String text, Action action)
       at Lamar.ServiceGraph.Initialize(PerfTimer timer)
       at Lamar.IoC.Scope..ctor(IServiceCollection services, PerfTimer timer)
       at Lamar.Container..ctor(IServiceCollection services)
       at Lamar.Microsoft.DependencyInjection.LamarServiceProviderFactory.CreateServiceProvider(IServiceCollection containerBuilder)
       at Microsoft.Extensions.Hosting.Internal.ServiceFactoryAdapter`1.CreateServiceProvider(Object containerBuilder)
       at Microsoft.Extensions.Hosting.HostBuilder.CreateServiceProvider()
       at Microsoft.Extensions.Hosting.HostBuilder.Build()
       at Company.Integration.Orchestrator.IntegrationsOrc.Program.Main(String[] args) in C:\Repos\integration-orc-service\Clients\IntegrationsOrc\Program.cs:line 64

I'm unsure if this has any relevance, but we're using a policy to reference the correct registered name when resolving the IDbRepository(and use an Attribute on the IDbRepositoryparameter to specify the name to resolve to)

 public class NamedInstancePolicy<T> : ConfiguredInstancePolicy
 {
     /// <inheritdoc/>
     protected override void apply(IConfiguredInstance instance)
     {
         foreach (var param in instance.ImplementationType.GetConstructors()
                                                                  .SelectMany(x => x.GetParameters())
                                                                  .Where(x => x.ParameterType == typeof(T)))
         {
             // Primary route is to resolve the instance based on a custom attribute
             var ipAttribute = param.GetCustomAttribute<InstancePolicyNameAttribute>();
             if (ipAttribute == null)
             {
                 // The IDbConnection parameter was not marked with the correct attribute
                 throw new InvalidOperationException($"A {param.ParameterType.Name} parameter in {instance.Name} does not contain the required InstancePolicyNameAttribute to define which instance should be resolved.");
             }

             // Parameter was correctly set up, assign the named instance to the constructor parameter
             instance.Ctor<T>(param.Name).IsNamedInstance(ipAttribute.InstanceName);
         }
     }
 }

I'm using the latest version of Lamar (8.0.1) and asp.net core 6.0.

This same error occurs if I register the same type with the same name twice. Any ideas how to resolve this? Maybe I could remove the previous named registration somehow (I'd need a way to do that within the OverrideServices method)?

magkal commented 4 months ago

Did you find a solution for this? I've run into the same issue with named registrations.

rizi commented 4 months ago

Did you find a solution for this? I've run into the same issue with named registrations.

I don't know if this works when using builder.OverrideService()...., but generally this is what work's for us:

/// <summary>
/// Registers the given instance with the given namen and removes the last already available registration for this type and with this name.
/// </summary>
/// <param name="instance">The instance which should be registered with the given name.</param>
/// <param name="name">The name of the registration.</param>
/// <typeparam name="TType">The type of the given instance.</typeparam>
/// <param name="serviceRegistry">The service registry is needed to remove already existing registrations.</param>
public static TType OverrideNamed<TType>(this TType instance, string name, ServiceRegistry serviceRegistry) where TType : Instance
{
    if (instance == null)
        throw new ArgumentNullException(nameof(instance));
    if (string.IsNullOrWhiteSpace(name))
        throw new ArgumentException(@"Value cannot be null or whitespace.", nameof(name));
    if (serviceRegistry == null)
        throw new ArgumentNullException(nameof(serviceRegistry));

    serviceRegistry.RemoveAll(serviceDescriptor => serviceDescriptor.ServiceType == instance.ServiceType && serviceDescriptor.ImplementationInstance is Instance currentInstance && currentInstance.Name.Equals(name, StringComparison.OrdinalIgnoreCase));

    instance.Named(name);

    return instance;
}