You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

动态实例化自定义Action的依赖注入方案优化咨询

Better Dependency Injection for Dynamically Loaded Actions

Great question! Your current event-driven approach does a good job of avoiding forcing all action classes to accept irrelevant dependencies, but we can refine this into a more flexible, maintainable solution—especially since your EventTriggerActionService lives in a shared library that needs to work with both full IoC containers and simple "Poor Man's DI" setups.

Here are the top improved approaches:

This pattern decouples your service from specific dependency types entirely, letting consuming apps plug in their own dependency resolution logic (whether that's Autofac, Microsoft.Extensions.DependencyInjection, or a custom manual implementation). Actions can then request exactly the dependencies they need, no per-dependency events required.

Step 1: Define a Universal Resolver Interface

public interface IDependencyResolver
{
    T Resolve<T>();
    object Resolve(Type serviceType);
}

Step 2: Update EventTriggerActionService

Inject the resolver instead of concrete dependencies, and pass it to your instantiated actions:

public class EventTriggerActionService 
{ 
    private readonly IDependencyResolver _resolver;

    public EventTriggerActionService(IDependencyResolver resolver) 
    { 
        _resolver = resolver; 
    } 

    public void Execute(EventTriggerAction eventTriggerAction, EventContext context) 
    { 
        var assemblyName = eventTriggerAction.EventAction.EventActionHandlerAssembly;
        var className = eventTriggerAction.EventAction.EventActionHandlerClass; 
        var actionHandlerAssembly = Assembly.Load(assemblyName); 
        var actionHandlerType = actionHandlerAssembly.GetType(className); 
        
        var action = (ActionBase)Activator.CreateInstance(actionHandlerType); 
        action.Arguments = eventTriggerAction.Arguments;
        action.SetDependencyResolver(_resolver); // Pass resolver to the action
        
        action.Execute(); 
    } 
}

Step 3: Refactor ActionBase to Use the Resolver

Add a method for actions to fetch dependencies on demand:

public abstract class ActionBase 
{ 
    private IDependencyResolver _resolver;

    public void SetDependencyResolver(IDependencyResolver resolver)
    {
        _resolver = resolver ?? throw new ArgumentNullException(nameof(resolver));
    }

    protected T GetDependency<T>()
    {
        if (_resolver == null)
            throw new InvalidOperationException("Dependency resolver not configured for this action.");
        return _resolver.Resolve<T>();
    }

    protected Dictionary<string, string> Arguments { get; set; } 

    public void Execute() 
    { 
        // Run shared logic here
        ExecuteAction(); 
    } 

    public abstract ResultBase ExecuteAction(); 
}

Step 4: Use the Resolver in Your Custom Action

public class SimpleEmailSender : ActionBase 
{ 
    public override ResultBase ExecuteAction() 
    { 
        // Fetch only the dependency you need
        var emailer = GetDependency<IEmailer>();
        emailer.Send(Arguments["EmailBody"]);
        return new SuccessResult();
    } 
}

Example Implementation for Consuming Apps

For an app using Autofac, the resolver might look like this:

public class AutofacDependencyResolver : IDependencyResolver
{
    private readonly IComponentContext _componentContext;

    public AutofacDependencyResolver(IComponentContext componentContext)
    {
        _componentContext = componentContext;
    }

    public T Resolve<T>() => _componentContext.Resolve<T>();
    public object Resolve(Type serviceType) => _componentContext.Resolve(serviceType);
}

Why This Works Better:

  • No more per-dependency events/delegates to maintain
  • Fully decouples your shared library from specific dependency types
  • Supports any IoC container or manual DI setup
  • Actions only request the dependencies they actually need

2. Property Injection with Container Support

If your consuming app uses an IoC container that supports property injection (like Autofac or Unity), you can leverage the container's built-in capabilities to inject dependencies into your dynamically loaded actions.

Update EventTriggerActionService

Let the container handle instantiation and injection:

public class EventTriggerActionService 
{ 
    private readonly IContainer _container;

    public EventTriggerActionService(IContainer container) 
    { 
        _container = container; 
    } 

    public void Execute(EventTriggerAction eventTriggerAction, EventContext context) 
    { 
        var assemblyName = eventTriggerAction.EventAction.EventActionHandlerAssembly;
        var className = eventTriggerAction.EventAction.EventActionHandlerClass; 
        var actionHandlerAssembly = Assembly.Load(assemblyName); 
        var actionHandlerType = actionHandlerAssembly.GetType(className); 
        
        // Let the container create the instance and inject properties
        var action = (ActionBase)_container.Resolve(actionHandlerType); 
        action.Arguments = eventTriggerAction.Arguments;
        
        action.Execute(); 
    } 
}

Update ActionBase for Property Injection

public abstract class ActionBase 
{ 
    // Mark properties for injection (container-specific attributes may apply)
    public IEmailer Emailer { get; set; }

    protected Dictionary<string, string> Arguments { get; set; } 

    public void Execute() 
    { 
        ExecuteAction(); 
    } 

    public abstract ResultBase ExecuteAction(); 
}

Pros & Cons:

  • Pros: Leverages container-native functionality, minimal custom code
  • Cons: Ties you to container-specific property injection rules, less flexible than the resolver pattern for mixed DI scenarios

3. Improved Constructor Injection (Optional)

If you prefer constructor injection (a best practice for mandatory dependencies), you can adjust your service to resolve constructor parameters dynamically instead of forcing all actions to accept the same dependencies.

Custom Action with Targeted Constructor

public class SimpleEmailSender : ActionBase 
{ 
    private readonly IEmailer _emailer;

    // Only inject the dependency this action needs
    public SimpleEmailSender(IEmailer emailer) 
    { 
        _emailer = emailer ?? throw new ArgumentNullException(nameof(emailer));
    }

    public override ResultBase ExecuteAction() 
    { 
        _emailer.Send(Arguments["EmailBody"]);
        return new SuccessResult();
    } 
}

Update EventTriggerActionService to Resolve Constructor Params

public class EventTriggerActionService 
{ 
    private readonly IDependencyResolver _resolver;

    public EventTriggerActionService(IDependencyResolver resolver) 
    { 
        _resolver = resolver; 
    } 

    public void Execute(EventTriggerAction eventTriggerAction, EventContext context) 
    { 
        var assemblyName = eventTriggerAction.EventAction.EventActionHandlerAssembly;
        var className = eventTriggerAction.EventAction.EventActionHandlerClass; 
        var actionHandlerAssembly = Assembly.Load(assemblyName); 
        var actionHandlerType = actionHandlerAssembly.GetType(className); 
        
        // Resolve parameters for the action's constructor
        var constructor = actionHandlerType.GetConstructors().First();
        var parameters = constructor.GetParameters()
                                    .Select(param => _resolver.Resolve(param.ParameterType))
                                    .ToArray();
        
        var action = (ActionBase)Activator.CreateInstance(actionHandlerType, parameters); 
        action.Arguments = eventTriggerAction.Arguments;
        
        action.Execute(); 
    } 
}

Why This Works:

  • Follows constructor injection best practices for required dependencies
  • Actions only receive the dependencies they declare
  • Works with the same IDependencyResolver abstraction from the first solution

Final Recommendation

The abstract dependency resolver pattern (Option 1) is the most robust choice for your scenario. It keeps your shared library flexible, supports any DI strategy, and eliminates the need for maintaining event handlers for every possible dependency type.

内容的提问来源于stack exchange,提问作者Vin Shahrdar

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 06:49:56