Extending Microsoft Dynamics CRM 2011: Build Attachment Download URL Links with JQuery

Recently, at a client engagement, I was tasked with building a custom CRM Web Resource that would allow users to download/open documents via a URL link and to view additional metadata about the document. The problem, that was being solved, was the business wanted to see a list of documents in one place that were found in 2 different custom entity types. The documents were attached to the Note (aka Annotation) objects related to said entities and the CRM out-of-the-box functionality will only show one related entity via a sub-grid.  

This custom solution contains an HTML5/CSS3 grid that displays metadata from the parent entities and a URL document link. The functionality of selecting the link would result in the default behavior, of Dynamics CRM 2011, to download/open a document. The development steps were pretty straight forward using JQuery as the primary logic language: retrieve the metadata from the entities of both types, drill down to the associated Note object, create the document URL link, and inject the needed metadata and links into the grid.

The solution logic did not raise any yellow or red flags to be concerned about. I created the CRM Web Resource and followed the steps that were mentioned: the grid was constructed and the metadata/URL links were populated. At that time I was thinking, “Easy-peezy.

Since I am one of those engineers that 1: is prideful of my work and 2: believes in following proper engineering practices, I moved on to testing the functionality before socializing that my work was complete. I selected one of the links, waiting for the browser dialog to display, to download or open the associated document. A dialog was displayed but with an error message instead.

The error was “Not Authorized.” I think to myself “What?!” I select a different link, for a sanity check, and received the same error message.

My next step was to debug:

 checkmark Power cord is plugged in?
checkmark Am I still breathing and consciously awake?
checkmark Am I logged into the CRM environment?
checkmark Can I download/open a document when viewing the Note form in CRM?

So what gives?

After investigating the actual page structure of the Note CRM form (with the browser f12 developer tools), I find a security token element that is associated to the document.

Well that’s interesting.

I proceed to copy the token information for a specific targeted Note and hard code it into my JQuery code. I test again and Voila! The desired prompt was displayed in the browser asking if I would like to download or open the document. Celebration time was cut down to a few milliseconds since now I am faced with a new problem.

It is impossible for me to know of all the targeted entities that will have documents attached to their Note object and to hardcode those values in the CRM Web Resource. I need to solve this new problem programmatically because we all love robust code, so it is off to investigation again.

What I found was that it is possible to access the targeted Notes form without actually having to load it into the browser. This solution assumes that the list of Annotation ID’s of the parent entities has already been retrieved and stored for processing. The additional straight forward steps are as follows: retrieve the associated form data using the ID, scrape the form data for the security token, and build the complete URL document link.

The solution steps are as follows:

Step 1:

Retrieve the server URL that will be used to build the location URL to the documents for both entities.

   var urlbase = window.parent.Xrm.Page.context.getClientUrl();

Step 2:

Since we all love proper code reuse, create a reusable function so that we can process many Note entities with the same code. This function only needs the Annotation ID of a targeted Note entity to process.

   function getDocumentUrl(annotId) { }

Step 3:

Build on the urlBase URL to include the location of the Note entity form of the parent entity via the Annotation ID.

   var URL = urlbase + '/userdefined/edit.aspx?etc=5&id={' + annotId + '}';

Step 4:

Retrieve the Note entity form with the built URL via JQuery Ajax. This will return the form HTML structure as a string. If it cannot be found, then we can bail out of the function and continue processing other Note entities.

   $.get(URL, function (data) { });

Step 5:

Once the form has been retrieved, parse it as HTML so we can navigate the DOM of the form to locate the security element.

   data = $.parseHTML(data);

Step 6:

Navigate the DOM, find the security element, and store it in a variable for later use.

   var securityTokenElement = $(data).find("[WRPCTokenUrl]");

Step 7:

Since we are defensive programmers, do a check to make sure the security element was found. If it was not found, we are done processing this Note entity and we can bail out of the function.

   if (securityTokenElement) { }

Step 8:

Once we have the security element, we can navigate to the security token URL to get the token.

   var securityTokenUrl = SecurityTokenElement.attr("WRPCTokenUrl");

Step 9:

If we found the security token, we can proceed with building the URL to download/open the specific document with the Annotation ID and the token.

   if (securityTokenUrl) {
      docUrl = urlbase + "/Activities/Attachment/download.aspx?AttachmentType=5&AttachmentId={" +
               annotId + "}&IsNotesTabAttachment=undefined" + SecurityTokenUrl;

Final Step:

Return the constructed URL to insert into the href of a HTML <a> tag.

   return docUrl;

The results:

Problem resolution is now complete and I can lift my head up in search of the next problem to tackle. The JQuery function to build the URL document link:

var urlbase = window.parent.Xrm.Page.context.getClientUrl();
* Function: getDocumentUrl(annotId)
*   This function gets the form data of a specific Annotation, retrieves
*   the security token for an attached document and builds the url for
*   opening/downloading the document via default CRM way.
*   There is a scenario that the token may not be found. In this case,
*   a null value is returned and it is up to the caller to handle the
*   scenario.
*   annotId:
*       the annotation id for the targeted form to retrieve
* Return: a document URL link or null if a security token is not found
function getDocumentUrl(annotId) {
    var URL = urlbase + '/userdefined/edit.aspx?etc=5&id={' + annotId + '}';
    var docUrl;

    // get the security token to build the href link. if the token cannot be found,
    // a null value is returned
    $.get(URL, function (data) {
        // get the form data via the 'URL'
        data = $.parseHTML(data);

        // locate the security element
        var securityTokenElement = $(data).find("[WRPCTokenUrl]");

        if (securityTokenElement) { // if the security element was found on the Note form
            // locate the security token within the security element
            var securityTokenUrl = SecurityTokenElement.attr("WRPCTokenUrl");

            // if the security token is located, build the url
            if (securityTokenUrl) {
                docUrl = urlbase + "/Activities/Attachment/download.aspx?AttachmentType=5&AttachmentId={" +
                         annotId + "}&IsNotesTabAttachment=undefined" + SecurityTokenUrl;

    return docUrl;

Note: error handling is not needed in this function because of the defensive checks. If the security element or token is not found, the code will step out of the function to the calling source.

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

Phone: 312-602-4000
Email: marketing@westmonroepartners.com
222 W. Adams
Chicago, IL 60606
Show Buttons
Share On Facebook
Share On Twitter
Share on LinkedIn
Hide Buttons