A better SharePoint Connector – part 8: Finally!

This article is the last in the series of the hunt for a better SharePoint Connector. It has been quite a rough journey.
In the previous article “A better SharePoint Connector – part 7: leftovers” I described the remaining functionality I had to implement. This article will describe the functionality you have to implement in order to get a working plugin.

The plugin steps that need to be registered are:

  • Retrieve (Post)
  • RetrieveMultiple (Post)
  • Create (Post)
  • Delete (Post)
    This step required a pre entity image (field: documentbody)

Inside the Execution() method of the plugin, I delegate the functionality based on the incoming message name. I also check if the plugin is executing on the Post Operation stage in the execution context (stage==40). 

Delegation inside Execute()
Switch to the required function.

 switch (context.MessageName.ToUpper())
                    {
                        case "RETRIEVE":
                            if (context.Stage==Plugin.LocalPluginContext.POSTOPERATION)  Retrieve(target, context);
                            break;
                        case "RETRIEVEMULTIPLE":
                            if (context.Stage==Plugin.LocalPluginContext.POSTOPERATION)  RetrieveMultiple(targetColl, context);
                            break;
                        case "CREATE":
                            if (context.Stage == Plugin.LocalPluginContext.POSTOPERATION) Create(target, localcontext);
                            break;
                        case "DELETE":
                            if (context.Stage == Plugin.LocalPluginContext.POSTOPERATION) Delete(targetReference, context);
                            break;
                    }

Retrieve / Retrieve Multiple
Basically the same functionality. The main difference is that the Retrieve Multiple will iterate through an EntityCollection and will omit the processing of the documentbody.

private void Retrieve(Entity target, IPluginExecutionContext context)
{
ModifyRecordForRetrieval(target,true);
}

private void RetrieveMultiple(EntityCollection resultCollection, IPluginExecutionContext context)
{
if (resultCollection != null)
{
foreach (Entity result in resultCollection.Entities)
ModifyRecordForRetrieval(result, false);
}
}

private void ModifyRecordForRetrieval(Entity target, bool retrieveDocumentBody)
{
bool isDocument = false;
string fileName = null;
string documentBody = null;

isDocument = target.Contains("isdocument") ? (target.GetAttributeValue<string>("isdocument") == "1") : false;
fileName = target.Contains("filename")? target.GetAttributeValue<string>("filename"): null;
documentBody = target.Contains("documentbody")?  target.GetAttributeValue<string>("documentbody"): null;

if (isDocument)
{
if (!string.IsNullOrEmpty(fileName) && !string.IsNullOrEmpty(documentBody))
{
// make a friendly filename
fileName = fileName.Trim();
if (fileName.EndsWith(".url", StringComparison.InvariantCultureIgnoreCase))
{
target.Attributes["filename"] = fileName.Remove(fileName.Length - 4);
}
}

if (retrieveDocumentBody)
{

// using a different constructot gives you access to sharepoint online
SharePointConnector connector = new SharePointConnector(LocalPluginContext.SITEURL);

// strip the sharepoint url reference from the .url file in the documentBody
string[] tokens = UnicodeEncoding.Unicode.GetString(Convert.FromBase64String(documentBody)).Split('\n');

foreach (string token in tokens)
{
if (token.Trim().StartsWith("URL=", StringComparison.InvariantCultureIgnoreCase))
{
documentBody = token.Trim().Remove(0, 4);
break;
}
}

// get the file from SharePoint
byte[] fileBytes = connector.GetFile(documentBody);
target.Attributes["documentbody"] = Convert.ToBase64String(fileBytes);
}
}
}

Create
Move the stored documentbody as a document to SharePoint and get the unique url of the document and store it as a .URL file inside the documentbody. Furthermore alter the file name. Enrich the SharePoint document with meta data.

private void Create(Entity target, LocalPluginContext context)
{
bool isDocument = false;
string fileName = null;
string documentBody = null;

isDocument = target.Contains("isdocument") ? (target.GetAttributeValue<string>("isdocument") == "1") : false;
fileName = target.Contains("filename") ? target.GetAttributeValue<string>("filename") : null;
documentBody = target.Contains("documentbody") ? target.GetAttributeValue<string>("documentbody") : null;

if (isDocument)
{
if (!string.IsNullOrEmpty(fileName) && !string.IsNullOrEmpty(documentBody))
{
// make a friendly filename
fileName = fileName.Trim();
byte[] fileBytes = Convert.FromBase64String(documentBody);
// using a different constructot gives you access to sharepoint online
SharePointConnector connector = new SharePointConnector(LocalPluginContext.SITEURL);

// build the dictionary to pass the values to be stored inside the list item
Dictionary<string, object> dict = new Dictionary<string, object>();
dict.Add("MoreText", "Some more text");
dict.Add("Description0", "A very long description spanning multiple lines");
dict.Add("SomeNumber", 13);
dict.Add("MyChoice", "Keuze 3");

// add the file to SharePoint, return the unique name
string sharepointfileName = connector.AddFile(fileName, LocalPluginContext.DOCLIBURL, fileBytes ,dict);
// store the sharepoint file url in a new .url file
documentBody = string.Format("[InternetShortcut]\r\nURL={0}\r\n", sharepointfileName);

target.Attributes["documentbody"] = Convert.ToBase64String(Encoding.UTF8.GetBytes(documentBody));

context.OrganizationService.Update(target);
}
}
}

Delete
After deleting the file from CRM, the file will also be deleted from SharePoint.

private void Delete(EntityReference targetreference, IPluginExecutionContext context)
{
Entity entity = context.PreEntityImages["annotation"];
if (entity != null)
{
string documentBody = documentBody = entity.Contains("documentbody") ? entity.GetAttributeValue<string>("documentbody") : null;

if (!string.IsNullOrEmpty(documentBody))
{
string[] tokens = UnicodeEncoding.Unicode.GetString(Convert.FromBase64String(documentBody)).Split('\n');

foreach (string token in tokens)
{
if (token.Trim().StartsWith("URL=", StringComparison.InvariantCultureIgnoreCase))
{
documentBody = token.Trim().Remove(0, 4);
break;
}
}

SharePointConnector connector = new SharePointConnector(LocalPluginContext.SITEURL);
connector.DeleteFile(documentBody);
}
}
}

Some final thoughts
The code snippets I provided need a little bit TLC before it can be used as production code:

  • Call the correct SharePoint Connector constructor (online and on-premise have a different constructor). This should be controlled by a settings object.
  • List of meta data fields to be added should be configurable. For the sake of the example I implemented a fixed set based on the document library I use on SharePoint.

As I showed in part 5 of this series, SharePoint is very picky about the fieldnames you are sending to it. You have to use the internal name (which often differs from the display name). In case you are not able to send a document to SharePoint, a misspelling is probably the cause. Internal field names are case sensitive!

Happy coding!

Leave a Reply

Your email address will not be published. Required fields are marked *