Showing posts with label c#. Show all posts
Showing posts with label c#. Show all posts

March 28, 2019

Update user’s calendar in Field Service schedule board

Configuring the work hours for a user is straightforward from the Dynamics 365 UI, a couple of clicks and you are done. However in the last years we saw an increase of Field Service projects, hence the necessity to automatize the creation and the update of user's works hours.
C# code to update the calendar entity has been around from CRM 2011, like this one:
Sample code to update user’s calendar programmatically (work hours) in CRM 2011

The code linked above works perfectly when you see the work hours inside the user calendar, however they don't appear inside the Field Service schedule board. How can we make sure the work hours appear everywhere?

Nearly 50 lines of code to create a work hour entry are (at least to me) a bit excessive, but this is the way Dynamics work. I tried to simplify the code by renaming the variables and adding the code necessary for the work hours to appear inside the schedule board.
// we start with the user Id
Guid userId = Guid.Empty; // instead of Guid.Empty here we should have the real user Id

// we retrieve the calendarid from the user entity
Entity user = service.Retrieve("systemuser", userId, new ColumnSet("calendarid"));
Guid userCalendarId = user.GetAttributeValue("calendarid").Id;

// we retrieve the calendar record in order to get the Business Unit and the Calendar Rules
Entity userCalendar = service.Retrieve("calendar", userCalendarId , new ColumnSet("businessunitid"));
Guid calendarBusinessUnitRef = userCalendar.GetAttributeValue("businessunitid");
EntityCollection calendarRules = userCalendar.GetAttributeValue("calendarrules");

// we create a new calendar record (inner)
Entity innerCalendar = new Entity("calendar");
innerCalendar["businessunitid"] = calendarBusinessUnitRef;

// Field Service Schedule Board: we must define the type as Inner Calendar
// https://docs.microsoft.com/en-us/dynamics365/customer-engagement/developer/types-calendars
innerCalendar["type"] = new OptionSetValue(-1);

Guid innerCalendarId = service.Create(innerCalendar);

// we create a calendar rule for the whole day we want to edit
Entity dayRule = new Entity("calendarrule");
dayRule["duration"] = 1440; // 24 hours in minutes
dayRule["effort"] = 1.0;
dayRule["extentcode"] = 1;
dayRule["pattern"] = "FREQ=DAILY;COUNT=1";
dayRule["rank"] = 0;
dayRule["timezonecode"] = 110; // 110 is (GMT+01:00) Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna
// GetAllTimeZonesWithDisplayNameRequest can be used to retrieve all the timezones with their code
dayRule["starttime"] = new DateTime(2019, 3, 28, 0, 0, 0, DateTimeKind.Utc);
dayRule["innercalendarid"] = new EntityReference("calendar", innerCalendarId);

// Field Service Schedule Board: we must also define the calendarid reference to the user calendar
dayRule["calendarid"] = new EntityReference("calendar", userCalendarId);

// we attach it to the Calendar Rules of the user
calendarRules.Entities.Add(dayRule);

// we update the user calendar to refresh the calendar rules collection
Entity updateUserCalendar = new Entity("calendar", userCalendarId);
updateUserCalendar["calendarrules"] = calendarRules;
service.Update(updateUserCalendar);

// we define the calendar rule containing our work hour 
Entity exactCalendarRule = new Entity("calendarrule");
exactCalendarRule["duration"] = 120; // 2 hours in minutes
exactCalendarRule["effort"] = 1.0;
exactCalendarRule["issimple"] = true;
exactCalendarRule["offset"] = 480; // 8 hours in minutes from start time (12:00)
exactCalendarRule["rank"] = 0;
exactCalendarRule["subcode"] = 1;
exactCalendarRule["timecode"] = 0;
exactCalendarRule["timezonecode"] = 110; // same timezone as the day rule
exactCalendarRule["calendarid"] = new EntityReference("calendar", innerCalendarId);

// we add the calendar rule to a collection
EntityCollection innerCalendarRules = new EntityCollection();
innerCalendarRules.EntityName = "calendarrule";
innerCalendarRules.Entities.Add(exactCalendarRule);

// we update the inner calendar with the new calendar rules 
innerCalendar["calendarrules"] = innerCalendarRules;
innerCalendar["calendarid"] = innerCalendarId;
service.Update(innerCalendar);

In the end to changes were necessary to make the work hour visible inside the Field Service schedule board:
  1. We must define the type for the Inner Calendar
  2. We must reference the user calendar inside the calendar rule the the whole day
I didn't discover this by myself but with the help of Microsoft support, hope it helps.

November 9, 2017

Catch the Revise Quote message inside a Plugin

A user in Dynamics CRM/365 can revise a Quote, it's a process where the old quote is closed and a new quote is created in draft state and ready to be updated.
Dynamics doesn't support a specific message that we can intercept (in order to register a plugin step) when a quote is revised, however one of the possible ways is to deal with the Create message.

When quote are revised the integer (Whole Number) field revisionnumber is increased, this field is managed by the platform as it is not valid for Create or for Update according to its Metadata.
So when a new quote is created the revision number is 0, and the revised quotes will have 1,2,3...
Fetching the quote with the previous revision number is not a clever method to retrieve the revised quote. For example if we have an original quote (0) in Closed state and a revised quote (1) in Closed state too (because we cancelled), we are able to revise the original quote (0) and the new quote will have 2 as revision number.

My friend Daryl LaBar (@ddlabar) suggested to check the ParentContext property inside the plugin in order to access to the ReviseQuote message that initialized the create message.
In the end I wrote a plugin (registered on the Create message, Pre-Operation, Synchronous) to check the Parent Context and get the reference of the revised quote. Here the code:
public void Execute(IServiceProvider serviceProvider)
{
  try
  {
    IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));

    if (context.InputParameters.Contains("Target") && context.InputParameters["Target"] is Entity)
    {
      Entity currentQuote = (Entity)context.InputParameters["Target"];
      if (currentQuote.LogicalName != "quote") { return; }
      // check the revision number first
      int revisionNumber = currentQuote.GetAttributeValue<int>("revisionnumber");
      if (revisionNumber > 0)
      {
        IPluginExecutionContext parentContext = context.ParentContext;
        // check if the Parent Context contains the QuoteId parameter of the ReviseQuote message
        if (parentContext.InputParameters.Contains("QuoteId") && parentContext.InputParameters["QuoteId"] is Guid)
        {
          Guid revisedQuoteId = (Guid)parentContext.InputParameters["QuoteId"];
          IOrganizationServiceFactory serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
          IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId);

          Entity revisedQuote = service.Retrieve("quote", revisedQuoteId, new ColumnSet(true));
          
          // rest of the code

        }
      }
    }
  }
  catch (Exception ex)
  {
    throw new InvalidPluginExecutionException(ex.Message);
  }
}

April 4, 2017

GetAttributeValue<object>, why not?

Today I wrote a small piece of code in order to replicate the exact same data from a CRM instance to another keeping the same ID, it was an exercise because I had to copy only a couple of entities, otherwise I could use one of the several tools made for this kind of operation (like KingswaySoft).

While I was writing the code I arrived to the point where I need to map the fields, so I started to write the usual GetAttributeValue<string>, GetAttributeValue<EntityReference>, ...
And I asked myself:
"source and target attributes are always of the same type, what if I use object for the Generic?"
"something like newEntity["name"] = oldEntity.GetAttributeValue<object>("name"); works?"

With a bit of surprise I found that actually works, so in the end I wrote this piece of code.
Some notes:
  • It uses CrmServiceClient, because there are two instances (source and target) one of them should contains the option RequireNewInstance = true;
  • The UpsertRequest works also when the entity has the ID defined, and not only when the entity is defined using an alternate key syntax
  • As suggested by Tinus Smith in his comment, if the purpose is to do an exact copy, it's not necessary to create the entityTarget and copy its values with GetAttributeValue<object>, instead the entitySource can be used directly inside the Target property of the UpdateRequest:
    UpsertResponse response = (UpsertResponse)target.Execute(new UpsertRequest { Target = entitySource });
MigrateEntity(source, target, "new_entity", new List { "new_name", "new_date", "new_lookupid" });

private static void MigrateEntity(CrmServiceClient source, CrmServiceClient target,
                                  string entityName, List<string> columns)
{
    QueryExpression querySource = new QueryExpression(entityName);
    querySource.ColumnSet = new ColumnSet(columns.ToArray());
    EntityCollection collSource = source.RetrieveMultiple(querySource);
    foreach (Entity entitySource in collSource.Entities)
    {
        try
        {
            Entity entityTarget = new Entity(entityName);
            entityTarget.Id = entitySource.Id;
            foreach (string column in columns)
            {
                entityTarget[column] = entitySource.GetAttributeValue<object>(column);
            }
            UpsertResponse response = (UpsertResponse)target.Execute(new UpsertRequest { Target = entityTarget });
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

January 7, 2016

Pay attention to the CRM version when using the SDK NuGet packages

Microsoft publishes the CRM SDK DLLs also as NuGet packages, you can find the list by browsing the crmsdk profile:

https://www.nuget.org/profiles/crmsdk

Because the packages are not separate by CRM version (for example a package for CRM 2015 and a package for CRM 2016) Microsoft simply updates the NuGet package increasing the version.
This can be an issue in some projects as happened to me some days ago. I was working on a plugin for CRM 2015, my project was targeting .NET 4.5.2 and I installed the current version of CoreAssemblies (8.0.1).
The build was successful but the plugin always returned the exception "Value cannot be null".

I spent some time to understand that the problem was the referenced DLL version, so I removed the NuGet package and I installed the 2015 specific version (7.1.1) using the Package Manager Console:

Install-Package Microsoft.CrmSdk.CoreAssemblies -Version 7.1.1

After the plugin worked like a charm.
As reference these are the current versions and the suggested .NET framework to use:
  • CRM 2011: 5.0.18 (.NET 4.0)
  • CRM 2013: 6.1.1 (.NET 4.0)
  • CRM 2015: 7.1.1 (.NET 4.5.2)
  • CRM 2016: 8.0.1 (.NET 4.5.2)

July 21, 2015

Visual Studio 2015 Shared Projects and CRM Plugins Development

Visual Studio 2015 is now available and inside the new features and improvements there is one that I like very much: Shared Projects. A Shared Project is intended to share easily the code between different platforms.
For C# projects this was already possible using (portable) class libraries and adding the assembly to the main project references.

Which is the big advantage of Shared Projects for a Dynamics CRM developer?
With Shared Projects the code is NOT compiled to a separate assembly but directly inside the main assembly of your project.

This is can be very useful for Plugin and Custom Workflow Activity development, because now we can create a common library with CRM methods to be used inside our plugins WITHOUT the need to use ILMerge for creating a single assembly in order to be registered inside Dynamics CRM.

As example I created a small shared project containing a simple method to check the current CRM Version:
using Microsoft.Crm.Sdk.Messages;
using Microsoft.Xrm.Sdk;
namespace CRM.Common
{
    public static class Utils
    {
        public enum CRMVersion
        {
            Unknown = 0, CRM2011 = 2011, CRM2013 = 2013, CRM2015 = 2015
        }

        public static CRMVersion GetCRMVersion(IOrganizationService service)
        {
            RetrieveVersionRequest versionRequest = new RetrieveVersionRequest();
            RetrieveVersionResponse versionResponse = (RetrieveVersionResponse)service.Execute(versionRequest);
            string version = versionResponse.Version;
            if (version.StartsWith("5")) { return CRMVersion.CRM2011; }
            if (version.StartsWith("6")) { return CRMVersion.CRM2013; }
            if (version.StartsWith("7")) { return CRMVersion.CRM2015; }
            return CRMVersion.Unknown;
        }
    }
}
After I created a normal Class Library project for my plugin and I added the Shared Project to the solution and a reference inside the Class Library:

The plugin code is very simple, it throws an exception indicating the CRM version:
using Microsoft.Xrm.Sdk;
using System;
using static CRM.Common.Utils;

namespace CRM.MyPlugin
{
    public class MyPlugin : IPlugin
    {
        public void Execute(IServiceProvider serviceProvider)
        {
            IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
            IOrganizationServiceFactory serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
            IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId);

            CRMVersion version = GetCRMVersion(service);
            throw new InvalidPluginExecutionException(version.ToString());
        }
    }
}
If we compile the solution only a single assembly is generated and if we analyze it using ILSpy we can confirm that the Shared Project is compiled inside the main assembly:

And the plugin works without issues:


Shared Projects can be very useful for Dynamics CRM development because will definitely improve the quality and the reuse of the code.

Shared Projects are also available for Visual Studio 2013 as separate addon, you can download from here: Shared Project Reference Manager

April 27, 2015

GetAttributeValue demystified

Note: the content of this post is similar to this post from Dave Berry but I wanted to approach the same argument with some example code and tips.

Dynamics CRM allows to create fields with the following data types:

Except for "Single Line of Text" and "Multiple Lines of Text" (both use string), each one uses a different underline data type in .NET, some of these data types are nullable, some are not.

What means nullable and why we need to care about this?
Let's start with an example: In our CRM we have two records, the first record has all the fields filled with a value, in the second one all the fields are empty. When we use GetAttributeValue and there is a value, the method (fairly) returns the value. But what happens with our empty record? The response is "depends".

GetAttributeValue uses Generics, so we can choose to get a nullable type or not:
bool boolean = entity.GetAttributeValue<bool>("new_boolean");
bool? booleanNullable = entity.GetAttributeValue<bool?>("new_boolean");
In this case, if the value is null (for a Boolean/Two Options field means that no value is set) the first variable will contains false, the second will contains null.
The next table is a summary:

CRM Type .NET Type can hold null? default value
Single Line of Text string Yes
Option Set OptionSetValue Yes
Two Options bool No false
Image byte[] Yes
Whole Number int No 0
Floating Point Number double No 0.0
Decimal Number decimal No 0
Currency Money Yes
Multiple Lines of Text string Yes
Date and Time DateTime No DateTime.MinValue
Lookup EntityReference Yes
For the types that can hold null, we use GetAttributeValue and after check if it's null or not:
EntityReference lookupRef = entity.GetAttributeValue<EntityReference>("new_lookupid");
if (lookupRef == null) {
   // no value set
} else {
   // we have a value
}
For the types that can't hold null we need to ask ourselves: "The default value is enough for the requirement?"

If we are in a loop and we need to do a sum of an int field, the default value (0) is ok, so we can just do
int totalSum = 0;
foreach (Entity row in RowCollection.Entities) {
   int number = entity.GetAttributeValue<int>("new_wholenumber");
   totalSum+=number;
}
but if we are doing a multiplication we need to skip the null values, so we use int?
int totalMulty = 0;
foreach (Entity row in RowCollection.Entities) {
   int? number = entity.GetAttributeValue<int?>("new_wholenumber");
   if (number != null) {
      totalMulty*=number;
   }
}
otherwise with a null value our totalMulty variable will be 0.

DateTime is a particular case, it can't hold null but the default value (MinValue = 01/01/0001) can't be a valid CRM value (as happens with bool and numeric fields) so we can do the following check:
DateTime dateTime = entity.GetAttributeValue<DateTime>("new_datetime");
if (dateTime == DateTime.MinValue) {
   // no value set
} else {
   // we have a value
}
Practically when we don't use the nullable form, Dynamics CRM is doing the following:
double floating = entity.GetAttributeValue<double>("new_floating");
// equals to
double floating = entity.GetAttributeValue<double?>("new_floating").GetValueOrDefault();
The combination of the nullable form and the GetValueOrDefault can be useful in some scenarios. Let's say that we need to do a data migration to an external system, but if the source decimal Quantity is null, the target decimal Quantity must be -1.
decimal quantity = entity.GetAttributeValue<decimal?>("new_quantity").GetValueOrDefault(-1);
In this way we deal automatically the null values and they are ready for the target system.

Now you are a Dynamics CRM True Survivor!

April 2, 2015

Simplified Connection with complicated passwords

CRM 2011 introduced a very easy way to connect to Dynamics CRM instances: the Simplified Connection (MSDN: https://msdn.microsoft.com/en-us/library/gg695810.aspx).
Basically it's necessary to build a connection string instead of dealing with the specific deployment type (OnPremise, IFD or Online).

The downside of using a Simplified Connection is its weakness management of passwords containing special characters like double quotes, single quotes, ampersands.

Considering the MSDN example for a CRM Online connection:
Url=https://contoso.crm.dynamics.com; Username=jsmith@contoso.com; Password=passcode;
If the password is ;abc123 (note the semicolon) an exception will be thrown with the following message:
Format of the initialization string does not conform to specification starting at index 102. The solution for this problem is to include the password inside single quotes, the following connection string will work:
Url=https://contoso.crm.dynamics.com; Username=jsmith@contoso.com; Password=';abc123';
Assuming the connection string is builded dynamically the following code can be used:
string connectionString =
    String.Format("Url={0}; Username={1}; Password='{2}';", url, username, password);
What if our complicated password contains single quotes as well? Let's consider for example the following password: ;a''bc'123
In this case the previous exception (Format of the initialization string) will be thrown again. This issue can be solved "escaping" the single quotes using a Replace:
string connectionString =
    String.Format("Url={0}; Username={1}; Password='{2}';",
    url, username, password.Replace("'","''"));
Please note that the escape must be done also if your connection string is stored inside your app/web.config:
<add key="CRM"
value="Url=https://contoso.crm.dynamics.com; Username=jsmith@contoso.com; Password=';a''bc'123';"/>
But in this case our replace method will not work, because it will replace also the single quotes delimiting the password. In this scenario I suggest to put inside the app/web.config a placeholder instead of the delimiting single quotes that will be replaced after (for example #XYZ#):
<add key="CRM"
value="Url=https://contoso.crm.dynamics.com; Username=jsmith@contoso.com; Password=#XYZ#;a''bc'123#XYZ#;"/>
Then after the connection string is loaded we do the escape and the replace:
string connectionString = ConfigurationManager.ConnectionStrings["CRM"].ConnectionString;
// escape the single quotes inside the password
connectionString = connectionString.Replace("'","''");
// replace the placeholder with single quotes
connectionString = connectionString.Replace("#XYZ#","'");
Of course this will not work if the password contains the placeholder as well, so it's better to choose a long placeholder.

When the password is stored inside the app/web.config it's necessary to deal with another problem, the case that our password contains XML special characters (mostly double quotes) because this file is an XML.

If it's necessary to encode the password the following .NET method can be used:
string xmlPassword = System.Security.SecurityElement.Escape(password);
The result for the password ;a''b"c'123 (note the double quote between b and c that will create problems if not encoded) will be ;a&apos;&apos;bc&apos;123, a valid string to be written inside the app/web.config.

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"];
    }
}

December 8, 2014

Retrieve the current CRM version

The CRM SDK DLLs can easily connect to different CRM versions, for example the CRM 2013 DLLs can be used also with CRM 2011/2015 environments.
Depending the version some messages (like the ExecuteMultipleRequest) are not available, the following function returns an enumerator with the current CRM version.
public enum CRMVersion
{
    Unknown,
    CRM2011,
    CRM2011UR12PLUS,
    CRM2013,
    CRM2013SP1,
    CRM2015
}

public CRMVersion GetCRMVersion(IOrganizationService service)
{
    RetrieveVersionRequest versionRequest = new RetrieveVersionRequest();
    RetrieveVersionResponse versionResponse = (RetrieveVersionResponse)service.Execute(versionRequest);

    string version = versionResponse.Version;
    if (version.StartsWith("5"))
    {
        try
        {
            int buildNumber = Convert.ToInt32(version.Substring(version.LastIndexOf(".") + 1));
            if (buildNumber > 3000) { return CRMVersion.CRM2011UR12PLUS; }
        }
        catch { }
        return CRMVersion.CRM2011;
    }
    if (version.StartsWith("6.0")) { return CRMVersion.CRM2013; }
    if (version.StartsWith("6.1")) { return CRMVersion.CRM2013SP1; }
    if (version.StartsWith("7")) { return CRMVersion.CRM2015; }
    return CRMVersion.Unknown;
}

October 4, 2014

Create custom currencies by code

Dynamics CRM allows to create custom currencies in addition to the standard ones provided by the system.

The entity involved is transactioncurrency and the follow attributes are required to create a new currency:
  • currencyname (string)
  • currencyprecision (int)
  • currencysymbol (string)
  • exchangerate (decimal)
  • isocurrencycode (string)
The attribute isocurrencycode requires an additional check, it must contains always three letters, digits and special characters are not allowed.

Example:
Entity newCurrency = new Entity("transactioncurrency");

newCurrency["currencyname"] = "My Currency";
newCurrency["currencyprecision"] = 2;
newCurrency["currencysymbol"] = "@";
newCurrency["exchangerate"] = 2m;
newCurrency["isocurrencycode"] = "MCU"; // 3 letters

Guid newCurrencyId = service.Create(newCurrency);

September 29, 2014

Check if a User has a specific Privilege

I came across a question on Dynamics Community forums where the asker wanted to check if a CRM User has or not a specific privilege of a custom entity.

He wanted to avoid the use of the RetrievePrincipalAccessRequest because it requires to provide an existing record id in order to perform the request. I agree with this, in addition the result of a RetrievePrincipalAccessRequest doesn't guarantee that the user has the privilege, but only that the user can access to that specific record (for example when the record is only shared to the user).

A good aspect of the privileges is that they respect a naming convention, so the Read privilege for the Account entity is called prvReadAccount, the Write privilege prvWriteAccount, etc etc. This naming convention is valid also for custom entity, so if we have an entity called new_SMS and we want to know if the user can create a new_SMS record, we will check for the prvCreatenew_SMS privilege.

The easy way it to check first that the privilege is inside the CRM and after to compare its Id with the values returned by a RetrieveUserPrivilegesRequest:
bool userHasPrivilege = false;

ConditionExpression privilegeCondition =
    new ConditionExpression("name", ConditionOperator.Equal, "prvCreatenew_SMS"); // name of the privilege
FilterExpression privilegeFilter = new FilterExpression(LogicalOperator.And);
privilegeFilter.Conditions.Add(privilegeCondition);

QueryExpression privilegeQuery = new QueryExpression
{
    EntityName = "privilege",
    ColumnSet = new ColumnSet(true),
    Criteria = privilegeFilter
};

EntityCollection retrievedPrivileges = service.RetrieveMultiple(privilegeQuery);
if (retrievedPrivileges.Entities.Count == 1)
{
    RetrieveUserPrivilegesRequest request = new RetrieveUserPrivilegesRequest();
    request.UserId = userId; // Id of the User
    RetrieveUserPrivilegesResponse response = (RetrieveUserPrivilegesResponse)service.Execute(request);
    foreach (RolePrivilege rolePrivilege in response.RolePrivileges)
    {
        if (rolePrivilege.PrivilegeId == retrievedPrivileges.Entities[0].Id)
        {
            userHasPrivilege = true;
            break;
        }
    }
}
The hard way it to build a single MEGA query to perform the same check (4 LinkEntity!):
bool userHasPrivilege = false;

QueryExpression privilegeQuery = new QueryExpression("privilege");
privilegeQuery.ColumnSet = new ColumnSet(true);
LinkEntity privilegeLink1 = new LinkEntity("privilege", "roleprivileges", "privilegeid", "privilegeid", JoinOperator.Inner);
LinkEntity privilegeLink2 = new LinkEntity("roleprivileges", "role", "roleid", "roleid", JoinOperator.Inner);
LinkEntity privilegeLink3 = new LinkEntity("role", "systemuserroles", "roleid", "roleid", JoinOperator.Inner);
LinkEntity privilegeLink4 = new LinkEntity("systemuserroles", "systemuser", "systemuserid", "systemuserid", JoinOperator.Inner);

ConditionExpression userCondition = new ConditionExpression("systemuserid", ConditionOperator.Equal, userId); // // Id of the User
ConditionExpression privilegeCondition = new ConditionExpression("name", ConditionOperator.Equal, "prvCreatenew_SMS"); // name of the privilege

privilegeLink4.LinkCriteria.AddCondition(userCondition);
FilterExpression privilegeFilter = new FilterExpression(LogicalOperator.And);
privilegeFilter.Conditions.Add(privilegeCondition);
privilegeQuery.Criteria = privilegeFilter;

privilegeLink3.LinkEntities.Add(privilegeLink4);
privilegeLink2.LinkEntities.Add(privilegeLink3);
privilegeLink1.LinkEntities.Add(privilegeLink2);
privilegeQuery.LinkEntities.Add(privilegeLink1);

EntityCollection retrievedPrivileges = service.RetrieveMultiple(privilegeQuery);
if (retrievedPrivileges.Entities.Count > 0) { userHasPrivilege = true; }

September 28, 2014

Entity.GetAttributeValue<T> and ActivityParty

GetAttributeValue<T> is a method of the Entity class, it is used to retrieve easily the entity's values. You can find a detailed overview by Dave Berry here:
Entity.GetAttributeValue Explained

Dynamics CRM includes a special data type called ActivityParty (an overview here) and despite its special status we can still use GetAttributeValue<T> method against the fields using this data type.

First of all the ActivityParty attributes hold an EntityCollection of activityparty entities, these entities are a pointer to the real records through the partyid attribute (EntityReference).
Entity email = service.Retrieve("email", emailId, new ColumnSet(true));
EntityCollection to = email.GetAttributeValue<EntityCollection>("to");
A big difference when using the GetAttributeValue<T> with the EntityCollection than the EntityReference is the case when the attribute is null.

With EntityReference the method GetAttributeValue<T> returns a null if the attribute is empty.
EntityReference parentAccountRef = account.GetAttributeValue<EntityReference>("parentaccountid");
if (parentAccountRef != null) { /* code here */ }
With EntityCollection we can have null if the attribute is not inside the collection or an EntityCollection with no values inside the Entities property if the attribute is empty but inside the attributes list.
The second scenario is very common, for example when retrieving an email, the attributes from,to,cc,bcc are always returned.
Entity email = service.Retrieve("email", emailId, new ColumnSet(true));
EntityCollection to = email.GetAttributeValue<EntityCollection>("to");
EntityCollection cc = email.GetAttributeValue<EntityCollection>("cc");
if (to != null) { Console.WriteLine("Records inside To: " + to.Entities.Count.ToString(); }
if (cc != null) { Console.WriteLine("Records inside Cc: " + cc.Entities.Count.ToString(); }
Even if we have an usable EntityCollection, it's necessary additional code to get the reference to the real records contained inside the ActivityParty attributes.
To simplify the access to the values I created an extension method (called GetEntityReferenceCollectionValue) to return an EntityReferenceCollection from an ActivityParty field:
public static EntityReferenceCollection GetEntityReferenceCollectionValue(
    this Entity entity, string attributeLogicalName)
{
    EntityReferenceCollection entityReferenceCollection = new EntityReferenceCollection();
    EntityCollection entityCollection = entity.GetAttributeValue<EntityCollection>(attributeLogicalName);
    if (entityCollection != null)
    {
        foreach (Entity item in entityCollection.Entities)
        {
            EntityReference partyIdReference = item.GetAttributeValue<EntityReference>("partyid");
            if (partyIdReference != null) { entityReferenceCollection.Add(partyIdReference); }
        }
    }
    return entityReferenceCollection;
}
EntityReferenceCollection is a class that holds a list of EntityReference, it's normally used inside the Associate/Disassociate operations, but nothing stop us to reuse it to hold all the EntityReference entries of an EntityCollection.

The use is straightforward:
Entity email = service.Retrieve("email", emailId, new ColumnSet(true));
EntityReferenceCollection to = email.GetEntityReferenceCollectionValue("to");
If it's necessary to retrieve the actual entities I created a method called GetEntitiesFromEntityReferenceCollection, it requires as parameters the IOrganizationService and the EntityReferenceCollection. The value returned is an IEnumerable<Entity>:
public static IEnumerable<Entity> GetEntitiesFromEntityReferenceCollection(
    IOrganizationService service, EntityReferenceCollection entityReferenceCollection)
{
    List<Entity> entities = new List<Entity>();
    foreach (EntityReference entityReference in entityReferenceCollection)
    {
        try
        {
            Entity entity = service.Retrieve(entityReference.LogicalName, entityReference.Id, new ColumnSet(true));
            entities.Add(entity);
        }
        catch { }
    }
    return entities.AsEnumerable<Entity>();
}
Example:
Entity email = service.Retrieve("email", emailId, new ColumnSet(true));
EntityReferenceCollection to = email.GetEntityReferenceCollectionValue("to");
var toEntities = GetEntitiesFromEntityReferenceCollection(service, to);

August 7, 2014

Dynamics CRM special data types

Dynamics CRM includes some data types not available for custom fields. Because these field types act slightly different from the standard ones, it's necessary to pay attention during development.

This post is focused on the following data types:
  • Owner
  • Customer
  • Party List (ActivityParty)
Owner
Owner is a data type used for the built-in OnwerId field. This field is present only when an entity has the Ownership set to User or Team, it's not present if the entity's ownership is Organization. It can hold one user or one team record.

Customer
Customer data type is used when a field can hold one account or one contact record. Contact and Lead entities use this data type (ParentCustomerId and CustomerId respectively).

Party List
Party List (also known as Activity Party) is the most complex data type, it can hold more than one record at the same time and the possible entities are defined by the ParticipationTypeMask attribute.
For example the Email To field can hold all these entities:


Client-side (JavaScript)
Handling these data types in JavaScript is quite easy, they look like and act as standard lookup fields, we need to only pay attention to the entity LogicalName (property entityType) of the record(s).

Owner
// set the owner of the current record to a team
// note: this code works only when the form is on create method, to update the owner it's always necessary to use the AssignRequest message

var team = new Array();
team[0] = new Object();
team[0].id = "c9c77fd3-036a-47e0-857d-a4d56f21d8ee";
team[0].name = "Sales Team";
team[0].entityType = "team";
Xrm.Page.getAttribute("ownerid").setValue(team);

// check if the owner is a team or a user
var owner = Xrm.Page.getAttribute("ownerid").getValue();
if (owner != null) {
    alert("The owner type of this record is " + owner[0].entityType);
}
Customer
// set the parent customer of a contact

var parent = new Array();
parent[0] = new Object();
parent[0].id = "cd7f603d-e199-4b66-9ca7-3386093e9a50";
parent[0].name = "Test Account";
parent[0].entityType = "account";
Xrm.Page.getAttribute("parentcustomerid").setValue(parent);

// check if the customer of a lead is an account or a contact
var customer = Xrm.Page.getAttribute("customerid").getValue();
if (customer != null) {
    alert("The customer type of this lead is " + customer[0].entityType);
}
Party List
// set the to field of an email to a contact and a user

var recipients = new Array();
recipients[0] = new Object();
recipients[0].id = "72bf360d-3dd8-4538-a5ce-91a4be6af517";
recipients[0].name = "John Smith";
recipients[0].entityType = "contact";

recipients[1] = new Object();
recipients[1].id = "d4e1f6f5-1a2d-4571-bec7-9bd1ea1696d0";
recipients[1].name = "Tom Green";
recipients[1].entityType = "user";

Xrm.Page.getAttribute("to").setValue(recipients);

// get the total count of records inside the To field
var to = Xrm.Page.getAttribute("to").getValue();
if (to != null) {
    alert("The total count of records inside To field is " + to.length);
    alert("The last record type is " + to[to.length-1].entityType);
}
Server-side (C#)
Server-side we have some differences, although Owner and Customer data types are EntityReference, Party List type is an ActivityParty.

Owner
// set the owner of the current record to a team
// note: this code works only when the form is on create method, to update the owner it's always necessary to use the AssignRequest message

// late bound
EntityReference team = new EntityReference("team", new Guid("c9c77fd3-036a-47e0-857d-a4d56f21d8ee"));
entity["ownerid"] = team;

// early bound
EntityReference team = new EntityReference(Team.EntityLogicalName, new Guid("c9c77fd3-036a-47e0-857d-a4d56f21d8ee"));
MyEntity.OwnerId = team; 

// check if the owner is a team or a user

// late bound
if (entity.Contains("ownerid") && entity["ownerid"] != null) {
    EntityReference owner = (EntityReference)entity["owner"];
    Console.WriteLine("The owner type of this record is " + owner.LogicalName);
}
// early bound
if (MyEntity.OwnerId != null) {
    Console.WriteLine("The owner type of this record is " + MyEntity.OwnerId.LogicalName);
}
Customer
// set the parent customer of a contact

// late bound
EntityReference parent = new EntityReference("account", new Guid("cd7f603d-e199-4b66-9ca7-3386093e9a50"));
entity["parentcustomerid"] = parent;

// early bound
EntityReference parent = new EntityReference(Account.EntityLogicalName, new Guid("cd7f603d-e199-4b66-9ca7-3386093e9a50"));
MyEntity.ParentCustomerId = parent; 

// check if the customer of a lead is an account or a contact

// late bound
if (entity.Contains("customerid") && entity["customerid"] != null) {
    EntityReference customer = (EntityReference)entity["customerid"];
    Console.WriteLine("The customer type of this lead is " + customer.LogicalName);
}

// early bound
if (MyEntity.ParentCustomerId != null) {
    Console.WriteLine("The customer type of this lead is " + MyEntity.ParentCustomerId.LogicalName);
}
Party List
// set the to field of an email to a contact and a user

// late bound
Entity to1 = new Entity("activityparty");
to1["partyid"] = new EntityReference("contact", new Guid("72bf360d-3dd8-4538-a5ce-91a4be6af517"));
Entity to2 = new Entity("activityparty");
to2["partyid"] = new EntityReference("user", new Guid("d4e1f6f5-1a2d-4571-bec7-9bd1ea1696d0"));
entity["to"] = new Entity[] { to1, to2 };

// early bound
ActivityParty To1 = new ActivityParty();
To1.PartyId = new EntityReference(Contact.EntityLogicalName, new Guid("72bf360d-3dd8-4538-a5ce-91a4be6af517"));
ActivityParty To2 = new ActivityParty();
To2.PartyId = new EntityReference(SystemUser.EntityLogicalName, new Guid("72bf360d-3dd8-4538-a5ce-91a4be6af517"));
MyEntity.To = new ActivityParty[] { To1 , To2 }

// get the total count of records inside the To field

// late bound

if (entity.Contains("to") && entity["to"] != null) {
    EntityCollection to = (EntityCollection)entity["to"];
    Console.WriteLine("The total count of records inside To field is " + to.Entities.Count.ToString());
    if (to.Entities.Count > 0) {
        EntityReference lastparty = (EntityReference)to.Entities.Last()["partyid"];
        Console.WriteLine("The last record type is " + lastparty.LogicalName);
    }
}

//early bound
if (MyEntity.To != null) {
    Console.WriteLine("The total count of records inside To field is " + MyEntity.To.Count().ToString());
    if (MyEntity.To.Count() > 0) {
        Console.WriteLine("The last record type is " + MyEntity.To.Last().PartyId.LogicalName);
    }
}

June 8, 2014

Retrieve the members of a specific Team

The code to retrieve the members of a specific Team is quite easy, it's a standard QueryExpression, but contains a linked entity with a condition inside, it's good as start point for similar queries.
// Id of the specific Team
Guid teamId = new Guid("DAC1F09E-02EF-E311-A2AB-D89D67630DBC");
// main query returing users
QueryExpression userQuery = new QueryExpression("systemuser");
// take all columns
userQuery.ColumnSet = new ColumnSet(true);
// this is the intersect condition
LinkEntity teamLink = new LinkEntity("systemuser", "teammembership", "systemuserid", "systemuserid", JoinOperator.Inner);
// this is the condition to use the specific Team
ConditionExpression teamCondition = new ConditionExpression("teamid", ConditionOperator.Equal, teamId);
// add the condition to the intersect
teamLink.LinkCriteria.AddCondition(teamCondition);
// add the intersect to the query
userQuery.LinkEntities.Add(teamLink); 
//get the results
EntityCollection retrievedUsers = service.RetrieveMultiple(userQuery);
// fetch the results
foreach (Entity user in retrievedUsers.Entities)
{
    // Id of the user
    var userId = user.Id;
    // FullName of the user 
    var userFullName = user.Contains("fullname") ? user["fullname"].ToString() : "";
}