CRM SharePoint Integration Business Connectivity Services

Integrating SharePoint and Microsoft CRM makes sense. Your sales team use CRM as they go about their daily work, but quite often non-sales people still need to be able to access and work with the same data. Although CRM 2011 has become part of the Office 365 package recently, they still run as separate instances in the cloud. Getting the data from CRM 2011 online displayed in SharePoint Online through the Business Connectivity Services is possible but needs a bit of coding and Azure magic. We are going to go through the steps to show you the kind of things involved to get this to happen. The scenario is we want to be able to display 30 days worth of orders in SharePoint Online through the Business Connectivity Services.

This post makes a number of assumptions:

1, You have an Azure account setup and the tools installed with Visual Studio 2010.

2, You know how to deploy a project to Azure

3, You have a CRM 2011 Online account and a SharePoint Online account (may be both are through Office 365)

4, You have downloaded the CRM 2011 SDK

 

Here’s an overview of what we are going to do:

1, Build a WCF Service that can pull data from CRM Online and present it in a BCS friendly manner

2, Run this WCF Service on Azure

3, Use SharePoint Designer 2010 to create an External Content Type that will display CRM data in SharePoint Online.

Generate Early Bound Types for CRM

Crm has a really flexible data model which allows you to add new custom fields to entities such as customers or sales. This also presents the problem that the web services are not strongly typed. There are some web service methods you can call to discover the data structure that is used within CRM, but Microsoft also ships a tool with the CRM SDK that can generate your CRM data model in code so you can use Linq to CRM to get access to your data.

Open a command prompt and move to the bin folder within the CRM SDK. crmsvcutil.exe is the tool we use to generate our code. At the command prompt type:

CrmSvcUtil.exe /url:https://[YourCrmSiteName].crm.dynamics.com/XRMServices/2011/Organization.svc /out:GeneratedCode.cs /username:”[your Live Id]” /password:”[your password]”

These are the parameters you need to use when using CRM 2011 online. If you are using CRM on premise or have any issues running this check out this page for some help:

http://msdn.microsoft.com/en-us/library/gg327844.aspx

Running the command above will generate a file called GeneratedCode.cs that we will add to our project shortly.

Create a WCF Azure Service

1, Open up Visual Studio 2010 and choose to Create a new Cloud –> Windows Azure Project called something like CrmBcsWcfLayer (see how many acronyms I can fit into a project name! Smile)

2, Select the WCF Service Web Role from the Azure project types available and once moved across to the right pane hover your mouse over it to rename it to something sensible

3, First thing we can do is add our GeneratedCode.cs file from earlier into our WCF project. This file is generated in the same directory where you ran the crmsvcutil tool.

4, Now we need to add some assembles from the CRM SDK. In the bin folder of the SDK select:

microsoft.xrm.sdk

Also add a reference to:

System.Security

5, To help us authenticate and run code against CRM Online we want to make use of a helper class from the CRM SDK. Add an existing item to your project and find {YOUR CRM SDK}sdksamplecodecshelpercodedeviceidmanager.cs

6, Open up IService.cs and delete the CompositeType class. The SalesOrder CRM object has many fields, but we only want to return a small subset. We will therefore define our own class that contains this subset of fields

[DataContract]
publicclass BcsSalesOrder
{
    [DataMember]
    public Guid SalesOrderId { get; set; }

    [DataMember]
    publicdecimal TotalAmount { get; set; }

    [DataMember]
    publicstring CustomerName { get; set; }

    [DataMember]
    public DateTime OrderDate { get; set; }
}

7, Still in IService.cs, delete the two old operation contracts from there. Add in the following interface

[OperationContract]
IEnumerable<BcsSalesOrder> GetRecentOrders();
        

8, Right click on Service1.svc and chose View Code. Clear out the GetData and GetDataUsingDataContract method.

9, We’re going to add some code to help us Authenticate against CRM. This code is taken from samples in the CRM ask so I can’t take any credit for it! Within the Service1 class paste in:

///<summary>/// Obtain the AuthenticationCredentials based on AuthenticationProviderType.
///</summary>///<param name="endpointType">An AuthenticationProviderType of the CRM environment.</param>///<returns>Get filled credentials.</returns>private AuthenticationCredentials GetCredentials(AuthenticationProviderType endpointType)
{

    AuthenticationCredentials authCredentials =new AuthenticationCredentials();
    switch (endpointType)
    {
        case AuthenticationProviderType.ActiveDirectory:
            authCredentials.ClientCredentials.Windows.ClientCredential =new System.Net.NetworkCredential(_userName,
                    _password,
                    _domain);
            break;
        case AuthenticationProviderType.LiveId:
            authCredentials.ClientCredentials.UserName.UserName = _userName;
            authCredentials.ClientCredentials.UserName.Password = _password;
            authCredentials.SupportingCredentials =new AuthenticationCredentials();
            authCredentials.SupportingCredentials.ClientCredentials =
                Microsoft.Crm.Services.Utility.DeviceIdManager.LoadOrRegisterDevice();
            break;
        default: // For Federated and OnlineFederated environments.                                authCredentials.ClientCredentials.UserName.UserName = _userName;
            authCredentials.ClientCredentials.UserName.Password = _password;
            // For OnlineFederated single-sign on, you could just use current UserPrincipalName instead of passing user name and password.
            // authCredentials.UserPrincipalName = UserPrincipal.Current.UserPrincipalName;  //Windows/Kerberosbreak;
    }

    return authCredentials;
}

///<summary>/// Discovers the organizations that the calling user belongs to.
///</summary>///<param name="service">A Discovery service proxy instance.</param>///<returns>Array containing detailed information on each organization that
/// the user belongs to.</returns>public OrganizationDetailCollection DiscoverOrganizations(
    IDiscoveryService service)
{
    if (service ==null) thrownew ArgumentNullException("service");
    RetrieveOrganizationsRequest orgRequest =new RetrieveOrganizationsRequest();
    RetrieveOrganizationsResponse orgResponse =
        (RetrieveOrganizationsResponse)service.Execute(orgRequest);

    return orgResponse.Details;
}

///<summary>/// Finds a specific organization detail in the array of organization details
/// returned from the Discovery service.
///</summary>///<param name="orgFriendlyName">The friendly name of the organization to find.</param>///<param name="orgDetails">Array of organization detail object returned from the discovery service.</param>///<returns>Organization details or null if the organization was not found.</returns>///<seealso cref="DiscoveryOrganizations"/>public OrganizationDetail FindOrganization(string orgFriendlyName,
    OrganizationDetail[] orgDetails)
{
    if (String.IsNullOrWhiteSpace(orgFriendlyName))
        thrownew ArgumentNullException("orgFriendlyName");
    if (orgDetails ==null)
        thrownew ArgumentNullException("orgDetails");
    OrganizationDetail orgDetail =null;

    foreach (OrganizationDetail detail in orgDetails)
    {
        if (String.Compare(detail.FriendlyName, orgFriendlyName,
            StringComparison.InvariantCultureIgnoreCase) ==0)
        {
            orgDetail = detail;
            break;
        }
    }
    return orgDetail;
}

///<summary>/// Generic method to obtain discovery/organization service proxy instance.
///</summary>///<typeparam name="TService">/// Set IDiscoveryService or IOrganizationService type to request respective service proxy instance.
///</typeparam>///<typeparam name="TProxy">/// Set the return type to either DiscoveryServiceProxy or OrganizationServiceProxy type based on TService type.
///</typeparam>///<param name="serviceManagement">An instance of IServiceManagement</param>///<param name="authCredentials">The user's Microsoft Dynamics CRM logon credentials.</param>///<returns></returns>private TProxy GetProxy<TService, TProxy>(
    IServiceManagement<TService> serviceManagement,
    AuthenticationCredentials authCredentials)
    where TService : classwhere TProxy : ServiceProxy<TService>
{
    Type classType =typeof(TProxy);

    if (serviceManagement.AuthenticationType !=
        AuthenticationProviderType.ActiveDirectory)
    {
        AuthenticationCredentials tokenCredentials =
            serviceManagement.Authenticate(authCredentials);
        // Obtain discovery/organization service proxy for Federated, LiveId and OnlineFederated environments.
        // Instantiate a new class of type using the 2 parameter constructor of type IServiceManagement and SecurityTokenResponse.return (TProxy)classType
            .GetConstructor(new Type[] { typeof(IServiceManagement<TService>), typeof(SecurityTokenResponse) })
            .Invoke(newobject[] { serviceManagement, tokenCredentials.SecurityTokenResponse });
    }

    // Obtain discovery/organization service proxy for ActiveDirectory environment.
    // Instantiate a new class of type using the 2 parameter constructor of type IServiceManagement and ClientCredentials.return (TProxy)classType
        .GetConstructor(new Type[] { typeof(IServiceManagement<TService>), typeof(ClientCredentials) })
        .Invoke(newobject[] { serviceManagement, authCredentials.ClientCredentials });
}

 

You’ll need to resolve some references once you paste this code in.

10, In Service1.svc we need to add a few class variables that we’ll set…

private String _discoveryServiceAddress ="https://dev.crm.dynamics.com/XRMServices/2011/Discovery.svc";
private String _organizationUniqueName ="";
// Provide your user name and password.private String _userName ="Live Id";
private String _password ="Password";

// Provide domain name for the On-Premises org.private String _domain ="mydomain";

Set the values for your Organization name, and the Live Id and password you want to connect as.

11, Add in the method that was defined in our interface:

public IEnumerable<BcsSalesOrder> GetRecentOrders()
{

}

12, Add in the following code to this method:

List<BcsSalesOrder> orders =new List<BcsSalesOrder>();

IServiceManagement<IDiscoveryService> serviceManagement =
            ServiceConfigurationFactory.CreateManagement<IDiscoveryService>(
            new Uri(_discoveryServiceAddress));
AuthenticationProviderType endpointType = serviceManagement.AuthenticationType;

// Set the credentials.AuthenticationCredentials authCredentials = GetCredentials(endpointType);


String organizationUri = String.Empty;
// Get the discovery service proxy.using (DiscoveryServiceProxy discoveryProxy =
    GetProxy<IDiscoveryService, DiscoveryServiceProxy>(serviceManagement, authCredentials))
{
    // Obtain organization information from the Discovery service. if (discoveryProxy !=null)
    {
        // Obtain information about the organizations that the system user belongs to.        OrganizationDetailCollection orgs = DiscoverOrganizations(discoveryProxy);
        // Obtains the Web address (Uri) of the target organization.        organizationUri = FindOrganization(_organizationUniqueName,
            orgs.ToArray()).Endpoints[EndpointType.OrganizationService];

    }
}


if (!String.IsNullOrWhiteSpace(organizationUri))
{
    IServiceManagement<IOrganizationService> orgServiceManagement =
        ServiceConfigurationFactory.CreateManagement<IOrganizationService>(
        new Uri(organizationUri));

    // Set the credentials.    AuthenticationCredentials credentials = GetCredentials(endpointType);

    // Get the organization service proxy.using (OrganizationServiceProxy organizationProxy =
        GetProxy<IOrganizationService, OrganizationServiceProxy>(orgServiceManagement, credentials))
    {
        // This statement is required to enable early-bound type support.        organizationProxy.EnableProxyTypes();

        var service = (IOrganizationService)organizationProxy;
        OrganizationServiceContext orgContext =new OrganizationServiceContext(service);

        IEnumerable<SalesOrder> crmOrders = from s in orgContext.CreateQuery<SalesOrder>()
                                            where s.DateFulfilled.Value > DateTime.Now.AddMonths(-1)
                                            select s;

        foreach (var salesOrder in crmOrders)
        {

            var bcsOrder =new BcsSalesOrder();
            bcsOrder.SalesOrderId = (Guid)salesOrder.SalesOrderId;
            bcsOrder.CustomerName = salesOrder.CustomerId.Name;
            bcsOrder.OrderDate = Convert.ToDateTime(salesOrder.DateFulfilled);
            bcsOrder.TotalAmount = salesOrder.TotalAmount.Value;

            orders.Add(bcsOrder);
        }

    }
}

return orders;

13, We can now test our WCF service to make sure it can return some data from CRM Online. Press F5 in Visual Studio and once the browser opens copy out the url from the address bar. Then open a Visual Studio command prompt from the start menu and type wcftestclient.

14, Once the WCF Test Client opens up go File –> Add a Service, and paste in your url

15, Click on the GetThisMonthsOrders from the tree view in the right Window and then click the Invoke button. This will try to execute your WCF method and if successful you should see data returned in the bottom window:

SNAGHTML41c7e1

If you get an error returned, you should be able to step through the code in Visual Studio and fix it. If you do not get any data returned, make sure you have some orders in CRM that have been fulfilled in the last month.

16, Now we can deploy this to Azure. Right click on the Azure project in Visual Studio and click Package

image

You will notice when you do this you get a warning about Microsoft.IdentityModel.dll being required. This is because microsoft.xrm.sdk has a dependancy on it. Follow the instructions in the Output console to add it as a reference to your project and also set it to ‘copy local’

17, Once the Service Package file and Cloud Service Configuration File have been produced you can deploy this to Azure.

18, Once your project has been deployed it is always a good thing to test it with the WcfTestClient again.

Getting the data into SharePoint Online

1, If you are at this stage then great! We can now get our data displaying in SharePoint Online. Open up SharePoint Designer and connect to your SharePoint Online site

2, Click on the External Content Types button in the Site Objects listing

image

3, Click the button in the ribbon to create a new External Content Type

4, Give your External Content Type a nice name such as Orders, and then click on the link to use the Operation Designer:

image

5, Click the button to ‘Add a connection’ and choose the type to be WCF Service

6, When viewing your service1.svc file running on Azure, you should see a link to the wsdl file. It’s basically the same as service1.svc but with ?wsdl on the end. Copy the whole url out and paste it into the Service Metadata Url field in SPD

7, Put the normal service1.svc full url into Service Endpoint URL, and you can then click OK:

SNAGHTML941779

8, With the Service Endpoint now displayed in the SPD connection manager, expand the tree so you can see the method we created and right click on it. Choose ‘New Read List Operation’ and step through the wizard. You do not actually need to change anything in the steps to get our simple example working except on the third screen select SalesOrderId as the Identifier for this ECT

image

9, Once the Operation Designer wizard has finished, click the Save button in the top left of SPD and your External Content Type will be created!

10, The final step! Woot! Log on to SharePoint Online and add a Business Data List web part to the page. Configure it to use your Orders External Content Type:

image

11, Click OK in the web part properties, and you should see Orders from your CRM system from the last 30 days (that the CRM users credentials you used in your WCF service have permission to see)

image

 

Authentication

You should be asking yourself right now – but this WCF web service is publically available and so anyone could call it and see my CRM data – and you are completely correct. As a ‘how-to’ article we are leaving things here, but if you were to be following this and using it as a basis for moving something into production you definitely need to consider how you will secure the WCF end point so only SharePoint Online and the BCS can call it. Steve Fox has a good blog post and a link to a lab to go through the different options around this, this should definitely be your next step:

SharePoint Online and Windows Azure: Developing Secure BCS Connections using WCF Services

Conclusion

Hopefully this post has given you a good introduction of the steps needed to get data from CRM 2011 Online displayed in SharePoint Online through the Business Connectivity Services. There are a few steps required to get this working, but the good news is we are working on some tools and services to make this a whole lot easier. If you’d like more information on these tools and be an early beta tester please drop me an email : nick@lightningtools.com

<nick/>