Sticky

This blog has moved to www.dreamingincrm.com. Please update your feed Url. Thank you.

22 July 2014

Retrieving Server Datetime using Javascript

I recently saw a forum post about retrieving server datetime using Javascript. My initial impressions were
1.) It is a bad idea to do this
2.) It is not possible

I attended DDDMelbourne recently and there was a talk about why bad ideas are the best ideas and so I set about trying to get this working. This looked like it could be done using Actions.

Problem No 1:
The simplest approach would be create a new Action with a Datetime output parameter and set this to Process' Execution Time. I did do this and here is when I found this first issue. CrmDatetime is STILL present in CRM 2013. Here is what I got when I executed the Action from the console.



Create a custom activity
This meant that I have to set the output Datetime parameter using a custom activity. This is the custom activity code. It is pretty simple. All it does it set the various output parameters of the Action.

namespace ServerTimeActivity
{
    using System;
    using System.Activities;
    using System.ServiceModel;
    using Microsoft.Xrm.Sdk;
    using Microsoft.Xrm.Sdk.Workflow;

    public sealed class RetrieveServerTimeActivity : CodeActivity
    {
        /// <summary>
        /// Executes the workflow activity.
        /// </summary>
        /// <param name="executionContext">The execution context.</param>
        protected override void Execute(CodeActivityContext executionContext)
        {
            // Create the tracing service
            ITracingService tracingService = executionContext.GetExtension<ITracingService>();

            if (tracingService == null)
            {
                throw new InvalidPluginExecutionException("Failed to retrieve tracing service.");
            }

            tracingService.Trace("Entered RetrieveServerTimeActivity.Execute(), Activity Instance Id: {0}, Workflow Instance Id: {1}",
                executionContext.ActivityInstanceId,
                executionContext.WorkflowInstanceId);

            // Create the context
            IWorkflowContext context = executionContext.GetExtension<IWorkflowContext>();

            if (context == null)
            {
                throw new InvalidPluginExecutionException("Failed to retrieve workflow context.");
            }

            tracingService.Trace("RetrieveServerTimeActivity.Execute(), Correlation Id: {0}, Initiating User: {1}",
                context.CorrelationId,
                context.InitiatingUserId);

            IOrganizationServiceFactory serviceFactory = executionContext.GetExtension<IOrganizationServiceFactory>();
            IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId);

            try
            {
                //TimeZoneInfo.ClearCachedData();
                //System.Globalization.CultureInfo.CurrentCulture.ClearCachedData();

                ServerDateTime.Set(executionContext, DateTime.Now);
                ServerDateTimeString.Set(executionContext, DateTime.Now.ToString(System.Globalization.CultureInfo.CurrentCulture));
                ServerTimezoneUtcOffset.Set(executionContext, TimeZoneInfo.Local.BaseUtcOffset.TotalMinutes);
                ServerTimezone.Set(executionContext, TimeZoneInfo.Local.ToString());
            }
            catch (FaultException<OrganizationServiceFault> e)
            {
                ServerDateTime.Set(executionContext, e.StackTrace);
                tracingService.Trace("Exception: {0}", e.ToString());

                // Handle the exception.
                throw;
            }

            tracingService.Trace("Exiting RetrieveServerTimeActivity.Execute(), Correlation Id: {0}", context.CorrelationId);
        }

        [Output("Server Datetime")]
        public OutArgument<DateTime> ServerDateTime { get; set; }

        [Output("Server Datetime String")]
        public OutArgument<string> ServerDateTimeString { get; set; }

        [Output("Server Timezone")]
        public OutArgument<string> ServerTimezone { get; set; }

        [Output("Server Timezone UTC Offset")]
        public OutArgument<double> ServerTimezoneUtcOffset { get; set; }

        [Output("Exception Stack Trace")]
        public OutArgument<string> ExceptionStackTrace { get; set; }
    }
}

The next step is to use this custom activity inside the Action. Here is what my Action looks like

Once this done we have generate the JS code for this action using the Sdk.Soap.js Action Message Generator. Generate the JS code for all the actions by executing the Sdk.SoapActionMessageGenerator.exe.

In order to use this in a form we need two scripts

1.) Sdk.Soap.js
2.) Sdk.new_ServerDateTime.min.js -> This is the name of my action

Add these scripts as webresource and reference them in the form or ribbon button that will require this functionality. Be mindful of the load sequence, as this not guaranteed due to the async nature of the script load. Follow this excellent tutorial from CRM MVP Scott Durow if you want to manage this issue, or refer to my earlier post about using require.js.

This is the client side JS you can use.
/// 
/// 
(function() {
    Sdk.Async.execute(new Sdk.new_ServerDateTimeRequest(),function(response) {
        console.log('Datetime String ' + response.getServerDatetimeString());
        console.log('Server UtcOffset ' + response.getServerUtcOffset());
        console.log('Server Datetime ' + response.getServerDateTime());
        console.log('Server Timezone String ' + response.getServerTimezone());
        
    }, function (message) { alert(message); });
})();
I ran the script from Chrome DevTools console and here is how the output looks like.
Observations
1.) The CRMOnline server that I am using is in APAC, but the server is in UTC timezone
2.) If the output parameter of an action is DateTime, it always returns local time and not the server time
3.) If the Datetime is converted to string then the server time is returned
4.) The culture on the CRMOnline server is en-US
5.) Assign value will throw an exception if the value being assigned is null

I hope this was helpful in understanding how awesome Actions and Sdk.Soap.js library are.

No comments:

Post a Comment