How to get current user time on client side (JavaScript)

Recently I got requirement to fill-in field on a web resource with current user time (CRM settings should be took into account).

I use following code to implement requirement:
1. Retrieve user settings 2. Ask server to execute UtcTimeToLocalTime request with user time zone provided

This is code:
DateTimeHelper = {};

(function (DateTimeHelper, jQuery, $) {

    DateTimeHelper.GetUserLocalTime = function (date) {
        var userId = context().getUserId();
        var settings = getCurrentUserSettings(userId);
        var timeZoneCode = settings.TimeZoneCode;
        var dateTime = Teoco.DateTimeHelper.UtcTimeToLocalTime(timeZoneCode, date);
        return dateTime;
    };

    DateTimeHelper.UtcTimeToLocalTime = function (timeZoneCode, dateTime) {
        var req = getXhr();
        req.open("POST", getServicePath(), false);
        req.setRequestHeader("SOAPAction", "http://schemas.microsoft.com/xrm/2011/Contracts/Services/IOrganizationService/Execute");
        req.setRequestHeader("Content-Type", "text/xml; charset=utf-8");

        var xml =
            "<s:Envelope xmlns:s='http://schemas.xmlsoap.org/soap/envelope/'>" +
            "  <s:Body>" +
            "    <Execute xmlns='http://schemas.microsoft.com/xrm/2011/Contracts/Services' xmlns:i='http://www.w3.org/2001/XMLSchema-instance'>" +
            "      <request i:type='b:LocalTimeFromUtcTimeRequest' xmlns:a='http://schemas.microsoft.com/xrm/2011/Contracts' xmlns:b='http://schemas.microsoft.com/crm/2011/Contracts'>" +
            "        <a:Parameters xmlns:c='http://schemas.datacontract.org/2004/07/System.Collections.Generic'>" +
            "          <a:KeyValuePairOfstringanyType>" +
            "            <c:key>TimeZoneCode</c:key>" +
            "            <c:value i:type='d:int' xmlns:d='http://www.w3.org/2001/XMLSchema'>" + timeZoneCode + "</c:value>" +
            "          </a:KeyValuePairOfstringanyType>" +
            "          <a:KeyValuePairOfstringanyType>" +
            "            <c:key>UtcTime</c:key>" +
            "            <c:value i:type='d:dateTime' xmlns:d='http://www.w3.org/2001/XMLSchema'>" + dateTime.toISOString() + "</c:value>" +
            "          </a:KeyValuePairOfstringanyType>" +
            "        </a:Parameters>" +
            "        <a:RequestId i:nil='true' />" +
            "        <a:RequestName>LocalTimeFromUtcTime</a:RequestName>" +
            "      </request>" +
            "    </Execute>" +
            "  </s:Body>" +
            "</s:Envelope>";

        req.send(xml);

        var date = null;

        if (req.status === 200) {
            var resultXml = req.responseXML;
            var dateString = $(resultXml).find("value").text()
            date = new Date(dateString);
        }
        else {
            alert("Failed to retrieve current user local time");
            throw "Failed to retrieve current user local time";
        }

        return date;
    };

    function getCurrentUserSettings(userId) {
        var request = "/UserSettingsSet?$filter=SystemUserId eq guid'" + userId + "'";

        var settings = null;

        callOData(request,
            function (data) {
                if(data != null && data.results != null && data.results.length != 0)
                {
                    settings = data.results[0];
                }
            },
            function (xhr) {
            });

        if (settings == null) {
            alert("Failed to load current user settings");
            throw "Failed to load current user settings";
        }

        return settings;
    }

    function callOData(request, onSuccess, onError) {
        var req = getXhr();
        req.open("GET", getODataPath() + request, false);
        req.setRequestHeader("Accept", "application/json");
        req.setRequestHeader("Content-Type", "application/json; charset=utf-8");

        req.send();

        if (req.status === 200) {
            onSuccess(JSON.parse(req.responseText).d);
        }
        else {
            onError(req);
        }
    }

    function getServicePath() {
        return getClientUrl() + "/XRMServices/2011/Organization.svc/web";
    }

    function getODataPath() {
        return getClientUrl() + "/XRMServices/2011/OrganizationData.svc/";
    }

    function getClientUrl() {
        var serverUrl = typeof context().getClientUrl !== "undefined" ? context().getClientUrl() : context().getServerUrl();
        if (serverUrl.match(/\/$/)) {
            serverUrl = serverUrl.substring(0, serverUrl.length - 1);
        }
        return serverUrl;
    }

    function context() {
        var oContext;
        if (typeof window.GetGlobalContext != "undefined") {
            oContext = window.GetGlobalContext();
        }
        else {
            if (typeof Xrm != "undefined") {
                oContext = Xrm.Page.context;
            }
            else if (typeof window.parent.Xrm != "undefined") {
                oContext = window.parent.Xrm.Page.context;
            }
            else {
                throw new Error("Context is not available.");
            }
        }
        return oContext;
    }

    function getXhr() {
        if (XMLHttpRequest) {
            return new XMLHttpRequest();
        }
        try {
            return new ActiveXObject('MSXML2.XMLHTTP.6.0');
        } catch (e) {
            try {
                return new ActiveXObject('MSXML2.XMLHTTP.3.0');
            } catch (e) {
                alert('This browser is not AJAX enabled.');
                return null;
            }
        }
    }

})(DateTimeHelper, jQuery, jQuery);
And this is usage example:
var today = Teoco.DateTimeHelper.GetUserLocalTime(new Date());

Microsoft Dynamics CRM Code Standards

Me and my friend Grigory Syomin created code standards we stick to.

We are glad to share document we did and hope that this document will be usefull to you as start point for your own standards. Here are links to doc and pdf.

Plugin to set records' names automatically

On each projects there are entities we can't force user to set names for (because it is needless). However it is good approach if all you records has names, especially if you have to choose these records in lookups.

I use plugins to set names of such entities. To simplify development of such plugins I did a base class and want to share it with you.

Let me show you example of the plugin which is based on SetNamePluginBase (base plugin) I share below.

using ExitoConsulting.Plugins.Base;
using Microsoft.Xrm.Sdk;

namespace ExitoConsulting.Plugins
{
    public class OwnerSetName : SetNamePluginBase
    {
        protected override string GetName(Entity entity, IPluginExecutionContext context, IOrganizationService service)
        {
            var fullName = entity.GetAttributeValue("ec_fullname");
            var clientId = entity.GetAttributeValue("ec_clientid");

            if(string.IsNullOrEmpty(fullName))
            {
                return null;
            }

            var name = fullName;
            if(!string.IsNullOrEmpty(clientId))
            {
                name = name + string.Format(" - {0}", clientId);
            }

            return name;
        }
    }
}

CRM 2011. Solution import fails with error "An item with the same key has already been added."

Today I was helping to resolve following error during solution import: "An item with the same key has already been added".

I took traces and found following:

EntityService.Update caught exception: System.ArgumentException: An item with the same key has already been added.
at System.Collections.Generic.Dictionary`2.Insert(TKey key, TValue value, Boolean add)
at Microsoft.Crm.Metadata.OptionSetService.GetExistingAttributePicklistInternal(Guid optionSetId, OptionSetType optionSetType, Nullable`1 optionValue, ISqlExecutionContext context)

Method "GetExistingAttributePicklistInternal" is retrieving picklist values from CRM and adding these values to a dictionary. It means that you have a problem with CRM instance you are deploying solution into.

I didn't have a chance to find a way how to fix this issue. We decided to use another environment for now and successfully deployed our solution.

Introducing Visual Studio Add-On "Microsoft Dynamics CRM Web Resources Updater"

This add-on will be useful for each Microsoft Dynamics CRM Developer. It allows to initiate upload and publish process directly from Visul Studio. The main benefit is that web resources upload and publish process takes as little time as possible because add-on is only take care about web resources that are changed. It will help to speed up development and debugging processes by allowing you to focus on the code you are crating.

See full description here: http://happycrm.blogspot.ru/p/crm-publisher.html

If you have any issue or comment just write me in comments or you can also leave a feedback on Codeplex or Visual Stuio Gallery and I will fix it shortly.

Hope you will love this add-on.

HOW TO: Organize Visual Studio Solution for Microsoft Dynamics CRM Customizations and Add-ons

Today I want to share how I organize Visual Studio Solution for small and medium size Microsoft Dynamics CRM implementation projects. For big projects solutions' structures are different and significantly depend on projects.

Below you can find common structure of a solution:

It doesn't mean that all solutions I did have the same structure. But all solutions have some or all projects from the list. Some solutions have even more projects.

Let me describe each project and folder one by one.

HOW TO: Create simple workflow activity in MS CRM 2015

Let's go throw Custom Workflow Activity creation process today.

To do that I need following:

  • Visual Studio 2015 and .NET Framework 4.5.2 installed on my dev. machine
  • Microsoft Dynamics CRM SDK 2015

1. Open Visual Studio and create new “Class Library” project

2. Add Reference
Press Add New Reference: