Refresh Security Token for Microsoft Dynamics CRM Connection

In my previous blog post I used singleton pattern to keep the connection open to Dynamics CRM organization service. The only caveat of this method is that you need to monitor your token expiry depending on your implementation method and renew it before expiry.

From MSDN best practices:

Monitor your WCF security token (Token) and refresh it before it expires so that you do not lose the token and have to start over with authentication. To check the token, create a custom class that inherits from the OrganizationServiceProxy or DiscoveryServiceProxy class and that implements the business logic to check the token. Or wrap the proxy classes in a new class. Another technique is to explicitly check the token before each call to the web service. Example code that demonstrates these techniques can be found in the ManagedTokenDiscoveryServiceProxy, ManagedTokenOrganizationServiceProxy, and AutoRefreshSecurityToken classes in the Helper code: ServerConnection class topic.

See the code on GitHub

This piece of code demonstrate how to do monitor the token and refresh it before it expires:

using System;
using System.ServiceModel;
using System.ServiceModel.Description;

using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Client;

namespace CRMConnectionHelper
{
    public sealed class ManagedTokenOrganizationServiceProxy : OrganizationServiceProxy
    {
        private AutoRefreshSecurityToken<OrganizationServiceProxy, IOrganizationService> _proxyManager;

        public ManagedTokenOrganizationServiceProxy(Uri serviceUri, ClientCredentials userCredentials)
            : base(serviceUri, null, userCredentials, null)
        {
            this._proxyManager = new AutoRefreshSecurityToken<OrganizationServiceProxy, IOrganizationService>(this);
        }

        public ManagedTokenOrganizationServiceProxy(IServiceManagement<IOrganizationService> serviceManagement,
            SecurityTokenResponse securityTokenRes)
            : base(serviceManagement, securityTokenRes)
        {
            this._proxyManager = new AutoRefreshSecurityToken<OrganizationServiceProxy, IOrganizationService>(this);
        }

        public ManagedTokenOrganizationServiceProxy(IServiceManagement<IOrganizationService> serviceManagement,
            ClientCredentials userCredentials)
            : base(serviceManagement, userCredentials)
        {
            this._proxyManager = new AutoRefreshSecurityToken<OrganizationServiceProxy, IOrganizationService>(this);
        }

        protected override void AuthenticateCore()
        {
            this._proxyManager.PrepareCredentials();
            base.AuthenticateCore();
        }

        protected override void ValidateAuthentication()
        {
            this._proxyManager.RenewTokenIfRequired();
            base.ValidateAuthentication();
        }
    }

    ///
<summary>
    /// Class that wraps acquiring the security token for a service
    /// </summary>

    public sealed class AutoRefreshSecurityToken<TProxy, TService>
        where TProxy : ServiceProxy<TService>
        where TService : class
    {
        private TProxy _proxy;

        ///
<summary>
        /// Instantiates an instance of the proxy class
        /// </summary>

        /// <param name="proxy">Proxy that will be used to authenticate the user</param>
        public AutoRefreshSecurityToken(TProxy proxy)
        {
            if (null == proxy)
            {
                throw new ArgumentNullException("proxy");
            }

            this._proxy = proxy;
        }

        ///
<summary>
        /// Prepares authentication before authenticated
        /// </summary>

        public void PrepareCredentials()
        {
            if (null == this._proxy.ClientCredentials)
            {
                return;
            }

            switch (this._proxy.ServiceConfiguration.AuthenticationType)
            {
                case AuthenticationProviderType.ActiveDirectory:
                    this._proxy.ClientCredentials.UserName.UserName = null;
                    this._proxy.ClientCredentials.UserName.Password = null;
                    break;
                case AuthenticationProviderType.Federation:
                case AuthenticationProviderType.LiveId:
                    this._proxy.ClientCredentials.Windows.ClientCredential = null;
                    break;
                default:
                    return;
            }
        }

        ///
<summary>
        /// Renews the token (if it is near expiration or has expired)
        /// </summary>

        public void RenewTokenIfRequired()
        {
            if (null != this._proxy.SecurityTokenResponse &&
            DateTime.UtcNow.AddMinutes(15) >= this._proxy.SecurityTokenResponse.Response.Lifetime.Expires)
            {
                try
                {
                    this._proxy.Authenticate();
                }
                catch (CommunicationException)
                {
                    if (null == this._proxy.SecurityTokenResponse ||
                        DateTime.UtcNow >= this._proxy.SecurityTokenResponse.Response.Lifetime.Expires)
                    {
                        throw;
                    }

                    // Ignore the exception 
                }
            }
        }
    }
}

now create an instance of ManagedTokenOrganizationServiceProxy instead of OrganizationServiceProxy :

//get the OrganizationService
//OrganizationServiceProxy _serviceproxy = new OrganizationServiceProxy(new Uri(config.XrmOrgServiceProxy), null, clientcred, null);
 ManagedTokenOrganizationServiceProxy _serviceproxy = new ManagedTokenOrganizationServiceProxy(new Uri(config.XrmOrgServiceProxy), clientcred);

Improving Dynamics CRM connection performance with Singleton pattern

Calling Dynamics CRM services is slow process.The authentication process adds the most overhead in establishing a connection to CRM. The call needs to go through multiple steps in order to be authenticated and authorized to access the data in Dynamics CRM.

If you have an application that needs to perform CRUD operation outside of CRM you can leave the connection open to Dynamics CRM with Singleton pattern.

Singleton pattern:

public class XrmConnectionProvider
    {
        private static IOrganizationService instance;
        private static object _lockObject = new object();

		private static IConfigurationService _configurationService;
        
		private XrmConnectionProvider() { }
		
        public static IOrganizationService CRMService
        {
            get
            {
                try
                {
                    lock (_lockObject)
                    {
                        if (instance == null)
                        {
							var container = IoC.Initialize();
							_configurationService = container.GetInstance<IConfigurationService>();


                            instance = Connect();
                        }
                        return instance;
                    }
                }
                catch (Exception ex)
                {
					throw new Exception("Unable to connect to CRM", ex);
                }

            }
        }
        private static IOrganizationService Connect()
        {
		     //user name and password stored in a config file
			var config = _configurationService.Get<XrmClientConfiguration>();

			Uri dInfo = new Uri(config.XrmUri);
			ClientCredentials clientcred = new ClientCredentials();
			clientcred.UserName.UserName = config.XrmClientCredUserName;
			clientcred.UserName.Password = config.XrmClientCredPassword;


			#region on-premise/online

			DiscoveryServiceProxy dsp = new DiscoveryServiceProxy(dInfo, null, clientcred, null);
            dsp.Authenticate();
            RetrieveOrganizationsRequest rosreq = new Microsoft.Xrm.Sdk.Discovery.RetrieveOrganizationsRequest();
            RetrieveOrganizationsResponse r = (RetrieveOrganizationsResponse)dsp.Execute(rosreq);

            //get the OrganizationService
            OrganizationServiceProxy _serviceproxy = new OrganizationServiceProxy(new Uri(config.XrmOrgServiceProxy), null, clientcred, null);
            //In order to use the generated types when dealing with the organization service, you have to add the ProxyTypesBehavior to the endpoint Behaviors collection. This instructs the OrganizationServiceProxy to look in the assembly for classes with certain attributes to use. The generated classes are already attributed with these attributes. Simply, this makes all interactions with the organization service to be done using the generated typed classes for each entity instead of the generic Entity class we used earlier.
            _serviceproxy.ServiceConfiguration.CurrentServiceEndpoint.EndpointBehaviors.Add(new ProxyTypesBehavior());
            //Do not forget to include _serviceproxy.EnableProxyTypes();. Without this line,you will be unable to use early binding.
            _serviceproxy.EnableProxyTypes();
            IOrganizationService service = (IOrganizationService)_serviceproxy;

            #endregion
            return service;
        }
    }

Now in your code you can call below class and it will only generate one instance instead of calling it for each request :

 var CRMService= XrmConnectionProvider.Connect();

Here is the result of URL based test on Visual studio online after applying above approach :

The test ran for duration of 120 seconds with 25 users load hitting my Web API application (hosted on azure) retrieving Dynamics CRM contacts.

Before:

url-based-test-dynamics-crm-before

After:

url-based-test-dynamics-crm-after

 

The only caveat to this method is that you need to monitor your token expiry depending on your implementation method and renew it before expiry.
You can read about it on this blog post.
But before that you need to increase the life time of token if you’ve configured your CRM for IFD.  By default the ADFS token last for 60 minutes. By using Windows PowerShell, you can change the TokenLifetime property for the relying party objects that you created from 60 minutes to a longer period, such as 480 minutes (8 hours).

From MSDN best practices:

Monitor your WCF security token (Token) and refresh it before it expires so that you do not lose the token and have to start over with authentication. To check the token, create a custom class that inherits from the OrganizationServiceProxy or DiscoveryServiceProxy class and that implements the business logic to check the token. Or wrap the proxy classes in a new class. Another technique is to explicitly check the token before each call to the web service. Example code that demonstrates these techniques can be found in the ManagedTokenDiscoveryServiceProxy, ManagedTokenOrganizationServiceProxy, and AutoRefreshSecurityToken classes in the Helper code: ServerConnection class topic.

Get and set user timezone in cookies with jQuery

Scenario:

  1. Find the user timezone from it’s browser default time and then get the confirmation from the user about it’s timezone.
  2. User should be able to select different time zone
  3. Store the timezone in a cookie
  4. Perform the above procedure only if the user is logged in (asp.net identity)

Solution:

  • Using jQuery check if user is authenticated (asp.net identity)
  • If authenticated check if the timezone cookie is already set
  • If the cookie is not set yet, get confirmation from user about it’s timezone using bootstrap modal and store it in a cookie

 

Add a bootstrap modal form to your page with timezone drop-down list:

 <div class='modal fade' id='modalTimeZoneApprove' tabindex='-1' role='dialog' aria-labelledby='myModalLabel'>
 <div class='modal-dialog' role='document'>
 <div class='modal-content'>
 <div class='modal-header'>
 <button type='button' class='close' data-dismiss='modal' aria-label='Close'><span aria-hidden='true'>&times;</span></button>
 <h4 class='modal-title'>Approve Your TimeZone</h4>
 </div>
 <div class='modal-body'>
 <p>Your default time zone is : <span id="spanTimeZone"></span></p>
 <br />
 <p>You can change your timezone: </p>
 <select class="form-control" id="zoneList">
 <option value="-13">--Select--</option>
 <option value="-12">(GMT-12:00) International Date Line West</option>
 <option value="-11">(GMT-11:00) Midway Island, Samoa</option>
 <option value="-10">(GMT-10:00) Hawaii</option>
 <option value="-9">(GMT-09:00) Alaska</option>
 <option value="-8">(GMT-08:00) Pacific Time (US & Canada)</option>
 <option value="-8">(GMT-08:00) Tijuana, Baja California</option>
 <option value="-7">(GMT-07:00) Arizona</option>
 <option value="-7">(GMT-07:00) Chihuahua, La Paz, Mazatlan</option>
 <option value="-7">(GMT-07:00) Mountain Time (US & Canada)</option>
 <option value="-6">(GMT-06:00) Central America</option>
 <option value="-6">(GMT-06:00) Central Time (US & Canada)</option>
 <option value="-6">(GMT-06:00) Guadalajara, Mexico City, Monterrey</option>
 <option value="-6">(GMT-06:00) Saskatchewan</option>
 <option value="-5">(GMT-05:00) Bogota, Lima, Quito, Rio Branco</option>
 <option value="-5">(GMT-05:00) Eastern Time (US & Canada)</option>
 <option value="-5">(GMT-05:00) Indiana (East)</option>
 <option value="-4">(GMT-04:00) Atlantic Time (Canada)</option>
 <option value="-4">(GMT-04:00) Caracas, La Paz</option>
 <option value="-4">(GMT-04:00) Manaus</option>
 <option value="-4">(GMT-04:00) Santiago</option>
 <option value="-3.5">(GMT-03:30) Newfoundland</option>
 <option value="-3">(GMT-03:00) Brasilia</option>
 <option value="-3">(GMT-03:00) Buenos Aires, Georgetown</option>
 <option value="-3">(GMT-03:00) Greenland</option>
 <option value="-3">(GMT-03:00) Montevideo</option>
 <option value="-2">(GMT-02:00) Mid-Atlantic</option>
 <option value="-1">(GMT-01:00) Cape Verde Is.</option>
 <option value="-1">(GMT-01:00) Azores</option>
 <option value="0">(GMT+00:00) Casablanca, Monrovia, Reykjavik</option>
 <option value="0">(GMT+00:00) Greenwich Mean Time : Dublin, Edinburgh, Lisbon, London</option>
 <option value="1">(GMT+01:00) Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna</option>
 <option value="1">(GMT+01:00) Belgrade, Bratislava, Budapest, Ljubljana, Prague</option>
 <option value="1">(GMT+01:00) Brussels, Copenhagen, Madrid, Paris</option>
 <option value="1">(GMT+01:00) Sarajevo, Skopje, Warsaw, Zagreb</option>
 <option value="1">(GMT+01:00) West Central Africa</option>
 <option value="2">(GMT+02:00) Amman</option>
 <option value="2">(GMT+02:00) Athens, Bucharest, Istanbul</option>
 <option value="2">(GMT+02:00) Beirut</option>
 <option value="2">(GMT+02:00) Cairo</option>
 <option value="2">(GMT+02:00) Harare, Pretoria</option>
 <option value="2">(GMT+02:00) Helsinki, Kyiv, Riga, Sofia, Tallinn, Vilnius</option>
 <option value="2">(GMT+02:00) Jerusalem</option>
 <option value="2">(GMT+02:00) Minsk</option>
 <option value="2">(GMT+02:00) Windhoek</option>
 <option value="3">(GMT+03:00) Kuwait, Riyadh, Baghdad</option>
 <option value="3">(GMT+03:00) Moscow, St. Petersburg, Volgograd</option>
 <option value="3">(GMT+03:00) Nairobi</option>
 <option value="3">(GMT+03:00) Tbilisi</option>
 <option value="3.5">(GMT+03:30) Tehran</option>
 <option value="4">(GMT+04:00) Abu Dhabi, Muscat</option>
 <option value="4">(GMT+04:00) Baku</option>
 <option value="4">(GMT+04:00) Yerevan</option>
 <option value="4.5">(GMT+04:30) Kabul</option>
 <option value="5">(GMT+05:00) Yekaterinburg</option>
 <option value="5">(GMT+05:00) Islamabad, Karachi, Tashkent</option>
 <option value="5.5">(GMT+05:30) Sri Jayawardenapura</option>
 <option value="5.5">(GMT+05:30) Chennai, Kolkata, Mumbai, New Delhi</option>
 <option value="5.75">(GMT+05:45) Kathmandu</option>
 <option value="6">(GMT+06:00) Almaty, Novosibirsk</option>
 <option value="6">(GMT+06:00) Astana, Dhaka</option>
 <option value="6.5">(GMT+06:30) Yangon (Rangoon)</option>
 <option value="7">(GMT+07:00) Bangkok, Hanoi, Jakarta</option>
 <option value="7">(GMT+07:00) Krasnoyarsk</option>
 <option value="8">(GMT+08:00) Beijing, Chongqing, Hong Kong, Urumqi</option>
 <option value="8">(GMT+08:00) Kuala Lumpur, Singapore</option>
 <option value="8">(GMT+08:00) Irkutsk, Ulaan Bataar</option>
 <option value="8">(GMT+08:00) Perth</option>
 <option value="8">(GMT+08:00) Taipei</option>
 <option value="9">(GMT+09:00) Osaka, Sapporo, Tokyo</option>
 <option value="9">(GMT+09:00) Seoul</option>
 <option value="9">(GMT+09:00) Yakutsk</option>
 <option value="9.5">(GMT+09:30) Adelaide</option>
 <option value="9.5">(GMT+09:30) Darwin</option>
 <option value="10">(GMT+10:00) Brisbane</option>
 <option value="10">(GMT+10:00) Canberra, Melbourne, Sydney</option>
 <option value="10">(GMT+10:00) Hobart</option>
 <option value="10">(GMT+10:00) Guam, Port Moresby</option>
 <option value="10">(GMT+10:00) Vladivostok</option>
 <option value="11">(GMT+11:00) Magadan, Solomon Is., New Caledonia</option>
 <option value="12">(GMT+12:00) Auckland, Wellington</option>
 <option value="12">(GMT+12:00) Fiji, Kamchatka, Marshall Is.</option>
 <option value="13">(GMT+13:00) Nuku'alofa</option>
 </select>
 </div>
 <div class='modal-footer'>
 <button type='button' class='btn btn-primary' id='confirmModalTimeZone'>Confrim</button>
 <button type='button' class='btn btn-default' data-dismiss='modal' id='closeModalApprove'>Cancel</button>
 </div>
 </div>
 </div>
 </div>

the modal will looks like this:
timezone

Now add JavaScript/jQuery methods to set the cookies if the users is authenticated (using asp.net Identity)

 function getCookie(cname) {
            var name = cname + "=";
            var decodedCookie = decodeURIComponent(document.cookie);
            var ca = decodedCookie.split(';');
            for (var i = 0; i < ca.length; i++) {
                var c = ca[i];
                while (c.charAt(0) == ' ') {
                    c = c.substring(1);
                }
                if (c.indexOf(name) == 0) {
                    return c.substring(name.length, c.length);
                }
            }
            return "";
        }

 function setCookie(cname, cvalue, exdays) {
            var d = new Date();
            d.setTime(d.getTime() + (exdays * 24 * 60 * 60 * 1000));
            var expires = "expires=" + d.toUTCString();
            document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/";
        }

 function checkCookie(cname) {
            var cookieName = getCookie(cname);
            if (cookieName != "") {
                alert("Welcome again " + cookieName);
            }
            else {
                $('#modalTimeZoneApprove').modal('show');
                cookieName = "GMT  " + get_time_zone_offset();
                if (cookieName != "" && cookieName != null) {
                    setCookie(cname, cookieName, 365); MediaQueryList
                }
            }
        }

  function get_time_zone_offset() {
            var current_date = new Date();
            return -current_date.getTimezoneOffset() / 60;
        }

 $(document).ready(function () {
            var isAuthenticated = '<%=HttpContext.Current.User.Identity.IsAuthenticated %>';
            if (isAuthenticated=="True") {

                var cookieName = getCookie("AccountTimeZone");
                if (cookieName != "") {
                    //do nothing!
                }
                else {
                    cookieName = "GMT  " + get_time_zone_offset();
                    $("#spanTimeZone").text(cookieName);
                    $('#modalTimeZoneApprove').modal('show');
                }

                $('#zoneList').on('change', function () {
                    $("#spanTimeZone").text($("#zoneList option:selected").text());
                })

                $("#confirmModalTimeZone").click(function () {
                    var zone = $("#spanTimeZone").text();
                    if (zone != "" && zone != null) {
                        setCookie("AccountTimeZone", zone, 365);
                    }
                    $('#modalTimeZoneApprove').modal('hide');
                });
            }

Dynamics CRM processes to attach with remote debugger

For debugging plugins and custom workflow activities, attach to these Dynamics CRM processes in visual studio :

  • Online: W3WP.exe
  • Offline: Microsoft.Crm.Application.Hoster.exe
  • Asynchronous registered plug-ins (post operation): CrmAsyncService.exe
  • Custom workflow assemblies: CrmAsyncService.exe
  • Sandbox (isolation mode): Microsoft.Crm.Sandbox.WorkerProcess.exe

(https://msdn.microsoft.com/en-us/library/gg328574.aspx)

Adding a Dynamics CRM Status Reason with custom values

The default status reasons for Dynamics CRM custom entities are 1 for active and 2 for inactive.

dynamics-crm-status-reason

It’s not possible to add Status Reason (statuscode) with values less than 551,870,000 through MS Dynamics CRM user interface and added new values are read only and incremental. If the default status reason is somehow deleted or you want to create a status reason with specific values, you can add it using CRM API’s.

Here is an example of adding an active status reason in CRM with value of 1:

(in my case the default active status reason was deleted)

         var response= ((InsertStatusValueResponse)ServiceContext.Execute
             (
                new InsertStatusValueRequest
                 {
                     AttributeLogicalName = "statuscode",
                     EntityLogicalName = YourEntity.EntityLogicalName,
                     Label = new Microsoft.Xrm.Sdk.Label("Active", 1033),
                     StateCode = 0, //Status: 0 active & 1 inactive 
                     Value = 1 
                 }
             )).NewOptionValue;

Add DLL to GAC for Microsoft Dynamics CRM SSIS packages

I recently developed a SSIS package for Microsoft Dynamics CRM and after deploying it on the production SQL server I got this Error:

 Data Flow Task:Error: System.IO.FileNotFoundException: Could not load file or assembly 'Microsoft.Xrm.Sdk, Version=6.0.0.0,
 Culture=neutral, PublicKeyToken=31bf3856ad364e35' or one of its dependencies. The system cannot find the file specified.
 File name: 'Microsoft.Xrm.Sdk, Version=6.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35'
 at Microsoft.SqlServer.Dts.Pipeline.ScriptComponentHost.HandleUserException(Exception e)
 at Microsoft.SqlServer.Dts.Pipeline.ScriptComponentHost.PreExecute()
 at Microsoft.SqlServer.Dts.Pipeline.ManagedComponentHost.HostPreExecute(IDTSManagedComponentWrapper100 wrapper)

Since multiple CRM SDK  assemblies has been referenced in my SSIS project, the package execution failed with file not found error. In order to fix the issue,  those DLLs should be added to GAC in production server and here are the steps  to do it:

  1. My production environment is Windows server 2012, SQL server 2012, .net framework 4.0.
  2. In order to register DLLs I used Gacutil tool. The tool is part of Visual Studio tool set and in order to have it in production: Copy these two Gacutil files from dev environment
    • C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Bin\NETFX 4.0 Tools\gacutil.exe
    • C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Bin\NETFX 4.0 Tools\gacutil.exe.config
  3. Paste two files to “C:\Windows\Microsoft.NET\Framework\v4.0.30319” folder in production (.net 4.0):
  4. Run the command prompt as administrator and execute below command for each one of the CRM DLLs. for instance:
    • “C:\Windows\Microsoft.NET\Framework\v4.0.30319\gacutil.exe” /i “C:\Microsoft.Xrm.Sdk.dll”

now the package should run successfully on production server.

Working with Microsoft Dynamics CRM Party List

Party lists are system data types and there is no supported way to modify them.

Unsupported : In order to modify party lists you need to access them in the DOM by getting their id attribute.

for instance if you want to change the filter on the “from” party list in activities, use below code on form load:

//change the default filter to account entity and disable the others parties

function FilterFromPartyList() {
  document.getElementById("from_i").setAttribute("defaulttype", "1");
  // document.getElementById("from_i").setAttribute("defaulttype", "1,2"); //+contact
 document.getElementById("from_i").setAttribute("lookuptypes", "1");
 document.getElementById("from_i").setAttribute("lookuptypeIcons", "/_imgs/ico_1 6_1.gif");
 document.getElementById("from_i").disableViewPicker = 1;
}

Here is the list of the attributes that you can access through DOM:


<img width="20" height="18" title="Select a value." class="ms-crm-InlineLookupEdit" id="from_i" alt="Select a value." src="/_imgs/search_normal.gif" isDisplayOnly="false" ForceSubmit="false" isEnableInlineLookupForEditForms="1" lookupstyle="single" lookuptypes="1" lookuptypeIcons="/_imgs/ico_16_1.gif" lookuptypenames="account:1:Account,contact:2:Contact,lead:4:Lead,systemuser:8:User" data-fdeid="PrimaryEntity" resolveemailaddress="0" attrName="from" attrPriv="7" createpermissiondictionary="account:true,contact:true,lead:true,systemuser:true" crmattributeid="ca991199-24b8-4a1f-9bd9-55fddb6dcdcd" isDeDupLookup="0" defaulttype="1" autoresolve="1" showproperty="1" allowUnresolvedPartiesOnEmailSend="0" isInline="true" inlineViewIds="undefined" allowFilterOff="0" disableMru="1" disableQuickFind="0" disableViewPicker="0" DefaultViewId="{A3AF4AB8-861D-4CFA-92A5-E6281FED7FAB}" lookupbrowse="0" lookupDialogMultipleSelect="0">