Monday, May 21, 2012

Integrating with Sharepoint

With the new built in integration of sharepoint list mode, crm offers the capability to integrate with sharepoint. However we are are often stuck with the builtin tool to create teh sharepoint folders which are not effective. So I am presenting a solution to automate the integration. In my previous post we have created a plugin to generate autonumber. We will use this to create the sharepoint folder.

Remember
1. Update the sharepoint site URL
2. If you want to use the defaults, update the GUID from your database
3. Remember to use the entity names as display names in sharepoint site

Assumption
1. Sharepoint site already created
2. Document libraries are created for each entity in CRM that integrated with sharepoint
(Created by default when you enable storing documents in shrepoint)


The below code will generate the sharepoint structure as below
spsite/entity/unique-identifier

If the folder already exists in sharepoint, it just links the folder in CRM else it creates the folder in Sharepoint and then created the document locations in CRM. You can customize in the code on which fileld to be used for the document location "GetSharepointAttribute(string EntityName)". Update this function to reflect your needs



using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xrm.Sdk;
using System.Xml;
using Microsoft.Xrm.Sdk.Query;
using Microsoft.Xrm.Sdk.Client;
using System.Net;

namespace SharepointIntegration
{
    public class SharepointIntegration : IPlugin
    {
        string debugText = "";
        public void Execute(IServiceProvider serviceProvider)
        {
            debugText = "Inside Execute \n";
            System.Threading.Mutex mtx = null;
            try
            {
                Microsoft.Xrm.Sdk.IPluginExecutionContext context = (Microsoft.Xrm.Sdk.IPluginExecutionContext)
                    serviceProvider.GetService(typeof(Microsoft.Xrm.Sdk.IPluginExecutionContext));
                if (context.InputParameters.Contains("Target") &&
                context.InputParameters["Target"] is Entity)
                {
                    Entity entity = (Entity)context.InputParameters["Target"];
                    debugText += "Inside if loop - target \n";

                    IOrganizationServiceFactory serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
                    IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId);


                    Entity spSite = new Entity("sharepointsite");
                    Entity spEntDocLoc = new Entity("sharepointdocumentlocation");
                    Entity spDocLoc = new Entity("sharepointdocumentlocation");
                    EntityCollection result = null;
                    string defaultSharepointSite = "http://spserver:8054/crm/documents";
                    string spSiteId = "8CAD9F38-0C4C-E111-9138-00155D01750A";
                    string defaultSite = "Default Site";
                    string defaultDocumentLocation = "";
                    string defaultEntityLocation = entity.LogicalName;
                    string fetchXmlTemplate = "", docLocAttr = "";
                    string absoluteURL = "";
                    bool useDefaults = false;

                    Guid _spSiteId, _spDocEntId, _spDocId;

                    defaultDocumentLocation = GetSharepointAttribute(entity.LogicalName);
                    docLocAttr = entity.GetAttributeValue<string>(defaultDocumentLocation);

                    if (docLocAttr == "" || docLocAttr == null || docLocAttr == "null")
                    {
                        throw new InvalidPluginExecutionException("Document location attribute is null");
                    }

                    absoluteURL = defaultSharepointSite + "/" + defaultEntityLocation + "/" + docLocAttr;
                    debugText += absoluteURL;

                    CreateSharePointFolder(absoluteURL);

                    fetchXmlTemplate = @"<fetch mapping='logical'>
                                                <entity name='[ENTITY_NAME]'><all-attributes/>
                                                    <filter type=""and"">
                                                            <condition attribute=""[ATTR_NAME]"" operator=""eq"" value='[ATTR_VALUE]'/></filter></entity></fetch>";

                    if (useDefaults)
                    {
                        _spSiteId = new Guid(spSiteId);
                        _spDocEntId = new Guid(GetDocEntId(entity.LogicalName));
                    }
                    else
                    {
                        debugText = debugText + "Before executing fetch \n";
                        result = GetResults(fetchXmlTemplate, "sharepointsite", "name", defaultSite, service);
                        debugText = debugText + "After executing fetch \n";
                        if (result.Entities.Count == 0)
                        {
                            debugText = debugText + "inside the if loop - sharepointsite create \n";

                            spSite.Attributes.Add("name", defaultSite);
                            spSite.Attributes.Add("description", "Default Sharepoint Location");
                            spSite.Attributes.Add("absoluteurl", defaultSharepointSite);
                            // Create a SharePoint site record named Sample SharePoint Site.
                            _spSiteId = service.Create(spSite);
                        }
                        else if (result.Entities.Count == 1)
                        {
                            debugText = debugText + "inside the if loop - sharepointsite found \n";
                            spSite = result.Entities.First();
                            _spSiteId = result.Entities.First().Id;
                        }
                        else
                        {
                            throw new InvalidPluginExecutionException("Multiple sharepoint locations are found");
                        }

                        debugText = debugText + "GUID for sharepoint site " + spSite.LogicalName + " is " + _spSiteId.ToString();
                        result = GetResults(fetchXmlTemplate, "sharepointdocumentlocation", "name", entity.LogicalName, service);
                        debugText = debugText + "After executing fetch for sharepoint document location\n";
                        if (result.Entities.Count == 0)
                        {
                            debugText += "inside the if loop - sharepoint document site create \n";
                            spEntDocLoc.Attributes.Add("name", entity.LogicalName);
                            spEntDocLoc.Attributes.Add("description", "Default SharePoint Document Location record for " + entity.LogicalName);
                            // Set the Sample SharePoint Site created earlier as the parent site.
                            spEntDocLoc.Attributes.Add("parentsiteorlocation", new EntityReference("sharepointsite", _spSiteId));
                            spEntDocLoc.Attributes.Add("relativeurl", entity.LogicalName);
                            debugText += "before sharepoint document site create \n";
                            _spDocEntId = service.Create(spEntDocLoc);
                            debugText += "after sharepoint document site create \n";
                        }
                        else
                        {
                            spEntDocLoc = result.Entities.First();
                            _spDocEntId = spEntDocLoc.Id;

                        }
                    }
                    debugText = debugText + "outside the if loop - sharepoint document site create \n";
                    result = GetResults(fetchXmlTemplate, "sharepointdocumentlocation", "name", docLocAttr, service);
                    debugText = debugText + "After executing fetch \n";
                    if (result.Entities.Count == 0)
                    {
                        debugText = debugText + "inside the if loop - sharepointsite create \n";
                        spDocLoc.Attributes.Add("name", docLocAttr);
                        spDocLoc.Attributes.Add("description", "Default SharePoint Document Location record for " + docLocAttr);
                        // Set the Sample SharePoint Site created earlier as the parent site.
                        spDocLoc.Attributes.Add("parentsiteorlocation", new EntityReference("sharepointdocumentlocation", _spDocEntId));
                        spDocLoc.Attributes.Add("relativeurl", docLocAttr);
                        // Associate this document location instance with the entity record
                        spDocLoc.Attributes.Add("regardingobjectid", new EntityReference(entity.LogicalName, entity.Id));
                        _spDocId = service.Create(spDocLoc);

                    }


                }
            }
            catch (Exception ex)
            {
                if (mtx != null)
                {
                    mtx.ReleaseMutex();
                    mtx = null;
                }
                throw new InvalidPluginExecutionException("An error occured in SharepointIntegration plugin" + ex + "\n" + debugText);

            }
            finally
            {
                if (mtx != null)
                {
                    mtx.ReleaseMutex();
                    mtx = null;
                }
                debugText = null;
            }
        }

        private string GetDocEntId(string EntityName)
        {
            switch (EntityName)
            {
                case "account":
                    return "F379A2D4-8A9A-E111-8459-00155D01750A";
                case "opportunity":
                    return "642D28B5-479B-E111-8459-00155D01750A";
                default:
                    return "";
            }
        }

        private EntityCollection GetResults(string FetchXML, string EntityName, string AttrName, string AttrValue, IOrganizationService service)
        {
            FetchXML = FetchXML.Replace("[ENTITY_NAME]", EntityName);
            FetchXML = FetchXML.Replace("[ATTR_NAME]", AttrName);
            FetchXML = FetchXML.Replace("[ATTR_VALUE]", AttrValue);
            debugText = debugText + FetchXML;
            return service.RetrieveMultiple(new FetchExpression(FetchXML));
        }

        private static string GetSharepointAttribute(string EntityName)
        {
            switch (EntityName)
            {
                case "account":
                    return "accountid";
                case "opportunity":
                    return "name";
                default:
                    return "";
            }
        }

        private static void CreateSharePointFolder(string docfolderUrl)
        {
            if (docfolderUrl == String.Empty || docfolderUrl.IndexOf("/") == -1)
            {
                return;
            }
            try
            {
                // last part is the folder name
                string folderName = docfolderUrl.Substring(docfolderUrl.LastIndexOf("/") + 1);
                // remove the folder name
                docfolderUrl = docfolderUrl.Replace("/" + folderName, "");
                // get the document libray name
                string docLib = docfolderUrl.Substring(docfolderUrl.LastIndexOf("/") + 1);
                // now remove the doc lib to leave the sharepoint site url
                string sharePointSiteUrl = docfolderUrl.Replace("/" + docLib, "");

                SharepointList.Lists myLists = new SharepointList.Lists();
                SharepointView.Views myViews = new SharepointView.Views();

                myLists.Url = sharePointSiteUrl + "/_vti_bin/lists.asmx";
                myViews.Url = sharePointSiteUrl + "/_vti_bin/views.asmx";
                myLists.UseDefaultCredentials = true;
                myViews.UseDefaultCredentials = true;

                XmlNode viewCol = myViews.GetViewCollection(docLib);
                XmlNode viewNode = viewCol.SelectSingleNode("*[@DisplayName='All Documents']");
                string viewName = viewNode.Attributes["Name"].Value.ToString();

                /*Get Name attribute values (GUIDs) for list and view. */
                System.Xml.XmlNode ndListView = myLists.GetListAndView(docLib, viewName);

                /*Get Name attribute values (GUIDs) for list and view. */
                string strListID = ndListView.ChildNodes[0].Attributes["Name"].Value;
                string strViewID = ndListView.ChildNodes[1].Attributes["Name"].Value;
                // load the CAML query
                XmlDocument doc = new XmlDocument();
                string xmlCommand;
                xmlCommand = "<Method ID='1' Cmd='New'><Field Name='FSObjType'>1</Field><Field Name='BaseName'>" + folderName + "</Field> <Field Name='ID'>New</Field></Method>";
                XmlElement ele = doc.CreateElement("Batch");
                ele.SetAttribute("OnError", "Continue");
                ele.SetAttribute("ListVersion", "1");
                ele.SetAttribute("ViewName", strViewID);

                ele.InnerXml = xmlCommand;

                XmlNode resultNode = myLists.UpdateListItems(strListID, ele);

                // check for errors
                NameTable nt = new NameTable();
                XmlNamespaceManager nsmgr = new XmlNamespaceManager(nt);
                nsmgr.AddNamespace("tns", "http://schemas.microsoft.com/sharepoint/soap/");
                if (resultNode != null)
                { // look for error text in case of duplicate folder or invalid folder name
                    XmlNode errNode = resultNode.SelectSingleNode("tns:Result/tns:ErrorText", nsmgr);
                    if (errNode != null)
                    {
                        // Write error to log;
                    }
                }

            }
            catch (Exception ex)
            {
                throw ex;
            }
        }


    }
}

6 comments:

Zarour said...

Thank you
but I have a null value when callin
XmlNode viewNode = viewCol.SelectSingleNode("*[@DisplayName='All Documents']");
string viewName = viewNode.Attributes["Name"].Value.ToString();

can you please help me ?

Unknown said...

HI,

use this below tool to track the request sent and received. See if you are receiving a proper xml back from sharepoint
http://webservicestudio.codeplex.com/

Mike Kristensen said...

Will this work with CRM and Sharepoint via Office 365? When trying to develop this solution on my local machine I get error related to 'SharepointLists'. Thanks!

Mike Kristensen said...
This comment has been removed by the author.
Unknown said...

Please check the language and the list name. It's different in different languages. Use the webservice studio to track the request and response and you can see the list name from the response and then use that list name in the code.

Unknown said...

Great article. Using this plugin approach, I'm trying to find a way to create new a document (say a Word or Excel file), and subsequenntly store/save the file in the SharePoint location assosiated with the active Entity (say Account or Opportunity). I'm struggling with finding a suitable angle on how to save the document to SharePoint from inside the Dynamics CRM plugin. Our environment is Office365 and Dynamics CRM 2015 (Online) with server to server integration enabled.