Microsoft Dynamics CRM allows developers to take advantage of web services at no additional cost. These web services allow for programmatic implementation of the system (usually via custom logic or of a custom interface), while controlling back-end business processes, security, and auditing.
Note
While Microsoft doesn’t charge for use of the web services included with Microsoft Dynamics CRM, there may be licensing charges depending on how your users interact with the data. Be sure to check the latest licensing guide for the current status.
Microsoft Dynamics CRM 2016 uses the common service-oriented architecture (SOA) concept for extensibility, which involves implementing a series of web services to provide extensibility support. By using these web services, you can integrate other applications and systems with Microsoft CRM and extend its capabilities.
Web services are based on Simple Object Access Protocol (SOAP) messages. This protocol uses Extensible Markup Language (XML), which provides usability through Hypertext Transfer Protocol (HTTP) communication transports. In simple terms, this gives applications and systems an easy way to communicate using standard protocols to interoperate and share functionalities.
Web services are language and platform independent, so they can be consumed by an application written in Java, Visual Basic, or C#. They also can be used on a Microsoft Windows–based operating system (OS) and on UNIX, Linux, and Macintosh platforms.
Note
The examples included in this chapter are written using C# with Visual Studio 2015. However, as shown in the “Examples of Web Services” section, later in this chapter, you can consume web services using JavaScript if desired.
CRM 2016 does not support web services endpoints from Microsoft Dynamics CRM (version 4.0 and lower); however, it does support CRM 2011, 2013, and 2015 endpoints.
The following sections describe the components and standards available in Microsoft Dynamics CRM 2016.
Windows Communication Foundation (WCF) is the next generation of web services. WCF is independent of the protocol to be used, so it is not tied to HTTP as the old web services were. WCF can be implemented and can use other protocols, such as TCP, HTTP, or peer-to-peer protocols that are configured on a configuration.
Representational state transfer (REST) is a modern and easy way to consume a web service without using the SOAP or Web Services Description Language (WSDL) protocols. By just using the query string for retrieve operations using the GET
method of the HTTP protocol and the POST
method for adding, the DELETE
method for deleting, and the PUT
method for updating operations, you can quickly get the data you want in XML format without having to create any special proxy client, as you would need to do with SOAP.
JavaScript Object Notation (JSON) is a simplified way to send and receive messages that improves the number of bytes being sent or received by a service comparing it with XML.
For example, an XML message like this:
<customers>
<customer>John</customer>
<customer>Lucas</customer>
</customers>
is formed like this in JSON:
"Customers" : { "customer" : "John" , "Lucas" }
As you can see, the JSON involves fewer characters than the corresponding XML message, but the result is the same.
Open Data Services (OData) is a standard way of communicating via the REST protocol that allows filtering, sorting, choosing the columns returned, and more.
Note
Microsoft CRM 2016 supports OData 2.0 for now, but it is being deprecated. Therefore, it is recommended that you start changing any OData code to use the new Web API explained later in this chapter.
To easily see the web services definitions and get the URL addresses of the WSDL documents from within Microsoft CRM 2016, follow these steps:
1. Go to Settings > Customizations (see Figure 23.1).
2. Select Developer Resources option, and the page shown in Figure 23.2 appears.
As you can see in Figure 23.2, you can download the WSDL definitions for two web services: The Discovery and Organization services.
To understand how to use these services, you need to create a new console application in C# to follow the examples in this chapter. To do so, open Visual Studio 2015 and choose File > New > Project. From the project templates, go to Visual C#/Windows and select Console Application from the installed templates (see Figure 23.3).
The next section explains the web services definition files.
Microsoft CRM 2016 has multi-tenancy capability, which means it supports more than one organization on the same server. You therefore need to query a general web service called XRMDiscoveryService, which will give you the right web service for the organization you want to work with. You can find this by navigating to http://servername:portnumber/XRMServices/2011/Discovery.svc?wsdl (for example, http://crm2016/XRMServices/2011/Discovery.svc?wsdl for CRM On-Premises).
Note
The port number can be omitted for the default port 80 of HTTP in this example.
To add a web reference for this service, go to Visual Studio 2015 and use the new console application project you created. Right-click the project name in the Solution Explorer and choose Add Service Reference from the menu, enter the URL of the Discovery Service, click Go, and then enter XrmSdk.Discovery in the Namespace field (see Figure 23.4). Click OK to add the WCF service reference to your project.
By querying this service, you can retrieve all the organizations that a specific user belongs to and get the right service location. Listing 23.1 shows how this is done for CRM On-Premises (non-IFD).
ConsoleApplication1
{
static void Main(string[] args)
{
Console.WriteLine(GetCrmServiceForOrganization("Webfortis"));
Console.ReadKey();
}
private static string GetCrmServiceForOrganization(string organizationName)
{
using (XrmSdk.Discovery.DiscoveryServiceClient myCrm =
new XrmSdk.Discovery.DiscoveryServiceClient())
{
myCrm.ClientCredentials.Windows.ClientCredential =
System.Net.CredentialCache.DefaultNetworkCredentials;
XrmSdk.Discovery.RetrieveOrganizationsRequest myRequest =
new XrmSdk.Discovery.RetrieveOrganizationsRequest();
XrmSdk.Discovery.RetrieveOrganizationsResponse myResponse =
(XrmSdk.Discovery.RetrieveOrganizationsResponse)myCrm.
Execute(myRequest);
foreach (XrmSdk.Discovery.OrganizationDetail detail in myResponse.
Details)
{
Console.WriteLine("Organization = " + detail.UniqueName);
if (detail.UniqueName == organizationName)
{
return detail.Endpoints[1].Value;
}
}
}
return "";
}
}
Note
In all the examples in this chapter, replace Webfortis
with your organization name.
This section shows how to get the correct Organization service endpoint address to use for the organization you want to work with. For a custom application you need the Organization service, which is structured like this:
http://servername:portnumber/XRMServices/2011/Organization.svc?wsdl
Here is an example:
http://crm2016/XRMServices/2011/Organization.svc?wsdl
To add a service reference for the Organization service, go to Visual Studio 2015. Then, using the new console application project you created, right-click the project name in the Solution Explorer and choose Add Service Reference from the menu. Enter the URL of the Organization service, click Go, and then enter XrmSdk in the Namespace field (see Figure 23.5). Click OK to add the service reference to your project.
The Organization service has the following methods:
Create
Retrieve
RetrieveMultiple
Delete
Disassociate
Execute
Update
Create
MethodThe Create
method is used to create new instances of an existing entity, such as a new Account or Contact entity.
This method has only one implementation: It returns a globally unique identifier (GUID), which is the unique identifier of the new entity to be created; it accepts one parameter of type Entity
.
Listing 23.2 shows how to create a new account programmatically in Visual Studio 2015 with C#:
private static string CreateAccount(string organizationName, string accountName)
{
using (XrmSdk.OrganizationServiceClient myCrm =
new XrmSdk.OrganizationServiceClient())
{
try
{
myCrm.Endpoint.Address =
new System.ServiceModel.EndpointAddress(GetCrmServiceForOrganization
(organizationName));
myCrm.ClientCredentials.Windows.ClientCredential =
System.Net.CredentialCache.DefaultNetworkCredentials;
XrmSdk.Entity newAccount = new XrmSdk.Entity();
newAccount.LogicalName = "account";
XrmSdk.AttributeCollection myAttributes = new XrmSdk.
AttributeCollection();
myAttributes.Add(new KeyValuePair<string, object>("name", accountName));
newAccount.Attributes = myAttributes;
Guid newAccountId = myCrm.Create(newAccount);
return newAccountId.ToString();
}
catch (Exception ex)
{
Console.WriteLine("General exception: " + ex.ToString());
return "General exception: " + ex.ToString();
}
}
}
Create
MethodTo test the Create
method, you need to pass the organization name and the name of the new account you want to create to the method parameters as follows:
Console.WriteLine("New Account GUID = " + CreateAccount("Webfortis", "New Account"));
You can put the line above inside the Main
function of your program to test it, so the Main
function should look like this:
static void Main(string[] args)
{
Console.WriteLine("New Account GUID = " + CreateAccount("Webfortis", "New
Account"));
Console.ReadKey(); //added for debugging purposes only
}
You can run this code by either pressing F5 or by going to the Debug menu and choosing the Start Debugging option. In either case, similar output appears, as shown in Figure 23.6.
Now if you go to the CRM web client application, you can see the new account (see Figure 23.7).
Be sure to assign a value for each required field for the entity. Even if you are creating those fields programmatically, you need to enter values for them in CRM as they won’t raise any exception if you avoid them, and you might end up creating records without required data.
Retrieve
MethodThe Retrieve
method gets an instance of an entity object. To get more than one instance of an entity, use the RetrieveMultiple
method (explained in the next section).
The Retrieve
method returns a class type of Entity
. The input parameters are the string of the entity name, the GUID of the instance of the entity, and a set of columns or fields you want to retrieve.
Tip
It is important to define the columns you want to retrieve in the last parameter; otherwise, you get null values even though the instance in the CRM system has values.
Listing 23.3 provides an example of the Retrieve
method.
private static XrmSdk.Entity RetrieveAccount(string organizationName, Guid
accountId)
{
using (XrmSdk.OrganizationServiceClient myCrm = new XrmSdk.
OrganizationServiceClient())
{
try
{
myCrm.Endpoint.Address =
new System.ServiceModel.EndpointAddress(GetCrmServiceForOrganization
(organizationName));
myCrm.ClientCredentials.Windows.ClientCredential =
System.Net.CredentialCache.DefaultNetworkCredentials;
XrmSdk.ColumnSet columns = new XrmSdk.ColumnSet();
// add more attributes if you want separated by comma below
columns.Columns = new string[] { "name", "accountid" };
Guid myAccountId = accountId;
XrmSdk.Entity myAccount = myCrm.Retrieve("account", myAccountId,
columns);
return myAccount;
}
catch (Exception ex)
{
Console.WriteLine("General exception: " + ex.ToString());
return null;
}
}
}
Retrieve
MethodAs you can see from the example in Listing 23.3, you need to know the GUID of the account record (or the entity you want to retrieve) in order to use this method. You can use the CreateAccount
method you created in the previous section to test this as follows:
static void Main(string[] args)
{
string newAccountId = CreateAccount("Webfortis", "Test Account");
Console.WriteLine("New Account GUID = " + newAccountId);
Console.WriteLine("Checking new account created = " +
RetrieveAccount("Webfortis", new Guid(newAccountId)
).Attributes[0].Value);
Console.ReadKey(); //added for debugging purposes only
}
After running this test, you see output similar to that shown in Figure 23.8.
You must know the GUID in order to use this code, so this method might not be practical if you know only the name and not the GUID. In such a case, you have to use the RetriveMultiple
method, as explained in the next section.
RetrieveMultiple
MethodThe RetrieveMultiple
method gets one or more instances of an entity. For example, you can use this method as shown in Listing 23.4 to retrieve all the accounts for an organization.
private static void GetAllAccounts(string organizationName)
{
using (XrmSdk.OrganizationServiceClient myCrm = new XrmSdk.
OrganizationServiceClient())
{
try
{
myCrm.Endpoint.Address =
new System.ServiceModel.EndpointAddress
(GetCrmServiceForOrganization(organizationName));
myCrm.ClientCredentials.Windows.ClientCredential =
System.Net.CredentialCache.DefaultNetworkCredentials;
// Creates a column set holding the names of the
// columns to be retrieved
XrmSdk.ColumnSet colsPrincipal = new XrmSdk.ColumnSet();
// Sets the Column Set's Properties
colsPrincipal.Columns = new string[] { "accountid", "name" };
// Create the Query Expression
XrmSdk.QueryExpression queryPrincipal =
new XrmSdk.QueryExpression();
// Set the QueryExpression's Properties
queryPrincipal.EntityName = "account";
queryPrincipal.ColumnSet = colsPrincipal;
/// Retrieve the accounts.
XrmSdk.EntityCollection myAccounts =
myCrm.RetrieveMultiple(queryPrincipal);
Console.WriteLine("
GetAllAccounts found {0} accounts
",
myAccounts.Entities.Length);
foreach (XrmSdk.Entity myEntity in myAccounts.Entities)
{
Console.WriteLine(myEntity.Attributes[1].Value);
}
}
catch (Exception ex)
{
Console.WriteLine("General exception: " + ex.ToString());
}
}
}
RetrieveMultiple
MethodTo test the method in Listing 23.4, you need to pass the organization name to the method parameter. The Main
function should be as follows:
static void Main(string[] args)
{
GetAllAccounts("Webfortis");
Console.ReadKey(); //added for debugging purposes only
}
You can run this code either by pressing F5 or by going to the Debug menu and choosing the Start Debugging option. In either case, similar output appears, as shown in Figure 23.9.
You can apply filters on the data you want, as well as retrieve only the fields or properties you want to get. For example, to retrieve all the accounts whose names match or start with the first few letters of each other, use the code in Listing 23.5.
private static List<XrmSdk.Entity> GetAllAccountsByName(string organizationName,
string accountName, XrmSdk.ConditionOperator conditionalOperator)
{
List<XrmSdk.Entity> accounts = null;
using (XrmSdk.OrganizationServiceClient myCrm =
new XrmSdk.OrganizationServiceClient())
{
try
{
myCrm.Endpoint.Address =
new System.ServiceModel.EndpointAddress
(GetCrmServiceForOrganization(organizationName));
myCrm.ClientCredentials.Windows.ClientCredential =
System.Net.CredentialCache.DefaultNetworkCredentials;
// Creates a column set holding the names of the
// columns to be retrieved
XrmSdk.ColumnSet colsPrincipal = new XrmSdk.ColumnSet();
// Sets the Column Set's Properties
colsPrincipal.Columns = new string[] { "accountid", "name" };
// Create a ConditionExpression
XrmSdk.ConditionExpression conditionPrincipal =
new XrmSdk.ConditionExpression();
// Sets the ConditionExpressions Properties so that the condition
// is true when the ownerid of the account Equals the principalId
conditionPrincipal.AttributeName = "name";
conditionPrincipal.Operator = conditionalOperator;
conditionPrincipal.Values = new object[1];
conditionPrincipal.Values[0] = accountName;
// Create the FilterExpression
XrmSdk.FilterExpression filterPrincipal =
new XrmSdk.FilterExpression();
// Set the FilterExpression's Properties
filterPrincipal.FilterOperator = XrmSdk.LogicalOperator.And;
filterPrincipal.Conditions =
new XrmSdk.ConditionExpression[] { conditionPrincipal };
// Create the Query Expression
XrmSdk.QueryExpression queryPrincipal =
new XrmSdk.QueryExpression();
// Set the QueryExpression's Properties
queryPrincipal.EntityName = "account";
queryPrincipal.ColumnSet = colsPrincipal;
queryPrincipal.Criteria = filterPrincipal;
/// Retrieve the accounts.
XrmSdk.EntityCollection myAccounts =
myCrm.RetrieveMultiple(queryPrincipal);
accounts = new List<ConsoleApplication1.XrmSdk.Entity>();
foreach (XrmSdk.Entity myEntity in myAccounts.Entities)
{
accounts.Add(myEntity);
Console.WriteLine(myEntity.Attributes[1].Value);
}
return accounts;
}
catch (Exception ex)
{
Console.WriteLine("General exception: " + ex.ToString());
return null;
}
}
}
To test the method in Listing 23.5, you need to pass the organization name to the method parameter. For this, the Main
function should be as shown in Listing 23.6.
static void Main(string[] args)
{
List<XrmSdk.Entity> accounts;
Console.WriteLine("Accounts that start with the letter A");
accounts = GetAllAccountsByName("Webfortis", "A%", ConsoleApplication1.
XrmSdk.ConditionOperator.Like);
if (accounts == null)
{
Console.WriteLine("No accounts found");
}
Console.WriteLine("Accounts equal to 'Test Account'");
accounts = GetAllAccountsByName("Webfortis", "Test Account",
ConsoleApplication1.XrmSdk.ConditionOperator.Equal);
if (accounts == null)
{
Console.WriteLine("No accounts found");
}
Console.ReadKey(); //added for debugging purposes only
}
You can run this code by either pressing F5 or by going to the Debug menu and choosing the Start Debugging option. In either case, similar output appears, as shown in Figure 23.10.
Delete
MethodThe Delete
method deletes an existing instance or record of an entity. This method doesn’t return any value and accepts two input parameters. The first parameter is a string containing the entity type, and the second parameter is the GUID of the instance of the entity you will delete.
The example in Listing 23.7 shows how to delete a new account programmatically in Visual Studio 2015 with C#.
private static string DeleteAccount(string organizationName,
Guid accountToDelete)
{
using (XrmSdk.OrganizationServiceClient myCrm =
new XrmSdk.OrganizationServiceClient())
{
try
{
myCrm.Endpoint.Address =
new System.ServiceModel.EndpointAddress
(GetCrmServiceForOrganization(organizationName));
myCrm.ClientCredentials.Windows.ClientCredential =
System.Net.CredentialCache.DefaultNetworkCredentials;
myCrm.Delete("account", accountToDelete);
return "Account successfully deleted";
}
catch (Exception ex)
{
Console.WriteLine("General exception: " + ex.ToString());
return "General exception: " + ex.ToString();
}
}
}
Delete
MethodTo test the Delete
method, you need to pass the organization name to the method parameter as well as the GUID of the account to be deleted. Because you might not know the GUID of the account but you might know the account name, you can use the GetAllAccountsByName
method you created previously.
Note
Don’t forget to replace Webfortis
with your organization name in all the examples in this chapter.
The Main
function to delete all the accounts that match with the name Test Account
should be as shown in Listing 23.8.
static void Main(string[] args)
{
List<XrmSdk.Entity> accounts;
Console.WriteLine("Accounts equal to 'Test Account'");
accounts = GetAllAccountsByName("Webfortis", "Test Account",
ConsoleApplication1.XrmSdk.ConditionOperator.Equal);
if (accounts == null)
{
Console.WriteLine("No accounts found");
}
else
{
foreach (XrmSdk.Entity myAccount in accounts)
{
Console.WriteLine(
DeleteAccount("Webfortis", new Guid(myAccount.Attributes[0].
Value.ToString())));
}
}
Console.ReadKey(); //added for debugging purposes only
}
You can run this code by either pressing F5 or by going to the Debug menu and choosing the Start Debugging option. In either case, similar output appears, as shown in Figure 23.11.
Execute
MethodThe Execute
method executes business logic. It returns a Response
object and accepts a parameter as the input of the Request
type. You can use this method as a wildcard for all the other methods. This means you can create an account by using this method because the class called CreateRequest
derives from Request
and can be used as the input parameter; you receive a CreateResponse
as the result. The same happens for UpdateRequest
, UpdateResponse
, RetrieveRequest
, and RetrieveResponse
.
However, this method is usually used for things you cannot do with the other methods, such as closing a CRM case. Although the Case entity can be updated, you cannot update its status by using the Update
method. To close a case, you need to use the method shown in Listing 23.9.
If you don’t remember how to create a case, refer to CHAPTER 10, “Working with Service.”
private static bool CloseCase(string organizationName, Guid caseId)
{
using (XrmSdk.OrganizationServiceClient myCrm =
new XrmSdk.OrganizationServiceClient())
{
try
{
myCrm.Endpoint.Address =
new System.ServiceModel.EndpointAddress
(GetCrmServiceForOrganization(organizationName));
myCrm.ClientCredentials.Windows.ClientCredential =
System.Net.CredentialCache.DefaultNetworkCredentials;
XrmSdk.Entity myIncidentResolution = new XrmSdk.Entity();
myIncidentResolution.LogicalName = "incidentresolution";
XrmSdk.EntityReference incidentId = new XrmSdk.EntityReference();
incidentId.LogicalName = "incident";
incidentId.Id = caseId;
myIncidentResolution.Attributes = new XrmSdk.AttributeCollection();
myIncidentResolution.Attributes.Add(
new KeyValuePair<string, object>("incidentid", incidentId));
XrmSdk.OrganizationRequest closeIncident =
new XrmSdk.OrganizationRequest();
closeIncident.RequestName = "CloseIncident";
closeIncident.Parameters = new XrmSdk.ParameterCollection();
XrmSdk.OptionSetValue statusValue = new XrmSdk.OptionSetValue();
statusValue.Value = -1;
closeIncident.Parameters.Add(new KeyValuePair<string,
object>("Status", statusValue));
closeIncident.Parameters.Add(new KeyValuePair<string,
object>("IncidentResolution", myIncidentResolution));
myCrm.Execute(closeIncident);
Console.WriteLine("Case successfully closed ");
return true;
}
catch (Exception ex)
{
Console.WriteLine("General exception: " + ex.ToString());
return false;
}
}
}
Execute
MethodWhen you try to test the code in Listing 23.9, you get a serialization error, and the case isn’t closed. To avoid any serialization errors, you need to update the reference.cs file associated with the CrmSdk
service with the directives on the OrganizationRequest
method, as shown in Listing 23.10.
[System.Runtime.Serialization.KnownTypeAttribute(typeof(OptionSetValue))]
[System.Runtime.Serialization.KnownTypeAttribute(typeof(Entity))]
[System.Runtime.Serialization.KnownTypeAttribute(typeof(EntityReference))]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute(
"System.Runtime.Serialization", "4.0.0.0")]
[System.Runtime.Serialization.DataContractAttribute(
Name="OrganizationRequest",
Namespace="http://schemas.microsoft.com/xrm/2011/Contracts")]
[System.SerializableAttribute()]
public partial class OrganizationRequest : object,
System.Runtime.Serialization.IExtensibleDataObject,
System.ComponentModel.INotifyPropertyChanged {
The reference.cs file can be found in the Service References folder of the project.
The following code closes the case:
static void Main(string[] args)
{
bool caseresult = CloseCase("Webfortis", new
Guid("752EC4FE-B5C9-E511-80D0-00155DFE0C17"));
Console.WriteLine("Case result = " + caseresult);
Console.ReadKey(); //added for debugging purposes only
}
Note
Be sure to replace Guid
with your case ID.
The Fetch
method executes a query defined by the FetchXML language. In that it uses the RetrieveMultiple
method, and the FetchXML is passed as a parameter using the FetchExpression
class.
Here are a few points to remember about the Fetch
method:
With Fetch
, you can get results in pages by limiting the number of records returned by page.
With Fetch
, you can just query the record count or use any aggregation method, such as Avg
, Count
, Max
, Min
, or Sum
.
Fetch
does not support a union join. To view all the tasks for an account by checking all the contacts of a contact, you need to perform separate Fetch
methods—one on the contact tasks and one on the account contacts—and then merge the results.
The resulting set of records depends on the user privilege.
You can use left outer joins.
There is a 5,000-row limitation on the results of the query per page.
Listing 23.11 shows how to use the Fetch
method to retrieve all contacts with all their properties.
This is the FetchXML code to use for grabbing the fullname of the contact:
<fetch mapping='logical'>
<entity name='contact'>
<attribute name='fullname'/>
</entity>
</fetch>
private static void GetAllContacts(string organizationName)
{
using (XrmSdk.OrganizationServiceClient myCrm =
new XrmSdk.OrganizationServiceClient())
{
try
{
myCrm.Endpoint.Address =
new System.ServiceModel.EndpointAddress(
GetCrmServiceForOrganization(organizationName));
myCrm.ClientCredentials.Windows.ClientCredential =
System.Net.CredentialCache.DefaultNetworkCredentials;
// Retrieve all Contacts.
StringBuilder fetchStr = new StringBuilder();
fetchStr.Append(@"<fetch mapping='logical'>");
fetchStr.Append(@"<entity name='contact'> <attribute name='fullname'/>");
fetchStr.Append(@"</entity></fetch>");
XrmSdk.FetchExpression fetchexp = new XrmSdk.FetchExpression();
fetchexp.Query = fetchStr.ToString();
XrmSdk.QueryBase mq = new XrmSdk.QueryBase();
// Fetch the results.
XrmSdk.EntityCollection fetchResult = myCrm.RetrieveMultiple(fetchexp);
Console.WriteLine("
GetAllContacts found {0} contacts
"
, fetchResult.Entities.Count());
foreach (XrmSdk.Entity entity in fetchResult.Entities)
{
Console.WriteLine("Contact fullname = {0}",
entity.Attributes[0].Value);
}
}
catch (Exception ex)
{
Console.WriteLine("General exception: " + ex.ToString());
}
}
}
If you are not familiar with the FetchXML language, you can create a query with the Advanced Find tool in the CRM web user interface and click the button Download Fetch XML to easily get the code you need for a programmatic call.
Update
MethodThe Update
method updates data related to an instance of an entity.
This method has only one implementation, and it doesn’t return a value. In the same way as the Create
method, it accepts one parameter of type Entity
. Because all the entities in CRM inherit from the Entity
base class, you can pass any Entity
class to this input parameter. To use this method, you must set at least the ID property of the entity to be updated (for example, set the accountid
property if you want to update an account).
Listing 23.12 shows how to update an existing account programmatically in Visual Studio 2015 with C#.
private static string UpdateAccountName(string organizationName,
Guid accountId, string newName)
{
using (XrmSdk.OrganizationServiceClient myCrm =
new XrmSdk.OrganizationServiceClient())
{
try
{
myCrm.Endpoint.Address =
new System.ServiceModel.EndpointAddress(
GetCrmServiceForOrganization(organizationName));
myCrm.ClientCredentials.Windows.ClientCredential =
System.Net.CredentialCache.DefaultNetworkCredentials;
XrmSdk.Entity myAccount = new XrmSdk.Entity();
myAccount.LogicalName = "account";
XrmSdk.AttributeCollection myAttributes =
new XrmSdk.AttributeCollection();
myAttributes.Add(new KeyValuePair<string, object>(
"accountid", accountId));
myAttributes.Add(new KeyValuePair<string, object>("name",
newName));
myAccount.Attributes = myAttributes;
myCrm.Update(myAccount);
return "Account successfully updated ";
}
catch (Exception ex)
{
Console.WriteLine("General exception: " + ex.ToString());
return "General exception: " + ex.ToString();
}
}
}
Caution
Note that only the properties you set are updated. This means that in Listing 23.12, only the company name property will be changed; the other properties will keep their values. This happens even though they are not set and they have null values when you send them to the Update
method.
The examples shown so far in this chapter use late binding. When you use late binding, accessing and using the Organization service is difficult, and you need to explicitly type both the entity name and the attribute name. This makes the code cumbersome to write and sometimes prone to errors.
Instead of using late binding, you can use early binding, which provides IntelliSense support and better code development and use of .NET technologies such as the .NET Framework Language-Integrated Query (LINQ). To use early binding, you must generate the entity classes by using the code generation tool called CrmSvcUtil.exe, a console application that you can find in the SDKin folder of the CRM 2016 software development kit (SDK).
Note
The default language of the CrmSvcUtil.exe tool is C#, but you can also use it to produce Visual Basic code by adding the /l:VB
parameter to it.
Here is an example of calling the CrmSvcUtil.exe tool:
CrmSvcUtil.exe /url:http://crm2016/Webfortis/XRMServices/2011/Organization.svc
/out:GeneratedCode.cs /username:bill /password:p@ssword!
Tip
You do not need a username and password if you’re working with an On-Premises deployment that uses Integrated Windows Authentication.
The CrmSvcUtil.exe command shown here takes some time to run because it generates code for everything you need to work with CRM 2016, so be patient.
If you get the GeneratedCode.cs file and include it in your solution, you can work with the Organization service easily. You also need to add references from the following assemblies to your solution (which you can also find in the SDKin folder of the CRM 2016 SDK):
microsoft.xrm.sdk.dll
microsoft.crm.sdk.proxy.dll
Note
You can also use NuGet to add the assemblies, via the following command:
Install-Package Microsoft.CrmSdk.CoreAssemblies
The last assemblies you need to reference are System.Runtime.Serialization and System.ServiceModel, which you can find in the Global Assembly Cache (GAC).
The following sections cover the following regular operations:
Create
Retrieve
RetrieveMultiple
Delete
Update
However, the following sections also use early binding so that you can see the differences between how you use these methods with late binding and with early binding.
Create
MethodTo create any instance of an entity such as a new account or contact, instead of using the Create
method described earlier in this chapter, use the code in Listing 23.13.
using Microsoft.Xrm.Sdk.Client;
using System.ServiceModel.Description;
public static string CreateAccount(string organizationName, string accountName)
{
ClientCredentials credentials = new ClientCredentials();
credentials.Windows.ClientCredential =
System.Net.CredentialCache.DefaultNetworkCredentials;
OrganizationServiceProxy _serviceProxy =
new OrganizationServiceProxy(new Uri("http://<<server name>>" +
organizationName + "/XRMServices/2011/Organization.svc"),
null, credentials, null);
_serviceProxy.ServiceConfiguration.CurrentServiceEndpoint.Behaviors.Add(
new ProxyTypesBehavior());
OrganizationServiceContext orgContext =
new OrganizationServiceContext(_serviceProxy);
Account account = new Account()
{
Name = accountName
};
orgContext.AddObject(account);
orgContext.SaveChanges();
return account.AccountId.ToString();
}
Retrieve
MethodTo retrieve any record of an entity such as an account or a contact, you can use LINQ, using the code in Listing 23.14.
private static Account RetrieveAccount(string organizationName, Guid accountId)
{
ClientCredentials credentials = new ClientCredentials();
credentials.Windows.ClientCredential =
System.Net.CredentialCache.DefaultNetworkCredentials;
OrganizationServiceProxy _serviceProxy =
new OrganizationServiceProxy(new Uri("http://<<server name>>" +
organizationName + "/XRMServices/2011/Organization.svc"),
null, credentials, null);
_serviceProxy.ServiceConfiguration.CurrentServiceEndpoint.Behaviors.Add(
new ProxyTypesBehavior());
OrganizationServiceContext orgContext =
new OrganizationServiceContext(_serviceProxy);
Account account = (from a in orgContext.CreateQuery<Account>()
where a.AccountId == accountId select a).Single();
return account;
}
RetrieveMultiple
MethodYou can also use LINQ to retrieve multiple records, as shown in Listing 23.15. When you do this, you don’t need to learn how to query and filter attributes in CRM 2016; the LINQ knowledge is enough.
private static void GetAllAccounts(string organizationName)
{
ClientCredentials credentials = new ClientCredentials();
credentials.Windows.ClientCredential =
System.Net.CredentialCache.DefaultNetworkCredentials;
OrganizationServiceProxy _serviceProxy =
new OrganizationServiceProxy(new Uri("http://<<server name>>/" +
organizationName + "/XRMServices/2011/Organization.svc"),
null, credentials, null);
_serviceProxy.ServiceConfiguration.CurrentServiceEndpoint.Behaviors.Add(
new ProxyTypesBehavior());
OrganizationServiceContext orgContext =
new OrganizationServiceContext(_serviceProxy);
/// Retrieve the accounts.
var accounts = from a in orgContext.CreateQuery<Account>() select a;
foreach (Account myAccount in accounts)
{
Console.WriteLine(myAccount.Name);
}
}
You can apply any filters you want, as you would do with any other LINQ filter.
Delete
MethodTo delete a record using the organization context, you do it in the following way:
Account account = (from a in orgContext.CreateQuery<Account>()
where a.AccountId == accountToDelete select a).Single();
orgContext.DeleteObject(account);
orgContext.SaveChanges();
Without using the organization context, you do it as follows:
Account account = (from a in orgContext.CreateQuery<Account>()
where a.AccountId == accountToDelete select a).Single();
_serviceProxy.Delete(Account.EntityLogicalName, accountToDelete);
Account account = (from a in orgContext.CreateQuery<Account>()
where a.AccountId == accountToUpdate select a).Single();
account.Name = "demo";
orgContext.UpdateObject(account);
orgContext.SaveChanges();
Without using the organization context, you do it as follows:
Account account = (from a in orgContext.CreateQuery<Account>()
where a.AccountId == accountToUpdate select a).Single();
account.Name = "demo";
_serviceProxy.Update(account);
Using the same Organization service described earlier, you can also access all the CRM metadata.
You can also use the Organization service to create, update, or delete entities, as well as to create or delete relationships within entities. You can also use it to programmatically add, delete, or modify attributes to an existing entity.
Using the metadata can be very useful for independent software vendors (ISVs) to look up entities on a setup installer to see whether a solution was already installed or to check whether any conflict exists with the entities of the CRM system on which a customization would be deployed.
By using the Execute
method, you can access the submethods necessary to interact with the metadata.
Execute
MethodThe Execute
method accepts the following submethods related to the metadata:
CreateAttribute
CreateEntity
CreateManyToMany
CreateOneToMany
CreateOptionSet
DeleteAttribute
DeleteEntity
DeleteOptionSet
DeleteOptionValue
InsertOptionValue
InsertStatusValue
OrderOption
RetrieveAllEntities
RetrieveAllManagedProperties
RetrieveAllOptionSets
RetrieveAttribute
RetrieveEntity
RetrieveMetadataChanges
RetrieveOptionSet
RetrieveRelationship
RetrieveTimestamp
UpdateAttribute
UpdateEntity
UpdateOptionSet
UpdateOptionValue
UpdateRelationship
UpdateStateValue
Each of these submethods is called by using the OrganizationRequest
and OrganizationResponse
methods of the organization service.
For example, you might use the Execute
method to find out whether the custom entity new_MyNewCustomEntity already exists in a CRM implementation.
Listing 23.16 shows how you can add the code for the example, using two parameters: the organization name and the entity name.
private static bool CheckEntity(string organizationName, string entityName)
{
try
{
ClientCredentials credentials = new ClientCredentials();
credentials.Windows.ClientCredential =
System.Net.CredentialCache.DefaultNetworkCredentials;
OrganizationServiceProxy _serviceProxy =
new OrganizationServiceProxy(new Uri("http://crmwoifd/" +
organizationName + "/XRMServices/2011/Organization.svc"),
null, credentials, null);
_serviceProxy.ServiceConfiguration.CurrentServiceEndpoint.Behaviors.Add(
new ProxyTypesBehavior());
Microsoft.Xrm.Sdk.Messages.RetrieveEntityRequest myRequest =
new Microsoft.Xrm.Sdk.Messages.RetrieveEntityRequest();
myRequest.LogicalName = entityName.ToLower();
Microsoft.Xrm.Sdk.Messages.RetrieveEntityResponse myResponse;
myResponse =
(Microsoft.Xrm.Sdk.Messages.RetrieveEntityResponse)
_serviceProxy.Execute(myRequest);
return true;
}
catch (Exception ex)
{
Console.WriteLine("General exception: " + ex.ToString());
return false;
}
}
Now if you want to check whether the account or new_MyNewCustomEntity entity exists on the organization with the name demo
, you can use the following code:
static void Main(string[] args)
{
Console.WriteLine("Account entity exists = " +
CheckEntity("demo", "Account"));
Console.WriteLine("new_ MyNewCustomEntity entity exists = "
+ CheckEntity("demo", "new_ MyNewCustomEntity"));
Console.ReadKey();
}
This code should return true
for the Account entity and false for the new_MyNewCustomEntity entity (assuming that you have not created any custom entity with the name new_MyNewCustomEntity on your CRM system).
Listing 23.17 shows how to programmatically create a custom entity using this web service.
private static bool CreateCustomEntity(string organizationName,
string entityName)
{
try
{
ClientCredentials credentials = new ClientCredentials();
credentials.Windows.ClientCredential =
System.Net.CredentialCache.DefaultNetworkCredentials;
OrganizationServiceProxy _serviceProxy =
new OrganizationServiceProxy(new Uri("http://<<server name>>/" +
organizationName + "/XRMServices/2011/Organization.svc"),
null, credentials, null);
_serviceProxy.ServiceConfiguration.CurrentServiceEndpoint.Behaviors.Add(
new ProxyTypesBehavior());
// Creates new entity
Microsoft.Xrm.Sdk.Metadata.EntityMetadata myNewEntity =
new Microsoft.Xrm.Sdk.Metadata.EntityMetadata();
myNewEntity.Description = CreateLabel(entityName);
myNewEntity.DisplayCollectionName = CreateLabel(entityName);
myNewEntity.DisplayName = CreateLabel(entityName);
myNewEntity.IsAvailableOffline = true;
myNewEntity.SchemaName = entityName;
myNewEntity.LogicalName = entityName;
myNewEntity.OwnershipType =
Microsoft.Xrm.Sdk.Metadata.OwnershipTypes.UserOwned;
// Creates primary attribute
Microsoft.Xrm.Sdk.Metadata.StringAttributeMetadata
myPrimaryAttr =
new Microsoft.Xrm.Sdk.Metadata.StringAttributeMetadata();
myPrimaryAttr.DisplayName = CreateLabel("Name");
myPrimaryAttr.Description = CreateLabel("This is the Name");
myPrimaryAttr.MaxLength = 100;
myPrimaryAttr.SchemaName = "new_Name";
myPrimaryAttr.Format = Microsoft.Xrm.Sdk.Metadata.StringFormat.Text;
myPrimaryAttr.RequiredLevel =
new Microsoft.Xrm.Sdk.Metadata.AttributeRequiredLevelManagedProperty(
Microsoft.Xrm.Sdk.Metadata.AttributeRequiredLevel.ApplicationRequired);
myPrimaryAttr.LogicalName = "new_name";
// Prepare request
Microsoft.Xrm.Sdk.Messages.CreateEntityRequest myRequest =
new Microsoft.Xrm.Sdk.Messages.CreateEntityRequest();
myRequest.Entity = myNewEntity;
myRequest.HasActivities = true;
myRequest.HasNotes = true;
myRequest.PrimaryAttribute = myPrimaryAttr;
Microsoft.Xrm.Sdk.Messages.CreateEntityResponse myResponse;
myResponse = (Microsoft.Xrm.Sdk.Messages.CreateEntityResponse)
_serviceProxy.Execute(myRequest);
return true;
}
catch (Exception ex)
{
Console.WriteLine("General exception: " + ex.ToString());
return false;
}
}
The code in Listing 23.17 uses the custom method CreateLabel
to simplify the code used to set up the labels. The labels must be managed in a collection because of the multilanguage feature. This example uses only the English language for the strings, but you can easily customize it to use other languages, like this:
private static Microsoft.Xrm.Sdk.Label CreateLabel(string myString)
{
Microsoft.Xrm.Sdk.Label myLabel = new Microsoft.Xrm.Sdk.Label();
Microsoft.Xrm.Sdk.LocalizedLabel myLabels =
new Microsoft.Xrm.Sdk.LocalizedLabel(myString,
1033);// English code
myLabel.LocalizedLabels.Add(myLabels);
return myLabel;
}
Now if you want to create a new entity with the name new_MyNewCustomEntity on the organization with the name demo
, you can use the following code:
static void Main(string[] args)
{
CreateCustomEntity("demo", "new_MyNewCustomEntity");
Console.ReadKey();
}
Note
Be sure the entity name does not already exist in the CRM system; if it does, the Execute
method will raise an exception. Alternatively, you can use the CheckEntity
method you created earlier and use the following code:
static void Main(string[] args)
{
if (!CheckEntity("demo", "new_MyNewCustomEntity"))
{
CreateCustomEntity("demo", "new_MyNewCustomEntity");
}
Console.ReadKey();
}
You must also set at least the primary attribute on the request, and it must be required. In addition, you can use this web service if you want to show all the options from an Option Set
attribute on another application.
As another example, suppose you need to retrieve all possible values for the Shipping Method
property for Accounts
. Because Shipping Method
is an Option Set
attribute, you must query the metabase to get the values, as shown in Listing 23.18.
private static void GetShippingMethod(string organizationName)
{
ClientCredentials credentials = new ClientCredentials();
credentials.Windows.ClientCredential =
System.Net.CredentialCache.DefaultNetworkCredentials;
OrganizationServiceProxy _serviceProxy =
new OrganizationServiceProxy(new Uri("http://crmwoifd/" +
organizationName + "/XRMServices/2011/Organization.svc"),
null, credentials, null);
_serviceProxy.ServiceConfiguration.CurrentServiceEndpoint.Behaviors.Add(
new ProxyTypesBehavior());
Microsoft.Xrm.Sdk.Messages.RetrieveAttributeRequest myRequest =
new Microsoft.Xrm.Sdk.Messages.RetrieveAttributeRequest();
myRequest.EntityLogicalName = Account.EntityLogicalName;
myRequest.LogicalName = "address1_shippingmethodcode";
Microsoft.Xrm.Sdk.Messages.RetrieveAttributeResponse myResponse;
myResponse = (Microsoft.Xrm.Sdk.Messages.RetrieveAttributeResponse)
_serviceProxy.Execute(myRequest);
foreach (Microsoft.Xrm.Sdk.Metadata.OptionMetadata myOption in
((Microsoft.Xrm.Sdk.Metadata.PicklistAttributeMetadata)
(myResponse.AttributeMetadata)).OptionSet.Options)
{
Console.WriteLine(myOption.Label.LocalizedLabels[0].Label);
}
}
Now if you want to test this method on the organization with the name demo
, you can use the following code:
static void Main(string[] args)
{
GetShippingMethod("demo");
Console.ReadKey();
}
Because the WCF web services and REST are platform independent, it is not strictly necessary to access the web services from a .NET assembly or a compiled application. You could access the web services using JavaScript, for example.
Note
Check the CRM 2016 SDK for more examples on accessing web services.
This section shows how to get and set the address from an account using JavaScript by querying the CRM OData (REST) service. This automatically sets the contact address when you select an account as the Company Name field (without requiring you to enter it again).
Note
To make this example work, you must select an account with the address fields populated with some data. In addition, keep in mind that OData has been deprecated, and Web API should be used moving forward.
Caution
The following example works only if you select a company name for a contact and not for a parent contact. However, after you review this example, you can modify it to work with parent contacts if desired.
The purpose of this example is to show how you can consume an OData (REST) service without having to build a .NET application or component to make some of your business customizations.
For more details about working with JavaScript customizations, refer to CHAPTER 22, “Customizing Entities.”
To create this example, follow these steps:
1. Go to Settings > Customizations > Customize the System and select the Contact entity in Microsoft CRM 2016 (see Figure 23.12).
2. Expand the Contact entity and click Forms in the left navigation pane (see Figure 23.13).
3. Double-click the Main form type to open it (see Figure 23.14).
4. Click the Account Name field and click Change Properties. The Field Properties dialog appears (see Figure 23.15).
5. Select the Events tab (see Figure 23.16).
6. Expand Form Libraries and click Add (see Figure 23.17).
7. Click New and enter a name, usually with a .js extension (for example, sampleScript.js), enter a display name, and select Script (JScript) from the Type dropdown (see Figure 23.18).
8. Click the Text Editor button and insert the code shown in Listing 23.19 into the text box (see Figure 23.19).
function GetAccountAddress() {
var CustomerID;
CustomerID = Xrm.Page.getAttribute("parentcustomerid").getValue();
// first of all checks if the user has selected a valid parent customer
// the Xrm.Page.getAttribute("parentcustomerid").getValue() parameter will give
us a
// vector with
// the GUID of the selected customer
if (CustomerID != null) {
//Cleans the old address first
Xrm.Page.getAttribute("address1_postalcode").setValue(null);
Xrm.Page.getAttribute("address1_line1").setValue(null);
Xrm.Page.getAttribute("address1_line2").setValue(null);
Xrm.Page.getAttribute("address1_line3").setValue(null);
Xrm.Page.getAttribute("address1_city").setValue(null);
Xrm.Page.getAttribute("address1_stateorprovince").setValue(null);
Xrm.Page.getAttribute("address1_country").setValue(null);
RetrieveAccountRecord(CustomerID[0].id);
}
else {
alert("Select Parent Customer first!");
}
}
function RetrieveAccountRecord(Id) {
var context = Xrm.Page.context;
serverUrl = context.getClientUrl();
ODataPath = serverUrl + "/XRMServices/2011/OrganizationData.svc";
var retrieveAccountReq = new XMLHttpRequest();
retrieveAccountReq.open("GET", ODataPath + "/AccountSet(guid'" + Id + " ')",
true);
retrieveAccountReq.setRequestHeader("Accept", "application/json");
retrieveAccountReq.setRequestHeader("Content-Type","application/json;
charset=utf-8");
retrieveAccountReq.onreadystatechange = function () {
RetrieveAccountReqCallBack(this);
};
retrieveAccountReq.send();
}
function RetrieveAccountReqCallBack(retrieveAccountReq) {
if (retrieveAccountReq.readyState == 4 /* complete */) {
if (retrieveAccountReq.status == 200) {
//Success
var retrievedAccount = JSON.parse(retrieveAccountReq.responseText).d;
Xrm.Page.getAttribute("address1_postalcode").setValue(retrievedAccount.
Address1_PostalCode);
Xrm.Page.getAttribute("address1_line1").setValue(retrievedAccount.
Address1_Line1);
Xrm.Page.getAttribute("address1_line2").setValue(retrievedAccount.
Address1_Line2);
Xrm.Page.getAttribute("address1_line3").setValue(retrievedAccount.
Address1_Line3);
Xrm.Page.getAttribute("address1_city").setValue(retrievedAccount.Address1_City);
Xrm.Page.getAttribute("address1_stateorprovince").setValue(retrievedAccount.
Address1_StateOrProvince);
Xrm.Page.getAttribute("address1_country").setValue(retrievedAccount.
Address1_Country);
}
}
}
9. Click OK to close the dialog.
10. Click Save, then Publish, and finally Close to close the Web Resource dialog.
11. Click Add to close the Look Up Web Resource dialog.
12. Under Event Handlers, click Add and enter GetAccountAddress in the Function field (see Figure 23.20).
13. Click OK to close the Handler Properties dialog.
14. Click OK to close the Field Properties dialog.
15. Click Save, then Publish, and finally Save & Close to close the Entity Contact window.
16. Click the Publish All Customizations button to publish all the customizations.
Note
With Dynamics CRM 2016, you don’t need to create another web resource for the JSON JavaScript, as you had to do in previous versions of Dynamics CRM. JSON is natively supported in all the modern web browser applications that Dynamics CRM 2016 supports.
To test the solution, follow these steps:
1. Go to Sales > Contacts.
2. Select a contact and double-click it to open it or click +New to create a new Contact. The form shown in Figure 23.21 appears.
3. Click the search icon near the Account Name field to select an account (see Figure 23.22).
4. Click OK to close the dialog. You now see the contact address automatically filled in with the selected account address (see Figure 23.23).
SEE the “Web API” section, later in this chapter, for information on implementing the code shown here with the new Web API code.
If you are not familiar with OData protocol and queries and you want to implement similar customizations, you can use the Silverlight OData Query Designer that is included in the Dynamics XRM Tools managed solution, which you can download from https://dynamicsxrmtools.codeplex.com. You can use this tool to build queries to filter data and select specific columns to be returned to improve the network traffic. While this tool was designed to work with Dynamics CRM 2015, it can still be imported into the 2016 version (see Figure 23.24).
You can use the Organization web service of Dynamics CRM 2016 in JavaScript, using modern SOAP app endpoints. OData can perform CRUD (create, retrieve, update, delete) operations, associate, and disassociate tasks. In addition to all the operations performed by OData, you can also perform the following operations:
Assign records
Retrieve metadata
Execute messages
You can create an XMLHttpRequest
object to post requests to the Organization service in JavaScript. The body of the request can be in XML, and you to parse the XML response. You can make both asynchronous and synchronous requests, but the former is preferred. You do not need to write any code for authentication if you are accessing the web service through web resources.
Note
Microsoft has provided the SOAPLogger solution in the SDK to create requests in XML format. You can find it at SDKSample CodeCSclientSOAPLogger.
Open the solution in Visual Studio 2015. Go to the Run method in the SOAPLogger.cs file. It provides the SoapLoggerOrganizationService proxy object named slos
. You should use it to write your code just as you do with an Organization service proxy.
The following example shows how to assign an account record to some other user. You have the account ID of the Account entity record and the user ID of the user to whom you want to assign the contact record. Listing 23.20 shows how the C# code looks in the SOAPLogger solution.
public void Run(ServerConnection.Configuration serverConfig)
{
try
{
// Connect to the Organization service.
// The using statement assures that the service proxy will be properly disposed.
using (_serviceProxy = ServerConnection.GetOrganizationProxy(serverConfig))
{
// This statement is required to enable early-bound type support.
_serviceProxy.EnableProxyTypes();
IOrganizationService service = (IOrganizationService)_serviceProxy;
using (StreamWriter output = new StreamWriter("output.txt"))
{
SoapLoggerOrganizationService slos =
new SoapLoggerOrganizationService(serverConfig.OrganizationUri, service,
output);
//Add the code you want to test here:
// You must use the SoapLoggerOrganizationService 'slos' proxy
//rather than the IOrganizationService proxy you would normally use.
Guid userId = new Guid("8604A92B-264E-E311-B23E-00155D00F819");
Guid accountId = new Guid("618B1AF2-A161-E311-9AC7-00155D00F819");
AssignRequest assign = new AssignRequest
{
Assignee = new EntityReference(SystemUser.EntityLogicalName, userId),
Target = new EntityReference(Account.EntityLogicalName, accountId)
};
AssignResponse assignResp = (AssignResponse)slos.Execute(assign);
}
}
}
// Catch any service fault exceptions that Microsoft Dynamics CRM throws.
catch (FaultException<Microsoft.Xrm.Sdk.OrganizationServiceFault>)
{
// You can handle an exception here or pass it back to the calling method.
throw;
}
}
Build the solution and run the application using F5; the console window then opens. You provide server information and CRM login credentials to execute the code (see Figure 23.25).
In the bin/debug folder is an output.txt file that will has a POST request and response in XML format. You need to pass this XML request message to XMLHttpRequest
and parse the response.
The next example switches to JavaScript code. Listing 23.21 is an example of assigning a record to a user using SOAP messages. You can execute this message using the following:
SDK.Sample.AssignRequest(accountid, userid);
where accountid
is the record GUID of the Account entity record, and userid
is the record GUID of the user to whom you want to assign the record.
(typeof (SDK) == "undefined")
{SDK= {__namespace: true }; }
SDK.Sample = {
_getClientUrl: function () {
var ServicePath = "/XRMServices/2011/Organization.svc/web";
var clientUrl = "";
if (typeof GetGlobalContext == "function") {
var context = GetGlobalContext();
clientUrl = context.getClientUrl();
}
else {
if (typeof Xrm.Page.context == "object") {
clientUrl = Xrm.Page.context.getClientUrl();
}
else { throw new Error("Unable to access the server URL"); }
}
if (clientUrl.match(//$/)) {
clientUrl = clientUrl.substring(0, clientUrl.length - 1);
}
return clientUrl + ServicePath;
},
AssignRequest: function (accountID, userID) {
var requestMain = "";
requestMain += "<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/
envelope/">"
requestMain += "<s:Body>"
requestMain += "<Execute xmlns="http://schemas.microsoft.com/xrm/2011/
Contracts/Services" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">"
requestMain += "<request xmlns:a="http://schemas.microsoft.com/xrm/2011/
Contracts">"
requestMain += "<a:Parameters xmlns:c="http://schemas.datacontract.
org/2004/07/System.Collections.Generic">"
requestMain += "<a:KeyValuePairOfstringanyType>"
requestMain += "<c:key>Target</c:key>"
requestMain += "<c:value i:type="a:EntityReference">"
requestMain += "<a:Id>" + accountID + "</a:Id>"
requestMain += "<a:LogicalName>account</a:LogicalName>"
requestMain += "<a:Name i:nil="true" />"
requestMain += "</c:value>"
requestMain += "</a:KeyValuePairOfstringanyType>"
requestMain += "<a:KeyValuePairOfstringanyType>"
requestMain += "<c:key>Assignee</c:key>"
requestMain += "<c:value i:type="a:EntityReference">"
requestMain += "<a:Id>" + userID + "</a:Id>"
requestMain += "<a:LogicalName>systemuser</a:LogicalName>"
requestMain += "<a:Name i:nil="true" />"
requestMain += "</c:value>"
requestMain += "</a:KeyValuePairOfstringanyType>"
requestMain += "</a:Parameters>"
requestMain += "<a:RequestId i:nil="true" />"
requestMain += "<a:RequestName>Assign</a:RequestName>"
requestMain += "</request>"
requestMain += "</Execute>"
requestMain += "</s:Body>"
requestMain += "</s:Envelope>"
var req = new XMLHttpRequest();
req.open("POST", SDK.Sample._getClientUrl(), false)
req.setRequestHeader("Accept", "application/xml, text/xml, */*");
req.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
req.setRequestHeader("SOAPAction", "http://schemas.microsoft.com/xrm/2011/
Contracts/Services/IOrganizationService/");
req.send(requestMain);
},
__namespace: true
};
ExecuteMultipleRequest
All message requests discussed in this chapter up to now (except for RetrieveMultiple
) work for only one record. If you have to create/update/delete on more than one record, you have to iterate through records and send individual organization message requests for each record, which takes a long time because of Internet latency in the case of Microsoft Dynamics CRM Online. Microsoft introduced a new message named ExecuteMultipleRequest
in CRM 2011 Update Rollout 12 in December 2012. This message accepts a collection of message requests, which can be a mix of independent CreateRequest
, UpdateRequest
, DeleteRequest
, and other messages.
ExecuteMultipleRequest
executes each request in the order in which it appears in the collection. Each message request is processed in a separate database transaction. The default batch size is 1,000 for On-Premises installations and also for CRM Online. If the batch size increases to more than 1,000, ExecuteMultipleRequest
throws a fault before executing the first request. ExecuteMultipleRequest
cannot invoke another ExecuteMultipleRequest
. The Settings
parameter of ExecuteMultipleSettings
has two members, as follows:
ContinueOnError—As the name suggests, if this value is set to true
, requests keep processing even after a fault has been returned from the last request. If it is set to false
, remaining requests are not processed in case of any error or fault.
ReturnResponses—If the value is set to true
, it returns responses from each message. If false
, it does not return responses.
Listing 23.22 creates a console application and uses the ExecuteMultipleRequest
message to create 1,000 contacts.
Note
For any missing references in Listing 23.22, refer to the code sample in the SDK, in the SDKSampleCodeCSDataManagementExecuteMultiple folder.
private static void CreateMultipleRecords(string organizationName)
{
ClientCredentials credentials = new ClientCredentials();
credentials.Windows.ClientCredential = System.Net.CredentialCache.
DefaultNetworkCredentials;
OrganizationServiceProxy _serviceProxy = new OrganizationServiceProxy
(new Uri("http://crmwoifd/" + organizationName + "/XRMServices/2011/
Organization.svc"), null, credentials, null);
_serviceProxy.ServiceConfiguration.CurrentServiceEndpoint.Behaviors.
Add(new ProxyTypesBehavior());
OrganizationServiceContext orgContext = new OrganizationServiceContext
(_serviceProxy);
OrganizationRequestCollection collection = new
OrganizationRequestCollection();
for (int i = 0; i < 1000; i++)
{
Contact myContact = new Contact();
myContact.FirstName = "FirstName" + i.ToString();
myContact.LastName = "LastName" + i.ToString();
CreateRequest createRequest = new CreateRequest
{
Target = myContact
};
collection.Add(createRequest);
}
ExecuteMultipleRequest exeMulRequest = new ExecuteMultipleRequest
{
Requests = collection,
Settings = new ExecuteMultipleSettings
{
ContinueOnError = false,
ReturnResponses = true
}
};
DateTime startTime = DateTime.Now;
ExecuteMultipleResponse exeMulResponse = (ExecuteMultipleResponse)
orgContext.Execute(exeMulRequest);
DateTime endTime = DateTime.Now;
Console.WriteLine("Time to create 1000 contacts: {0}", endTime
- startTime);
Console.ReadKey();
}
The new Web API interface officially replaces the old OData 2.0 (which is deprecated in Dynamics CRM 2016), with the new version of OData, version 4.0. There are several differences between this new version of OData and the previous version.
The first difference is the authentication that is implemented using OAuth 2.0, as well as the format and the results, are returned. This new version returns data in JSON format, while the previous version returns XML. This makes the new version lighter with regard to web traffic and improves the network performance and avoids the need to convert the XML return to JSON in JavaScript calls, as was required previously.
Because Web API is based on REST, you can use it with HTTP in JavaScript or C# with .NET or any other platform or programming language.
The Web API endpoint looks like this:
org-url/api/data/v8.0/
where org-url is your CRM organization URL, like this:
https://2016crmbook.crm.dynamics.com
You can always find the right endpoint URL by going to the CRM web interface and navigating to Settings > Customizations > Developer Resources. You can find the Web API endpoint in the Interface Web API section.
If you use the endpoint with JavaScript from the CRM web interface, using web resources, you don’t need to authenticate. If you try to use this API from an external application like a Windows Forms Application, in C#, you need to authenticate using the OAuth protocol, explained in the next section.
OAuth is a standard protocol for authentication used by most of the popular social networking systems, such as Facebook, Twitter, and LinkedIn. The version currently implemented is 2.0, and for On-Premises implementations it requires IFD.
For more information about IFD, SEE CHAPTER 28, “Forms Authentication.”
You must authenticate with OAuth if you want to consume the Web API web services from an external client application.
The OAuth endpoints for CRM are https://login.windows.net/common/oauth2/authorize (multi-tenant) and https://login.windows.net/tenantID/oauth2/authorize (single tenant) for CRM On-Premises the OAuth endpoint is https://serverFQDNaddress/adfs/ls.
When working with OAuth, you need to obtain an authentication token to access the web service. You can use the following code in C# to do that from a CRM Online organization:
String resource = "https://webfortis.crm.dynamics.com";
_authenticationContext = new AuthenticationContext(_oauthUrl, false );
AuthenticationResult result = await _authenticationContext.AcquireTokenAsync(
resource, clientID );
The recommendation for OAuth with the CRM Web API is to use the Azure Active Directory Authentication Library (ADAL). With this you can get a client ID that you can pass to the authentication method as follows:
// TODO Substitute your correct CRM root service address,
string resource = "https://mydomain.crm.dynamics.com";
// TODO Substitute your app registration values that can be obtained after you
// register the app in Active Directory on the Microsoft Azure portal.
string clientId = "e5cf0024-a66a-4f16-85ce-99ba97a24bb2";
string redirectUrl = "http://localhost/SdkSample";
// Authenticate the registered application with Azure Active Directory.
AuthenticationContext authContext =
new AuthenticationContext("https://login.windows.net/common", false);
AuthenticationResult result = authContext.AcquireToken(resource, clientId, new
Uri(redirectUrl));
To create records of the Account entity, you can use the following code in JavaScript:
function CreateAccountRecord() {
PostOdata({ name: "New Account" });
}
You can implement a common JavaScript method called PostData
to use on all the examples in this section:
function PostOdata( jsonMessage) {
clientURL = Xrm.Page.context.getClientUrl();
var req = new XMLHttpRequest()
req.open("POST", encodeURI(clientURL + "/api/data/v8.0/accounts"), true);
req.setRequestHeader("Accept", "application/json");
req.setRequestHeader("Content-Type", "application/json; charset=utf-8");
req.setRequestHeader("OData-MaxVersion", "4.0");
req.setRequestHeader("OData-Version", "4.0");
req.onreadystatechange = function () {
if (this.readyState == 4 /* complete */) {
req.onreadystatechange = null;
if (this.status == 204) {
var Uriresponse = this.getResponseHeader("OData-EntityId");
alert(Uriresponse)
}
else {
var error = JSON.parse(this.response).error;
alert("error " + error.message);
}
}
};
req.send(JSON.stringify(jsonMessage));
}
You can create more than one entity record on a single OData call. The following example shows how to create one record for the account entity and then create one record for the Contact entity and associate the contact and the account on the same single JSON object:
PostOdata(
{
name: "New Account",
"primarycontactid":
{
"firstname": "Marc",
"lastname": "Wolenik"
}
});
To retrieve records, you can use the same sample used for JavaScript in the “Examples of Web Services” section. You saw earlier how to get the address of the Parent Account record in the Contact entity using OData 2.0. You do the same with Web API by replacing the methods RetrieveAccountRecord
and RetrieveAccountRecordCallback
as follows:
function RetrieveAccountRecord(recordId) {
var clientUrl = Xrm.Page.context.getClientUrl();
var webApiUrl = clientUrl + "/api/data/v8.0/accounts(" + recordId.replace
("{", "").replace("}", "") + ")";
var retrieveAccountRequest = new XMLHttpRequest();
retrieveAccountRequest.open("GET", webApiUrl, true);
retrieveAccountRequest.setRequestHeader("Accept", "application/json");
retrieveAccountRequest.setRequestHeader("Content-Type", "application/json;
charset=utf-8");
retrieveAccountRequest.setRequestHeader("OData-MaxVersion", "4.0");
retrieveAccountRequest.setRequestHeader("OData-Version", "4.0");
retrieveAccountRequest.setRequestHeader("Prefer",
"odata-include-annotations="*.*"");
retrieveAccountRequest.onreadystatechange = RetrieveAccountRecordCallback;
retrieveAccountRequest.send(null);
}
function RetrieveAccountRecordCallback() {
if (this.readyState === 4 /*Complete*/) {
if (this.status === 200 /*Success*/) {
var retrievedRecord = JSON.parse(this.responseText);
Xrm.Page.getAttribute("address1_postalcode").setValue(retrievedRecord.
address1_postalcode);
Xrm.Page.getAttribute("address1_line1").setValue(retrievedRecord.
address1_line1);
Xrm.Page.getAttribute("address1_line2").setValue(retrievedRecord.
address1_line2);
Xrm.Page.getAttribute("address1_line3").setValue(retrievedRecord.
address1_line3);
Xrm.Page.getAttribute("address1_city").setValue(retrievedRecord.
address1_city);
Xrm.Page.getAttribute("address1_stateorprovince").setValue
(retrievedRecord.address1_stateorprovince);
Xrm.Page.getAttribute("address1_country").setValue(retrievedRecord.
address1_country);
var addressText = "";
if (retrievedRecord.address1_line1 != null) {
addressText += retrievedRecord.address1_line1 + "
";
}
if (retrievedRecord.address1_line2 != null) {
addressText += retrievedRecord.address1_line2 + "
";
}
if (retrievedRecord.address1_line3 != null) {
addressText += retrievedRecord.address1_line3 + "
";
}
if (retrievedRecord.address1_city != null) {
addressText += retrievedRecord.address1_city + ", ";
}
if (retrievedRecord.address1_stateorprovince != null) {
addressText += retrievedRecord.address1_stateorprovince + " ";
}
if (retrievedRecord.address1_postalcode != null) {
addressText += retrievedRecord.address1_postalcode + "
";
}
if (retrievedRecord.address1_country != null) {
addressText += retrievedRecord.address1_country;
}
Xrm.Page.getAttribute("address1_composite").setValue(addressText);
}
}
}
To update a record with Web API, you need to use the PATCH
HTTP command. Here is an example of how to update a record using JavaScript, in which you need to pass the GUID of the account record you want to update:
UpdateAccount("00000000-0000-0000-0000-000000000001", { name: "New updated name for
Account" });
function UpdateAccount( accountId, jsonMessage) {
clientURL = Xrm.Page.context.getClientUrl();
var req = new XMLHttpRequest()
req.open("PATCH", encodeURI(clientURL + "/api/data/v8.0/accounts(" + accountId +
")"), true);
req.setRequestHeader("Accept", "application/json");
req.setRequestHeader("Content-Type", "application/json; charset=utf-8");
req.setRequestHeader("OData-MaxVersion", "4.0");
req.setRequestHeader("OData-Version", "4.0");
req.onreadystatechange = function () {
if (this.readyState == 4 /* complete */) {
req.onreadystatechange = null;
if (this.status == 204) {
alert("account updated")
}
else {
var error = JSON.parse(this.response).error;
alert("error " + error.message);
}
}
};
req.send(JSON.stringify(jsonMessage));
}
To delete a record with Web API, you need to use the DELETE
HTTP command. Here is an example of how to delete a record using JavaScript, in which you need to pass the GUID of the account record you want to delete, using a context similar to this:
DeleteAccount("00000000-0000-0000-0000-000000000001");
function DeleteAccount( accountId) {
clientURL = Xrm.Page.context.getClientUrl();
var req = new XMLHttpRequest()
req.open("DELETE", encodeURI(clientURL + "/api/data/v8.0/accounts(" + accountId
+ ")"), true);
req.setRequestHeader("Accept", "application/json");
req.setRequestHeader("Content-Type", "application/json; charset=utf-8");
req.setRequestHeader("OData-MaxVersion", "4.0");
req.setRequestHeader("OData-Version", "4.0");
req.onreadystatechange = function () {
if (this.readyState == 4 /* complete */) {
req.onreadystatechange = null;
if (this.status == 204) {
alert("account deleted")
}
else {
var error = JSON.parse(this.response).error;
alert("error " + error.message);
}
}
};
req.send();
}
There are many other operations you can do with Web API. Probably one of the most useful operations you can do is an “upsert,” which is a combination of an update and an insert in the same call. Basically, an upsert tries first to update a record, but if the record doesn’t exist, it inserts the record instead of failing.
Note
Refer to the Dynamics CRM 2016 SDK for a complete list of the operations you can do with Web API.
In this chapter, you have learned about the web services that the Microsoft CRM 2016 system exposes and how to use them to extend CRM’s functionality and make customizations.
This chapter discusses the Discovery service that is used to find the right access endpoint for an organization, which is especially useful for multi-tenancy environments. The examples in this chapter show how to use the Organization service to create new records for any entity and how to update, delete, and retrieve existing records. This chapter also looks at the metadata web service that is used to make customizations programmatically (for example, to create a new custom entity or attributes). This chapter also covers how to use the deprecated OData version 2.0 service by using REST on JavaScript code and how you can use ExecuteMultipleRequests
to do bulk operations and also the new Web API, which uses the version 4.0 of the OData protocol and the OAuth authentication that will need to use to connect to CRM from external client applications using Web API.