HL7 Programming using .NET and NHAPI - Creating HL7 Messages
Introduction
This is part of my HL7 article series. Recently, a vistor to my blog requested that I do an article for HL7 programming using .NET. It was a timely request since I had been thinking about doing one for sometime as well. It inspired me to quickly do an introductory tutorial to HL7 using .NET and C# (see HL7 Programming using .NET - A Short Tutorial) where I explained all the basics one needs to understand about what goes into HL7 2.x programming if you are new to this area. However, a lot more is required to build a real-life HL7 system which includes support for many aspects of HL7 message processing such as message parsing, encoding, validation as well as message acknowledgment. In this article, we will dive deeper into building HL7-enabled .NET applications using a .NET HL7 framework called "NHAPI" (a port of the original Java-based HAPI HL7 framework which I covered in my previous tutorials) that helps us to easily build many HL7 processing capabilities within our custom .NET applications.
NHAPI and .NET - An Overview
.NET is a development platform similar to Java which enables applications developers to develop a wide range of applications such as console, desktop, web and mobile applications. It was first released in the early 2000s, and is one of the most popular development platforms today. It offers programmers a variety of programming languages choose from such as C#, F#, Visual Basic.NET, IronRuby, IronPython, etc, but the C# language has been the most popular choice for programmers using this platform. Although the .NET platform came later than Java, it has also taken an extremely interesting evolution in terms of things such as language support (particularly through powerful languages such as C#), a thriving community of passionate software developers who actively use as well as shape it everyday, and also through the wide range of commercial and open source tooling that exists for this platform as a result. These factors have helped create a rich eco-system around this platform. This in turn enables people using it to solve a wide range of information technology-related problems.
When C# and the .NET framework first came on the scene in early 2000, it was very common to emulate or port popular Java projects into C# and .NET, and many great as well as not so great projects emerged as a result. NHapi is an example of a great project that was a port of the HAPI framework for Java that we have looked at in a number of my previous articles. NHapi has been around in some shape or form for a number of years now. The authors of this library have taken a lot of care to not just emulate the Java implementation but instead use the strength and expressiveness of the C# language where possible to make it well suited to the Microsoft platform. However, many high-level constructs within NHapi still closely follow the original implementation on which it is based. So, people with some exposure to the original Java-based HAPI project should find it easy to follow or relate to if needing to implement a .NET HL7 solution as well. NHAPI does not have the same feature set as HAPI (no built-in support for sending and receiving messages for example), but we will look at ways to mitigate these challenges by augmenting NHAPI using some additional tools and libraries. Let us begin our exploration of the NHAPI framework by looking at how to create HL7 messages. I will follow the same format as I did in the Java/HAPI version of my tutorial so it is easy to follow for beginners.
“At high tide, fish eat ants; at low tide, ants eat fish.” ~ Thai Proverb
Tools and Resources Needed
- .NET Framework 4.5 or higher
- A .NET IDE or Editor such as Visual Studio IDE or Visual Studio Code (even a text editor should suffice)
- NHAPI GitHub page is located here
- Install NHAPI Nuget Package using NuGet Package Manager
- You can also find all the code demonstrated in this tutorial on GitHub here
Quick Introduction to ADT Messages
HL7 ADT messages are one of the most popular message types used in HL7-enabled systems and are used communicate core patient information captured in the EMR (Electronic Medical Record) to other ancillary/supporting systems such as lab, radiology, etc. This message is often transmitted when the patient information is either captured initially or when it changes because of events such as admission, transfer or discharge in a clinical setting. There are many types of ADT messages including ADT A01 (used during admit), A02 (used during transfer), A03 (used during end of visit or discharge), etc. There are also other HL7 message types that pertain to orders, results, clinical reports, scheduling and billing. The NHAPI library provides built-in support to create all these message types. In this particular tutorial, I will use the NHAPI library to create a ADT A01 message. This message type or trigger event that we will create will include the following message segments that are mandatory:
- MSH segment: Every HL7 message will carry this segment if you recall from my earlier tutorial. This segment will carry information such as the sender and receiver of the message, the type of message, and the date and time information of this message
- EVN segment: This segment will include information pertaining to the event that occurred (in our case this is due to an admission of a fictional patient)
- PID segment: This segment carries information pertaining to patient demographics such as such as name of the patient, patient identifier and address, etc.
- PV1 segment: This segment typically contains information about the patient’s stay, where they are assigned and also the referring doctor
Message construction gets pretty ugly if we don't separate the various steps involved into smaller methods. The approach I show below is a suggestion only. Please feel free to create your own factory methods, builder classes and any other utility classes needed especially if you are wanting to support multiple message types in your application.
using System;
using System.Diagnostics;
using System.IO;
using NHapi.Base.Model;
using NHapi.Base.Parser;
namespace NHapiCreateMessageSimpleExample
{
public class Program
{
static void Main(string[] args)
{
try
{
// create the HL7 message
// this AdtMessageFactory class is not from NHAPI but my own wrapper
LogToDebugConsole("Creating ADT A01 message...");
var adtMessage = AdtMessageFactory.CreateMessage("A01");
// create these parsers for the file encoding operations
var pipeParser = new PipeParser();
var xmlParser = new DefaultXMLParser();
// print out the message that we constructed
LogToDebugConsole("Message was constructed successfully..." + "\n");
// serialize the message to pipe delimited output file
WriteMessageFile(pipeParser, adtMessage, "C:\\HL7TestOutputs", "testPipeDelimitedOutputFile.txt");
// serialize the message to XML format output file
WriteMessageFile(xmlParser, adtMessage, "C:\\HL7TestOutputs", "testXmlOutputFile.xml");
}
catch (Exception e)
{
//in real-life, do something about this exception
LogToDebugConsole($"Error occured while creating HL7 message {e.Message}");
}
}
private static void WriteMessageFile(ParserBase parser, IMessage hl7Message, string outputDirectory, string outputFileName)
{
if (!Directory.Exists(outputDirectory))
Directory.CreateDirectory(outputDirectory);
var fileName = Path.Combine(outputDirectory, outputFileName);
LogToDebugConsole("Writing data to file...");
if (File.Exists(fileName))
File.Delete(fileName);
File.WriteAllText(fileName, parser.Encode(hl7Message));
LogToDebugConsole($"Wrote data to file {fileName} successfully...");
}
private static void LogToDebugConsole(string informationToLog)
{
Debug.WriteLine(informationToLog);
}
}
}
There are many variations of the factory method pattern, and most of them tend to implement a static method which is responsible for constructing an object and returning this created object. It is the static method that tends to abstract the construction process of an object. You typically define an interface for creating an object in the superclass and you then let the subclasses decide which classes to instantiate depending on the parameters passed. However, you can also delegate the construction to another class entirely. So, lots of options and approaches are available to you. Ultimately, the factory method lets the main class (or the "client") defer any instantiation to other classes based on the parameters passed. Here, I am simply using a builder class inside the factory method.
using System;
using NHapi.Base.Model;
namespace NHapiCreateMessageSimpleExample
{
public class AdtMessageFactory
{
public static IMessage CreateMessage(string messageType)
{
//This patterns enables you to build other message types
if (messageType.Equals("A01"))
{
return new OurAdtA01MessageBuilder().Build();
}
//if other types of ADT messages are needed, then implement your builders here
throw new ArgumentException($"'{messageType}' is not supported yet. Extend this if you need to");
}
}
}
“Violence is the last refuge of the incompetent.” ~ Isaac Asimov
In the code shown below and as described earlier the delegation of the actual construction of our ADT A01 message is left to a ADT message builder class called "OurAdtA01MessageBuilder" here for the lack of better imagination on my part. This builder class shown below helps construct the various parts of the ADT A01 message step by step. Using the builder pattern will be extremely useful even when using a HL7 library like NHAPI since there is just way too many optional segments and components that you will deal with when dealing with the HL7 2.x standard. The other advantage of this approach is that you can pass in data into the constructor of the builder class to build the ADT message with the required data values from your HL7 application (not shown here). This a style I prefer. However, feel free to choose any style that suits you or your organization. This is after all a tutorial on using NHAPI and not on software design. :-)
using System;
using System.Globalization;
using NHapi.Model.V23.Message;
namespace NHapiCreateMessageSimpleExample
{
internal class OurAdtA01MessageBuilder
{
private ADT_A01 _adtMessage;
/*You can pass in a domain or data transfer object as a parameter
when integrating with data from your application here
I will leave that to you to explore on your own
Using fictional data here for illustration*/
public ADT_A01 Build()
{
var currentDateTimeString = GetCurrentTimeStamp();
_adtMessage = new ADT_A01();
CreateMshSegment(currentDateTimeString);
CreateEvnSegment(currentDateTimeString);
CreatePidSegment();
CreatePv1Segment();
return _adtMessage;
}
private void CreateMshSegment(string currentDateTimeString)
{
var mshSegment = _adtMessage.MSH;
mshSegment.FieldSeparator.Value = "|";
mshSegment.EncodingCharacters.Value = "^~\\&";
mshSegment.SendingApplication.NamespaceID.Value = "Our System";
mshSegment.SendingFacility.NamespaceID.Value = "Our Facility";
mshSegment.ReceivingApplication.NamespaceID.Value = "Their Remote System";
mshSegment.ReceivingFacility.NamespaceID.Value = "Their Remote Facility";
mshSegment.DateTimeOfMessage.TimeOfAnEvent.Value = currentDateTimeString;
mshSegment.MessageControlID.Value = GetSequenceNumber();
mshSegment.MessageType.MessageType.Value = "ADT";
mshSegment.MessageType.TriggerEvent.Value = "A01";
mshSegment.VersionID.Value = "2.3";
mshSegment.ProcessingID.ProcessingID.Value = "P";
}
private void CreateEvnSegment(string currentDateTimeString)
{
var evn = _adtMessage.EVN;
evn.EventTypeCode.Value = "A01";
evn.RecordedDateTime.TimeOfAnEvent.Value = currentDateTimeString;
}
private void CreatePidSegment()
{
var pid = _adtMessage.PID;
var patientName = pid.GetPatientName(0);
patientName.FamilyName.Value = "Mouse";
patientName.GivenName.Value = "Mickey";
pid.SetIDPatientID.Value = "378785433211";
var patientAddress = pid.GetPatientAddress(0);
patientAddress.StreetAddress.Value = "123 Main Street";
patientAddress.City.Value = "Lake Buena Vista";
patientAddress.StateOrProvince.Value = "FL";
patientAddress.Country.Value = "USA";
}
private void CreatePv1Segment()
{
var pv1 = _adtMessage.PV1;
pv1.PatientClass.Value = "O"; // to represent an 'Outpatient'
var assignedPatientLocation = pv1.AssignedPatientLocation;
assignedPatientLocation.Facility.NamespaceID.Value = "Some Treatment Facility";
assignedPatientLocation.PointOfCare.Value = "Some Point of Care";
pv1.AdmissionType.Value = "ALERT";
var referringDoctor = pv1.GetReferringDoctor(0);
referringDoctor.IDNumber.Value = "99999999";
referringDoctor.FamilyName.Value = "Smith";
referringDoctor.GivenName.Value = "Jack";
referringDoctor.IdentifierTypeCode.Value = "456789";
pv1.AdmitDateTime.TimeOfAnEvent.Value = GetCurrentTimeStamp();
}
private static string GetCurrentTimeStamp()
{
return DateTime.Now.ToString("yyyyMMddHHmmss",CultureInfo.InvariantCulture);
}
private static string GetSequenceNumber()
{
const string facilityNumberPrefix = "1234"; // some arbitrary prefix for the facility
return facilityNumberPrefix + GetCurrentTimeStamp();
}
}
}
MSH|^~\&|Our System|Our Facility|Their Remote System|Their Remote Facility|20180918121258||ADT^A01|123420180918121258|P|2.3
EVN|A01|20180918121258
PID|378785433211||||Mouse^Mickey||||||123 Main Street^^Lake Buena Vista^FL^^USA
PV1||O|Some Point of Care^^^Some Treatment Facility|ALERT||||99999999^Smith^Jack^^^^^^^^^^456789||||||||||||||||||||||||||||||||||||20180918121258
The results of running the above program are shown above. The two output files generated by the program should give you an idea of what a pipe delimited HL7 file and XML formatted file look like. Please note that the pipe delimited output file has special unprintable characters within it that help in the "message enveloping" that is required. You can inspect this file using any Hex editor if you are curious to see where these characters are in located the file.
Conclusion
That concludes this little tutorial illustrating how we can create an ADT HL7 message and its various constituent parts using the NHAPI library. NHAPI provides a large library of strongly typed classes which makes the task of generating various HL7 2.x messages very easy. The various classes and their methods and other utility functions provided by the library help remove any tedium and guess work when assembling, parsing and validating HL7 messages that we often need to tackle when developing a .NET-based message processing application. However, you should note that the NHAPI library does not have all the features compared to the HAPI Java library which served as the original inspiration for this library. These include functionality such as sending and receiving messages. Fortunately, there are several additional tools that work well with the NHAPI framework and help fill these gaps in functionality. We will employ these tools in conjunction with NHAPI to create more sophisticated HL7 messages which include custom segments (also known as "Z-Segments") in subsequent tutorials. However, in the next tutorial in my HL7 article series, I will cover how we can combine NHAPI with another tool called "NHapi Tools" to transmit a HL7 message to a HL7 receiving system. See you then!