动态实例化自定义Action的依赖注入方案优化咨询
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:
1. Abstract Dependency Resolver Interface (Recommended)
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
IDependencyResolverabstraction 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

