HL7 C# code cheat sheet

Getting Started

This guide provides examples in C# demonstrating how you can write code that manipulates HL7 either in HL7 Soups scripting ‘Run Code’ activities/transformers or using our custom activity extensions where you create your own DLL’s for processing.

At the start of your code block, both scripting and custom activities have objects representing the running instance of the workflow and the current activity passed to them, which is the focus of your programming. Additionally, the HL7 Soup API provides an HL7 message interface and a helper class that simplify HL7 programming.

Let’s take a look at what available:

IWorkflowInstance (available as the ‘workflowInstance’ object variable)

Provides details about the currently running workflow instance, including variable values and all executed activities with their messages.

Activities

All the activities that have executed so far.

CurrentActivityInstance

The currently executing activity instance. The same value as the activityInstance variable.

ReceivingActivityInstance

The receiving activity instance (the initial activity).

GetVariable

Get the value of a variable by name.

SetVariable

Set the value of a variable by name.

 

IActivityInstance (available as the ‘activityInstance’ object variable)

Details about the current running activity instance. The type IActivityInstance is also used by the workflow properties Activities, CurrentActivityInstance, and ReceivingActivityInstance.

Message

The message sent to the activity as set in the workflow designer in the message template box (often message received by the first activity in the workflow). See IMessage below.

ResponseMessage

The response message that this activity returns (optional). This value is not available to your code Transformers as they execute before the activity creates this value. If you need to manipulate the returned message via Transformers, then do so in the transformers of following activities that use the values. See IMessage below.

Filtered

If the activity was filtered or not. Filtered activities are not executed.

 

IMessage

The message and response message are both of type IMessage, and this is the base class for all messages regardless of message type (e.g. HL7, XML, JSON, or CSV):

Text

The read-only text of the message.

GetValueAtPath

Gets the value at the specified path. The message type determines the syntax of the path. E.g. MSH-9.1 in HL7, [2] for CSV, Parent\Child\Grandchild for JSON or XML.

SetValueAtPath

Sets the value at the specified path. Values are automatically encoded for the message type, which escapes any characters used in the structure of the message type. E.g. in XML a < becomes &lt;.

SetStructureAtPath

Sets the structure and values of the message at this path by putting in the text exactly as it is written even if it contains characters used in the structure of the message type. Therefore, this can alter the structure of the data and changes will be reflected after this has executed.

 

IHL7Message

Inherits from IMessage and provides features specific to HL7 messages. Casting an IMessage to an IHL7Message allows you to use these features (assuming it is an HL7 message).

GetSegments

Gets a list of all the segments in the HL7 message. A segment header can be passed as an argument to get a list of all the segments using that specific header. E.g. passing OBX returns a list of all the OBX segments.

GetSegment

Gets a specific segment by its location code or path. E.g. PID, or OBX[2].

AddSegment

Adds one or more segments to the end of the current message. Multiple segments can be separated by a "\r" in the string.

RemoveSegment

Removes the segment from the current message.

GetPart

Gets any part from the message by its path, E.G. "OBX-5.2". This can then be cast to the parts type interface. E.G. IHL7Segment, IHL7Field, IHL7Component, or IHL7SubComponent. The path might look like PID-5, or OBX[2]-5.2

BeginUpdate

Requests that updates are begun. Used for integrations so the message can be updated by an activity without it having to recalculate its content after each update, which improves the performance. Must be followed with an EndUpdate call or changes will not be reflected in the message.

EndUpdate

States that updates have been completed. The message will then reload the changes.

 

IHL7Segment, IHL7Field, IHL7Component, IHL7Subcomponent

These are the all available from the IHL7Message and can be located in the same ways as documented above for IMessage where it finds segments. E.g. IHL7Field belong to IHL7Segment, and IHL7Component belong to IHL7Fields.

We won’t document the functions here as they are basically the same as IHL7Message, and by using IntelliSense you get a pretty good idea of what can be done. The examples below also show how to read, add, and loop over them etc.

Note that the base interface of all these types is IHL7Part.

HL7Helpers

The HL7 Helpers are a set of helper functions that simplify HL7 in a C# environment. Specifically converting between date or encoding\escaping HL7 structure characters.

GetDateFromHL7Date

Converts a string containing an HL7 Date into a DateTime. HL7 dates generally look like yyyyMMddTHHmmss, but all HL7 date formats are accepted by this method.

GetHL7Date

Converts a string containing a date or a DateTime into a string containing an HL7 date. HL7 Dates typically have the format yyyyMMddHHmmss, but if you don’t pass a format automatically adjusts to the required precision depending on the date.

HL7Decode

Decodes text from HL7 into plain text. Converts \F\ to |, \S\ to ^, \R\ to ~, \T\ to & and \E\ to \.

HL7Encode

Encodes text so it can be inserted into an HL7 message when it contains reserved HL7 characters. Converts | to \F\, ^ to \S\, ~ to \R\, & to \T\, \ to \E\ and crlf to the value of the CarriageReturnEscapeCharacter setting in the app config file.

 

Paths

Paths define the location of the different constituent parts that comprise a message. They allow you to refer to different groupings of these parts with varying levels of precision depending on their objective. As they are passed as strings of text, it is possible to construct or manipulate the path in the code before they are used.

HL7 Paths

It is common to use an HL7 path as an argument in the above API. It should be noted that in HL7 Soup, you can always right-click on text within an HL7 message and select ‘Copy Path’ to copy that text’s path to the clipboard.

A path is broken down as:

Segment[RepeatSegmentIndex]-Field[RepeatFieldIndex].Component[RepeatCompnentIndex].SubComponent

When working with IHL7Segment, IHL7Field, etc., some methods take a location as an argument. This location is just the subset of the path that represents the desired item. e.g. a location of 4 would be the equivalent of MSH-4 in an ISegment for location "MSH"

 

JSON and XML Paths

It is possible to use .net syntax to work with both JSON and XML messages, but it is worth noting that you can also address sections in these documents via GetValueAtPath and SetValueAtPath. The path is a simplified XPath expression where you can traverse the document hierarchy with the node names separated by forward-slashes and item indexes in square brackets. Writing the final node starting with an @ represents an attribute (e.g. Patient/ID/@attribute).

breakfast/food[2]/name

 

CSV Paths

Just use the 0 based index in a square bracket. E.g. [0] is the 1st and [1] is the 2nd item in the CSV.

Default Namespaces

The following using statements are included in all scripts but are not shown in the editor.

System, System.Collections.Generic, System.Linq, System.Text, and HL7Soup.Integrations

Others can be added via the standard ‘using’ syntax.

Writing Code for Transformers vs Activities

When creating code, you can either do so with a Code Activity or Transformer, and it's essential to recognise the difference.

In both cases, the activityInstance has a Message value populated with the current activities Message Template.

Transformers can be added to any Activity type and are executed before the activity is. They, therefore, provide an opportunity to manipulate the Message Template before the activity execution. For the same reason, they cannot manipulate the ResponseMessage of an activity as this is created later by the activity itself. Transformers should focus on manipulating the activityInstance.Message or Creating variables.

Coded Activities have a ResponseMessage, so these are great for building a complex message from scratch that can provide values for reuse throughout the workflow. They are also great for running .net code that processes your data in some useful way — e.g. calling a method in one of your DLL's directly.

Code Examples

The sample code is written for a 'Run Code' activity. The examples that reference the response message will error for a code transformer because of the references to responseMessage won't exist as they are executed before a response is created.

Find the message

Firstly we get the message and response message from the activity and place them into variables. Although this isn't required, it makes the rest of our code more readable. Because this is an HL7 tutorial, we also typecast the messages to an IHL7Message variable ready to take advantage of the more advanced HL7 functions available when referencing a message as this type.

//Find your message
IMessage message = activityInstance.Message;
IMessage responseMessage = activityInstance.ResponseMessage;

//Cast a message as IHL7Message.  This provides many advanced features specific to HL7 Messages such as looping
IHL7Message hL7Message = (IHL7Message)activityInstance.Message;
IHL7Message hL7ResponseMessage = (IHL7Message)activityInstance.ResponseMessage;

 

Get and Set values in a message

All message types can use the GetValueAtPath and SetValueAtPath to work with the message.

//Read a value
string firstName = message.GetValueAtPath("PID-5.2");

//Write a value (note that in HL7 if you write to a location that doesn't exist in the message, the message will be expanded with empty segments or fields so that item does exist)
responseMessage.SetValueAtPath("PID-20", firstName);

 

Converting HL7 date

HL7 uses an entirely different date format to .net. While it's possible in c# to convert between these formats, it's a little fiddly to write. These helper functions make it very straight forward.

//Read a DateTime value from an HL7 Message
DateTime messageDate = HL7Helpers.GetDateFromHL7Date(message.GetValueAtPath("MSH-7"));

//Write a Date to an HL7 Message
responseMessage.SetValueAtPath("MSH-7", HL7Helpers.GetHL7Date(DateTime.Now));

 

Workflow Variables

Workflow variables are frequently used in HL7 Soup because of their ability to be represented directly in a message or settings via the ${variableName} syntax. Upon execution the ${variableName} is replaced with the variables value.

//Get the Value of a variable
string value = workflowInstance.GetVariable("ReceivedDate");

//Set the value of a variable
workflowInstance.SetVariable("MyNewReceivedDate", value);

 

Creating a new message

Sometimes you might need to construct an HL7 message from a string containing one. In this sample, we populate the response message with HL7 in a string of text. This would overwrite the value configured as the response message in the workflow designer. As soon as this code is executed, the message is parsed, and sections in the message become available via GetValueAtPath etc.

//Seed the response message with an new message template.  Note that the activity needs to have the response message enabled
hL7ResponseMessage.SetText(@"MSH|^~\&|HL7Soup|Instance1|HL7Soup|Instance2|200911021022||MDM^T01^MDM_T01|64322|P|2.5.1
PID||||||||||||||||||||||||||||||||||||||");

 

Working with HL7 segments

Using C# code for the manipulation of the segments inside an HL7 Message is often far more flexible and concise than using a UI. Particularly when working with OBR and OBX values, this API makes it simple.

//Get a Segment
IHL7Segment patientSegment = hL7Message.GetSegment("PID");

//Get all segments and loop over them
IHL7Segments allSegments = hL7Message.GetSegments();
foreach (IHL7Segment segment in allSegments)
{
                            //Do Something
}

//Get all segments of a type and loop over them (e.g. loop over all OBX segments)
IHL7Segments obxSegments = hL7Message.GetSegments("OBX");
foreach (IHL7Segment obx in obxSegments)
{
                            //write all obx segments into a new message
    hL7ResponseMessage.AddSegment(obx);
}

//Add a new Segment with the text of the segment
hL7ResponseMessage.AddSegment("NTE|1|O|MyComment|");

//Get the newly added segment (which is just the last segment once added)
IHL7Segment newSegment = hL7ResponseMessage.GetSegments().Last<IHL7Segment>(); 

//Alter a Field in the segment.  E.g. Get the comment at field at location 3 and set it's text to something different
newSegment.GetField(3).SetText("DifferentComment");

//Loop over all the fields in a segment
foreach (IHL7Field field in newSegment.GetFields())
{
     //Do Something
}

//Remove single Segment
hL7ResponseMessage.RemoveSegment(newSegment);


//Remove multiple segments: 
// - In this example we remove all IN1 segments from the message.
// - We use BeginUpdate and EndUpdate to prevent the message from updating until we
// - have finished. Without this, the message would update after the first segment
// - was deleted, and all other segments would not delete as the referenced
// - segments would belong to another message version.
IHL7Message destinationMessage = (IHL7Message)activityInstance.Message; //Get the destination message 
var in1 = destinationMessage.GetSegments("IN1"); //Get all the IN1 Sgments
destinationMessage.BeginUpdate(); //Set BeginUpdate as we are updating multiple Segments and don't want our list to change untill we complete
foreach (var seg in in1) //Loop over all the IN1 segments
{
    destinationMessage.RemoveSegment(seg); //remove the individual Segment  
}
destinationMessage.EndUpdate(); //Update the message

 

Working with HL7 Fields

Fields divide segments into their parts, separating each by the pipe character. Repeat fields allow for the repetition of values (e.g. multiple phone numbers), son knowing how to deal with this added complexity is useful.

//Get an IHL7Field from the message
IHL7Field hL7Field = (IHL7Field)hL7Message.GetPart("PID-5");

//or get the IHL7Field from the Segment
hL7Field = hL7Message.GetSegment("PID").GetField(5);

//Determine if it is a repeat field
bool areThereRepeatedNames = hL7Field.IsFieldRepeated;

//loop over the repeat fields and add them to the response message
if (areThereRepeatedNames)
{
                            foreach (var repeatedFields in hL7Field.GetRelatedRepeatFields()) //Alternative arguments can exclude the current field, but this loop includes the value of hl7Field too.
    {
                            //Do Something
    }
}

//Add a repeating field to a message via HL7 Field (e.g. repeated phone number) .
IHL7Field phoneNumber = (IHL7Field)hL7ResponseMessage.GetPart("PID-13");
phoneNumber.SetText("1234~4321");

//However, adding repeating fields are simpler by using the IMessage.SetStructureAtPath method as it doesn't require the location to already exist
responseMessage.SetStructureAtPath("PID-41", "One~Two");

 

Escaping HL7 control characters

Escaping control characters or "HL7 Encoding" the message is mostly done automatically for you. However, if you need to do this manually, then these functions are available via the helper class.

//Escape structure characters in the text 
IHL7Field address = (IHL7Field)hL7ResponseMessage.GetPart("PID-11");
address.SetTextEncoded("Corner & High and Main street");

//or the same as above using IMessage.SetStructureAtPath so the field didn't already have to exist in the message
responseMessage.SetValueAtPath("PID-50", "peas & queues");

 

Determine the message type

Determine and validate that your code is being used for the correct message type.

//Check if a message is of a particular type (HL7, xml, json)
if (message is IHL7Message)
{
                            //hl7
}
else if (message is IXmlMessage)
{
                            //xml
}
else if (message is IJsonMessage)
{
                            //json
}

 

Adding External References

You may need to reference code in an dll that is not added to the code editor. This could be either .net dll's not included, or third party dll's you'd like to use.

To do this you can use the #r code to reference an external Dll. This example shows how to add a reference to System.Data.SqlClient for accessing a SQL Server (note that the path may vary).

#r "C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.7.1\System.Data.dll"
or
#r "C:\Windows\Microsoft.NET\Framework64\v4.0.30319\System.Data.dll"

 

Download 30 Day Free Trial of HL7 Soup