ENGINEERED CODE BLOG

Adding SharePoint Integration to the Employee Self-Service Portal Without Server-side Code – Part 4

In my fourth and final post in the series on how you can add an integration with SharePoint to the Employee Self-Service Portal in Dynamics 365 without using server-side code, I’ll show you how you can add the ability to upload a file from the Portal directly into SharePoint.

The Challenge

In my last few posts, I’ve described how you can add a list of documents that are in a SharePoint folder related to a record on a Portal for Dynamics 365 Entity Form, under the assumption that the user logs into the Portal with Azure AD, and their account also has SharePoint access. Now, we want to give the user on the portal the ability to upload a file.

At first glance, if you know the SharePoint REST API, you’d think this would be really straight-forward. And it is, if you assume that the SharePoint folder has already been created and the Document Location record in Dynamics has been setup. However, for new records, this is not the case out-of-the-box. But first, let’s cover the code that does the uploading, assuming the folder already exists and is linked to the Dynamics record.

Code to Upload a New File

Like getting a listing of all files in a SharePoint folder, uploading a new file to the folder can be done in a single request, by making a POST request to the following URL, with the contents of the file in the body:

http://site url/_api/web/GetFolderByServerRelativeUrl('/Folder Name')/Files/add(url='filename.txt',overwrite=true)

With the JavaScript File API making our lives a lot easier than it used to be when it comes to dealing with file inputs, it’s not really too hard to allow the user to select a file, get the contents of that file, and to upload to SharePoint. Here is a modified version from Part 3 of this series that does it:

$(document).ready(function() {  
    DynPCA.UserId = '{{user.emailaddress1}}';
    DynPCA.Auth.Params = {
        ClientId: 'ccbf02d7-6a9e-4d85-b9be-b359113b2a83',
        Tenant: 'b7d0e39e-887b-41af-93ab-53328f41531f',
        RedirectUri: window.location.protocol + "//" + window.location.host,
        ApiScopes: '{{sharepointdomain}}',
    };
    DynPCA.Auth.Init();  
    refreshSharePointTable();    
});

function refreshSharePointTable() {
    DynPCA.App.ExecuteRequest(
        'GET', 
        null, 
        'json', 
        true,         
        "{{sharepointsiteurl}}/_api/web/getfolderbyserverrelativeurl('{{url}}')/files", 
        { "Content-Type": "application/json", "Access-Control-Allow-Origin": "*" })
    .done(function (data) {
        var sharePointDiv = $('.sharePointDocuments');        
        if(sharePointDiv.length == 0) sharePointDiv = $('<div class="sharePointDocuments">').insertAfter('.entity-form');
        sharePointDiv.empty();
        
        var table = $('<table class="table table-striped sharePointTable">').appendTo(sharePointDiv);
        table.append("<tr><th>Document</th><th></th></tr>");

        $.each(data.value, function(i, e) {
            table.append("<tr><td>" + e.Name + "</td><td><a target='_blank' href='https://ecjul18.sharepoint.com" + e.ServerRelativeUrl + "'>View</a></td></tr>");
        });
        var fileInput = $('<input type="file"/>').appendTo(sharePointDiv);
        $('<button type="button" class="btn btn-default">UploadFile</button>').appendTo(sharePointDiv).click(function() {
            var file = fileInput[0].files[0]
            if (file) {  
                fr = new FileReader();
                fr.onload = function() {
                    DynPCA.App.ExecuteRequest(
                        'POST', 
                        fr.result, 
                        'json', 
                        false, 
                        "{{sharepointsiteurl}}/_api/web/getfolderbyserverrelativeurl('{{url}}')/files/add(url='" + file.name + "',overwrite=true)", 
                        { "Content-Type": "application/json", "Access-Control-Allow-Origin": "*" })
                    .done(function (data) {
                        refreshSharePointTable();
                    }).fail(function (jqXHR, textStatus, errorThrown) {
                        alert(errorThrown);
                    });    
                };
                fr.readAsArrayBuffer(file);
            } else {
                alert('You must select a file!');
            }
        });        
    }).fail(function (jqXHR, textStatus, errorThrown) {
        alert(errorThrown);
    });    
}

I’ve added a button that, when clicked, will read the contents of the file selected in the file input, and make the request to SharePoint to create the file. Once complete, the table is refreshed and displays the newly updated list of files. Again, as I mentioned in Part 3, the UI is quite rudimentary, but functionally it achieves what we’re hoping to do.

Before you try this, you need to add another SharePoint permission to your App Registration in Azure AD – selecting Read and write items in all site collections and clicking Grant Permissions should do the trick.

You can get the full Web Template code here.

In addition to a more polished UI, another nice enhancement would be uploading large files in chunks. We’ve done it on a similar type of project – if you’re interested check out the this Stack Overflow thread as it gives you a great head start.

But What If The Folder Doesn’t Exist?

As I mentioned, the above code assumes that a SharePoint folder exists, and is associated to the record using a Document Location. However, even if you’ve properly configured your Dynamics 365/SharePoint integration, in most cases, this may not be true. This is because, by default, Dynamics does not create the SharePoint folder or the Document Location until a user browses to the Documents area for that particular record in the Dynamics 365 interface. This means that when a portal user creates a case, more than likely no SharePoint folder will exist yet, and thus our functionality won’t work.

To work around this, we have a few options:

  • You could use a workflow on the create of the entity to create the Document Location. However, this will not create the corresponding folder in SharePoint. To do that, you could in theory write a custom plugin that uses the SharePoint REST API.
  • I found this blog post describing how to create Document Locations and SharePoint folders using Flow.
  • It sounds like there may be a commercially-available product that contains a custom workflow activity that could accomplish exactly what we’re looking to do. I haven’t used it, but it may be worth a try: DocumentsCorePack from mscrm-addons.com

If you have any other creative solutions to this problem, please let me know in the comments below!

That brings this series to a close. Soon we’ll be able to try the out-of-the-box SharePoint integration with Portals, but until then, I hope this helps!

One response to “Adding SharePoint Integration to the Employee Self-Service Portal Without Server-side Code – Part 4”

Leave a Reply

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

Contact

Engineered Code is a web application development firm and Microsoft Partner specializing in web portals backed by Dynamics 365. Led by a professional engineer, our team of technology experts are based in Regina, Saskatchewan, Canada.