Update Document properties from D365

This is in continuation to my previous blog where I discussed how to display custom document properties from SharePoint on Associated Documents Grid in D365. Here is the link to the blog.

In this blog I am going to list out how to update these properties from D365.

On updating Associated grid with the new fields, business users were happy as they can see metadata for each document uploaded against the case record. However, this resulted in users navigating to SharePoint to update these properties manually whenever any document gets uploaded to SharePoint. Hence the requirement to allow D365 users to update these document properties directly from D365.

There are multiple ways to achieve this functionality. The method I chose is to create a Canvas app on SharePoint Document library and invoke that app from a ribbon button on the document grid.

Let’s start with Canvas App. I am not detailing how to create canvas app. Instead I just detail what I did. I created a 3 form Canvas app where data source set to Case library on SharePoint. With in the case document library my goal is to update a specific document’s properties. So, I need to pass the selected document from Dynamics to SharePoint. This can be achieved via query string parameters.

Once you connect to SharePoint document library, you need to edit the formula to accept query string parameters.


Filter(Case, ‘Folder path’=Coalesce(Param(“DocLocation”),”incident/21Jun21-Testing-1_78B03811874D4530B0765A4EC719DBBA/”) && ‘File name with extension’ = Coalesce(Param(“fileName”),”SP_UploadDoc.docx”))

As you see, I have used Coalesce method which evaluates its arguments and returns the first value that isn’t blank. In the above formula, I a filtering the case document library with specific case and specific document. So on load of the app, it loads specific document related properties from the case identified.

On the next page I am allowing user to add/modify the properties and give them an option to submit. And the last page is a success message


Now that the Canvas app is ready and tested, how would I invoke this form Dynamics 365 Case Entity. For this I created a ribbon button on the SharePoint Document entity on the sub grid. On click of this I invoked couple of JavaScript methods to call a HTML page.

Retrieve default site details from D365. This step will make sure script we write is not environment depenedent.

function GetDefaultSite() {
    return new Promise(function (resolve, reject) {
        Xrm.WebApi.online.retrieveMultipleRecords("sharepointsite", "?$select=absoluteurl").then(
            function success(results) {
                for (var i = 0; i < results.entities.length; i++) {
                    resolve(results.entities[i]["absoluteurl"]);
                }
            },
            function (error) {
                reject(error.message);
            }
        );
    });
}

Retrieve document location from D365. As each case gets unique relative URL, it is better we retrieve and use it to avoid environmental dependencies.

function GetDocumentLocaiton(caseid) {
    return new Promise(function (resolve, reject) {
        Xrm.WebApi.online.retrieveMultipleRecords("sharepointdocumentlocation", "?$select=relativeurl&$filter=_regardingobjectid_value eq " + caseid).then(
            function success(results) {
                for (var i = 0; i < results.entities.length; i++) {
                    resolve(results.entities[i]["relativeurl"]);
                }
            },
            function (error) {
                reject(error.message);
            }
        );
    });
}

Each power app gets a unique id and tenant id which can be stored in Settings entity in Dynamics. This can be retrieved and appended to the base URL of the app. i.e. ” https://apps.powerapps.com/play/appId?tenantId&#8221;. retrieve the case selected and the document selected and create parameters and prepare the final URL to invoke the Canvas app. Store the final URL in session Storage so we can get it on the HTML page onload method.

async function UpdateMetadata(selectedItems, primaryControl) {
    let formContext = primaryControl;
    if (formContext.data.entity.getEntityName() === "incident") {
        var selectedItem = selectedItems[0];
        var recordid = formContext.data.entity.getId().replace('{', '').replace('}', '');
        var caseName = await GetDocumentLocaiton(recordid);
        var docLoc = "incident/" + caseName + "/";
        var fileName = selectedItem.Name;
        var setResults = await GetAppSettings("UpdateMetadata");
        var appId;
        var tenantId;
        for (var i = 0; i < setResults.entities.length; i++) {
            var mad_configuration = setResults.entities[i]["mad_configuration"];
            var mad_name = setResults.entities[i]["mad_name"];
            if (mad_name === "CanvasAppId") {
                appId = mad_configuration;
            }
            else if (mad_name === "TenantId") {
                tenantId = mad_configuration;
            }
        }

        var powerAppUrl = "https://apps.powerapps.com/play/" + appId + "?tenantId=" + tenantId;
        var parameters = "?DocLocation=" + docLoc + "&fileName=" + fileName;
        var finalUrl = powerAppUrl + parameters;
        sessionStorage.setItem("finalURL", finalUrl);
        var pageInput = {
            pageType: "webresource",
            webresourceName: "mad_EmbedCanvasApp.htm"
        };
        var navigationOptions = {
            target: 2,
            width: 600, // value specified in pixel
            height: 980, // value specified in pixel
            position: 1,
            title: "Update Document properties"
        };
        Xrm.Navigation.navigateTo(pageInput, navigationOptions).then(
            function success() {
                var entityFormOptions = {};
                entityFormOptions["entityName"] = "incident";
                entityFormOptions["entityId"] = recordid;
                Xrm.Navigation.openForm(entityFormOptions).then(
                    function (success) {
                        formContext.getControl('tab_8').setFocus(true);
                        console.log(success);
                    },
                    function (error) {
                        console.log(error);
                    }
                );
            },
            function error() {
                // Handle errors
            }
        );
    }
    else {
        Common.showAlert('This feature is available only for Cases', "Update Metadata");
    }
}

Once URL is ready, an iFrame is embedded into HTML and set the source URL with the URL created in the above method.

<html>
<style>
    body {
        margin: 0;
        overflow-wrap: break-word;
        position: fixed;
        top: 0;
        left: 0;
        bottom: 0;
        right: 0;
        width: 100%;
        height: 100%;
        border: none;
        margin: 0;
        padding: 0;
        overflow: hidden;
        z-index: 999999;
    }

    iframe {
        display: block;
        background: #000;
        border: none;
        height: 100vh;
        width: 100vw;
    }
</style>

<head>
    <title>Update Document Metadata</title>
</head>
<body onload="onLoad()">
    <iframe id="embeddedCanvasApp" style="background: #FFFFFF;" src="" allow="geolocation; microphone; camera">
    </iframe>
    <script type="text/javascript" src="ClientGlobalContext.js.aspx"></script>
    <script type="text/javascript" src="mad_EmbedCanvasAppscripts.js"></script>
</body>

</html>

Define Onload method

function onLoad() {
    let appUrl = sessionStorage.getItem("finalURL");
    document.getElementById('embeddedCanvasApp').src = appUrl;
}

On publishing these scripts, canvas app and updating the settings entity with app Id and Tenant Id, System opens the canvas app on selecting a document in Associated Document grid and clicking button.

Hope this helps.