Understanding D365 CE solution Deployment – Part 1

Today we are going to talk about one important topic – solution deployment in D365 CE.

Before we move on to the topic directly, let us understand the need of discussing the solution deployment in Dynamics CRM. Yes at the moment we are only talking about the solution deployment which includes the customization like configuration changes in entities, fields, business rules, workflows, sitemaps etc and our plugins, custom workflows, actions, java scripts etc.

Data migration / movement is a different vast topic altogether and we can discuss about it in later posts.

Generally in any CRM implementations we would have three basic environments as mentioned below

  • Development
  • SIT
  • Production

Based on the implementation complexity and flexibility of budget, we might add few more environments like Staging, UAT etc

It has been also observed that apart from development environment,  individual developers might use the free trial versions for their own development and then move the changes or even recreate the changes in actual allotted development environment.

As the best practice, we should never do any changes in the CRM default solution which is created when an organisation is created. Hence we generally create new solution in development environment and ask all team members to do the changes in the same solution. They can add the required components and start their changes.

Once the changes are completed and are needed to be moved to SIT or staging environment, solution deployment comes in picture. There are different ways to deploy the solution from one environment to another.  I think we should discuss all the options as its important to understand this to know the implementation in depth.

Different options for solution deployment

  • Manual deployment : Here we export the solution from source environment manually and import it in the destination environment
  • Using Package Deployer:  Here we export the solution manually from the source and create a package for the deployment. This package can be deployed by using Package Deployer utility provided by Microsoft
  • Automated deployment using DevOps: Here everything is automated including the export of the solution, creating package and even up to the deployment to destination

We can opt for any approach for the solution deployment, but we need to first understand the types of solutions and its layers.

Let us go back to early days of application development and deployment in .Net era. When we wanted to deploy our code and did not want to allow anybody to do the changes, we used to create the dll file and deploy the dll file. Similarly we work in Dynamics CRM and would like to distribute the sealed solutions, we have the option of using Managed solutions.

Managed vs Unmanaged solution

  • Whenever we create new solution in Dynamics CRM, it is by default unmanaged solution. When we export such solution from source instance for importing into the target instance, it can be exported as Managed or unmanaged.
  • If we import Managed solution in target instance, it can not be again exported
  • If we delete the Managed solution, all customization from that solution will be deleted along with the data in the deleted entities and field
  • Generally we should avoid uninstalling or deleting managed solutions from production instances, any changes should be handled by patches instead
  • If we delete unmanaged solution, individual customization will not be deleted. In fact deleting the unmanaged solution. will always remain in the default solution
  • Once changes done in unmanaged solution can not be rolled back
  • While we import the managed solution, we can decide the fields which can be allowed to customize

We would go through detailed steps on how to actually export and import the solutions and Patch deployment in my subsequent blogs

Continue reading “Understanding D365 CE solution Deployment – Part 1”

What is Fakes? Unit Testing in Dynamics 365 CE Plugins

Unit testing of CRM plugins is always a challenge and if the plugin is used for interface and connecting interface of other application is not ready, it becomes even more challenging. How to pass values to plugins and check if our code is working as expected? Here is the need to test the plugins in isolation.

Now let us see how isolation is possible. Let us discuss with a simple example. We have a plugin which is expected to run on Account entity’s Create message and it sets the Business Unit of the account based on the business unit of the user who is creating the account. We want to test this plugin, even before registering this to the CRM organisation. Hence we need to understand how we can pass the user object, account object to this plugin to test it.

Microsoft provides testing framework called Microsoft Fakes. This  feature  is highly integrated with Visual studio and is available since Visual studio 2012. Microsoft Fakes comes in two features , Stub and Shim.

As per Microsoft definitions are below

  • stub replaces a class with a small substitute that implements the same interface. To use stubs, you have to design your application so that each component depends only on interfaces, and not on other components. (By “component” we mean a class or group of classes that are designed and updated together and typically contained in an assembly.)
  • shim modifies the compiled code of your application at run time so that instead of making a specified method call, it runs the shim code that your test provides. Shims can be used to replace calls to assemblies that you cannot modify, such as .NET assemblies.

Let us go back to our example of plugin and see how we can  isolate this plugin for testing.

In the CRM plugin we have below code, just a small retrieval of business unit based on the account owner and assigning it to the account business unit.

try
{
businessUnit = RetrieveUserBusinessUnitId(targetAccount.OwnerId.Id, serviceContext);

if (businessUnit != null)
{
targetAccount.new_BusinessUnit = businessUnit;
}

}

private EntityReference RetrieveUserBusinessUnitId(Guid ownerId, CrmServiceContext serviceContext)
{
var businessUnit = (from o in serviceContext.SystemUserSet
where o.Id == ownerId
select o.BusinessUnitId).FirstOrDefault();
return businessUnit;
}

Above code gets the account owner from the target entity of the plugin context and then connects to CRM service to retrieve the business unit of the account owner. Hence to test this plugin, we should be able to isolate this plugin and pass these two values  by writing the some pseudo code.

Let us see how we work on isolation.

I am not detailing here on how to add the Fakes project in our solution so that we can add the unit test cases classes, but focusing on the coding.

Once we add the Unit testing project and added necessary assemblies, this is how the code would look like

using Microsoft.VisualStudio.TestTools.UnitTesting;

using Microsoft.Xrm.Sdk.Fakes;

using System.Fakes;

Above assembly referenced are for Fakes and then we need to add rest of the required references for our execution. We are considering mynamespace and myplugin as the namespace and plugin names here.

Below code is required in every fakes class, similar to the default code for plugin writing. The Testclass  contains stub service provider, stub plugin execution context and stub organization service against the actual objects in the plugin.

namespace mynamespace.UnitTest

{

    [TestClass]

    public class myPluginUnitTest

    {

        private Entity TestEntity { get; set; }

        private static StubIServiceProvider ServiceProvider { get; set; }

        private static StubIPluginExecutionContext PluginExecutionContext { get; set; }

        private static StubIOrganizationService OrganizationService { get; set; }

        public myPluginUnitTest()

       {

        }

        private TestContext testContextInstance;

        /// <summary>

        ///Gets or sets the test context which provides information about and functionality for the current test run.

        ///</summary>

        public TestContext TestContext

        {

            get

            {

                return testContextInstance;

            }

            set

            {

                testContextInstance = value;

            }

        }

ClassInitialize initializes the instances of org service, context and also adds user to the context object.

        [ClassInitialize]

        public static void ClassInit(TestContext textContext)

        {

            var context = new StubIPluginExecutionContext();

            var tracingService = new StubITracingService();

            var orgFactory = new StubIOrganizationServiceFactory();

            ServiceProvider = new StubIServiceProvider();

            OrganizationService = new StubIOrganizationService();

            PluginExecutionContext = context;

            //override GetService behaviour and return our stubs

            ServiceProvider.GetServiceType =

                (type) =>

                {

                    return HC.GetType(type, context, tracingService, orgFactory);

                };

            context.UserIdGet = () => Guid.Empty;

            //return our stub organizationservice

            orgFactory.CreateOrganizationServiceNullableOfGuid = (userId) => OrganizationService;

            //write trace logs to output. only works when debugging tests

            tracingService.TraceStringObjectArray = (message, args) => Debug.WriteLine(message, args);

        }

TestInitialize function informs the message of the plugin execution context

        [TestInitialize]

        public void TestInit()

        {

            //setup initial values for each test

            PluginExecutionContext.MessageNameGet = () => “Create”;

        }

Below code cleans up the objects

        [TestCleanup]

        public void TestCleanup()

        {

            TestEntity = null;

        }

Then comes the actual TestMethod which will get executed for testing

Here we have to add all required objects which are expected in the plugin code.We are creating the business unit object which is expected by the plugin.We also add attributes to the target  of plugin execution context

[TestMethod]
public void Myplugin()
{
Account targetacc = new Account();
targetacc.Id = new Guid();
targetacc.OwnerId = new EntityReference(Account.EntityLogicalName, new Guid(“5835DE9E-4962-E811-A95E-000D3A361C23”));

targetacc.new_BusinessUnit = new EntityReference(Account.EntityLogicalName, new Guid(“5835DE9E-4962-E811-A95E-000D3A361C23”));

var t = new ParameterCollection();
t.Add(“Target”, targetacc);
PluginExecutionContext.InputParametersGet = () => t;
PluginExecutionContext.MessageNameGet = () => “Create”;
PluginExecutionContext.StageGet = () => 20;
PluginExecutionContext.PrimaryEntityIdGet = () => Guid.NewGuid();
PluginExecutionContext.PrimaryEntityNameGet = () => Account.EntityLogicalName;

We are creating a user entity object with business unit entity reference added to it. We will pass this object as response to the Linq statement in RetrieveuserbusinessunitID

var Sysusrentity = new SystemUser();
Sysusrentity.Id = Guid.NewGuid();
Sysusrentity.BusinessUnitId = new EntityReference(SystemUser.EntityLogicalName, new Guid(“5835DE9E-4962-E811-A95E-000D3A361C23”));

Here is how the object is passed as response to the query.

OrganizationService.ExecuteOrganizationRequest = orgReq =>
{
RetrieveMultipleResponse response = null;

if (((Microsoft.Xrm.Sdk.Query.QueryExpression)((RetrieveMultipleRequest)orgReq).Query).EntityName
== SystemUser.EntityLogicalName)
{
List<Entity> entities = new List<Entity> { Sysusrentity };
response = new RetrieveMultipleResponse
{
   Results =
   new ParameterCollection
   {
  “EntityCollection”,
  new EntityCollection
  (entities)
   }
};
return response;
}
return new OrganizationResponse();
};

CallPluginFakes();

}

CallPluginFakes function has reference of the actual plugin to be executed for the testing and also has definitions for the parameters used in plugin registration tool such as  secure and unsecure configurations

public void CallPluginFakes()

        {

            string expectedExceptionMessage = “”;

            string actualThrownExceptionMessage = “”;

            string unsecureConfig = “”;

            string secureConfig = “”;

            try

            {

                var plugin = new myplugin(unsecureConfig, secureConfig);

                plugin.Execute(ServiceProvider);

            }

            catch (InvalidPluginExecutionException ex)

            {

                actualThrownExceptionMessage = ex.Message;

            }

With above simple example, we have seen how Fakes can be used for Unit testing of CRM plugins.

There are other scenarios which might have to be handled in Fakes class such as Retrieve, RetrieveMultiple, Execute etc,

We can see those in details later posts.

Continue reading “What is Fakes? Unit Testing in Dynamics 365 CE Plugins”