Creating a CCF Customer Provider for Dynamics CRM


CCF includes a reference capability for retrieving customer data via server side request. This is provides an ability to access one or more data sources from a known access point within your network. This capability was developed to deal with the often stringent requirement that certain types of service calls, and data access must be done from a “known” or "Service” account.  This set of bits over time has become known as the “Customer provider” or “Customer Service” depending on which CCF generation you started with :).


This feature set lets you define data sources and the structure of the data that is returned to CCF.  This provider is run under a CCF service account, and can also access the CCF SSO system to request credentials for specific systems, based on the CCF service account.


The reference implementation that ships with CCF includes the full source code for the sample customer database. 

If you installed the developer tools for CCF you will find this at %INSTALLDRIVE%\ Microsoft CCF 2009\Reference Implementations\AgentDesktop.sln


For this provider, we are going to modify the base example to talk to Dynamics CRM and Search for Accounts and Contacts in CRM 4.


What do you need to get started


A CCF 2009 server and client, with the developer tools installed
A Dynamics CRM 4 server installed and working
Dynamics CRM SDK
Visual Studio 2008


Getting Started


So First off, we need to set some rules down about what we are going to consider a “customer”.  For this example a “customer” is an Account + Contact Combination where the Contact is pointing to an account.
For the sake of simplicity I’m going to start with a blank VS solution rather than opening the Reference project and weeding though to the bits we need.
Open Visual Studio and add an existing project:


%installdirectory%\Microsoft CCF 2009\Reference Implementations\Microsoft.Ccf\Csr\WebServices\Customer\Provider\Microsoft.Ccf.Csr.WebServices.Customer.Provider.csproj


Your project should look like this:


image


The CCF agent desktop talks to the customer WCF Service, which is installed in the Microsoft.Ccf.Csr.WebServices.Customer web site. The customer service provides a “provider” that allows CCF to query one or more search sources to identify a customer. The provider configuration is managed for the CCF deployment by the CCF Configuration system.


Modifying the CCF Customer Data contract


We need to define what our customer record for CCF is going to look like.  The CCF customer record is a WCF Data Contract used by CCF to describe the “Customer” as such, the out of the box bits usually are not good enough.
 
Open up the CustomerProvider.cs File in Visual Studio and locate the region labeled Implementation helpers


Expand that region and you will see a class like :



/// <summary>
/// Sample customer record definition.
/// </summary>
[DataContract]
public class CustomerRecord
{
    /// <summary>
    /// The customer's ID
    /// </summary>
    [DataMember]
    public string CustomerID;

This is the description of the data that CCF will get when a call to the customer service returns.
We need to make our first changes to this area. 
We need to capture the Account ID and Contact ID associated with our CRM query as we will need those later when we are working with CRM inside CCF.  We will also want to capture the Account Name and an Email address.


After the last entry in the CustomerRecord class ( should be PhoneMobile ) add:



[DataMember]
public Guid AccountID;
[DataMember]
public Guid ContactID;
[DataMember]
public string AccountName;
[DataMember]
public string EmailAddress; 



Don’t forget the [DataMember] attribute.  That is required by WCF so that it understands how to treat that value.


We are done with this file… this minor change will show up in CCF when we rebuilt the Customer service reference in the main projects.   Close this file down.


Building the CRM Provider


Add a Class to our project and call it CRM4CustomerProvider.cs.
Your new class should look like this: 



namespace Microsoft.Ccf.Csr.WebServices.Customer.Provider
{
    public class CRM4CustomerProvider
    {
    }
}

 


Derive your class from Customer provider and Implement the abstract Class… you do that by adding “ : CustomerProvider “ to your class name. Once you do that you should get an auto complete prompt asking you to implement the abstract class, say Yes. 
The updated class should now have number of members; it should be similar to this:



public class CRM4CustomerProvider : CustomerProvider
{
     ...
     public override CustomerProvider.CustomerRecord[] 
                      GetCustomersByANI(string ani, int maxRecords)
     {
            throw new NotImplementedException();
     }
     ...
}

There should be 8 total methods in the class now, all following the same pattern as you see above.

Each of these methods can be called from the CCF client to identify a customer.  I am going to work just with the overridden method “GetCustomersByAni” for now.


Next we need to add a constructor to the class; CCF uses the provider constructors to send over relevant information from the CCF configuration system. In the case of the Customer Provider, it only sends a connection string.  So even though we are not going to use it for this, we still need to handle it.
To do that, add this right after the class declaration



public CRM4CustomerProvider(string connectionstring)
            : base(connectionstring)
        { }

Now we need to add in the CRM connectivity. 
This is pretty simple to do for deployed CRM environments, and just a bit more complicated for CRM Online deployments.  I am going to use a CRM Deployed example for now.   CRM offers 2 ways to access the X/CRM API, The CRM SDK includes a number of helper library’s, functions and classes to aid in connecting and dealing with CRM data and the other is direct access to the X/CRM Web Service and the X/CRM Metadata Service.  The web service offers strongly typed access to CRM data and types.  So for this example we are going to use the web service API for X/CRM.


We need to add a reference to the X/CRM Web service API.   You can find more info on setting up a connection to CRM Web Service here
In my case, I’m going to call it XRMSvc:


image 


After adding the references,  my project should look like this :


image


*note: for the demo I am showing, I’m going to hardcode some of the variables. In a “real” version, you would need to make things like user id ,pw and url configurable, the CCF configuration server is a good place for this, or the web.config file for the customer service.


We are going barrow some bits from the CRM SDK samples and create a few utility functions to help us with access to the X/CRM API.


We will use HTTP Caching to Cache the XRMSvc Connection. This is important, as the CRM service is a bit expensive, time wise, to setup the first time.


To do that, we need to add a reference to the project for System.Web so that we can access the HttpRuntime Class. This class will give us a number of common features for working with web services and web sites.  We are going to use the static Cache class from this namespace to handle the cache for us.  Once you have added the System.Web  reference to the project, open the CRM4CustomerProvider.cs file and add a using statement to it.
At this point the using should look like this:



using System;
using System.Collections.Generic;
using System.Text;
using System.Web;

We now need to add a method that will check to see if the service is in the server cache, and if not, we need to initialize the service and login.

Add this method to the class:



/// <summary>
/// try to get X/CRM Service from cache, if not , 
/// try to init a new instance and add it to cache.
/// </summary>
/// <returns></returns>
private XRMSvc.CrmService GetXrmSvc()
{
    // Check cache for an instance of the service. 
     XRMSvc.CrmService XrmSvc = 
               (XRMSvc.CrmService)HttpRuntime.Cache["CRMCustomerProvider"];
     if (XrmSvc == null)
     {
         // Didnt find a instance in cache, 
         // Intilize a new instance. 
         string orgName = "YOUR ORG HERE";
         string serverUrl = "YOUR URL HERE";
         XrmSvc = GetCrmService(serverUrl , orgName);
         
         // Add it to cache. 
         HttpRuntime.Cache.Add(
             "CRMCustomerProvider",
             XrmSvc,
             null,
             System.Web.Caching.Cache.NoAbsoluteExpiration,
             new TimeSpan(0, 20, 0), 
             System.Web.Caching.CacheItemPriority.Normal,
             null);
     }
    // Return the service from cache. 
     return XrmSvc;
}

Replace “Your Org Here” with the name of your CRM org,  and Server URL with the ip, or name : port of your server.


For example, if your CRM Web site is at http://contoso:5551/mscrm/… then the variables you would use here would be:


orgName = “mscrm”;
serverUrl = “http://contoso:5551”;


The X/CRM API also has discovery services that will figure out the proper org name for you, and, in the case of CRM Online, help you get the correct URL’s to connect to.


In the GetXrmSvc method, we are checking the server cache to see if we have an existing copy of the CRMCustomerProvider object, which is our X/CRM Service connection.  If we have it, we exit right away, if we don’t, then we need to call the “GetCrmService()” method to set it up.  When we get a response for that, we immediately set it into the cache for the next request.


You may notice the TimeSpan(0, 20, 0) in the cache command.  That is telling the http cache to hold on to this for up to 20 min of inactivity, after someone touches it, reset the counter for another 20 min. This help us avoid having to rebuild the X/CRM API interface for each request.


Now we need to add the GetCrmService method,  for that we are going to barrow a bit of the CRM SDK code.  Specifically, I’m going to grab a method from the crmserviceutility.cs file that ships with the CRM SDK.  If you have downloaded that SDK, you will find this class in %installpath%\sdk\server\howto\cs


The method we want to use is called GetCrmService.  Copy that method into the CRM4CustomerProvider.cs file after our “GetXrmSvc()” method call.  Remove the ‘static’ tag from the GetCrmService method and use the Visual Studio auto complete to add the using statement to the file to reference the X/CRM Web Service.

When you’re done it should look like this:



/// <summary>
/// Set up the CRM Service.
/// </summary>
/// <param name="organizationName">My Organization</param>
/// <returns>CrmService configured with AD Authentication</returns>
public CrmService GetCrmService(string crmServerUrl, string organizationName)
{
    // Get the CRM Users appointments
    // Setup the Authentication Token
    CrmAuthenticationToken token = new CrmAuthenticationToken();
    token.OrganizationName = organizationName;
 
    CrmService service = new CrmService();
 
    if (crmServerUrl != null &&
        crmServerUrl.Length > 0)
    {
        UriBuilder builder = new UriBuilder(crmServerUrl);
        builder.Path = "//MSCRMServices//2007//CrmService.asmx";
        service.Url = builder.Uri.ToString();
    }
 
    service.Credentials = System.Net.CredentialCache.DefaultCredentials;
    service.CrmAuthenticationTokenValue = token;
 
    return service;
}

One more thing to do with this method, the crmserviceutility.cs method uses “DefaultCredentials” for authenticating to the X/CRM system.  As this a provider, it will inherit the identity of the IIS AppPool that it is running under.  There are a number of ways to work this out, however for or example we are just going to update that and hardcode in a user account / pw to get it up and working. 


Note, as I said above, this is for demo use only, for a “real” environment you need to handle this using AppPool identity or the CCF SSO system.


We need to update the line:



service.Credentials = System.Net.CredentialCache.DefaultCredentials;

to



service.Credentials = new System.Net.NetworkCredential("USER", "PW", "DOMAIN");

Putting your ID’s in for the user, PW and domain.

Now that all the housekeeping is done, we are ready implement the GetCustomersByAni method.

At the moment, that method looks like this:



public override CustomerProvider.CustomerRecord[] 
        GetCustomersByANI(string ani, int maxRecords)
{
     throw new NotImplementedException();
}

The goal of this method is to identify a customer based on their phone number and return that customer’s information to CCF.  This method is usually called from CCF’s lookup UI or by its AddSession methods. I say usually, because it can be called by developer code pretty much anywhere within the CCF environment.


The number that we are going to look for is handed to us in the ani field, MaxRecords is pretty self explanatory.   So first off lets add some paranoia code in to check for the null’s, empty strings and failures.



public override CustomerProvider.CustomerRecord[] 
                           GetCustomersByANI(string ani, int maxRecords)
{
    if (string.IsNullOrEmpty(ani))
        throw new ArgumentNullException("ani");  // No phone number provided
 
    // Get the CRM Service Instance. 
    CrmService svc = GetXrmSvc();
    if (svc == null)
        throw new Exception("Unable to initialize CRM connection");

Now we are going to form the query.  To do that we need to know a bit about what we are looking for in CRM.  We are going to locate a contact by a phone number on the contact. CRM has several phone number associated with the contact out of the box, a quick look at the CRM metadata browser for the contact will give us what we need to know.  You can find it here: http://<servername>:<port>/sdk/list.aspx .

In my case, it looks like this:


image


We can see 3 of the 4 phone number fields that we want to query against here, the other is “mobilephone”.   We are going to guess that the number we are looking for is going to be in one of them. However we don’t necessary know the format that they are being stored in, we need to handle that as well.


One of things we have going for us here is that X/CRM’s query api accepts wild cards.. so we just need to do a bit of C# magic to parse out our string and build an appropriate query string.
 
We add this:



StringBuilder sTN = new StringBuilder();
sTN.Append("%");
foreach (char c in ani)
{
    if (Char.IsDigit(c))
    {
        sTN.Append(c);
        sTN.Append("%");
    }
    else
    {
        if (c == '+')
            sTN.Append(c);
    }
}
ani = sTN.ToString();

What we are doing here is transforming a randomly formatted phone number into something that we can use to search regardless of the actual format in the CRM data system. So (123) 456-7890 turns into %1%2%3%4%5%6%7%8%9%0%.  We can use this to search the CRM data system for, and get a hit, as long as it can find the numbers in order, regardless of the format.


We only need a few things out of the contact record at this point. And as you saw from the metadata browser there is a LOT of stuff in there, even before you add your own customizations to it.  We need to create list of columns to return.


We add this:



ColumnSet cols = new ColumnSet();
cols.Attributes = new string[] { "contactid", "parentcustomerid", 
    "firstname", "lastname", "address1_line1" , "address1_city" , 
    "address1_stateorprovince" , "address1_postalcode" , 
    "telephone1" , "telephone2" , "telephone3" , "mobilephone",
    "emailaddress1"};

This identifies the bits that we are interested in returning from the query to X/CRM.  There are number of better ways to do this 🙂 however in the interests of simplicity, I show you the sledge hammer approach.


With our columns for the query of the contact record identified, we now need to build the filter.  In X/CRM a filter is a collection of conditions. So we need to build a condition that covers each of our search fields.


We add this:



// Create Conditions
ConditionExpression condition_telephone1 = new ConditionExpression();
condition_telephone1.AttributeName = "telephone1";
condition_telephone1.Values = new string[] { ani };
condition_telephone1.Operator = ConditionOperator.Like;
conditions.Add(condition_telephone1);
 
ConditionExpression condition_telephone2 = new ConditionExpression();
condition_telephone2.AttributeName = "telephone2";
condition_telephone2.Values = new string[] { ani };
condition_telephone2.Operator = ConditionOperator.Like;
conditions.Add(condition_telephone2);
 
ConditionExpression condition_telephone3 = new ConditionExpression();
condition_telephone3.AttributeName = "telephone3";
condition_telephone3.Values = new string[] { ani };
condition_telephone3.Operator = ConditionOperator.Like;
conditions.Add(condition_telephone3);
 
ConditionExpression condition_mobiletn = new ConditionExpression();
condition_mobiletn.AttributeName = "mobilephone";
condition_mobiletn.Values = new string[] { ani };
condition_mobiletn.Operator = ConditionOperator.Like;
conditions.Add(condition_mobiletn);

 


Again, there are more efficient ways to do this, but it gets the job done.
We have the Conditions, now we need to add it to a filter.

We add:



FilterExpression filter = new FilterExpression();
filter.Conditions = conditions.ToArray();  // Our conditions 
filter.FilterOperator = LogicalOperator.Or;

The OR is to allow for a hit on any of the conditions to return.
Now we tie it all together with an X/CRM Query command.



QueryExpression query = new QueryExpression();
query.EntityName = EntityName.contact.ToString();
query.ColumnSet = cols;  // the cols we want. 
query.Criteria = filter; // the filter

We are now ready to do make the query to X/CRM for the contact data. For that we use the RetrieveMultipleRequest class from the CRM SDK. That will allow us to bring back several records.



RetrieveMultipleRequest retrieve = new RetrieveMultipleRequest();
retrieve.Query = query;

We will catch the response from the query using the RetrieveMultipleResponse class from the CRM SDK. We are also going to define the List that will hold onto our customer records as we get them back from CRM. Note; the X/CRM API handles errors by throwing exceptions from its service, so we need to account for that as well.


 


 



List<CustomerRecord> results = new List<CustomerRecord>();
RetrieveMultipleResponse retrieved;
try
{
    retrieved = (RetrieveMultipleResponse)svc.Execute(retrieve);
}
catch (SoapException ex1)
{
    System.Diagnostics.Trace.WriteLine("CRM SOAP EXCEPTION : " + 
            ex1.Detail.InnerText); 
}
catch (Exception ex)
{
    System.Diagnostics.Trace.WriteLine("CRM EXCEPTION : " + 
            ex.Message); 
}

The line that is actually placing the request is:
retrieved = (RetrieveMultipleResponse)svc.Execute(retrieve);


If successful, we will get a bit of data back answering our query request,  if not, you will get a SOAP error from the X/CRM API telling you what it didn’t like.


Next we need to look at the response and parse it out for our data, because we can get multiple responses, we need a foreach loop.



if (retrieved != null)
{
    foreach (BusinessEntity bizEnt in 
            retrieved.BusinessEntityCollection.BusinessEntities)
    {
        CustomerRecord cr = new CustomerRecord();  // result Record. 
        contact ct = (contact)bizEnt;
        cr.City = ct.address1_city;
        cr.ContactID = ct.contactid.Value;
        cr.Country = ct.address1_country;
        ct.contactid.Value.ToString();
        cr.EmailAddress = ct.emailaddress1;
        cr.FirstName = ct.firstname;
        cr.LastName = ct.lastname;
        cr.PhoneHome = ct.telephone2;
        cr.PhoneMobile = ct.mobilephone;
        cr.PhoneWork = ct.telephone1;
        cr.State = ct.address1_stateorprovince;
        cr.Street = ct.address1_line1;
        cr.ZipCode = ct.address1_postalcode;

We have most of our Contact Record; however we still need a few more bits from the account.  I’m just going to show you the code, as the method to get it is very similar to what we have done already with the important difference that I will use RetrieveRequest instead of RetrieveMultipleRequest. This form of request is “targeted” at a given X/CRM entity and ID. I have the ID of the related account from the ct.parentcustomerid field; this allows us to use this faster version of the query.


We add:



TargetRetrieveAccount tgAccount = new TargetRetrieveAccount();
tgAccount.EntityId = ct.parentcustomerid.Value;
 
ColumnSet Accountcols = new ColumnSet();
Accountcols.Attributes = new string[] { "accountid", "name" };
 
RetrieveRequest req = new RetrieveRequest();
req.ColumnSet = Accountcols; // Col's 
req.Target = tgAccount; //what we are looking for. 
 
RetrieveResponse resp;
try
{
    resp = (RetrieveResponse)svc.Execute(req);  // get the account
    account ar = (account)resp.BusinessEntity; // Cast it.
    cr.AccountID = ar.accountid.Value;
    cr.AccountName = ar.name;
 
    results.Add(cr); // add to the result list. 
}
catch (SoapException ex1)
{
    System.Diagnostics.Trace.WriteLine("CRM SOAP EXCEPTION : " + 
            ex1.Detail.InnerText);
}
catch (Exception ex)
{
    System.Diagnostics.Trace.WriteLine("CRM EXCEPTION : " + ex.Message);
}

We are almost done with this method. 
We just need to return our results now, so after the last catch closes.



return results.ToArray(); 

And we build.


If put together right, you should build without errors.


Testing your work


 


 


Now, before we register it with CCF, we should test it.  Its quite a bit simpler to trouble shoot CCF providers outside the provider containers, as this allows us to focus in on the specific method if an issue is discovered.  It also allows us to isolate any issues the correct area on CCF.


To do that, we are going to use a simple Winform with a button.


Add a Winform project to the solution and call it “CRMProviderTester”.
Your solution should now look like:


image


Add a project reference to the Microsoft.Ccf.Csr.WebServices.Customer.Provider project.
You will also need to add a file reference for Microsoft.Ccf.Common.Providers.  you can find that file in the %INSTALLDRIVE%\ Microsoft CCF 2009\Framework folder.
Add a button to the form and implement the Click Event.


Next, Add the following to the using’s for the form;



using Microsoft.Ccf.Csr.WebServices.Customer.Provider;

In the click event handler, create an instance of our provider.  You can pass in string.empty for the connection string as we are not using it. We also need to put in some code to invoke the GetCustomerByAni method


We add:



CRM4CustomerProvider provTest = new CRM4CustomerProvider(string.Empty);
CRM4CustomerProvider.CustomerRecord[] recs = 
                        provTest.GetCustomersByANI("1234567890", 10);
if (recs != null)
{
    MessageBox.Show(string.Format("there are {0} records" , recs.Length));
}

We are ready to run our test program now, if it works, and you have a record in your system that matches the search request, you will get message box with the number of records returned.  If not you will get a 0.  If you get a 0 and you know your record to exist, look at the output window of VS for what’s wrong. 

Most of the time the first issues are security related, the user account you connect to the service with will have the same privileges in X/CRM that you would, had you logged into the CRM web UI using that account. So if you cannot access it there, you will not be able to access it via the X/CRM API.


That wraps up this post. In the next post we will cover deploying the provider to the CCF configuration system, testing it, and then we will go though modifying CCF to accept our new Record and create a session for it.

Comments (1)

  1. cbmdotdk says:

    Hi Matt,

    Truely a great post…

Skip to main content