March 30, 2015

JavaScript OData Pagination with synchronous calls

One limit of the OData endpoint is that the response can only include up to 50 records, so if your result set has more records it's necessary to reiterate the request using the url inside the __next property contained inside the returned object.

The following code shows how to fetch the request using synchronous calls, because sometimes you need (or want) to block the user :)
function getODataRecords(ODataUrl) {

    // we return an object with a similar structure as the OData endpoint
    var allRecords = new Object();
    allRecords.results = new Array();
    
    // we loop until we have an url to query
    var queryUrl = ODataUrl;
    while(queryUrl != null) {

        // we build the request
        var ODataRequest = new XMLHttpRequest(); 
        ODataRequest.open("GET", queryUrl, false); // false = synchronous request
        ODataRequest.setRequestHeader("Accept", "application/json"); 
        ODataRequest.setRequestHeader("Content-Type", "application/json; charset=utf-8"); 
        ODataRequest.send();

        if (ODataRequest.status === 200) {
            var parsedResults = JSON.parse(ODataRequest.responseText).d;
            if (parsedResults != null && parsedResults.results != null) {

                // we add the results to our object
                for (var i = 0; i < parsedResults.results.length; i++) {
                    allRecords.results.push(parsedResults.results[i]);
                }

                // check if there are more records and set the new url, otherwise we set to null the url
                if (parsedResults.__next != null) {
                    queryUrl = parsedResults.__next;
                } else {
                    queryUrl = null;
                }
            }
        } else {
            // if the request has errors we stop and return a null result
            queryUrl = null;
            allRecords = null;
        }
    }

    return allRecords;
}

// sample function to return all the accounts
function GetAllAccounts() {
    var serverUrl;
    if (Xrm.Page.context.getClientUrl !== undefined) {
        serverUrl = Xrm.Page.context.getClientUrl();
    } else {
        serverUrl = Xrm.Page.context.getServerUrl();
    }
    var ODataPath = serverUrl + "/XRMServices/2011/OrganizationData.svc";
    var accountQueryUrl = ODataPath + "/AcccountSet?$select=AccountNumber,Name";

    // call our new method
    var retrievedAccounts = getODataRecords(accountQueryUrl);

    // alert each result
    if (retrievedAccounts != null) {
        for (var i = 0; i < retrievedAccounts.results.length; i++) {
            alert(retrievedAccounts.results[i].Name + " - " + retrievedAccounts.results[i].AccountNumber);
        }
    }
}

March 9, 2015

Japan and Australia Data Center Discovery URLs

Microsoft launched the new Data Center in Japan for CRM Online, you can read the announcement by Bob Stutz here:
Japan Datacenter Open for Business: Now Serving Japanese CRM Online Customers.

The Australia Data Center is also operative and available for companies from Australia, New Zealand and Fiji.

Microsoft defined also the Discovery Web Service URL of these data centers, the addresses are:
https://disco.crm7.dynamics.com/XRMServices/2011/Discovery.svc (Japan)
https://disco.crm6.dynamics.com/XRMServices/2011/Discovery.svc (Australia)

The Japanese data center is identified by the crm7 host, the Australian one by crm6. This will affect also the organization url, for example if Contoso Ltd. has two branches, one in Japan and the other one in Australia, their urls will be:
https://contosojapan.crm7.dynamics.com
https://contosoaustralia.crm6.dynamics.com

The relative MSDN page has been updated to include this change, the next table is a recap:

HostLocation
crmNorth America
crm4EMEA
crm5APAC
crm2South America
crm9North America 2 (CRM Online for Government)
crm7Japan
crm6Australia

March 3, 2015

Moment.js and Dynamics CRM: Multi Language support

The current version of Dynamics CRM supports 45 languages (an excel file with the list can be downloaded here: http://1drv.ms/18Ih0Ux) mapped to LCID codes.
This LCID code is also the one returned by the following methods: Xrm.Page.context.getUserLcid and Xrm.Page.context.geOrgLcid (the language of the user and the base language of the organization).

Moment.js is a powerful library to manipulate dates in JavaScript and it has support for internationalization but accepts only a locale string ("en", "it", ...) and not the LCID value returned by the above methods. I created a small JavaScript library (crm_lang.js) that accepts the 45 LCID CRM codes in order to return their corresponding Moment.js locale strings. The library can be downloaded from Technet Gallery:

http://gallery.technet.microsoft.com/scriptcenter/Momentjs-and-Dynamics-CRM-8bd3a264

Moment.js is very useful when comes to manipulate dates (add or subtracts days) and more important for this sample to display the date in various formats:
var now = moment();
alert(now.format());                                // "2015-03-03T08:02:17-05:00" (ISO 8601)
alert(now.format("dddd, MMMM Do YYYY, h:mm:ss a")); // "Tuesday, March 3rd 2015, 3:25:50 pm"
As I wrote before it supports internationalization, but it requires the locale string:
var now = moment();
now.locale("es"); // set the language to Spanish
alert(now.format("LLLL")); // "martes, 3 de marzo de 2015 8:00"
with the library I created, it's easier to set the right locale:
var now = moment();
var userLcid = Xrm.Page.context.getUserLcid(); //suppose is 1043 (Dutch)
var userLocale = CRMLanguages.getMomentLocale(userLcid);
now.locale(userLocale); // or now.locale(CRMLanguages.getMomentLocale(Xrm.Page.context.getUserLcid()));
alert(now.format("LLL")); // "3 maart 2015 08:00"
Moment.js constructor accepts a standard JavaScript date object, this makes easier to works with CRM form values:
var createdOn = Xrm.Page.getAttribute("createdon").getValue(); // get the date value
var m_date = moment(createdOn); // create the Moment.js object
m_date.locale(CRMLanguages.getMomentLocale(Xrm.Page.context.getUserLcid())); // set the language
m_date.add(3, "days"); // we add 3 days to the createdon date
alert(m_date.format("LLL")); // "2015年3月6日午前8時0分" //1041 Japanese
It's also possible to return the date as a standard JS object, the method is toDate()
// following the previous code
var threeDaysLater = m_date.toDate();
Xrm.Page.getAttribute("new_datecheck").setValue(threeDaysLater);
As you can see Moment.js can be very useful, make sure you read the documentation http://momentjs.com/docs/ and if you need a multi language support you can use my library. LLAP!

February 9, 2015

JSON and CRM Sandbox Plugins

Today I was working on a plugin and one of the requirements was a call to a web service passing some POST parameters, nothing complicated but I want to share part of the process.

Normally I register my plugins always inside Sandbox, the main reason is that I don't need to care where the plugin is executed (in this specific case the development is OnPremise but the production is Online), the second reason is that my user is often forgotten to be added as Deployment Administrator :)

One of the parameter was a JSON Object passed as string, practically I needed to do the C# equivalent of a JSON.stringify in order to pass a complex structure. An example can be the following Course object (JavaScript):
var Course = new Object();
Course.Name = "CRM Development 1";
Course.Teacher = "Prof. John Smith";

Course.Students = new Array();
Course.Students[0] = new Object();
Course.Students[0].ID = "001";
Course.Students[0].Name = "Walter Davis";

Course.Students[1] = new Object();
Course.Students[1].ID = "002";
Course.Students[1].Name = "Mark Harris";

var parameter1 = JSON.stringify(Course);
Because I was inside a Sandbox plugin I couldn't use the Newtonsoft.Json library and I didn't want to waste time trying to merge it inside my plugin.

The .NET framework provides different methods to create a JSON output, in particular JavaScriptSerializer (from System.Web.Script.Serialization namespace) and DataContractJsonSerializer (from System.Runtime.Serialization.Json namespace).

JavaScriptSerializer is very easy to use (it has a Serialize method returning a string) but doesn't work inside Sandbox, so I used DataContractJsonSerializer:
Course course = new Course();
// ...

string parameter1 = "";
using (MemoryStream memoryStream = new MemoryStream())
{
   DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(Course));
   serializer.WriteObject(memoryStream, course);
   parameter1 = Encoding.Default.GetString(memoryStream.ToArray());
}
A bit longer code but compatible with Sandbox and CRM Online.

January 27, 2015

New CRM 2015 exams available: MB2-707 Customization & Configuration and MB2-706 Online Deployment

The following CRM 2015 exams are now available:

MB2-706: Microsoft Dynamics CRM Online Deployment
MB2-707: Microsoft Dynamics CRM Customization and Configuration

The exams can be already scheduled and until 31 May 2015 there is also the Second Shot offer, meaning that you can retake the exam for free if you didn't pass the first time.

All the Dynamics CRM 2015 exams are listed here:
Dynamics CRM 2015 Certifications

January 20, 2015

Get the Id of records created using Early Bound OrganizationServiceContext

The other day my friend Raj (@RajYRaman) retweeted a post from Dynamics CRM PFE Team about the best practice to let CRM to choose the Guid of new records instead of using Guid.NewGuid():

http://blogs.msdn.com/b/crminthefield/archive/2015/01/19/the-dangers-of-guid-newguid.aspx

With IOrganizationService object is very easy to get the Id of the new record, because it's the value returned by the Create method independently if late bound or early bound is used:
IOrganizationService service = new OrganizationService(crmConnection);

// late bound
Entity lateAccount = new Entity("account");
lateAccount["name"] = "Late Bound Account";
Guid lateAccountId = service.Create(lateAccount);

// early bound
Account earlyAccount = new Account();
earlyAccount.Name = "Early Bound Account";
Guid earlyAccountId = service.Create(earlyAccount);
This works because Early Bound classes inherit from the Entity class.
public partial class Account : Microsoft.Xrm.Sdk.Entity ...
But how we can get the Id of the records when we use the OrganizationServiceContext in combination with the AddObject and SaveChanges methods?
Using Guid.NewGuid() in order to specify the Id before creating the record was one of my mistakes when I first used Early Bound classes. My badly written code was:
IOrganizationService service = new OrganizationService(crmConnection);
XrmContext context = new XrmContext(service);

// WORST CODE EVER, DON'T DO THIS!
Account earlyAccount = new Account();
Guid badGeneratedId = Guid.NewGuid();
earlyAccount.Id = badGeneratedId;
earlyAccount.Name = "Early Bound Account";
context.AddObject(earlyAccount);
context.SaveChanges();
The reason was to avoid parsing the results of the SaveChanges method in order to get the Id. SaveChanges doesn't return void but a SaveChangesResultCollection object with the details of the save operation.

But checking the results is not necessary, in fact the OrganizationServiceContext updates the tracked records adding the Guid value to the Id property. We just need to read the Id property after the SaveChanges:
Account earlyAccount = new Account();
earlyAccount.Name = "Early Bound Account";
context.AddObject(earlyAccount);
context.SaveChanges();

Guid crmGeneratedId = earlyAccount.Id; 
MSDN Documentation (at least at the time I discover this) isn't so clear explaining this behavior, hope it helps!

January 14, 2015

Retrieve the Saved Views (UserQuery) of all CRM users

Dynamics CRM allows the users to create personal views (using Advanced Find) and eventually share them with other users.

The entity used to store these views is called UserQuery and the main properties are:
  • Name: Name given to the saved view
  • FetchXml: String that specifies the query in Fetch XML language
  • OwnerId: Unique identifier of the user or team who owns the saved view
In order to retrieve the saved views we can use FetchXml or a QueryExpression, but the result will always contain only the saved views of the user executing the query.

Consider the following simplified scenario:
  • CRM has only two users: John (System Administrator role) and Bob (Sales Manager role)
  • John has a personal view for the Account Entity (not shared)
  • Bob has a personal view for the Contact Entity (not shared)
If John executes a query to return all the UserQuery records, the result will contain only his Account personal view, despite his System Administrator role.
What if we want to retrieve all the saved views for all the users? A possibility is to impersonate each CRM user, run the query and combine the results.
To implement this solution we rely on the CallerId property of the OrganizationServiceProxy object in combination with the "Act on Behalf of Another User" privilege.
The OrganizationServiceProxy gives us the possibility to impersonate the user by code, the "Act on Behalf of Another User" privilege is required to allow this impersonation.

The following code is a simplified example of the solution:
// url and credentials
string organizationUrl = "https://mycompany.crm.dynamics.com/XRMServices/2011/Organization.svc";
string userName = "john@mycompany.onmicrosoft.com";
string password = "JohnPassword";

// authentication code
ClientCredentials credentials = new ClientCredentials();
credentials.UserName.UserName = userName;
credentials.UserName.Password = password;
IServiceManagement<IOrganizationService> orgServiceManagement = ServiceConfigurationFactory.CreateManagement(new Uri(organizationUrl));
AuthenticationCredentials authCredentials = new AuthenticationCredentials();
authCredentials.ClientCredentials = credentials;
AuthenticationCredentials tokenCredentials = orgServiceManagement.Authenticate(authCredentials);
SecurityTokenResponse organizationTokenResponse = tokenCredentials.SecurityTokenResponse;

// IOrganizationService and OrganizationServiceProxy objects
OrganizationServiceProxy serviceProxy;
IOrganizationService service;

using (serviceProxy = new OrganizationServiceProxy(orgServiceManagement, organizationTokenResponse))
{
    service = (IOrganizationService)serviceProxy;
    
    // Dictionary to contain all the saved views
    Dictionary<Guid, Entity> dictPersonalViews = new Dictionary<Guid, Entity>();

    // retrieve first all the CRM Users
    QueryExpression systemUsers = new QueryExpression("systemuser");
    systemUsers.ColumnSet = new ColumnSet(true);
    EntityCollection userCollection = service.RetrieveMultiple(systemUsers);


    // for each User we launch the query to retrieve the saved views
    foreach (Entity systemUser in userCollection.Entities)
    {
        QueryExpression personalViews = new QueryExpression("userquery");
        personalViews.ColumnSet = new ColumnSet(true);

        // we set the CallerId property to impersonate the current iteration user
        serviceProxy.CallerId = systemUser.Id;
        EntityCollection viewCollection = serviceProxy.RetrieveMultiple(personalViews);

        foreach (Entity personalView in viewCollection.Entities)
        {
            // we want a list without duplicates (shared views or automatically shared to SYSTEM and INTEGRATION users)
            if (!dictPersonalViews.ContainsKey(personalView.Id))
            {
                dictPersonalViews.Add(personalView.Id, personalView);
            }
        }
    }

    // we can process the values (Entity objects) of the dictionary
    foreach (Entity personalView in dictPersonalViews.Values)
    {
       string viewName = personalView["name"].ToString();
       string viewFetchXml = personalView["fetchxml"].ToString();
       EntityReference viewOwnerIdRef = (EntityReference)personalView["ownerid"];
    }
}