简体   繁体   中英

Autofac how to use both default and non-default ctor based on context

I have two classes like:

public class P 
{
    public P( Guid g )  {}
}
public class Lp
{
    public Lp() {}
    public Lp( P p ) {}
}   

I register both of them with Autofac 6.2.0 like:

builder.RegisterType< P >();
builder.RegisterType< Lp >();

Now I want to resolve an Lp using the default ctor. When I try to resolve Lp like:

var c = builder.Build();
using( var scope = c.BeginLifetimeScope() )
{
   scope.Resolve<Lp>();
}

I get an error that none of the constructors found for type P can be invoked with the available services and parameters. Well, yes, that's true.

Apparently, since type P has been registered, Autofac thinks it should be able to create a P and use it to create the Lp instead of recognizing that it has no way of creating a P without a Guid and thus use the default Lp ctor. If I don't register P , the resolve succeeds. If I add a default ctor to P , the resolve succeeds (except that it uses the non-default Lp ctor).

The diagnostics aren't very interesting, but it looks like:

{   Resolve Request Starting   {
    Service: CtsMasterTs.Positions.Lp
    Component: CtsMasterTs.Positions.Lp

    Pipeline:
    -> CircularDependencyDetectorMiddleware
      -> ScopeSelectionMiddleware
        -> SharingMiddleware
          -> RegistrationPipelineInvokeMiddleware
            -> ActivatorErrorHandlingMiddleware
              -> DisposalTrackingMiddleware
                -> Lp (ReflectionActivator)
                  Resolve Request Starting
                  {
                    Service: CtsMasterTs.Positions.P
                    Component: CtsMasterTs.Positions.P

                    Pipeline:
                    -> CircularDependencyDetectorMiddleware
                      -> ScopeSelectionMiddleware
                        -> SharingMiddleware
                          -> RegistrationPipelineInvokeMiddleware
                            -> ActivatorErrorHandlingMiddleware
                              -> DisposalTrackingMiddleware
                                -> P (ReflectionActivator)
                                X- P (ReflectionActivator)
                              X- DisposalTrackingMiddleware
                            X- ActivatorErrorHandlingMiddleware
                          X- RegistrationPipelineInvokeMiddleware
                        X- SharingMiddleware
                      X- ScopeSelectionMiddleware
                    X- CircularDependencyDetectorMiddleware
                  }
                  Resolve Request FAILED
                    Autofac.Core.DependencyResolutionException: None of the constructors found with
'Autofac.Core.Activators.Reflection.DefaultConstructorFinder' on type
'CtsMasterTs.Positions.P' can be invoked with the available services
and parameters:
                    Cannot resolve parameter 'System.Guid g' of constructor 'Void .ctor(System.Guid)'.
                       at Autofac.Core.Activators.Reflection.ReflectionActivator.GetAllBindings(ConstructorBinder[]
availableConstructors, IComponentContext context, IEnumerable`1
parameters) in
/_/src/Autofac/Core/Activators/Reflection/ReflectionActivator.cs:line
175
                       at Autofac.Core.Activators.Reflection.ReflectionActivator.ActivateInstance(IComponentContext
context, IEnumerable`1 parameters) in
/_/src/Autofac/Core/Activators/Reflection/ReflectionActivator.cs:line
134
                       at Autofac.Core.Activators.Reflection.ReflectionActivator.<ConfigurePipeline>b__11_0(ResolveRequestContext
ctxt, Action`1 next) in
/_/src/Autofac/Core/Activators/Reflection/ReflectionActivator.cs:line
104
                       at Autofac.Core.Resolving.Pipeline.ResolvePipelineBuilder.<>c__DisplayClass14_0.<BuildPipeline>b__1(ResolveRequestContext
ctxt) in
/_/src/Autofac/Core/Resolving/Pipeline/ResolvePipelineBuilder.cs:line
262
                       at Autofac.Core.Resolving.Middleware.DisposalTrackingMiddleware.Execute(ResolveRequestContext
context, Action`1 next) in
/_/src/Autofac/Core/Resolving/Middleware/DisposalTrackingMiddleware.cs:line
32
                       at Autofac.Core.Resolving.Pipeline.ResolvePipelineBuilder.<>c__DisplayClass14_0.<BuildPipeline>b__1(ResolveRequestContext
ctxt) in
/_/src/Autofac/Core/Resolving/Pipeline/ResolvePipelineBuilder.cs:line
262
                       at Autofac.Core.Resolving.Middleware.ActivatorErrorHandlingMiddleware.Execute(ResolveRequestContext
context, Action`1 next) in
/_/src/Autofac/Core/Resolving/Middleware/ActivatorErrorHandlingMiddleware.cs:line
36
                X- Lp (ReflectionActivator)
              X- DisposalTrackingMiddleware
            X- ActivatorErrorHandlingMiddleware
          X- RegistrationPipelineInvokeMiddleware
        X- SharingMiddleware
      X- ScopeSelectionMiddleware
    X- CircularDependencyDetectorMiddleware   }   Resolve Request FAILED: Nested Resolve Failed } Operation FAILED  
Autofac.Core.DependencyResolutionException: An exception was thrown
while activating CtsMasterTs.Positions.Lp -> CtsMasterTs.Positions.P.
---> Autofac.Core.DependencyResolutionException: None of the constructors found with
'Autofac.Core.Activators.Reflection.DefaultConstructorFinder' on type
'CtsMasterTs.Positions.P' can be invoked with the available services
and parameters:   Cannot resolve parameter 'System.Guid g' of
constructor 'Void .ctor(System.Guid)'.
     at Autofac.Core.Activators.Reflection.ReflectionActivator.GetAllBindings(ConstructorBinder[]
availableConstructors, IComponentContext context, IEnumerable`1
parameters) in
/_/src/Autofac/Core/Activators/Reflection/ReflectionActivator.cs:line
175
     at Autofac.Core.Activators.Reflection.ReflectionActivator.ActivateInstance(IComponentContext
context, IEnumerable`1 parameters) in
/_/src/Autofac/Core/Activators/Reflection/ReflectionActivator.cs:line
134
     at Autofac.Core.Activators.Reflection.ReflectionActivator.<ConfigurePipeline>b__11_0(ResolveRequestContext
ctxt, Action`1 next) in
/_/src/Autofac/Core/Activators/Reflection/ReflectionActivator.cs:line
104
     at Autofac.Core.Resolving.Pipeline.ResolvePipelineBuilder.<>c__DisplayClass14_0.<BuildPipeline>b__1(ResolveRequestContext
ctxt) in
/_/src/Autofac/Core/Resolving/Pipeline/ResolvePipelineBuilder.cs:line
262
     at Autofac.Core.Resolving.Middleware.DisposalTrackingMiddleware.Execute(ResolveRequestContext
context, Action`1 next) in
/_/src/Autofac/Core/Resolving/Middleware/DisposalTrackingMiddleware.cs:line
32
     at Autofac.Core.Resolving.Pipeline.ResolvePipelineBuilder.<>c__DisplayClass14_0.<BuildPipeline>b__1(ResolveRequestContext
ctxt) in
/_/src/Autofac/Core/Resolving/Pipeline/ResolvePipelineBuilder.cs:line
262
     at Autofac.Core.Resolving.Middleware.ActivatorErrorHandlingMiddleware.Execute(ResolveRequestContext
context, Action`1 next) in
/_/src/Autofac/Core/Resolving/Middleware/ActivatorErrorHandlingMiddleware.cs:line
36
     --- End of inner exception stack trace ---
     at Autofac.Core.Resolving.Middleware.ActivatorErrorHandlingMiddleware.Execute(ResolveRequestContext
context, Action`1 next) in
/_/src/Autofac/Core/Resolving/Middleware/ActivatorErrorHandlingMiddleware.cs:line
48
     at Autofac.Core.Resolving.Pipeline.ResolvePipelineBuilder.<>c__DisplayClass14_0.<BuildPipeline>b__1(ResolveRequestContext
ctxt) in
/_/src/Autofac/Core/Resolving/Pipeline/ResolvePipelineBuilder.cs:line
262
     at Autofac.Core.Resolving.Pipeline.ResolvePipelineBuilder.<>c__DisplayClass14_0.<BuildPipeline>b__1(ResolveRequestContext
ctxt) in
/_/src/Autofac/Core/Resolving/Pipeline/ResolvePipelineBuilder.cs:line
262
     at Autofac.Core.Resolving.Middleware.SharingMiddleware.Execute(ResolveRequestContext
context, Action`1 next) in
/_/src/Autofac/Core/Resolving/Middleware/SharingMiddleware.cs:line 58
     at Autofac.Core.Resolving.Pipeline.ResolvePipelineBuilder.<>c__DisplayClass14_0.<BuildPipeline>b__1(ResolveRequestContext
ctxt) in
/_/src/Autofac/Core/Resolving/Pipeline/ResolvePipelineBuilder.cs:line
262
     at Autofac.Core.Resolving.Pipeline.ResolvePipelineBuilder.<>c__DisplayClass14_0.<BuildPipeline>b__1(ResolveRequestContext
ctxt) in
/_/src/Autofac/Core/Resolving/Pipeline/ResolvePipelineBuilder.cs:line
262
     at Autofac.Core.Resolving.Middleware.CircularDependencyDetectorMiddleware.Execute(ResolveRequestContext
context, Action`1 next) in
/_/src/Autofac/Core/Resolving/Middleware/CircularDependencyDetectorMiddleware.cs:line
94
     at Autofac.Core.Resolving.Pipeline.ResolvePipelineBuilder.<>c__DisplayClass14_0.<BuildPipeline>b__1(ResolveRequestContext
ctxt) in
/_/src/Autofac/Core/Resolving/Pipeline/ResolvePipelineBuilder.cs:line
262
     at Autofac.Core.Resolving.ResolveOperation.GetOrCreateInstance(ISharingLifetimeScope
currentOperationScope, ResolveRequest request) in
/_/src/Autofac/Core/Resolving/ResolveOperation.cs:line 150
     at Autofac.Core.Resolving.ResolveOperation.ExecuteOperation(ResolveRequest
request) in /_/src/Autofac/Core/Resolving/ResolveOperation.cs:line 182

This is a problem for me, because I need to be able to create Lp either with or without a P . Elsewhere in the code I use injected Func<Lp> and Func<P, Lp> as factories. I also use injected Func<Guid, P> to create P objects.

So my question is - how do I either:

A) Get Autofac to look through to the P ctor and understand that it can't use the non-default Lp ctor without a P parameter, OR

B) Work some Autofac magic in other ways (eg lambda registrations?) to allow Func<Lp> and Func<P, Lp> to both work? I've looked briefly at using ctor selection, but it seems that would force using the same ctor every time the type is resolved.

Other information -.Net Framework 4.8, VS2019, Windows 10, WPF. This is a boiled-down example that demonstrates what's happening in my (more complex) application code.

Update - Is this a code smell? I'm still trying to wrap my head around that.

The use case is that P is immutable. Lp is the "editable" version of P. When a new P is needed, I create a new Lp with the default ctor, edit the property values, and then create a P from it using Lp.CreateP(). If P already exists and needs to be edited, I create a new Lp using Lp( P i_p ), edit the property values, and then replace the original P with a new P using Lp.CreateP(). It seems reasonable to me, but maybe I'm missing something.

This is not really a common use case. The vast majority of usage would be either:

  • Select the constructor that should always be used (either use the constructor with P or don't, but not "different by context"); OR
  • Use the constructor that can be filled in by the most available things in DI (which is the default behavior for Autofac)

This use case is more like... "I want to use the default Autofac behavior _except in this one nested scope where I need to override which constructor is used." I would recommend analyzing the design that necessitates this because it seems like a code smell.

However, the best you can do is to re-register Lp in the child lifetime scope and force the constructor you want. The resolution will happen based on the override and should do what you want.

var builder = new ContainerBuilder();
builder.RegisterType<P>();
builder.RegisterType<Lp>();
var container = builder.Build();

// This will use the `Lp(P)` constructor - most matched
var first = container.Resolve<Lp>();

using(var scope = container.BeginLifetimeScope(b =>
  {
    b.RegisterType<Lp>().UsingConstructor(Array.Empty<Type>());
  })
{
  // This should use the default no-parameter constructor.
  var second = scope.Resolve<Lp>();
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM