Template Device
The Template Device is a Custom Code Device with a base class which includes support for a Razor templating engine. This engine is designed to produce text output (json, cda, html, etc.) based on a template and a context.
The template is usually a mix of text, tags, and sometimes C# code, and parts of the template reference a context object. When the engine runs, it merges the template with the context object to produce text.
For example, you might have a template Hello, my name is @Name, and a context property Public string Name => “John Hancock”. When the engine runs, it substitutes the @Name value within the template with the Name property on the context object, producing Hello, my name is John Hancock.
Razor syntax is well-used within the .NET community, and is a core technology of ASP.NET.
The Template device has a Templates node where you can create and edit Razor templates. Two sample templates are included in the Template device - one HTML and the other CDA.
The Razor template references the Template Context class (in the code node) and pulls any values from this class when creating the text output.
The Template Code class simply specifies which class and template should be used - typically this class remains unchanged.
Let’s take a look at the Template Context class. Remember that this class will provide all the values to be injected into the textual output. In the following example, we will pull data from an HL7v2 message and place it into a CDA message.
The Template Context class takes an HL7Message object and exposes well-defined properties:
using System;
using Connexion.Core.HL7;
namespace Connexion.Device
{
/// <summary>
/// This class is bound to the @Model keyword in the template. Adding properties and functions to
/// this class will make them available for use within your template(s).
/// </summary>
public class TemplateContext
{
public TemplateContext(HL7Message message)
{
Message = message;
}
// Expose the HL7 message to the template
public HL7Message Message { get; }
public string DocumentDateTime
{
get
{
if(Message.OBR != null)
{
string date = Message.OBR.ObservationDateTime_07.Value.Trim();
if (string.IsNullOrWhiteSpace(date))
date = Message.MSH.DateTimeOfMessage_07.Value.Trim();
return date;
}
return string.Empty;
}
}
public string SourceSystem
{
get { return Message.MSH.SendingApplication_03.NamespaceID_01.Value.Trim(); }
}
public string Title
{
get { return Message.OBR != null ? Message.OBR.UniversalServiceIdentifier_04.Text_02.Value.Trim() : String.Empty; }
}
public string AuthorDateTime
{
get { return Message.OBR != null ? Message.OBR.ObservationDateTime_07.Value.Trim() : String.Empty; }
}
public string AuthorId
{
get { return Message.OBR != null ? Message.OBR.PrincipalResultInterpreter_32.Name_01.IDNumber_01.Value.Trim() : "0"; }
}
public string AuthorLastName
{
get { return Message.OBR != null ? Message.OBR.PrincipalResultInterpreter_32.Name_01.FamilyName_02.Value.Trim() : String.Empty; }
}
public string AuthorFirstName
{
get { return Message.OBR != null ? Message.OBR.PrincipalResultInterpreter_32.Name_01.GivenName_03.Value.Trim() : String.Empty; }
}
public string AuthorMiddleName
{
get { return Message.OBR != null ? Message.OBR.PrincipalResultInterpreter_32.Name_01.SecondAndFurtherGivenNamesOrInitialsThereof_04.Value.Trim() : String.Empty; }
}
public string OrderId
{
get { return Message.OBR != null ? Message.OBR.FillerOrderNumber_03.EntityIdentifier_01.Value.Trim() : String.Empty; }
}
public string ServiceEventId
{
get { return Message.OBR != null ? Message.OBR.FillerOrderNumber_03.EntityIdentifier_01.Value.Trim() : String.Empty; }
}
public string ServiceEventCode
{
get { return Message.OBR != null ? Message.OBR.UniversalServiceIdentifier_04.Identifier_01.Value.Trim() : String.Empty; }
}
public string ServiceEventDateTime
{
get { return Message.OBR != null ? Message.OBR.ObservationDateTime_07.Value.Trim() : String.Empty; }
}
public string EncompassingEncounterId
{
get { return Message.PID != null ? Message.PID.PatientAccountNumber_18.IDNumber_01.Value.Trim() : String.Empty; }
}
public string EncompassingEncounterDateTime
{
get
{
return (Message.PV1 != null && !String.IsNullOrWhiteSpace(Message.PV1.AdmitDateTime_44.Value))
? Message.PV1.AdmitDateTime_44.Value
: (Message.OBR != null ? Message.OBR.ObservationDateTime_07.Value : String.Empty);
}
}
public string AttendingPhysicianId
{
get { return Message.PV1 != null ? Message.PV1.AttendingDoctor_07.First.IDNumber_01.Value : String.Empty; }
}
public string AttendingPhysicianLastName
{
get { return Message.PV1 != null ? Message.PV1.AttendingDoctor_07.First.FamilyName_02.Surname_01.Value : String.Empty; }
}
public string AttendingPhysicianFirstName
{
get { return Message.PV1 != null ? Message.PV1.AttendingDoctor_07.First.GivenName_03.Value : String.Empty; }
}
public string AttendingPhysicianSuffix
{
get { return Message.PV1 != null ? Message.PV1.AttendingDoctor_07.First.Suffix_05.Value : String.Empty; }
}
}
}The razor template then references these properties using the @Model.PropertyName tag. Since the context class also exposes the HL7Message directly (as the Message property), it can access the source HL7Message via the @Model.Message property.
Note the @* notation denotes a comment.
@* The following line tells this editor the type of the @Model object. By default it points to your CustomDevice class.
Notice this declaration uses all-lower case. *@
@model TemplateContext
@*Optionally include namespace declarations to enable intellisense for objects within that namespace*@
@using Connexion.Core.HL7;
<ClinicalDocument xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="urn:hl7-org:v3 CDA.ReleaseTwo.CommitteeBallot02.Dec.2003.xsd"
xmlns="urn:hl7-org:v3"
xmlns:mm="http://mmodal.com/cdaExtensions"
xmlns:voc="urn:hl7-org:v3/voc"
xmlns:cda="urn:hl7-org:v3"
mm:major="2"
mm:minor="0">
<typeId extension="Your_Extension" root="1.11.111.1.111111.1.1"/>
<templateId root="1.11.111.1.111111.1.1"/>
<id extension="@Model.OrderId" root="1.11.111.1.111111.1.1"/>
<title>@Model.Title</title>
<effectiveTime value="@Model.DocumentDateTime"/>
<confidentialityCode code="N" codeSystem="1.11.111.1.111111.1.1"/>
<languageCode code="en-US"/>
<setId extension="Your_Extension" root="1.11.111.1.111111.1.1"/>
<mm:cdaType level="CDA_Level" type="CDA_Type" sourceSystem="@Model.SourceSystem" />
<versionNumber value="1"/>
<recordTarget>
<patientRole>
@*Here we are accessing the HL7 message directly*@
<id extension="@Model.Message.PID.PatientIdentifierList_03.First.IDNumber_01.Value" root="1.11.111.1.111111.1.1"/>
<patient>
<name>
<family>@Model.Message.PID.PatientName_05.First.FamilyName_01.ToString().Trim()</family>
<given>@Model.Message.PID.PatientName_05.First.GivenName_02.ToString().Trim()</given>
<given>@Model.Message.PID.PatientName_05.First.SecondAndFurtherGivenNamesOrInitialsThereof_03.Value.Trim()</given>
</name>
<administrativeGenderCode codeSystem="1.11.111.1.111111.1.1" code="@Model.Message.PID.AdministrativeSex_08.Value.Trim()"/>
<birthTime value="@Model.Message.PID.DateTimeOfBirth_07.Value.Trim()"/>
</patient>
</patientRole>
</recordTarget>
<author>
<time value="@Model.AuthorDateTime"/>
<assignedAuthor>
@*Here we are accessing wrapper functions*@
<id extension="@Model.AuthorId" root="1.11.111.1.111111.1.1"/>
<assignedPerson>
<name>
<family>@Model.AuthorLastName</family>
<given>@Model.AuthorFirstName</given>
<suffix>@Model.AuthorMiddleName</suffix>
</name>
</assignedPerson>
</assignedAuthor>
</author>
@*Show a section only if we have an input value*@
@if(!string.IsNullOrEmpty(@Model.OrderId))
{
<inFulfillmentOf>
<order>
<id extension="@Model.OrderId" root="1.11.111.1.111111.1.1"/>
<priorityCode/>
</order>
</inFulfillmentOf>
}
@if(!string.IsNullOrEmpty(@Model.ServiceEventId))
{
<documentationOf>
<serviceEvent classCode="ACT">
<id extension="@Model.ServiceEventId" root="1.11.111.1.111111.1.1"/>
<code code="@Model.ServiceEventCode"/>
<effectiveTime value="@Model.ServiceEventDateTime"/>
</serviceEvent>
</documentationOf>
}Note that razor syntax also supports C# keywords (like if) as well as loops and other programming concepts.
When run, the Template device produces the following output:
Simply changing the template from CDA to HTML changes the output to HTML.
Sub Templates
The Template device supports referencing templates from within other templates. For example, you may want to create a Patient template which represents the standard CDA content for patient.
You can then reference this template from within other templates (wherever you need to output a patient).
This allows you to create templates which represent common items, and then build more complex templates from them.
There are a lot of great tutorials about Razor syntax, as well as its great performance.