Embeding SharePoint Documents Page Inside Dynamics CRM Forms
Dynamics CRM Developer Series:
Attention CRM/.NET Developers: This post will show you how to embed a SharePoint documents page directly inside CRM forms.
The Situation
When the Dynamics CRM and SharePoint integration is configured, documents can be stored in SharePoint but related to (and managed from) CRM records.
For example, let’s say your Organization tracks scanned purchase orders, or tracks C.V.s, or some other form of document related to specific records in CRM. The Dynamics CRM and SharePoint integration is a great way to leverage both platforms’ strength: SharePoint is great at managing documents, and CRM is great at relating entities together.
The Problem
However, sometimes it is desirable to have an “at a glance” view of documents associated with a record. In order to view the documents in SharePoint that are related to a record, the user must navigate to a related entities area outside of the form. (Similar to Connection, or other related entities)
For most related entities, a sub-grid can be used in the form to display the relationships immediately on the form, but there is no simple workaround for the Documents.
The Solution
If you navigate to the Documents area on a record with Document Management enabled, a quick inspection of the DOM will reveal that it is an iframe pointing to a SharePoint resource.
Fortunately, the URL of this resource is predictable and easy to reconstruct. It follows the format:
https:///crmgrid/crmgridpage.aspx?langId=&locationURL=<encodeURIComponent(folderURL)>&pageSize=250
For example:
SharePoint URL | https://sharepoint.company.com/sites/crmDocs |
Absolute Folder URL | https://sharepoint.company.com/sites/crmDocs/contact/John Smith/ contract/Service Agreement_0000000000000000000000000000000 |
Language Code | en-US |
CRMGrid URL | https://sharepoint.company.com/sites/crmDocs/crmgrid/crmgridpage.aspx?langId=en- US&locationURL=httpss%3A%2F%2Fsharepoint.company.com%2Fsites%2FcrmDocs %2Fcontact%2FJohn%20Smith%2Fcontract%2FService %20Agreement_0000000000000000000000000000000&pageSize=250 |
Conveniently, Dynamics CRM stores the information about which folders documents are stored in within entities called SharePointDocumentLocations
and SharePointSites
. These entities can be queried from JavaScript in a web resource through the OrganizationData.svc
endpoint.
The ID of the current record can be found with the Xrm.Page.data.entity.getId
method.
var id = Xrm.Page.data.entity.getId();
An XMLHttpRequest can be sent from JavaScript to query the service endpoint like so:
var req = new XMLHttpRequest();
var uri = encodeURI(Xrm.Page.context.getClientUrl() + "/XRMServices/2011/OrganizationData.svc/SharePointDocumentLocationSet?$select=SharePointDocumentLocationId&$filter=RegardingObjectId/Id eq guid'" + id + "'");
req.open("GET", uri, true);
req.setRequestHeader("Accept", "application/json");
req.setRequestHeader("Content-Type", "application/json; charset=utf-8");
req.onreadystatechange = function () {
if (this.readyState === 4) {
req.onreadystatechange = null;
if (this.status === 200) {
var returned = JSON.parse(req.responseText).d;
var result = returned.results[0];
var SharePointDocumentLocationId = result.SharePointDocumentLocationId;
}
}
};
req.send();
This will get the default SharePointDocumentLocation
entity ID for the current record. Note that this is an asynchronous request so you cannot reliably return the results, instead you will need to set up a callback or some other method of handling the response.
Deceptively, the SharePointDocumentLocation
has an attribute called “AbsoluteURL
”. For example:
https://crm.company.com/XRMServices/2011/OrganizationData.svc/SharePointDocumentLocationSet(guid'00000000-0000-0000-0000-000000000000')?$select=AbsoluteURL
https://micrm.crgdemo.com/XRMServices/2011/OrganizationData.svc/SharePointDocumentLocationSet(guid’00000000-0000-0000-0000-000000000000′)
2016-05-18T19:43:06Z
Tauntingly, however, it is NULL. Thankfully, there is a way around this problem. Each SharePointDocumentLocation
has a RelativeUrl
and a ParentSiteOrLocation
. The “RelativeUrl
” fragment will be of the form “Service Agreement_0000000000000000000000000000000
” and the “ParentSiteOrLocation
” value will be an entity reference to either another SharePointDocumentLocation
, or the host SharePointSite
record. Therefore, we can recursively query our way up the chain of SharePointDocumentLocations
until we have constructed the absolute URL of the folder for this record.
At this point, you may be wondering: If I already know the URL of my SharePoint site. Why not just assume that the record will be in a location of the format "//_/" and be done with it? |
The answer is this: Depending on how the Document Management settings are configured, the folder tree might be anything like:
For example, if the Contact documents are configured to be stored under Account folders, then when you first navigate to a Contact document location it will prompt you to automatically create These potential inconsistencies mean we should rely on the |
To complicate matters, because we have to crawl the SharePointDocumentLocation
tree recursively, we have to make repeated asynchronous XMLHttpRequests
. This means we cannot rely on a simple recursive function that returns the relative URL because asynchronous functions require callbacks, not return statements.
The solution we came up with, is to create the following method:
var _relativeFolderURL = '';
function BuildFolderUrlRecursively(locationID) {
var req = new XMLHttpRequest();
var uri = encodeURI(Xrm.Page.context.getClientUrl() + "/XRMServices/2011/OrganizationData.svc/SharePointDocumentLocationSet(guid'" + locationID + "')?$select=RelativeUrl,ParentSiteOrLocation");
req.open("GET", uri, true);
req.setRequestHeader("Accept", "application/json");
req.setRequestHeader("Content-Type", "application/json; charset=utf-8");
req.onreadystatechange = function () {
if (this.readyState === 4) {
req.onreadystatechange = null;
if (this.status === 200) {
var returned = JSON.parse(req.responseText).d;
var ParentSiteOrLocation = returned.ParentSiteOrLocation;
_relativeFolderURL = returned.RelativeUrl + '/' + ._relativeFolderURL;
if (ParentSiteOrLocation.LogicalName === 'sharepointdocumentlocation') {
BuildFolderUrlRecursively(ParentSiteOrLocation.Id);
} else if (ParentSiteOrLocation.LogicalName === 'sharepointsite') {
GetSharePointSiteURL(returned.ParentSiteOrLocation.Id);
}
}
}
};
req.send();
}
This will continuously request its way up the chain, building out the _relativeFolderUrl
variable as:
- Service Agreement_0000000000000000000000000000000/
- contract/Service Agreement_0000000000000000000000000000000/
- John Smith/contract/Service Agreement_0000000000000000000000000000000
- contact/John Smith/contract/Service Agreement_0000000000000000000000000000000
Then it hits a SharePointSite
reference instead of a SharePointDocumentLocation
reference, and requests the site’s absolute URL with the following function:
var _siteURL = '';
function GetSharePointSiteURL(siteID) {
var req = new XMLHttpRequest();
var uri = encodeURI(Xrm.Page.context.getClientUrl() + "/XRMServices/2011/OrganizationData.svc/SharePointSiteSet(guid'" + siteID + "')?$select=AbsoluteURL");
req.open("GET", uri, true);
req.setRequestHeader("Accept", "application/json");
req.setRequestHeader("Content-Type", "application/json; charset=utf-8");
req.onreadystatechange = function () {
if (this.readyState === 4) {
req.onreadystatechange = null;
if (this.status === 200) {
var returned = JSON.parse(req.responseText).d;
_siteURL = returned.AbsoluteURL;
initDocumentIFrame();
}
}
};
req.send();
}
Now that we have the URL of the site, and the relative URL of the folder, we can set the ‘src’ of an iFrame to show the same view within the form as you see in the Documents section. This is accomplished the following function:
function initDocumentIFrame() {
_siteURL = _siteURL + (siteURL.endsWith('/') ? '' : '/');
var lcidMap = { 1033: "en-US", 1036: "fr-FR", 4105: "en-CA" }
var lang = (lcidMap[Xrm.Page.context.getUserLcid()] ? lcidMap[Xrm.Page.context.getUserLcid()] : "en-US");
var absoluteFolderURL = o._siteURL + o._relativeFolderURL;
var webPartUrl = o._siteURL + "crmgrid/crmgridpage.aspx?langId=" + lang + "&locationUrl=" + encodeURIComponent(absoluteFolderURL) + "&pageSize=250";
document.getElementById(o._iframeID).src = webPartUrl;
}
To make this solution robust and compact, all the components were packed into a single HTML web resource: (DocumentsIFrame.html)
(Note: Download the full code here)
This web resource can then be added to forms which have document management enabled, without requiring any functional changes.
For additional convenience, an unmanaged solution for Dynamics CRM 2016 containing this web resource is available for download here.
We can help! If you have any questions or would like assistance configuring your Dynamics System for automated exchange rate updates, please contact the experts at CRGroup at 1.800.576.6215 or email us at crg@crgroup.com
About the Author:
Matthew Foy is a .NET developer working on CRGroup’s SharePoint and CRM consultancy team. He has a passion for robust solutions and a keen interest in solving problems.
You might also like to check out:
Tags In
Categories
- Accounting / Finance (33)
- Atlassian (2)
- Budgeting & Costing (20)
- Business Applications (19)
- Business Intelligence (25)
- Cloud (8)
- Collaboration & Sharing (13)
- Company News (22)
- Corporate Performance Management (21)
- Dynamics 365 Business Central (19)
- Dynamics CRM (13)
- Dynamics GP (42)
- Enterprise Resource Planning (10)
- Events (8)
- GP Add-Ons (1)
- Leadership/Business Management (8)
- SharePoint (18)
- Talent Management (9)