JasperFx / lamar

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

registration error misses original reason #204

Closed oleksabor closed 4 years ago

oleksabor commented 4 years ago

Hi

Please help me how to get the error origin in case if Lamar (4.0.0) Container can not resolve the dependency:

[Subject("containerTest")]
public class nested_parameters
{
    static Container sut;
    Establish init = () => sut = new Container(_ =>
    {
        _.Scan(x => { x.TheCallingAssembly(); x.WithDefaultConventions(); });
    });

    Because of = () => {; };

    It some_not_null = () => sut.GetInstance<SomeImpl>().ShouldNotBeNull();
    It another_not_null = () => sut.GetInstance<AnotherImpl>().ShouldNotBeNull();

    It interface_resolved = () => sut.GetInstance<IInversion>().ShouldNotBeNull();
}
public class SomeImpl
{
    public SomeImpl(AnotherImpl impl) { }
}
public class AnotherImpl
{
    public AnotherImpl(IInversion pl) { }
}
public interface IInversion {   }
public class Inversion2 : IInversion    {   }

interface_resolved fails because Inversion2 class name does not match to the interface name. The error message is ok and I understand what to do:

No service registrations exist or can be derived for IInversion

another_not_null fails with error message:

No service registrations exist or can be derived for AnotherImpl

This message does not contain the error origin and I do not know why it fails.

The same is for the some_not_null

No service registrations exist or can be derived for SomeImpl

Is there a chance to get the IInversion interface name in the error message for both some_not_null and another_not_null cases ?

The exception VS test output looks like:

Result StackTrace:  
Lamar.IoC.LamarMissingRegistrationException: No service registrations exist or can be derived for MessageClient.Test.AnotherImpl
   at Lamar.IoC.Scope.GetInstance(Type serviceType)
   at Lamar.IoC.Scope.GetInstance[T]()
   at MessageClient.Test.nested_parameters.<>c.<.ctor>b__6_3() in D:\MessageClient.Test\ContainerRegistryTest.cs:line 51
Result Message: No service registrations exist or can be derived for MessageClient.Test.AnotherImpl

There is no inner exception to get missing interface registration info unfortunately

Regards.

oleksabor commented 4 years ago

I've found that public bool ServiceGraph.CouldBuild(Type concreteType) calls the public ConstructorInfo ConstructorInstance<T>.DetermineConstructor(ServiceGraph services, out string message)

and there is error properly formed.

Cannot fill the dependencies of any of the public constructors Available constructors:new Bug_204_registration_error_misses_original_reason.AnotherImpl(Bug_204_registration_error_misses_original_reason.IInversion pl) *Bug_204_registration_error_misses_original_reason.IInversion is not registered within this container and cannot be auto discovered by any missing family policy

However message variable value stays in the ServiceGraph.CouldBuild and never used any more.

Exception is raised in the public object Scope.GetInstance(Type serviceType) that is not aware about the original reason.

The simplest way is to throw the exception from the ConstructorInstance<T>.DetermineConstructor However I'm afraid that this can break legacy logic that handles instance creation errors with if ( != null) checks.

Does it have sense to add new ServiceGraph property that will hold the ConstructorInstance<T>.DetermineConstructor error? Then new property can be used when Scope.GetInstance raises exception, like

public class ServiceGraph : IDisposable
{
public string CouldBuildError {get; protected set;}
public bool CouldBuild(Type concreteType)
{
    var constructorInstance = new ConstructorInstance(concreteType, concreteType, ServiceLifetime.Transient);
    foreach (var policy in InstancePolicies)
    {
        policy.Apply(constructorInstance);
    }

    var ctor = constructorInstance.DetermineConstructor(this, out string message);

    CouldBuildErrror = message;
    return ctor != null && message.IsEmpty();
}
}
public class Scope : IServiceContext
// ENDSAMPLE
{
public object GetInstance(Type serviceType)
{
    assertNotDisposed();
    var resolver = ServiceGraph.FindResolver(serviceType);

    if (resolver == null)
    {
        throw new LamarMissingRegistrationException(ServiceGraph.CouldBuildError, serviceType);
    }

    return resolver(this);
}
}

The only one problem I see is multi threading issue if ServiceGraph instance is shared between several Scope (public Scope(ServiceGraph serviceGraph, Scope root))

jeremydmiller commented 4 years ago

@oleksabor Good idea, and done. Just a wee bit different than what you suggested. This will be in Lamar 4.1 by early next week.