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
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); } } } }
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); } }
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; }
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); } }
private static byte[] ConvertFileIntoByteArray(string firstTemplateDocumentBody) { byte[] file = Convert.FromBase64String(firstTemplateDocumentBody); return file; }
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
© All Rights Reserved. Inkey IT Solutions Pvt. Ltd. 2024
Leave a Reply