Build EDI files Last updated: 2024-08-23

Generating EDI files with EDI Writer

Generating an EDI file with ediFabric .NET is a three-step process:

  1. Create an instance of the required EDI template (or C# class), e.g. TS837P for a medical claim or TSINVOIC for an invoice.
  2. Populate that instance with data, usually, this requires custom code to convert either a database/domain object or export from an ERP in CSV, XML, JSON, etc. format, to the EDI template.
  3. Write the instance to an EDI file or a stream using EDI writer.

The red path below depicts the generation of an EDI file:

  • A .NET object (instance of its EDI template)
  • Is processed through an EDI writer (EdiFabric)
  • To produce an EDI file
edi template standard

How to create and populate .NET objects

When you need to generate, let's say an invoice in the X12 standard, you'll need to create an instance of the EDI template, that represents an invoice according to the X12 standard. 

Once the instance is available, you can begin populating it with custom code, which maps your internal objects or database export, to the structure of the EDI template.

X12

var result = new TS810();

result.ST = new ST();
result.ST.TransactionSetIdentifierCode_01 = "810";
result.ST.TransactionSetControlNumber_02 = controlNumber.PadLeft(9, '0');

result.BIG = new BIG();
result.BIG.Date_01 = "20000513";
result.BIG.InvoiceNumber_02 = "SG427254";
result.BIG.Date_03 = "20000506";
result.BIG.PurchaseOrderNumber_04 = "508517";
result.BIG.ReleaseNumber_05 = "1001";

EDIFACT

var result = new TSINVOIC();

result.UNH = new UNH();
result.UNH.MessageReferenceNumber_01 = controlNumber.PadLeft(14, '0');
result.UNH.MessageIdentifier_02 = new S009();
result.UNH.MessageIdentifier_02.MessageType_01 = "INVOIC";
result.UNH.MessageIdentifier_02.MessageVersionNumber_02 = "D";
result.UNH.MessageIdentifier_02.MessageReleaseNumber_03 = "96A";
result.UNH.MessageIdentifier_02.ControllingAgencyCoded_04 = "UN";

result.BGM = new BGM();
result.BGM.DOCUMENTMESSAGENAME_01 = new C002();
result.BGM.DOCUMENTMESSAGENAME_01.Documentmessagenamecoded_01 = "380";
result.BGM.Documentmessagenumber_02 = "IN432097";

HL7

var result = new TSRDSO13();

result.MSH = new MSH();

result.MSH.SendingApplication_02 = new SendingApplication();
result.MSH.SendingApplication_02.NamespaceID_01 = senderId;
result.MSH.SendingFacility_03 = new SendingFacility();
result.MSH.SendingFacility_03.NamespaceID_01 = senderFacility;
result.MSH.ReceivingApplication_04 = new ReceivingApplication();
result.MSH.ReceivingApplication_04.NamespaceID_01 = receiverId;
result.MSH.ReceivingFacility_05 = new ReceivingFacility();
result.MSH.ReceivingFacility_05.NamespaceID_01 = receiverFacility;
result.MSH.DateTimeOfMessage_06 = DateTime.Now.Date.ToString("yyyyMMdd") + DateTime.Now.TimeOfDay.ToString("hhmm");
result.MSH.MessageType_08 = new MessageType();
result.MSH.MessageType_08.MessageCode_01 = "RDS";
result.MSH.MessageType_08.TriggerEvent_02 = "O13";
result.MSH.MessageType_08.MessageStructure_03 = "RDS_O13";
result.MSH.MessageControlID_9 = controlNr;
result.MSH.ProcessingID_10 = new ProcessingID();
result.MSH.ProcessingID_10.ProcessingID_01 = "P";
result.MSH.VersionID_11 = new VersionID();
result.MSH.VersionID_11.VersionID_01 = "2.6";

result.LoopPID = new Loop_PID_TSRDSO13();
result.LoopPID.PID = new PID();
result.LoopPID.PID.PatientID_02 = new ExtendedCompositeIDWithCheckDigit();
result.LoopPID.PID.PatientID_02.IDNumber_01 = "0008064993";
result.LoopPID.PID.PatientID_02.AssigningAuthority_04 = new ExtendedAssigningAuthority();
result.LoopPID.PID.PatientID_02.AssigningAuthority_04.ExtendedAssigningAuthority01 = "ENT";
result.LoopPID.PID.PatientID_02.IdentifierTypeCode_05 = "PE";
result.LoopPID.PID.PatientID_02.EffectiveDate_07 = "20030806";
result.LoopPID.PID.PatientID_02.ExpirationDate_08 = "200507";

NCPDP

var result = new TSB1();

result.G1 = new TransactionHeader();
result.G1.TransactionReferenceNumber_1 = tranRefNumber;
result.G1.BINNumber_2 = binNumber;
result.G1.VersionReleaseNumber_3 = "D0";
result.G1.TransactionCode_4 = "B1";
result.G1.ProcessorControlNumber_5 = processorNumber;
result.G1.TransactionCount_6 = "1";
result.G1.ReversalRequest_7 = reversalReq;
result.G1.BillingRequest_8 = billingReq;
result.G1.PredeterminationofBenefitsRequest_9 = preReq;
result.G1.SoftwareVendorCertificationID_10 = "";

result.AM04Loop = new Loop_AM04_TSB1();
result.AM04Loop.AM04 = new AM04();
result.AM04Loop.AM04.CardholderID_C2 = "DK86342S";
result.AM04Loop.AM04.EligibilityClarificationCode_C9 = "0";
result.AM04Loop.AM04.PersonCode_C3 = "001";

SCRIPT

var result = new TSNEWRX();

result.UIH = new UIH();
result.UIH.MESSAGEIDENTIFIER_01 = new S306();
result.UIH.MESSAGEIDENTIFIER_01.MessageType_01 = "SCRIPT";
result.UIH.MESSAGEIDENTIFIER_01.MessageVersionNumber_02 = "010";
result.UIH.MESSAGEIDENTIFIER_01.MessageReleaseNumber_03 = "006";
result.UIH.MESSAGEIDENTIFIER_01.MessageFunction_04 = "NEWRX";
result.UIH.MessageReferenceNumber_02 = tranRefNumber;
result.UIH.DATEANDTIME_05 = new S300();
result.UIH.DATEANDTIME_05.Date_01 = "19971001";
result.UIH.DATEANDTIME_05.Time_02 = "081322";

VDA

var result = new TS4905();

result.S511 = new S511();
result.S511.RecordType_01 = "511";
result.S511.Version_02 = "02";
result.S511.CustomerCode_03 = "59012";
result.S511.SupplierCode_04 = "00323625";
result.S511.TransmissionNumberOld_05 = oldNumber.ToString();
result.S511.TransmissionNumberNew_06 = newNumber.ToString();
result.S511.TransmissionDate_07 = "121115";
result.S511.StartingDate_08 = "111231";

There are multiple approaches on how to convert or map between objects in .NET, however, this is not the premise of this article. To be able to properly construct a .NET object, sufficient knowledge is need for:

  • The source of the data (database, CSV file, service model, XML, JSON, etc.)
  • The destination EDI template, which matches the implementation guideline
  • How to map the source to the destination, using custom .NET mapping code, or JSON/XML serialization

Concrete examples of creating EDI .NET objects:

It is also possible to map the internal structure to the JSON or XML representation of a particular EDI template, and then simply use the available tooling in .NET to deserialize it into a POCO.

Note

For mapping ideas, take a look at how XSLT and Automapper can transform .NET objects: X12 mapping examples, EDIFACT mapping examples.

How to write EDI objects

The purpose of an EDI writer is to write an already populated C# instance of a particular EDI template, to a text EDI file or stream. The writer is a simple extension of the .NET's StreamWriter, which caters to a few extra EDI-related things such as:

  • To convert instances of EDI templates to text layout as expected by the EDI standard.
  • To automatically apply the correct message, group, and interchange trailers. This counts the number of included segments, messages, and groups and matches control numbers.
  • To apply the correct EDI separators.
  • To apply segment postfixes if any.
  • To apply control segments, such as ISA, UNB, MSH, etc. 

All Edi writers provide a fast, non-cached, forward-only way to generate streams or files that contain EDI data. It is up to the consumer to preserve the sequence of what is being written.

Note

There is no need to write any of the EDI trailers because they are set automatically.

All writers implement IDisposable and need to be disposed of. This ensures that all buffered data is properly written and the internal buffer is cleared.

X12

using (var stream = new MemoryStream())
{
   using(var writer = new X12Writer(stream))
   {
       writer.Write(SegmentBuilders.BuildIsa("1"));
       writer.Write(SegmentBuilders.BuildGs("1"));

       //  1.  Write the first invoice
       writer.Write(SegmentBuilders.BuildInvoice("1"));

       //  2.  Write the second invoice
       writer.Write(SegmentBuilders.BuildInvoice("2"));
   }
}

EDIFACT

using (var stream = new MemoryStream())
{
   using(var writer = new EdifactWriter(stream))
   {
       writer.Write(SegmentBuilders.BuildUnb("1"));

       //  1.  Write the first invoice
       writer.Write(SegmentBuilders.BuildInvoice("1"));

       //  2.  Write the second invoice
       writer.Write(SegmentBuilders.BuildInvoice("2"));
   }
}

HL7

using (var writer = new Hl7Writer(stream))
{
    //  Envelope
    writer.Write(SegmentBuilders.BuildFhs("LAB1", "LAB", "DEST2", "DEST", "TESTFILE", "1234"));
    writer.Write(SegmentBuilders.BuildBhs("LAB1", "LAB", "DEST2", "DEST", "TESTBATCH", "1234"));

    //  Write the first dispense
    writer.Write(SegmentBuilders.BuildDispense("LAB1", "LAB", "DEST2", "DEST", "1"));
    //  Write the second dispense
    writer.Write(SegmentBuilders.BuildDispense("LAB1", "LAB", "DEST2", "DEST", "2"));
}

NCPDP

using (var writer = new NcpdpTelcoWriter(stream))
{
    //  Write the transmission header
    writer.Write(SegmentBuilders.BuildTransmissionHeader());

    //  Write the claim 1
    writer.Write(SegmentBuilders.BuildClaim("1"));

    //  Write the claim 2
    writer.Write(SegmentBuilders.BuildClaim("2"));
}

SCRIPT

using (var writer = new NcpdpScriptWriter(stream))
{
    //  Write the transmission header
    writer.Write(SegmentBuilders.BuildInterchangeHeader("1"));

    //  Write the prescription request 1
    writer.Write(SegmentBuilders.BuildPrescriptionRequest("1"));

    //  Write the prescription request 2
    writer.Write(SegmentBuilders.BuildPrescriptionRequest("2"));
}

VDA

using (var writer = new VdaWriter(stream, Environment.NewLine))
{
    writer.Write(deliveryInstruction1);
    writer.Write(deliveryInstruction2);
}

Examples in GitHub:

Batches of EDI transactions

EDI writer allows multiple EDI interchanges, EDI groups, and EDI transactions to be written to the same stream or file. It doesn't matter for the EDI writer if a single item or multiple items are written out and very large sets of data can be written one by one.

All standards that begin with envelopes, like X12 (ISA, GS), EDIFACT (UNB), HL7 (FHS, BHS), need to write their envelopes first, and then all remaining EDI transactions.

Control segments

Control segments are written out using an overload of the EDI writer that takes an exact control segment type. Envelope segments must be written according to the hierarchical order specified by the EDI standard, e.g. for X12 the order is ISA, GS, then all messages, for EDIFACT the order is UNB, UNG (optional), then all messages, etc.

X12

EDIFACT

EDI writers

Common EDI writer settings

All EDI writers can be configured with additional settings which change the behavior of the EDI writer.

  • Encoding - this is the System.Encoding to use when writing to the output stream or file. UTF8 by default.
  • PreserveWhitespace - this is to preserve white spaces in data elements initialized with blanks. False by default.
  • Postfix - this is the postfix string to be applied at the end of each segment. Null by default.
  • Separators - this is the set of separators to be used when generating the EDI document. It uses the default delimiter sets for X12 and EDIFACT when not explicitly set. See Default EDI separators and Custom EDI separators.
  • AutoTrailers - Whether to automatically apply trailers to groups and interchanges. See Turn the auto trailers off.
  • Obfuscate - Whether to obfuscate the values of all data elements of alpha and alphanumeric types. The obfuscation symbol is 'X'. The example value of "Jon Doe" will be obfuscated to "XXXXXXX", e.g., every character, including blanks, is replaced with an 'X'. False by default.

Apply writer settings to EDI writer

X12

X12WriterSettings settings = new X12WriterSettings();
settings.AutoTrailers = true;
settings.Encoding = Encoding.UTF8;
settings.Postfix = Environment.NewLine;
settings.PreserveWhitespace = true;
settings.Separators = Separators.X12;

using (var writer = new X12Writer(stream, settings))

EDIFACT

EdifactWriterSettings settings = new EdifactWriterSettings();
settings.AutoTrailers = true;
settings.Encoding = Encoding.UTF8;
settings.Postfix = Environment.NewLine;
settings.PreserveWhitespace = true;
settings.Separators = Separators.Edifact;

using (var writer = new EdifactWriter(stream, settings))

HL7

Hl7WriterSettings settings = new Hl7WriterSettings();
settings.AutoTrailers = true;
settings.Encoding = Encoding.UTF8;
settings.Postfix = Environment.NewLine;
settings.PreserveWhitespace = true;
settings.Separators = Separators.Hl7;

using (var writer = new Hl7Writer(stream, settings))

SCRIPT

NcpdpScriptWriterSettings settings = new NcpdpScriptWriterSettings();
settings.AutoTrailers = true;
settings.Encoding = Encoding.UTF8;
settings.Postfix = Environment.NewLine;
settings.PreserveWhitespace = true;
settings.Separators = Separators.NcpdpScript;

using (var writer = new NcpdpScriptWriter(stream, settings))

Default EDI separators

EDI separators (or EDI delimiters) are used to separate the different EDI items from one another. A valid set of delimiters must be used, with each type of delimiter at the correct position within the EDI file.

If not explicitly specified the writer will use the default separators per standard:

EDI Standard Component Data Element Escape Repetition Segment Subelement
X12 > *   ^ ~  
EDIFACT : + ? * '  
HL7 ^ | \ ~ \r &
NCPDP SCRIPT : + / * '  

Note

X12 repetition separator was introduced in version 4020 is not available for the earlier versions.

Custom EDI separators

Sometimes the delimiters used by a trading partner are slightly different than the default separator set. To change the default separators and generate an EDI file using custom separators use the static Separators class and pass it to the EDI writer (at the first item written out, usually the interchange header).

Every segment or EDI transaction written after this point will use the new separators until a new separator set is applied.

X12

using (var stream = new MemoryStream())
{
   using(var writer = new X12Writer(stream))
   {
       var isa = new ISA();
       // construct the interchange header ...

       var newSeparators = Separators.X12;
       // change the segment separator, the rest are the default for X12
       newSeparators.Segment = '\n';

       writer.Write(isa, newSeparators);

       // everything written after this point will use the custom separators
   }
}

EDIFACT

using (var stream = new MemoryStream())
{
   using(var writer = new EdifactWriter(stream))
   {
       var unb = new UNB();
       // construct the interchange header ...

       var newSeparators = Separators.Edifact;
       // change the segment separator, the rest are the default for X12
       newSeparators.Segment = '\n';

       writer.Write(unb, newSeparators);

       // everything written after this point will use the custom separators
   }
}

HL7

using (var writer = new Hl7Writer(stream))
{
	//  Set a custom subcomponent separator.
  var separators = Separators.Hl7;
  separators.SubComponent = '#';

  //  Write the dispense
  writer.Write(SegmentBuilders.BuildDispense("LAB1", "LAB", "DEST2", "DEST", "1"), true, separators);
}

SCRIPT

using (var writer = new NcpdpScriptWriter(stream))
{
  //  Set a custom segment separator.
  var separators = Separators.NcpdpScript;
  separators.Segment = '|';

  //  Write the interchange header
  writer.Write(SegmentBuilders.BuildInterchangeHeader(), separators);
  //  Write the prescription request
  writer.Write(SegmentBuilders.BuildPrescriptionRequest());
}

Write transactions without envelopes

It is possible to write EDI transactions without envelopes. To do so, a separator set must be supplied to the writer settings manually.

X12

var settings = new X12WriterSettings() { Separators = Separators.X12 }
using (var stream = new MemoryStream())
{
   using(var writer = new X12Writer(stream, settings))
   {
       var m810_1 = new TS810();
       // construct the first message ...
       await writer.WriteAsync(m810_1);

       var m810_2 = new TS810();
       // construct other messages ...
       await writer.WriteAsync(m810_2);
   }
}

EDIFACT

var settings = new EdifactWriterSettings() { Separators = Separators.Edifact}
using (var stream = new MemoryStream())
{
   using(var writer = new EdifactWriter(stream, settings))
   {
       var invoic_1 = new TSINVOIC();
       // construct the first message ...
       await writer.WriteAsync(invoic_1);

       var invoic_2 = new TSINVOIC();
       // construct other messages ...
       await writer.WriteAsync(invoic_2);
   }
}

Examples in GitHub

Turn the auto trailers off

In situations where auto trailers are not needed, for example when writing to the same file at different times of the day, auto trailers can be turned off. There are two modes for that - transaction trailers and interchange\group trailers.

Turn off the auto trailer on EDI transactions

Set the AutoTrailer argument of the Write method of EDI writer to false.

using (var stream = new MemoryStream())
{
   using(var writer = new EdifactWriter(stream))
   {
       var invoic_1 = new TSINVOIC();

       // construct the first message ...
       await writer.WriteAsync(invoic_1, false);
   }
}

Turn off the auto trailers on EDI interchanges and EDI groups

Set the AutoTrailers flag of EDI writer settings to false.

var settings = new X12WriterSettings() { AutoTrailers = false  }
using (var stream = new MemoryStream())
{
    using(var writer = new X12Writer(stream, settings))
    {
        var isa = new ISA();
        // construct the interchange header ...
        await writer.WriteAsync(isa);

        var gs = new GS();
        // construct the group header ...
        await writer.WriteAsync(gs);

        var m810_1 = new TS810();
        // construct the first message ...
        await writer.WriteAsync(m810_1);
    }

    // no trailers are written
}

Generating EDI files without EDI writer

EDI files can be produced without using the built-in X12 and EDIFACT writers at all. To do so use a combination of .NET's StreamWriter and the ToEdi() method which is available for all transactions and control segments.

The ToEdi() method needs a separator set to output the transaction or control segment as a string.

X12

var settings = new X12WriterSettings() { Separators = Separators.X12 }
using (var stream = new MemoryStream())
{
   using(var writer = new StreamWriter(stream))
   {
       var isa = new ISA();
       // construct the interchange header ...
       writer.Write(isa.ToEdi(Separators.X12));

       var gs = new GS();
       // construct the group header ...
       writer.Write(gs.ToEdi(Separators.X12));

       var m810 = new TS810();
       // construct the first message ...
       writer.Write(m810.ToEdi(settings));

      var ge = new GE();
       // construct the group trailer ...
       writer.Write(ge.ToEdi(Separators.X12));

      var iea = new IEA();
       // construct the interchange trailer ...
       writer.Write(iea.ToEdi(Separators.X12));

      writer.Flush();
   }
}

EDIFACT

var settings = new EdifactWriterSettings() { Separators = Separators.Edifact}
using (var stream = new MemoryStream())
{
   using(var writer = new StreamWriter(stream))
   {
       writer.Write(Separators.Edifact.ToUna());

       var unb = new UNB();
       // construct the interchange header ...
       writer.Write(unb.ToEdi(Separators.Edifact));

       var invoic = new TSINVOIC();
       // construct the first message ...
       writer.Write(invoic.ToEdi(settings));

       var unz = new UNZ();
       // construct the interchange trailer ...
       writer.Write(unz.ToEdi(Separators.Edifact));

       writer.Flush();
   }
}