Chapter 23. Web Services

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.


Web Services Fundamentals

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

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

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

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.


Note

For more information about JSON, visit www.json.org.


Open Data Services

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).

Image

FIGURE 23.1 Navigating to Settings > Customizations.

2. Select Developer Resources option, and the page shown in Figure 23.2 appears.

Image

FIGURE 23.2 Downloading web services description files.

As you can see in Figure 23.2, you can download the WSDL definitions for two web services: The Discovery and Organization services.

Image

FIGURE 23.3 Creating a console application in Visual Studio 2015.

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.

Discovery Web Service

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.

Image

FIGURE 23.4 Adding a web reference to the Discovery WCF service.

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).

LISTING 23.1 Querying a Service Namespace

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.


Organization Service

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

Adding a Service Reference

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.

Image

FIGURE 23.5 Adding a service reference for the Organization service.

The Organization service has the following methods:

Image Create

Image Retrieve

Image RetrieveMultiple

Image Delete

Image Associate

Image Disassociate

Image Execute

Image Update

Create Method

The 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#:

LISTING 23.2 Creating a New Account

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();
        }
    }
}

Testing the Create Method

To 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.

Image

FIGURE 23.6 Creating an account through the Main Data web service.

Now if you go to the CRM web client application, you can see the new account (see Figure 23.7).

Image

FIGURE 23.7 Reviewing the new account created through the Main Data web service.

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 Method

The 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.

LISTING 23.3 Using 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;
                }
           }
        }

Testing the Retrieve Method

As 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.

Image

FIGURE 23.8 Testing the Retrieve method of the Main Data web service.

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 Method

The 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.

LISTING 23.4 Retrieving an Organization’s Accounts

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());
                }
            }
        }

Testing the RetrieveMultiple Method

To 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.

Image

FIGURE 23.9 RetrieveMultiple example.

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.

LISTING 23.5 Retrieving Matching Accounts

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

Testing the Account-Matching Method

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.

LISTING 23.6 Function to Pass the Organization Name

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.

Image

FIGURE 23.10 RetrieveMultiple method output.

Delete Method

The 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#.

LISTING 23.7 Deleting a New Account 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();
                }
            }
        }

Testing the Delete Method

To 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.

LISTING 23.8 Deleting Demo Accounts

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.

Image

FIGURE 23.11 Account deletion example.

Execute Method

The 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.

Image If you don’t remember how to create a case, refer to CHAPTER 10, “Working with Service.”

LISTING 23.9 Using the Update Method to Update Status

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

Testing the Execute Method

When 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.

LISTING 23.10 Updating the reference.cs File

[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.


Fetch Method

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:

Image With Fetch, you can get results in pages by limiting the number of records returned by page.

Image With Fetch, you can just query the record count or use any aggregation method, such as Avg, Count, Max, Min, or Sum.

Image 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.

Image The resulting set of records depends on the user privilege.

Image You can use left outer joins.

Image 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>

LISTING 23.11 Using the Fetch Method to Retrieve Contacts

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());
        }
    }
}


Note

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 Method

The 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#.

LISTING 23.12 Updating an Existing Account 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.


Early Binding

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.



Note

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):

Image microsoft.xrm.sdk.dll

Image 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).

Regular Operations for Early Binding

The following sections cover the following regular operations:

Image Create

Image Retrieve

Image RetrieveMultiple

Image Delete

Image 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 Method

To 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.

LISTING 23.13 Creating a New Account

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 Method

To retrieve any record of an entity such as an account or a contact, you can use LINQ, using the code in Listing 23.14.

LISTING 23.14 Retrieving an Existing Account Record

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 Method

You 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.

LISTING 23.15 Retrieving Multiple Account Records with LINQ

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 Method

To 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);

Update Method

To update a record using the organization context, you do it in the following way:

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);

Metadata

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 Method

The Execute method accepts the following submethods related to the metadata:

Image CreateAttribute

Image CreateEntity

Image CreateManyToMany

Image CreateOneToMany

Image CreateOptionSet

Image DeleteAttribute

Image DeleteEntity

Image DeleteOptionSet

Image DeleteOptionValue

Image DeleteRelationship

Image InsertOptionValue

Image InsertStatusValue

Image OrderOption

Image RetrieveAllEntities

Image RetrieveAllManagedProperties

Image RetrieveAllOptionSets

Image RetrieveAttribute

Image RetrieveEntity

Image RetrieveMetadataChanges

Image RetrieveOptionSet

Image RetrieveRelationship

Image RetrieveTimestamp

Image UpdateAttribute

Image UpdateEntity

Image UpdateOptionSet

Image UpdateOptionValue

Image UpdateRelationship

Image 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.

LISTING 23.16 Checking for Entity Existence Using Two Parameters

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.

LISTING 23.17 Creating a Custom Entity

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();
}



Note

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.

LISTING 23.18 Querying the Metabase to Retrieve Values

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();
}

Examples of Web Services

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.


JavaScript

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.

Image 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).

Image

FIGURE 23.12 Contact customization.

2. Expand the Contact entity and click Forms in the left navigation pane (see Figure 23.13).

Image

FIGURE 23.13 Forms customization.

3. Double-click the Main form type to open it (see Figure 23.14).

Image

FIGURE 23.14 Main form type customization.

4. Click the Account Name field and click Change Properties. The Field Properties dialog appears (see Figure 23.15).

Image

FIGURE 23.15 Field Properties dialog.

5. Select the Events tab (see Figure 23.16).

Image

FIGURE 23.16 Events tab.

6. Expand Form Libraries and click Add (see Figure 23.17).

Image

FIGURE 23.17 Adding form libraries.

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).

Image

FIGURE 23.18 New web resource.

8. Click the Text Editor button and insert the code shown in Listing 23.19 into the text box (see Figure 23.19).

LISTING 23.19 Consuming an OData (REST) Service

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);
    }
    }
}

Image

FIGURE 23.19 JavaScript code to call the CRM OData (REST) service.

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).

Image

FIGURE 23.20 Handler properties.

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.

Image

FIGURE 23.21 Creating a new contact.

3. Click the search icon near the Account Name field to select an account (see Figure 23.22).

Image

FIGURE 23.22 Selecting a company.

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).

Image

FIGURE 23.23 The new contact address filled automatically with the selected account address.

Image 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).

Image

FIGURE 23.24 OData Query Designer in the XRM Tools 2015 managed solution.

Modern SOAP Endpoints

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:

Image Assign records

Image Retrieve metadata

Image 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.

LISTING 23.20 C# Code for 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;
   }
  }


Note

Be sure to replace Guid in the Listing 23.20 example with your own GUID, or the code will fail.


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).

Image

FIGURE 23.25 SOAPLogger console window.

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.

LISTING 23.21 Assigning a GUID 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:

Image 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.

Image 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.


LISTING 23.22 Creating a Console Application in C#

  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();
}

Web API

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

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.

Image 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));

Creating Records

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

Retrieving Records

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);
        }
    }
}

Updating Records

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));
}

Deleting Records

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();
}

Other Operations

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.


Summary

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset