Wednesday, December 6, 2017

How to Create Disabled Users in Dynamics 365/CRM

This article discusses 2 possible options for creating inactive/disabled users within Dynamics 365/CRM. This type of users consume no license, and need not be associated with Active Directory, or Microsoft/Office 365 account, for authentication; and you would want to have them in your Dynamics CRM to, for example, hold data records migrated from another system.

Option #1 - Writing code.

At the minimum you have to set the following required attributes for a new disabled user:

       Entity _disabledUser = new Entity("systemuser");

       _disabledUser["firstname"] = "John";
       _disabledUser["lastname"] = "Smith";
       _disabledUser["issyncwithdirectory"] = false;
       _disabledUser["isdisabled"] = true;
       _disabledUser["internalemailaddress"] = "john.smith@fakemail.com";
       _disabledUser["businessunitid"] = new EntityReference("businessunit", new Guid("7AFD896E-B3AD-E711-A967-000D3A192828"));
       _disabledUser["domainname"] = "john.smith@fakedomain.com";

       _service.Create(_disabledUser);

Note that the phantom domainname (User Name) must be unique and not to duplicate with any other existing enabled or disabled users. The internalemailaddress is allowed to be duplicated value when I run the above code.

This option requires making program, and could be quick to generate a few users. But when the legacy users run in the hundreds, a better option is to use a software utility to read the legacy user list and generate them on the destination Dynamics system, with just a push of a button. And no coding needed. I'm talking about option 2 next.

Option #2 - Integration software.

If you've done some Dynamics data migration and integration, you might hear of Kingswaysoft SSIS Integration Toolkit for Microsoft Dynamics 365. The toolkit comes with components that allow you to connect to the source and destination Dynamics CRM sites to read and write data records. Of course all the standard ETL components are available for your SSIS data transformation project. The Toolkit is free if you don't want to deploy to run automatically on schedule, which is fine here since producing disabled users is not something to do daily.

Here's the snapshot of the data flow:























The Derived Columns component contains constant values for issyncwithdirectory, isdisabled, and businessunitid that will be used in the destination component for every disabled user record:
















The rest of required user data will come from the source. So the destination field mappings will look like this:



















Here I'm calling the Upsert action on the systemuser entity. Create action would be more optimal.


























Hope this helps with your Dynamics 365/CRM migration project. I'm sure there are other options. The SSIS Integration Toolkit from Kingswaysoft really helps jump start my project with so many legacy users in the old CRM system.

Tuesday, October 24, 2017

Adding Mixture of Unit Testing Frameworks to Dynamics 365/CRM Plug-in Project

Introduction:

Fake Xrm Easy is a very useful, and considerably easy, open source framework to unit test your Dynamics 365/CRM plug-ins. You might already know about the other authentic, and not so "easy", unit testing framework Fakes introduced by Microsoft.

There are occasions when it is necessary, or sometimes more convenient, to use both frameworks in one unit testing project. In this post I will demo such a mixture in a simple plug-in project. I will also walk through steps of adding unit test project to an existing Visual Studio plug-in solution as a beginner guide to jump start a unit test initiative for your Dynamics 365/CRM plug-in.

The Plug-In:

My demo plug-in is a pre-operation, registered on the creation of a Note. When a new Note is created, creation date and user full name will append to the end of the Note's title.

For example, entering title "Demo Unit Test" will save new Note title as "Demo Unit Test: Note created on 12/31/2020 03:09:15 PM by Tuan Nguyen". You might recall this is the default behavior in Dynamics CRM 2011 without the need of a custom plug-in. If no title is entered, new Note will have title "Title: Note created on 12/31/2020 03:15:15 PM by Tuan Nguyen".

Here's the simplified version of the plug-in code in a VS project called TXN.Plugin.Annotation :

using System;
using System.Linq;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Query;

namespace TXN.Plugin.Annotation

{
    public class PreCreate: IPlugin
    {
        public void Execute(IServiceProvider serviceProvider)
        {
            IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
            IOrganizationService service = ((IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory))).CreateOrganizationService(new Guid?(context.UserId));

            if (context.InputParameters.Contains("Target") && (context.InputParameters["Target"] is Entity))

            {
                Entity entity = context.InputParameters["Target"] as Entity;

                if (entity.LogicalName == "annotation" && context.MessageName.ToUpper() == "CREATE")

                {
                    string title = "Title";
                    string fullName = string.Empty;

                    if (!string.IsNullOrEmpty(entity.GetAttributeValue<string>("subject")))

                        title = entity.GetAttributeValue<string>("subject");                          
                            
                    var fetchXml = @"<fetch version='1.0' output-format='xml-platform' mapping='logical' distinct='false'>
                                     <entity name='systemuser'>
                                        <attribute name='fullname' />
                                        <attribute name='systemuserid' />
                                        <filter type='and'>
                                            <condition attribute='systemuserid' operator='eq' uitype='systemuser' value='{0}' />
                                        </filter>
                                    </entity>
                                    </fetch>";

                    fetchXml = string.Format(fetchXml, context.UserId.ToString());

                            
                    EntityCollection users = service.RetrieveMultiple(new FetchExpression(fetchXml));

                    if (users != null && users.Entities.Count() > 0)

                        fullName = users.Entities[0].GetAttributeValue<string>("fullname");
                            
                    title = string.Format("{0}: Note created on {1} by {2}", title, PluginDateTime.getDateTimeNow(), fullName);
                    entity.Attributes["subject"] = title;                                
                }
            }
        }
    }

    public static class PluginDateTime

    {
        public static string getDateTimeNow()
        {
            return string.Format("{0:M/d/yyyy hh:mm:ss tt}", DateTime.Now);
        }
    }

}



Add Unit Test Project:

First we need to provision an empty place holder unit test project in the solution before adding MS Fakes and FakeXrmEasy unit testing methods to it later:

1. Add a new project to Visual Studio solution containing the above plug-in project, using the Unit Test Project template installed with Visual Studio. Here I add the unit test project called TXN.Plugin.UnitTest:



























2. At this point the default unit test project can be compiled and run without problem as it isn't really testing anything yet:

























Unit Testing with Fakes Framework:

We want to unit test the static method in the plug-in, getDataTimeNow(), which makes a system call DateTime.Now. And the proper approach is to use Shims type of MS Fakes Framework to simulate the date time return value.

First thing is to have references to the plug-in project so we can make calls to plug-in methods in unit testing class. Right click on References node in UnitTest project to Add Reference; and select the plug-in project:





















The plug-in assembly TXN.Plugin.Annotation should now be visible in the References list of UnitTest project.

Next we add the Fakes assembly to References list. Since Datetime class is in System.dll, right click on System under References and select Add Fakes Assembly. A few system fakes assemblies along with a Fakes folder will be automatically created.

Now add code to TestMethod1() to unit test static method getDateTimeNow():



Mix in FakeXrmEasy Unit Testing:

At this point we have unit tested the static method resides in the same plug-in assembly using Fakes framework. But we have not tested the plug-in at all. We will install the FakeXrmEasy framework to the same UnitTest project before programming the plug-in tests. The true beauty of FakeXrmEasy is that you can fake everything, well almost everything, to come up with a unit test case, without the need to deploy the plug-in. And coding is much shorter and more readable compared to other frameworks. I guess that's why they call it Easy. I think the name makes sense.

To install FakeXrmEasy assemblies, right click on References node from UnitTest project, and select Manage Nuget Packages. Enter 'FakeXrmEasy' to search for the online package. Choose the FakeXrmEasy version compatibled to your compiled plug-in and hit Install:



















When installation completes the whole new set of assemblies will be added to the project and we are now ready to write unit test functions against the plug-in. And then run the unit tests:





























The code is presented at the end of the post and I won't go over the details of it. Some test methods are redundant in the example here for the purpose of a demo.

However I do want to point out that in one of the test methods, Note_With_Fully_Custom_Title(), we have mixed both unit testing frameworks MS Fakes and FakeXrmEasy to achieve the effect of simulating a system-dependent value within the plug-in operation.

I hope you have fun in unit testing discipline with your Dynamics 365/CRM plug-ins.

using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using FakeXrmEasy;
using Microsoft.Xrm.Sdk;

namespace TXN.Plugin.UnitTest
{
    [TestClass]
    public class UnitTest1
    {
        XrmFakedContext fakeContext = new XrmFakedContext();
        Entity target = new Entity("annotation") { Id = Guid.NewGuid() };

        [TestMethod]
        public void Note_Contains_Title()
        {
            var fakedPlugin = fakeContext.ExecutePluginWithTarget<TXN.Plugin.Annotation.PreCreate>(target);

            Assert.IsTrue(target.Attributes.ContainsKey("subject"));
        }

        [TestMethod]
        public void Note_Begins_With_Empty_Title()
        {
            var fakedPlugin = fakeContext.ExecutePluginWithTarget<TXN.Plugin.Annotation.PreCreate>(target);
            string title = target.Attributes["subject"].ToString();

            Assert.IsTrue(title.StartsWith("Title: Note created on"));
        }

        [TestMethod]
        public void Note_Begins_With_Non_Empty_Title()
        {
            target.Attributes["subject"] = "My Note Title";
            var fakedPlugin = fakeContext.ExecutePluginWithTarget<TXN.Plugin.Annotation.PreCreate>(target);

            Assert.IsTrue(target.Attributes["subject"].ToString().StartsWith("My Note Title: Note created on"));
        }

        [TestMethod]
        public void Note_With_Fully_Custom_Title()
        {
            Guid callerId = Guid.NewGuid();
            fakeContext.CallerId = new EntityReference("systemuser", callerId);

            Entity user = new Entity("systemuser");
            user.Id = callerId;
            user["fullname"] = "Tuan Awesome Nguyen";

            fakeContext.GetFakedOrganizationService().Create(user);

            target.Attributes["subject"] = "Test Note Title";

            using (Microsoft.QualityTools.Testing.Fakes.ShimsContext.Create())
            {
                System.Fakes.ShimDateTime.NowGet = () =>
                {
                    return new DateTime(2025, 10, 10, 11, 12, 13);
                };

                var fakedPlugin = fakeContext.ExecutePluginWithTarget<TXN.Plugin.Annotation.PreCreate>(target);

                Assert.IsTrue(target.Attributes["subject"].ToString().Equals("Test Note Title: Note created on 10/10/2025 11:12:13 AM by Tuan Awesome Nguyen"));
            }
        }

        [TestMethod]
        public void TestMethod1()
        {
            using (Microsoft.QualityTools.Testing.Fakes.ShimsContext.Create())
            {
                System.Fakes.ShimDateTime.NowGet = () =>
                {
                    return new DateTime(2020, 10, 10, 11, 12, 13);
                };

                var componentUnderTest = TXN.Plugin.Annotation.PluginDateTime.getDateTimeNow();

                Assert.IsTrue(componentUnderTest.ToString().Equals("10/10/2020 11:12:13 AM"));
            }
        }
    }
}