Inkey Solution Logo
banner

Blogs

Append Content Dynamically in Document Template and Generate the Merged Document in Dynamics 365 for Sales and SharePoint

, February 4, 2021 2644 Views

We all have been using Document Templates which are directly available by system and these templates contains static content for all records. What if we need to append some content dynamically in one Word template and need to send the whole document in email attachment?

We have come across a solution for this.

For one of our clients, we had one document template for “Account” but the whole content was divided into two portions: first portion was common for all records and the second portion we need to append from the attachments of the related notes.

Now the complex part was to append the content of the note attachment in between the common content just like shown in below image:

Below is the approach we have taken to achieve this:

In Dynamics we split the common content template into two Document Templates named Template 1 and Template 2 having the common content.

Users will add the document on the NOTE of the Account entity which is the dynamic content we need to append in between the existing Document templates.

A plugin will trigger on Post create of Note entity that is calling a Web API to generate the word documents and merge them into one.

Below is the code to call Web API from Plugin

using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Query;
using System;
using System.Net.Http;
using System.Linq;

namespace Dynamics_Plugins
{
  public class PostNotesCreateMergeWordTemplates : IPlugin
  {

    #region Public Methods

    /// <summary>
    /// Merge the Word Templates of Account record based on Attached Word Documents.
    /// </summary>
    public void Execute(IServiceProvider iServiceProvider)
    {
      // Obtain the execution context from the service provider.
      IPluginExecutionContext iPluginExecutionContext = (IPluginExecutionContext)iServiceProvider.GetService(typeof(IPluginExecutionContext));

      // Obtain the organization service factory reference.
      IOrganizationServiceFactory iOrganizationServiceFactory = (IOrganizationServiceFactory)iServiceProvider.GetService(typeof(IOrganizationServiceFactory));

      // Obtain the tracing service reference.
      ITracingService iTracingService = (ITracingService)iServiceProvider.GetService(typeof(ITracingService));

      // Obtain the organization service reference.
      IOrganizationService iOrganizationService = iOrganizationServiceFactory.CreateOrganizationService(iPluginExecutionContext.UserId);

      try
      {
        if (((Entity)iPluginExecutionContext.InputParameters["Target"]).LogicalName.Equals("annotation"))
        {
          Entity annotationEntity = (Entity)iPluginExecutionContext.InputParameters["Target"];

          Boolean isAttachmentExist = annotationEntity.GetAttributeValue<Boolean>("isdocument");
          EntityReference regardingEntityReference = annotationEntity.GetAttributeValue<EntityReference>("objectid");

          if (isAttachmentExist && //Check if Note Contains Attachment or not
              regardingEntityReference != null &&
              regardingEntityReference.LogicalName.Equals("account"))
          {
            string fileToMerge = annotationEntity.GetAttributeValue<string>("filename");

            if (fileToMerge.EndsWith(".docx"))
            {
              using (HttpClient httpClient = new HttpClient())
              {
                httpClient.DefaultRequestHeaders.Add("cache-control", " no-cache");
                HttpResponseMessage httpResponseMessage = null;
                string apiURL = $"https://yourapi.azurewebsites.net/api/AccountsGenerateDocument?annotationId={annotationEntity.Id}";

                try
                {
                  iTracingService.Trace("Calling Internal API");
                  httpResponseMessage = httpClient.GetAsync(apiURL).Result;
                  iTracingService.Trace("Intenal API Called");
                }
                catch { }
                iTracingService.Trace($"httpResponseMessage != null : {httpResponseMessage != null}");

                if (httpResponseMessage != null)
                {
                  iTracingService.Trace($"httpResponseMessage.IsSuccessStatusCode : {httpResponseMessage.IsSuccessStatusCode}");
                  iTracingService.Trace($"httpResponseMessage.ReasonPhrase : {httpResponseMessage.ReasonPhrase}");
                  iTracingService.Trace($"httpResponseMessage.Content.ReadAsStringAsync().Result : {httpResponseMessage.Content.ReadAsStringAsync().Result}");

                  if (httpResponseMessage.IsSuccessStatusCode == true)
                  {
                    iTracingService.Trace($"Success {httpResponseMessage.ReasonPhrase}");
                  }
                }
              }
            }
          }
        }
      }
      catch (Exception ex)
      {
        throw new InvalidPluginExecutionException(ex.Message);
      }
    }
    #endregion
  }
}

Reason for using Web API for Merge document functionality:

To generate the Document from template, we need to Use “SetWordTemplate” request of OrganizationRequest class of Microsoft.XRM.SDK. And this request we can not execute from Plugin code. That is why we have built the API to generate the document from the template.

From plugin we need to pass annotation id to the Web API controller and control will call below method which will do below things:

  • Get the NOTE record on which the document has been added

  • Get and generate document from Template 1 and Template 2 and attach in related notes

  • Merge 3 documents

  • Create a NOTE on regarding entity with merged document as attachment

API CONTROLLER

using System;
using System.Text;
using System.Web.Http;
using Microsoft.Xrm.Sdk;

namespace MergeDocumentService.Controllers
{
  public partial class AccountsGenerateDocumentController : ApiController
  {
    public IHttpActionResult Get(string annotationId)
    {
      IOrganizationService iOrganizationService = null;
      StringBuilder errorLogMessage = new StringBuilder(string.Empty);

      try
      {
        if (string.IsNullOrWhiteSpace(annotationId) == false)
        {
          MergeTemplatesAndGenerateWordDocument(annotationId, iOrganizationService);
        }
        else
        {
          errorLogMessage.AppendLine("AnnotationId should not be null.");
        }
        return Ok(errorLogMessage.ToString());
      }
      catch (Exception ex)
      {
        string innerException = ex.InnerException != null ? ex.InnerException.Message : string.Empty;
        string errMessage = $"Exception:{ex.Message}{Environment.NewLine}InnerException:{innerException}{Environment.NewLine}ErrorLogMessage:{errorLogMessage.ToString()}";
        return Ok(errMessage);
      }
    }
  }
}

METHOD TO CALL FROM API CONTROLLER

using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Query;
using OpenXmlPowerTools;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.IO;
using System.Security;
using System.Text;

internal static void MergeTemplatesAndGenerateWordDocument(string annotationId, IOrganizationService iOrganizationService)
    {
      try
      {
        iOrganizationService = OrganizationService.GetOrganizationService();

        if (iOrganizationService != null)
        {
          Guid annotationIdGuid = new Guid(annotationId);
          Entity userCreatedAnnotaionEntity = iOrganizationService.Retrieve("annotation",
                                                                             annotationIdGuid,
                                                                             new ColumnSet("isdocument", "objectid", "filename", "documentbody", "mimetype"));
          if (userCreatedAnnotaionEntity != null)
          {
            bool isAttachmentExist = userCreatedAnnotaionEntity.GetAttributeValue<bool>("isdocument");
            EntityReference regardingEntityReference = userCreatedAnnotaionEntity.GetAttributeValue<EntityReference>("objectid");

            if (isAttachmentExist && //Check if Note Contains Attachment or not
                regardingEntityReference != null &&
                regardingEntityReference.LogicalName.Equals("account"))
            {
              string userCreatedfileName = userCreatedAnnotaionEntity.GetAttributeValue<string>("filename");
              string userCreatedFileDocumentBody = userCreatedAnnotaionEntity.GetAttributeValue<string>("documentbody");
              string userCreatedFileDocumentBody = userCreatedAnnotaionEntity.GetAttributeValue<string>("documentbody");

              #region Get First Document Template
              bool isFoundTemplate = GetDocumentTemplate("documenttemplate", "Template 1", regardingEntityReference, iOrganizationService);
              if (!isFoundTemplate)
              {
                throw new InvalidPluginExecutionException("Template Not Found");
              }
              #endregion

              #region Get Second Document Template

              GetDocumentTemplate("documenttemplate", "Template 2", regardingEntityReference, iOrganizationService);

              #endregion

              string firstTemplateDocumentBody = string.Empty;
              string lastTemplateDocumentBody = string.Empty;

              string firstTemplateFileName = string.Empty;
              string lastTemplateFileName = string.Empty;

              Guid firstTemplateNoteGuid = Guid.Empty;
              Guid lastTemplateNoteGuid = Guid.Empty;

              #region Fetch Note For First Document Template

              FetchNoteEntity("annotation",
                              "Template 1",
                              regardingEntityReference, 
                              out firstTemplateFileName,
                              out firstTemplateDocumentBody,
                              out firstTemplateNoteGuid,
                              iOrganizationService);

              #endregion

              #region Fetch Note For second Document Template

              FetchNoteEntity("annotation", 
                              "Template 2", 
                              regardingEntityReference, 
                              out lastTemplateFileName, 
                              out lastTemplateDocumentBody, 
                              out lastTemplateNoteGuid, 
                              iOrganizationService);

              #endregion

              byte[] finalMemoryStream = ConvertFileIntoByteArray(firstTemplateDocumentBody);
              byte[] fileToMergeMemoryStream = ConvertFileIntoByteArray(userCreatedFileDocumentBody);
              byte[] lastFileToMergeMemoryStream = ConvertFileIntoByteArray(lastTemplateDocumentBody);

              #region Merge Documents Templates

              List<byte[]> lstDocumentsToMerge = new List<byte[]>();
              lstDocumentsToMerge.Add(finalMemoryStream);
              lstDocumentsToMerge.Add(fileToMergeMemoryStream);
              lstDocumentsToMerge.Add(lastFileToMergeMemoryStream);

              byte[] finalMergedFileContentFile = MergeDocuments(lstDocumentsToMerge);

              #endregion

              #region CreateFinalDocumentFile and attch in Note

              Entity createNoteEntity = new Entity("annotation");
              Guid newlyCreatedMergedNoteId = Guid.Empty;
              Entity newlyCreatedCreatedAnnotaionEntity = null;

              if (finalMergedFileContentFile != null)
              {
                string mergedDocumentBody = Convert.ToBase64String(finalMergedFileContentFile);

                createNoteEntity.Attributes.Add("subject", "Merged Document");
                createNoteEntity.Attributes.Add("notetext", annotationId);
                createNoteEntity.Attributes.Add("documentbody", mergedDocumentBody);
                createNoteEntity.Attributes.Add("filename", $"{title}.docx");
                createNoteEntity.Attributes.Add("isdocument", true);
                createNoteEntity.Attributes.Add("mimetype", userCreatedAnnotaionEntity.GetAttributeValue<string>("mimetype"));
                createNoteEntity.Attributes.Add("objectid", regardingEntityReference);
                newlyCreatedMergedNoteId = iOrganizationService.Create(createNoteEntity);
              }

              #endregion
            }
          }
        }
      }
      catch (Exception ex)
      {
        throw new Exception(ex.Message);
      }
    }

SOME METHODS USED IN ABOVE CODE:

GetDocumentTemplate

This will generate the document and will attach the same in NOTE of the regarding entity

private static bool GetDocumentTemplate(string templateEntityName,
                                            string templateName,
                                            EntityReference targetEntityReference,
                                            IOrganizationService iOrganizationService)
    {
      bool isFoundTemplate = false;
      try
      {
        if (targetEntityReference != null)
        {
          QueryExpression documentTemplateQueryExpression = new QueryExpression(templateEntityName);
          documentTemplateQueryExpression.Criteria.AddCondition("name", ConditionOperator.Equal, templateName);
          documentTemplateQueryExpression.TopCount = 1;

          EntityCollection documentTemplateEntityCollection = iOrganizationService.RetrieveMultiple(documentTemplateQueryExpression);

          if (documentTemplateEntityCollection != null &&
              documentTemplateEntityCollection.Entities.Count > 0)
          {
            isFoundTemplate = true;
            Entity documentTemplate = documentTemplateEntityCollection.Entities[0];

            OrganizationRequest organizationRequest = new OrganizationRequest("SetWordTemplate");

            organizationRequest["Target"] = new EntityReference(targetEntityReference.LogicalName, targetEntityReference.Id);
            organizationRequest["SelectedTemplate"] = new EntityReference(documentTemplate.LogicalName, documentTemplate.Id);

            iOrganizationService.Execute(organizationRequest);
          }

        }
      }
      catch (Exception ex)
      { throw new Exception(ex.Message); }

      return isFoundTemplate;
    }

FetchNoteEntity

Fetch Related Notes Of Document Template

internal static void FetchNoteEntity(string noteEntityName,
                                         string documentName,
                                         EntityReference regardingEntityReference,
                                         out string templateFileName,
                                         out string templateDocumentBody,
                                         out Guid noteEntityRecord,
                                         IOrganizationService iOrganizationService)
    {
      templateFileName = string.Empty;
      templateDocumentBody = string.Empty;
      noteEntityRecord = Guid.Empty;

      try
      {

        QueryExpression notesQueryExpression = new QueryExpression(noteEntityName);
        notesQueryExpression.ColumnSet.AddColumns("filename", "documentbody");

        notesQueryExpression.Criteria.AddCondition("filename", ConditionOperator.BeginsWith, documentName); 
        notesQueryExpression.Criteria.AddCondition("filename", ConditionOperator.EndsWith, ".docx");
        notesQueryExpression.Criteria.AddCondition("objectid", ConditionOperator.Equal, regardingEntityReference.Id);
        notesQueryExpression.AddOrder("createdon", OrderType.Descending);
        notesQueryExpression.TopCount = 1;

        EntityCollection notesEntityCollection = iOrganizationService.RetrieveMultiple(notesQueryExpression);

        if (notesEntityCollection != null &&
            notesEntityCollection.Entities.Count > 0)
        {
          Entity notesEntity = notesEntityCollection.Entities[0];

          templateFileName = notesEntity.GetAttributeValue<string>("filename");
          templateDocumentBody = notesEntity.GetAttributeValue<string>("documentbody");
          noteEntityRecord = notesEntity.Id;
        }

      }
      catch (Exception ex)
      { throw new Exception(ex.Message); }
    }

ConvertFileIntoByteArray

private static byte[] ConvertFileIntoByteArray(string firstTemplateDocumentBody)
    {
      byte[] file = Convert.FromBase64String(firstTemplateDocumentBody);

      return file;
    }

MergeDocuments

You need to install “OpenXmlPowerTools” version “4.5.3.2” Nuget package from Nuget Package Manager in Visual studio to merge the documents.

public static byte[] MergeDocuments(IList<byte[]> documentsToMerge)
    {
      List<Source> documentBuilderSources = new List<Source>();
      foreach (byte[] documentByteArray in documentsToMerge)
      {
        documentBuilderSources.Add(new Source(new WmlDocument(string.Empty, documentByteArray), false));
      }

      WmlDocument mergedDocument = DocumentBuilder.BuildDocument(documentBuilderSources);
      return mergedDocument.DocumentByteArray;
    }

Hope this blog helps you!!


ATM Inspection PowerApp to ease ATM inspection and report generation process.
https://www.inkeysolutions.com/microsoft-power-platform/power-app/atm-inspection

Insert data into Many-to-Many relationship in Dynamics CRM very easily & quickly, using the Drag and drop listbox.
http://www.inkeysolutions.com/what-we-do/dynamicscrmaddons/drag-and-drop-listbox

Comply your Lead, Contact, and User entities of D365 CRM with GDPR compliance using the GDPR add-on.
https://www.inkeysolutions.com/microsoft-dynamics-365/dynamicscrmaddons/gdpr

Create a personal / system view in Dynamics CRM with all the fields on the form/s which you select for a particular entity using the View Creator.
http://www.inkeysolutions.com/what-we-do/dynamicscrmaddons/view-creator

Admin

More posts by

Leave a Reply

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

The maximum upload file size: 2 MB. You can upload: image, audio, video, document, spreadsheet, interactive, text, archive, code, other. Drop file here

Would you like to digitize your business and put it on the cloud?
Do you need clear, concise reports for your organization?