Dependency Injection in MS Dynamics CRM
Asked Answered
C

2

11

I am currently getting started with the extending of Microsoft Dynamics CRM using Plugins.

Is it possible to add Dependency injection to these plugins (for testing, loose coupling, etc. purposes)? Where can I register my IoC-container so that it's used over all the plugins of the same type?

Caaba answered 15/1, 2014 at 14:55 Comment(1)
You can vote for this feature on ideas.dynamics.com/ideas/dynamics-crm/814208Getup
B
8

We've been trying to unit test and apply dependency injection on our Dynamics CRM application. Unfortunately, as Microsoft support and consultants are confirmed, there is no supported way to do it. You may either transfer all of your plugin business logic to an another business class and apply dependency injection or stop thinking about it.

If you choose to fight back with Dynamics CRM, you need to define a static field on a plugin super class which will be your DI Container. As follows,

public abstract class SuperPlugin : IPlugin{
       public void Execute(IServiceProvider serviceProvider){
            // initialize a static container instance if not available
            var containerWrapper = new ContainerWrapper{
                Container = serviceProvider.GetService(typeof(IPluginExecutionContext)),
                Resolver = //static resolver instance of dependency container
            };
            OnExecution(containerWrapper);
       }
       public abstract void OnExecution(IDependencyResolver resolver);
}

I really cannot understand why Microsoft doesn't simply let us register some components to the IServiceProvider implementation that they are using internally.

Ps. Since your SuperPlugin class is an IPlugin, you may forget to write the interface implementation on the sub class. But we encountered some bugs on Plugin Registration tool that is shipped with official Dynamics CRM SDK. So in case you may have the same problem you should also implement your plugins as follows,

public class MyPlugin : SuperPlugin, IPlugin{
  public abstract void OnExecution(IDependencyResolver resolver){};
}

Edit: See a small example that explains the concept https://github.com/nakahparis/DIForCRM

Bombycid answered 28/4, 2014 at 11:13 Comment(3)
Nice workaround. How would you instantiate the dependency resolver? Using a static class or a singleton structure? I'm a bit worried about using a static resolver since you'll need a dependency resolver that's fully synchronized (can be accessed by multiple threads at the same time) and at the same time performant enough to handle many concurrent plugins. Any thoughts on that?Caaba
Yeah you are right but since there is no other way I prefer to use Lazy<> :) check the gist I have created, gist.github.com/msusur/e34be94cbceac20ea364 but you also need to separate the Setup method and Factory method to other classes in order to apply single responsibility.Bombycid
Hi jrosseel, I have a case on dynamics crm portal, you can vote up for asking microsoft to add this feature. connect.microsoft.com/dynamicssuggestions/feedback/details/…Bombycid
G
8

Plugins in CRM are the Bane of Unit Testing:

  • Issues with non-plugin test
    • No way to temporarily disable
    • Easy to forget it is running
  • Issues with testing plugins themselves
    • Unable to unit test and attach to process
    • A lot to mock out, Pipeline, Service Provider etc
    • Runs multi-threaded

This has led me to the following solution for testing plugins:

  • Get rid of the plugin context as quickly as possible, extracting out all objects and service required from it right away.
  • Create an ExecutePlugin method to hook unit tests into, and immediately call this method after extracting the objects from the plugin context.
  • Push as much code as possible into the business layer.

This results in plugins that look like this (with a heavy use of extension methods to make this simpler):

public void Execute(IServiceProvider provider)
{
    var context = provider.GetContext();
    var service = provider.GetService(context);
    var target = GetTarget<Contact>(context);
    if (target == null || !target.ContainsAllNonNull(c => new
        {
            c.FirstName,
            c.LastName,
        }))
    {
        // Entity is of the wrong type, or doesn't contain all of the required attributes
        return;
    }

    ExecutePlugin(service, target);
}

public void ExecutePlugin(IOrganizationService service, Contact target){
    // Logic Goes Here
}

Once this is done, the only thing you need to unit test the ExceutePlugin is your own IOrganizationService that mocks out the required calls and you have your unit testing done. I don't even bother unit testing the Execute method. Either it'll work, or it won't and blow chow on the first use from within CRM.

Greig answered 15/1, 2014 at 16:13 Comment(2)
Thanks for this nice guideline on testing in CRM! I'm still wondering about Dependency Injection though, do you know anything about that?Caaba
@jrosseel I'm assuming this is for unit testing? What dependencies were you hoping to mock/stub?Greig
B
8

We've been trying to unit test and apply dependency injection on our Dynamics CRM application. Unfortunately, as Microsoft support and consultants are confirmed, there is no supported way to do it. You may either transfer all of your plugin business logic to an another business class and apply dependency injection or stop thinking about it.

If you choose to fight back with Dynamics CRM, you need to define a static field on a plugin super class which will be your DI Container. As follows,

public abstract class SuperPlugin : IPlugin{
       public void Execute(IServiceProvider serviceProvider){
            // initialize a static container instance if not available
            var containerWrapper = new ContainerWrapper{
                Container = serviceProvider.GetService(typeof(IPluginExecutionContext)),
                Resolver = //static resolver instance of dependency container
            };
            OnExecution(containerWrapper);
       }
       public abstract void OnExecution(IDependencyResolver resolver);
}

I really cannot understand why Microsoft doesn't simply let us register some components to the IServiceProvider implementation that they are using internally.

Ps. Since your SuperPlugin class is an IPlugin, you may forget to write the interface implementation on the sub class. But we encountered some bugs on Plugin Registration tool that is shipped with official Dynamics CRM SDK. So in case you may have the same problem you should also implement your plugins as follows,

public class MyPlugin : SuperPlugin, IPlugin{
  public abstract void OnExecution(IDependencyResolver resolver){};
}

Edit: See a small example that explains the concept https://github.com/nakahparis/DIForCRM

Bombycid answered 28/4, 2014 at 11:13 Comment(3)
Nice workaround. How would you instantiate the dependency resolver? Using a static class or a singleton structure? I'm a bit worried about using a static resolver since you'll need a dependency resolver that's fully synchronized (can be accessed by multiple threads at the same time) and at the same time performant enough to handle many concurrent plugins. Any thoughts on that?Caaba
Yeah you are right but since there is no other way I prefer to use Lazy<> :) check the gist I have created, gist.github.com/msusur/e34be94cbceac20ea364 but you also need to separate the Setup method and Factory method to other classes in order to apply single responsibility.Bombycid
Hi jrosseel, I have a case on dynamics crm portal, you can vote up for asking microsoft to add this feature. connect.microsoft.com/dynamicssuggestions/feedback/details/…Bombycid

© 2022 - 2024 — McMap. All rights reserved.