在我的应用程序中,我有一个名为EventTriggerActionService
的类,它负责实例化驻留在不同程序集中的自定义操作。它使用类名和程序集名称通过Reflection加载模块,并执行它。
我的主要问题是:如何将所需的依赖项注入这些自定义操作?我的所有操作都来自一个名为ActionBase
的基类。
我的初始解决方案是首先通过构造函数注入提供所有依赖项,只需将它们作为参数添加到(ActionBase)Activator.CreateInstance();
中,但这是有问题的,因为我不想强制执行所有派生操作在其他行动的依赖中。
这是我的第二个解决方案:我决定使用事件将“依赖提供者”放在EventTriggerActionService
中。使用此方法,将在EventTriggerActionService
中注入所有自定义操作的所有依赖项,并且自定义操作将通过触发事件来请求依赖项。
这是我的EventTriggerActionService
:
public class EventTriggerActionService
{
private IEmailerFactory _emailerFactory;
public EventTriggerActionService(IEmailerFactory emailerFactory)
{
_emailerFactory = emailerFactory;
}
public void Execute(EventTriggerAction eventTriggerAction, EventContext context)
{
var assemblyName = eventTriggerAction.EventAction.EventActionHandlerAssembly;
var className = eventTriggerAction.EventAction.EventActionHandlerClass;
Assembly actionHandlerAssembly = Assembly.Load(assemblyName);
Type actionHandlerType = actionHandlerAssembly.GetType(className);
var action = (ActionBase)Activator.CreateInstance(); // Instantiate the action by its base class, ActionBase
action.Arguments = data.Arguments; // This is a dictionary that contains the arguments of the action
// Register all the dependency providers here
GetEmailer emailerHandler = GetEmailer;
actionHandlerType.GetEvent("GetEmailer").AddEventHandler(action, emailerHandler); // Register the event handler
action.Execute(); // Execute the Action
}
// The custom action "Requests" a dependency by firing this event
private void GetEmailer(ref IEmailer emailer)
{
emailer = _emailerFactory.Create();
}
}
public delegate void GetEmailer(ref IEmailer emailer);
这是ActionBase
:
public abstract class ActionBase
{
public event GetEmailer GetEmailer;
private IEmailer _emailer;
protected IEmailer Emailer
{
get
{
if (_emailer == null)
{
GetEmailer(ref _emailer);
}
return _emailer;
}
}
protected Dictionary<string,string> Arguments { get; set; }
public void Execute()
{
// Perform some common logic here
ExecuteAction(); // Execute the custom action
}
public abstract ResultBase ExecuteAction();
}
这是我的自定义操作之一:
public class SimpleEmailSender : ActionBase
{
public override ResultBase ExecuteAction()
{
Emailer.Send(Arguments["EmailBody"]);
}
}
请记住,EventTriggerActionService
位于隔离的库中,并将由不同的消费者应用程序使用。消费者应用程序可以选择使用IoC容器,也可以选择穷人的DI。
我的问题是,对我的问题有更优化的解决方案吗?我认为我的解决方案肯定解决了强制依赖于所有派生操作的问题。
答案 0 :(得分:1)
我认为您的主要问题在于您的基类ActionBase
。
此类中的Action
方法基本上是一个包装器,它在调用实际操作之前执行一些公共逻辑。
常识需要一些外部服务(此处为IEmailer
)才能运行,您不希望对派生类强制使用哪种依赖。
使用事件的一种更简单的方法可能是创建一个公共IEmailer
属性,并在EventTriggerActionService.Execute
方法中创建操作时设置它。
这样可以防止您必须通过构造函数传递引用,同时仍然可以访问IEmailer instance
。
你会得到以下内容:
public abstract class ActionBase
{
protected IEmailer Emailer { get; set; }
protected Dictionary<string,string> Arguments { get; set; }
public void Execute()
{
// Do something with Emailer.
ExecuteAction(); // Execute the custom action
}
public abstract ResultBase ExecuteAction();
}
public class EventTriggerActionService
{
// omit for readability
public void Execute(EventTriggerAction eventTriggerAction, EventContext context)
{
// omit for readability
var action = (ActionBase)Activator.CreateInstance(); // Instantiate the action by its base class, ActionBase
action.Arguments = data.Arguments; // This is a dictionary that contains the arguments of the action
action.Emailer = _emailerFactory.Create();
// omit for readability
}
// omit for readability
}
但是,由于您可以控制调用操作的方式,为什么要强制公共逻辑进入基类?
您可以轻松提取常用逻辑并将其与操作分开 例如:
public interface IActionBase
{
ResultBase ExecuteAction();
}
public class SimpleEmailSender : IActionBase
{
private Dictionary<string, string> arguments;
private IEmailer Emailer;
public SimpleEmailSender(Dictionary<string, string> arguments, IEmailer emailer)
{
this.arguments = arguments;
this.emailer = emailer;
}
public override ResultBase ExecuteAction()
{
this.emailer.Send(Arguments["EmailBody"]);
return null;
}
}
public class EventTriggerActionService
{
// omit for readability
public void Execute(EventTriggerAction eventTriggerAction, EventContext context)
{
// omit for readability
this.RunPreLogic();
action.ExecuteAction();
this.RunPostLogic();
// omit for readability
}
public void RunPreLogic() {}
public void RunPostLogic() {}
// omit for readability
}
从示例中可以看出,逻辑已从基类中删除。从提供这些服务的需要中释放任何派生类。 剩下的唯一事情是将所需的服务注入到创建的类的构造函数中。
我过去使用的DI容器总是提供一些注入服务的方式。如果你不能(或者不想)使用容器,那么提取构造函数信息并自己解决它应该不会太难。
如果您使用的是ASP.NET Core,则默认的Microsoft依赖注入为此提供实用程序类ActivatorUtilities
(msdn)
如果您不想在EventTriggerActionService
中使用公共逻辑,则可以始终将其提取到单独的服务中,或者执行处理公共逻辑的特殊操作。
答案 1 :(得分:0)
因为EventTriggerActionService
不知道它正在处理什么类型,所以必须让自定义操作决定他们想要什么。所以,我认为服务定位器模式是一个很好的解决方案。假设您在EventTriggerActionService
方面定义了服务定位器,如:
public static class ServiceLocator
{
private static Dictionary<string, Type> container = new Dictionary<string, Type>();
public static void RegisterService(string name, Type implType)
{
container.Add(name, implType);
}
public static objcet GetService(string name)
{
return Actinator.CreateInstance(container[name]);
}
}
将您的ActionBase
和自定义操作更改为:
public abstract class ActionBase
{
private static MethodInfo getServiceMI;
static ActionBase()
{
// Find out which assembly implement ServiceLocator.
var asm = AppDomain.CurrentDomain.GetAssemblies().First(x => x.GetTypes().Any(y => y.Name == "ServiceLocator"));
// Get MethodInfo of ServiceLocator.GetService.
getServiceMI = asm.GetType("ServiceLocator").GetMethod("GetService", BindingFlags.Public | BindingFlags.Static);
}
protected static object GetService(string name)
{
return getServiceMI.Invoke(null, new object[] { name });
}
// And your original staff.
}
public class ActionSample : ActionBase
{
IDependency1 d1 = GetService("name1");
...
IDependencyN dN = GetService("nameN");
public override ResultBase ExecuteAction()
{
d1.DoSomething();
...
dN.DoSomething();
}
}
最后,在EventTriggerActionService
中,在创建自定义操作的实例和实例之前注册所有依赖项。
public class EventTriggerActionService
{
static EventTriggerActionService()
{
ServiceLocator.RegisterService("name1", typeof(dependencyImpl1));
...
ServiceLocator.RegisterService("nameN", typeof(dependencyImplN));
}
public void Execute(EventTriggerAction eventTriggerAction, EventContext context)
{
var assemblyName = eventTriggerAction.EventAction.EventActionHandlerAssembly;
var className = eventTriggerAction.EventAction.EventActionHandlerClass;
Assembly actionHandlerAssembly = Assembly.Load(assemblyName);
var action = (ActionBase)Activator.CreateInstance(); // Instantiate the action by its base class, ActionBase
action.Arguments = data.Arguments; // This is a dictionary that contains the arguments of the action
action.Execute(); // Execute the Action
}
}
那里的所有代码都只是为了展示这个想法。您可以自定义ServiceLocator
或其他符合您目的的内容。