HL7-V2 3.0.0

HL7-V2

NuGet downloads github build

This is a lightweight library for building and parsing HL7 2.x messages, for .NET Standard, .NET Core, and .NET 5+. It is not tied to any particular version of HL7 nor validates against one.

Usage and compatibility

This library is distributed via nuget and targets two frameworks:

  • .NET Standard 2.0 for maximum compability, covering more than 40 .NET frameworks
  • .NET 8.0 for better performance under the new Microsoft's cross-platform framework

For using the classes and methods mentioned below, declare de following namespace:

using Efferent.HL7.V2;

Object construction

Create a Message object and pass a raw HL7 message in text format

Message message = new Message(strMsg);

// Parse this message

bool isParsed = false;

try
{
    isParsed = message.ParseMessage();
}
catch(Exception ex)
{
    // Handle the exception
}

Adding a message header

For adding a header segment to a new message object, use the AddSegmentMSH() method, after constructing an empty message:

message.AddSegmentMSH(sendingApplication, sendingFacility, 
    receivingApplication, receivingFacility,
    security, messageType, 
    messageControlId, processingId, version);

⚠️ Notice that every HL7 message needs a header segment to be considered valid

Message extraction

If the HL7 message is coming from a MLLP connection (see the official documentation), the message needs to be cleared from the MLLP prefixes and suffixes. Also, consider there can be more than one message in a single MLLP frame.

For this purpose, there is an ExtractMessages() method, to be used as follows:

// extract the messages from a buffer containing a MLLP frame
var messages = MessageHelper.ExtractMessages(buffer);

// construct and process each message
foreach (var strMsg in messages)
{
    Message message = new Message(strMsg);
    message.ParseMessage(); // Required by most operations
    // do something with the message object
}

Bypass Validation

Message  message = new Message(strMsg)
message.ParseMessage(true);

Accessing Segments

Get list of all segments

List<Segment> segList = message.Segments();

Get List of list of repeated segments by name

For example if there are multiple IN1 segments

List<Segment> IN1List = message.Segments("IN1");

Access a particular occurrence from multiple IN1s providing the index

Note index 1 will return the 2nd element from list

Segment IN1_2 = message.Segments("IN1")[1];

Get count of IN1s

int countIN1 = message.Segments("IN1").Count;

Access first occurrence of any segment

Segment IN1 = message.DefaultSegment("IN1");

// OR

Segment IN1 = message.Segments("IN1")[0];

Accessing Fields

Access field values

string SendingFacility = message.GetValue("MSH.4");

// OR

string SendingFacility = message.DefaultSegment("MSH").Fields(4).Value;

// OR

string SendingFacility = message.Segments("MSH")[0].Fields(4).Value;

Segment ocurrences

string ContactPhone = message.GetValue("NK1(2).5"); // Second occurrence of NK1

Check if field is componentized

bool isComponentized = message.Segments("PID")[0].Fields(5).IsComponentized;

// OR

bool isComponentized = message.IsComponentized("PID.5");

Check if field has repetitions

bool isRepeated = message.Segments("PID")[0].Fields(3).HasRepetitions;

// OR

bool isRepeated = message.HasRepetitions("PID.3");

Adding repeating field

var enc = new HL7Encoding();
Segment PID = new Segment("PID", enc);
Field f = new Field(enc);
f.HasRepetitions = true;

// Adding field f1 to f
Field f1 = new Field("A", enc);
f.AddRepeatingField(f1);

// Adding field f2 to f
Field f2 = new Field("B", enc);
f.AddRepeatingField(f2);

Get list of repeated fields

List<Field> repList = message.Segments("PID")[0].Fields(3).Repetitions();

Get particular repetition i.e. 2nd repetition of PID.3

Field PID3_R2 = message.GetValue("PID.3[2]");

// OR

Field PID3_R2 = message.GetValue("PID.3(2)");

Update value of any field i.e. to update PV1.2 – patient class

message.SetValue("PV1.2", "I");

// OR

message.Segments("PV1")[0].Fields(2).Value = "I";

Access some of the required MSH fields with properties

string version = message.Version;
string msgControlID = message.MessageControlID;
string messageStructure = message.MessageStructure;

Generate ACKs

To generate an ACK message

Message ack = message.GetACK();

To generate negative ACK (NACK) message with error message

Message nack = message.GetNACK("AR", "Invalid Processing ID");

It may be required to change the application and facility fields

Message ack = message.GetACK();
ack.SetValue("MSH.3", appName);
ack.SetValue("MSH.4", facility);

⚠️ Take into account that a message shall be previously parsed before attempting to generate an ACK or NACK message.

Accessing Components

Access particular component i.e. PID.5.1 – Patient Family Name

string PatName1 = message.GetValue("PID.5.1");

// OR

string PatName1 = message.Segments("PID")[0].Fields(5).Components(1).Value;

Check if component is sub componentized

bool isSubComponentized = message.Segments("PV1")[0].Fields(7).Components(1).IsSubComponentized;

// OR

bool isSubComponentized = message.IsSubComponentized("PV1.7.1");

Update value of any component

message.Segments("PID")[0].Fields(5).Components(1).Value = "Jayant";

// OR

message.SetValue("PID.5.1", "Jayant");

Adding new Segment

// Create a Segment with name ZIB
Segment newSeg = new Segment("ZIB");
 
// Create Field ZIB_1
Field ZIB_1 = new Field("ZIB1");
// Create Field ZIB_5
Field ZIB_5 = new Field("ZIB5");
 
// Create Component ZIB.5.2
Component com1 = new Component("ZIB.5.2");
 
// Add Component ZIB.5.2 to Field ZIB_5
// 2nd parameter here specifies the component position, for inserting segment on particular position
// If we don’t provide 2nd parameter, component will be inserted to next position (if field has 2 components this will be 3rd, 
// If field is empty this will be 1st component
ZIB_5.AddNewComponent(com1, 2);
 
// Add Field ZIB_1 to segment ZIB, this will add a new filed to next field location, in this case first field
newSeg.AddNewField(ZIB_1);
 
// Add Field ZIB_5 to segment ZIB, this will add a new filed as 5th field of segment
newSeg.AddNewField(ZIB_5, 5);
 
// Add segment ZIB to message
bool success = message.AddNewSegment(newSeg);

New Segment would look like this:

ZIB|ZIB1||||ZIB5^ZIB.5.2

After evaluated and modified required values, the message can be obtained again in text format

string strUpdatedMsg = message.SerializeMessage();

Remove Trailing Components

var message = new Message();

// create ORC segment
var orcSegment = new Segment("ORC", new HL7Encoding());

// add fields
for (int eachField = 1; eachField <= 12; eachField++)
{
    orcSegment.AddEmptyField();
}

// add components to field 12
for (int eachField = 1; eachField < 8; eachField++)
{
    orcSegment.Fields(12).AddNewComponent(new Component(new HL7Encoding()));
}

// add values to components
orcSegment.Fields(12).Components(1).Value = "should not be removed";
orcSegment.Fields(12).Components(2).Value = "should not be removed";
orcSegment.Fields(12).Components(3).Value = "should not be removed";
orcSegment.Fields(12).Components(4).Value = ""; // should not be removed because in between valid values
orcSegment.Fields(12).Components(5).Value = "should not be removed";
orcSegment.Fields(12).Components(6).Value = ""; // should be removed because trailing
orcSegment.Fields(12).Components(7).Value = ""; // should be removed because trailing
orcSegment.Fields(12).Components(8).Value = ""; // should be removed because trailing

orcSegment.Fields(12).RemoveEmptyTrailingComponents();
message.AddNewSegment(orcSegment);

string serializedMessage = message.SerializeMessage(false);

Remove a Segment

Segments are removed individually, including the case where there are repeated segments with the same name

    // Remove the first segment with name NK1
    bool success = message.RemoveSegment("NK1") 

    // Remove the second segment with name NK1
    bool success = message.RemoveSegment("NK1", 1) 

Encoded segments

Some contents may contain forbidden characters like pipes and ampersands. Whenever there is a possibility of having those characters, the content shall be encoded before calling the 'AddNew' methods, like in the following code:

var obx = new Segment("OBX", new HL7Encoding());

// Not encoded. Will be split into parts.
obx.AddNewField("70030^Radiologic Exam, Eye, Detection, FB^CDIRadCodes");  

// Encoded. Won't be parsed nor split.
obx.AddNewField(obx.Encoding.Encode("domain.com/resource.html?Action=1&ID=2"));  

Copying a segment

The DeepCopy method allows to perform a clone of a segment when building new messages. Countersense, if a segment is referenced directly when adding segments to a message, a change in the segment will affect both the origin and new messages.

Segment pid = ormMessage.DefaultSegment("PID").DeepCopy();
oru.AddNewSegment(pid);

Null elements

Null elements (fields, components or subcomponents), also referred to as Present But Null, are expressed in HL7 messages as double quotes, like (see last field):

EVN|A04|20110613083617||""

Whenever requested individually, those elements are returned as null, rather than double quotes:

var expectEmpty = evn.Fields(3).Value; // Will return an empty string
var expectNull = evn.Fields(4).Value; // Will return null

If this behavior is not desirable, it can be disabled by setting Encoding.PresentButNull to null before parsing:

var message = new Message(msg);
message.Encoding.PresentButNull = null;
message.ParseMessage();

Date Handling

A couple of date handling methods have been added, for parsing elements containing valid date/times, including time zones, as described in the HL7 standard. Examples:

// With time zone
string value1 = "20151231234500.1234+2358";
TimeSpan offset;
DateTime? dt1 = MessageHelper.ParseDateTime(value1, out offset);

// Date/time only
string value2 = "20151231234500";
DateTime? dt2 = MessageHelper.ParseDateTime(value2);

ParseDateTime will catch exceptions by default and return null in case of invalid dates. For preventing this mechanism, add an extra argument as true, like:

try
{
    var dt1 = MessageHelper.ParseDateTime(value1, out TimeSpan offse, true);
    var dt2 = MessageHelper.ParseDateTime(value2, true);
}
catch 
{
    // do something here
}

Credits

This library took Jayant Singh's HL7 parser (2013) as a foundation. Since then, it has been modified fundamentally, with respect to features, code quality, bugs and typos. For more information about the original implementation read:

The field encoding and decoding methods have been based on https://github.com/elomagic/hl7inspector

No packages depend on HL7-V2.

.NET 8.0

  • No dependencies.

.NET Standard 2.0

  • No dependencies.

Version Downloads Last updated
3.4.0 5 05/07/2025
3.3.0 1 05/09/2025
3.2.0 1 05/09/2025
3.1.0 1 05/09/2025
3.0.3 1 05/09/2025
3.0.2 1 05/09/2025
3.0.1 1 05/09/2025
3.0.0 1 05/09/2025