Inversion of Control in Dynamics 365
Developers and architects, are you developing an application with Dynamics 365? How can you make sure to focus on the use cases of the application in the context of the business domain and not on the way it is achieved? Learn more on dependency injection and inversion of control to achieve this.
Typical focus – Implementation details hiding the use case
I will focus on the implementation of this concept in Dynamics 365 because the way plugins are written presents a bit of a challenge to put this into practice. Indeed, the architecture of a plugin seems to resist inversion of control. Let's use the following example. When an account is created, we want to create a follow-up task to check in with the account holder in a week. (This is an example plugin from the Dynamics 365 Software Development Kit and simplified using my colleague’s PluginBase.) Normally, when writing a plugin, we would focus on the event that occurs, for instance, the creation of an account, and write the necessary logic to create a task. Reading the code is then necessary to understand its purpose. Now let's introduce the inversion of control principle. When an account is created, the task is also created, but I do not know how; I just want to know that it is created. The idea is to abstract away the implementation details. Why? Because implementation details should depend on the use case, and not the other way around. This keeps the code focused on what it is supposed to do. Before any adjustments, the code would look like this, which means we must read it to understand its purpose: Before
Converting from implementation details to use case first logic
The first step to implement inversion of control is to move the implementation details away into another class. I will then be able to test this class without using Dynamics CRM (making it a unit test instead of an integration test).This is what it looks like after moving the logic into another class: After moving away into another class - 3 files We can see here that the purpose of the plugin is clear. The interface is illustrated below and the concrete class CreateTaskOnAccountCreationCommand implements it essentially in the same way as before.
Integrating Ninject in your Dynamics 365 Plugin project in a supported way
I have previoulsy worked on a project that did not have dependency injection. In such a situation, creating the objects become burdensome when the constructor has 10 or more parameters. This is where dependency injection comes into play. To remove the dependence on the implementation details, we give Ninject the mandate of creating the instance of the class. This inversion of control framework is usually included by using Nuget to import its library. However, because Dynamics 365 does not support the merging of mutiple libraries, this solution isn't possible. As a workaround, we can download all the source code for Ninject, copy all the source files (*.cs) to our project, and start using it in the same way. No merging of libraries is required, and the Ninject license allows us to do this. In order to abstract away the implementation details and no longer be tightly coupled to them, meaning that the plugin will no longer show how the details are implemented, we start using Ninject. Why would we do this? Because it has the advantage of hiding the implementation details. So what? Well, now we can swap them out with another implementation, as needed. And we can test the implementation independently of Dynamics 365.
Introducing Dependency Injection with Ninject
The key parts are adding the dependency resolver, Ninject, and binding the interfaces to their implementations. To do this we add a reference to Ninject:
Creating an instance of a class with Ninject
Now in this way, the plugin does not know how the task creation is implemented, but upon reading the code, we know that this is what is intended. The inversion of control is performed by Ninject:
This means that when I want to run the logic that is, to actually create a task upon the creation of an account, Ninject will create an instance of the CreateTaskOnAccountCreationCommand class. It makes the link between the interface that tells me that I want to execute the command and how to actually do it. This has the advantage of allowing us to swap an implementation out for another by simply changing the Dependency Resolver. Finally, Dependency injection means that Ninject creates the objects with all their members for us. If that were not the case, things would be more tedious. For example, we could refactor this code into smaller parts: the task creation would be one class, and an orchestrator that invokes it upon the account creation would be another class. Now you might say this is overkill for such a simple need, but for more complex projects with many dependencies, this becomes interesting very quickly. Here is the orchestrator, wich needs the “create” command when it is instantiated: However, the command also needs two other parameters when it is instantiated:
Creating a class instance and passing in the Organization Service from the plugin
Without Ninject and inversion of control, we would need to create all the objects and supply the necessary parameters to all the constructors. Instead, we simply tell Ninject to bind the interfaces to the classes that implement them and sometimes specify parameters, for instance for the command:
When Ninject creates the CreateTaskOnAccountCreationCommand object, it will choose the most complex constructor and create the necessary objects. In this case, it will create an instance of the CreateTaskCommand object and pass it to the CreateTaskOnAccountCreationCommand constructor. To create the CreateTaskCommand object, it will create an instance of the TaskFactory object and pass it to the CreateTaskCommand constructor as well as the OrganizationService instance that the plugin supplied when it ran.
Coming back to the why
In brief, all this hiding away of the implementation details makes the code easier to read and to understand. Indeed, when you decouple the plugin stub from its implementation, it becomes easy to replace it and test it. In fact, we are detached from the implementation of concrete plugins. This streamlines the swapping out of the implementation details for another implementation, and it can be done all in one place. It also simplifies the logic testing in each of the classes, because we can implement an interface that tests the respective bits of code without testing the whole thing.
So, the advantages of doing all this are to:
- Focus on the use cases.
- Keep the code cleaner and easier to understand, maintain, and test.
- Be more decoupled to simplify the swapping of pieces of the code.
Ultimately, this practice is most beneficial in large projects, but not worth the effort in small projects such as our example. Nevertheless, this example does illustrate the principles that can be applied in a large project. This article is an initiative of our expert Jean-François Fortin.
Want to learn more?
Our team can assist you with personalized advice, trainings, or new customized solutions. Optimize your technologies and daily process: get in touch with one of our experts.