Dependency injection is one of the best practices in modern software development. With dependency injection one can inject dependencies into our program and easily change the implementation without changing the overall method signature or specification, this is a pattern called Inversion of Control (IoC), where the funcionality of a given program could be modified by changing the implementation (classes) without changing the specification (interfaces) (or at least, minimising such changes).
Dependency injection also makes the code easily unit testable as we can inject mocks (or fake) dependencies in a unit test project more easily.
When it comes to plugins though, there is one caveat.
Unlike traditional approaches that use the constructor to inject these dependencies, the Dataverse / CRM / Dynamics 365 only gives the possibility of having 2 types of plugin constructors: parameterless constructors, and contructors with configurations.
Unless you want to use Secure and Unsecure configurations, the parameterless constructor is the constructor that should be used.
A plugin with a parameterless constructor looks more or less like:
public class FollowupPlugin: IPlugin
{
public FollowupPlugin()
{
}
}
public class FollowupPlugin: IPlugin
{
public FollowupPlugin(string unsecureConfiguration, string secureConfiguration)
{
}
}
This constructor receives the unsecure and secure configurations when the plugin is executed.
For more information about how to use unsecure and secure configurations please check the official documentation about unsecure and secure configurations.
As you can see, there are only two types of constructors available, and we can’t pass extra dependencies into them so you might be wondering….
There are a couple of approaches:
One way of injecting dependencies is via properties. Let’s assume a very simple example where we want to call an external web service from a plugin.
As an example, let’s assume we want to perform a validation to check if a VAT ID is a valid tax ID according to the European Union’s VIES web service.
An interface that is used to validate a VAT ID, which is a string, could look more or less like:
public interface IViesValidationService
{
bool isVatIdValid(string vatId);
}
The plugin that would validate the VAT ID on update of the account record could use the service like:
public class OnAccountUpdatePlugin: IPlugin
{
public IViesValidationService ViesValidationServiceProperty { get; set; }
public void Execute(IServiceProvider serviceProvider)
{
IViesValidationService viesValidationService =
ViesValidationServiceProperty != null ?
ViesValidationServiceProperty : new ViesValidationService();
//Use viesValidationService from now on....
IPluginExecutionContext context = (IPluginExecutionContext)
serviceProvider.GetService(typeof(IPluginExecutionContext));
if (context.InputParameters.Contains("Target") &&
context.InputParameters["Target"] is Entity)
{
Entity entity = ((Entity)context.InputParameters["Target"]);
var vatIdFromTarget = entity["dv_vatid"];
if(!string.IsNullOrWhiteSpace(vatIdFromTarget))
{
var isValid = viesValidationService.isVatIdValid(vatIdFromTarget);
// do whatever...
}
}
}
}
With a pattern like that, we can effectively inject a fake validationService via the ViesValidationServiceProperty.
When the plugin is executed in production, Dataverse will create an instance of the plugin without setting that property (which will be null), and so, it’ll use an instance of the real service (ViesValidationService).
NOTE: Please make sure to never set the ViesValidationServiceProperty as it might be cached by Dataverse and eventually disposed. In that scenario you would be reusing a disposed service which would throw an exception. Always use local variables
In order to test a plugin with external dependencies, we would need to:
This example uses FakeItEasy, you could use other mocking frameworks.
using FakeXrmEasy.Plugins;
public class OnAccountUpdatePluginTests : FakeXrmEasyTestsBase
{
private readonly IViesValidationService _viesValidationService;
public OnAccountUpdatePluginTests()
{
_viesValidationService = A.Fake<IViesValidationService>();
A.CallTo(() => _validationService.isVatIdValid(A<string>._)).ReturnsLazily(() =>
{
return true;
})
}
[Fact]
public void Should_call_vies_validation_service_on_update_of_vat_id()
{
_context.EnableProxyTypes(Assembly.GetExecutingAssembly()); //Needed to be able to return early bound entities
var pluginContext = _context.GetDefaultPluginContext();
//Set a dummy vat ID property
pluginContext.InputParameters.Add("Target", new Account()
{
dv_vatid = "myVatId"
});
//Create a specific instance of the account update plugin with the fake service
var accountPlugin = new OnAccountUpdatePlugin()
{
ViesValidationServiceProperty = _viesValidationService
};
//Execute the plugin with a specific plugin instance
_context.ExecutePluginWith(pluginContext, accountPlugin);
A.CallTo(() => _validationService.isVatIdValid("myVatId")).MustHaveHappened();
}
}
You could, however, also use other another custom constructor to inject these dependencies. That constructor won’t be called by the platform though, only the parameterless constructor or the constructor with configurations will be called. And so the section below covers that approach.
Now, let’s assume we actually add another constructor that takes a IViesValidationService dependency, and also update it so it no longer uses a property but a private variable.
It would look like:
public class OnAccountUpdatePlugin: IPlugin
{
private readonly IViesValidationService _viesValidationService;
public OnAccountUpdatePlugin()
{
_viesValidationService = new ViesValidationService();
}
public OnAccountUpdatePlugin(IViesValidationService viesService)
{
_viesValidationService = viesService;
}
}
The code in production will invoke the parameterless constructor which will use a default implementation, but the code in our test project will use the other custom constructor instead, which will pass a fake service.
When using this approach you also need to use the .ExecutePluginWith() methods in FakeXrmEasy that take a custom plugin instance as a parameter.
using FakeXrmEasy.Plugins;
public class OnAccountUpdatePluginTests : FakeXrmEasyTestsBase
{
private readonly IViesValidationService _viesValidationService;
public OnAccountUpdatePluginTests()
{
_viesValidationService = A.Fake<IViesValidationService>();
A.CallTo(() => _validationService.isVatIdValid(A<string>._)).ReturnsLazily(() =>
{
return true;
})
}
[Fact]
public void Should_call_vies_validation_service_on_update_of_vat_id()
{
_context.EnableProxyTypes(Assembly.GetExecutingAssembly()); //Needed to be able to return early bound entities
var pluginContext = _context.GetDefaultPluginContext();
//Set a dummy vat ID property
pluginContext.InputParameters.Add("Target", new Account()
{
dv_vatid = "myVatId"
});
//Create a specific instance of the account update plugin with the fake service passed into the constructor
var accountPlugin = new OnAccountUpdatePlugin(_viesValidationService);
//Execute the plugin with a specific plugin instance
_context.ExecutePluginWith(pluginContext, accountPlugin);
A.CallTo(() => _validationService.isVatIdValid("myVatId")).MustHaveHappened();
}
}
Simlarly to plugins, we can use properties to inject external dependencies in code activities.
A codeactivity that does something similar to the plugin above would look as follows:
public class ViesValidationCodeActivity: CodeActivity
{
public IViesValidationService ViesValidationServiceProperty { get; set; }
[Input("VAT ID")]
public InArgument<string> VatId { get; set; }
[Output("Result")]
public OutArgument<bool> IsValid { get; set; }
protected override void Execute(CodeActivityContext executionContext)
{
IViesValidationService viesValidationService =
ViesValidationServiceProperty != null ?
ViesValidationServiceProperty : new ViesValidationService();
var vatId = this.VatId.Get(executionContext);
var isValid = viesValidationService.isVatIdValid(vatId);
this.IsValid.Set(executionContext, isValid);
}
}
Similarly to plugins, in order to test a code activity with external dependencies, we would need to:
using FakeXrmEasy.CodeActivities;
public class ViesValidationCodeActivityTests : FakeXrmEasyTestsBase
{
private readonly IViesValidationService _viesValidationService;
public ViesValidationCodeActivityTests()
{
_viesValidationService = A.Fake<IViesValidationService>();
A.CallTo(() => _validationService.isVatIdValid(A<string>._)).ReturnsLazily(() =>
{
return true;
})
}
[Fact]
public void Should_call_vies_validation_service()
{
_context.EnableProxyTypes(Assembly.GetExecutingAssembly()); //Needed if we want to use early bound entities
var workflowExecutionContext = _context.GetDefaultWorkflowContext();
var inputs = new Dictionary<string, object>()
{
{ "VatId", "VAT_ID_DUMMY_VALUE" }
};
var codeActivity = new ViesValidationCodeActivity()
{
ViesValidationServiceProperty = _viesValidationService
};
//Execute the code activity with a specific instance
var result = _context.ExecuteCodeActivity<ViesValidationCodeActivity>(workflowExecutionContext, inputs, codeActivity);
A.CallTo(() => _validationService.isVatIdValid("VAT_ID_DUMMY_VALUE")).MustHaveHappened();
Assert.True((bool) result["IsValid"]);
}
}