Vin Shahrdar
Vin Shahrdar

Reputation: 1231

"On Demand" dependency injection for dynamically instantiated classes

In my application, I have a class called EventTriggerActionService, which is responsible for instantiating custom actions that reside in different assemblies. It uses the class name and assembly name to load the module via Reflection, and executes it.

My main problem is: How do I inject the required dependencies into these custom actions? My actions all derive from a base class called ActionBase.

My initial solution was to first supply all the dependencies via constructor injection by just adding them as parameters in the (ActionBase)Activator.CreateInstance();, but this was problematic, because I didn't want to force all of my derived actions to take in the dependencies of other actions.

Here is my second solution: I decided to use events to put "Dependency Providers" in EventTriggerActionService. Using this approach, all of the dependencies for all custom actions will be injected in EventTriggerActionService, and the custom action will request the dependency by firing the event.

Here is my 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);

This is 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();
}

This is one of my custom actions:

public class SimpleEmailSender : ActionBase
{
    public override ResultBase ExecuteAction()
    {
        Emailer.Send(Arguments["EmailBody"]);
    }
}

Keep in mind that EventTriggerActionService resides in an isolated library, and will be used by different consumer applications. The consumer applications can choose to use IoC containers or just do Poor Man's DI.

My question is, is there a more optimal solution to my problem? I think my solution definitely addresses the issue of forcing the dependencies to all derived actions.

Upvotes: 2

Views: 4395

Answers (2)

Alex.Wei
Alex.Wei

Reputation: 1883

Because EventTriggerActionService don't know what type it's dealing with, it has to let custom actions decide what they want. So, I think that service locator pattern is a good solution for this. Assume you have service locator defined on EventTriggerActionService side like:

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]);
    }
}

Change your ActionBase and custom actions a little like:

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();
    }
}

Finally, in EventTriggerActionService, register all dependencies before create and instance of custom action.

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
    }
}

All codes there are just for showing the idea. Your can customize ServiceLocator or other things to suit your purpose.

Upvotes: 0

RMH
RMH

Reputation: 837

I think your main problem resides with your base class ActionBase.

The Action method within this class is basically a wrapper that executes some common logic before invoking the actual action. The common knowledge requires some external services (here the IEmailer) to function, which dependency you do not want to force upon the derived classes.


Maybe a simpler approach to using events would be to make a public IEmailer property and set it when the action is created in the EventTriggerActionService.Execute method.

This would prevent you from having to pass down the reference through the constructor, while still being able to access the IEmailer instance. You would get something along the lines of:

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
}

However, as you have control over the way the actions get invoked, why force the common logic into the base class?

You can easily extract the common logic and separate it from the action.
For example:

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
}

As you can see from the example, the logic is removed from the base class. Releasing any derived class from the need to supply those services. The only thing left is to inject the required services into the constructor of the created class.

The DI containers I've used in the past always provided some way of injecting services. If you cannot (or don't want to) use a container, it shouldn't be too hard to extract constructor information and resolve it yourself.

If you are using the ASP.NET Core, the default Microsoft dependency injection provides the utility class ActivatorUtilities for this (msdn)


In the case you do not want your common logic in the EventTriggerActionService, you can always extract it into a separate service, or make a special action that handles the common logic.

Upvotes: 3

Related Questions