EDIdEv - Electronic Data Interchange Development

An AS2 Server Example

This page explains the AS2 Server example written in C# using the Framework EDI (FREDI) component posted at EDIINT AS2.

 

Overview

An AS2 server is basically an HTTP server that is able to receive AS2 messages and sends out MDN acknowledgments.  Because ASP already has HTTP server functionalities, the AS2 Server program was developed as an ASP application.  Below is its algorithm:

  1. Receive AS2 document by HTTP.

  2. Process AS2 document and extract its message content.

  3. If MDN was requested, then create MDN and send it.

 

Receiving an AS2 document

AS2 documents that are uploaded to the AS2 server are received as a file stream from an HTTP connection, and then are saved to a binary file in the procedure "SaveAS2ToFile".

 FileStream fsAs2 = new FileStream(uploadFileName, FileMode.Create, FileAccess.Write);
 BinaryWriter writerAs2 = new BinaryWriter(fsAs2);

 int buffSize = 512;
 bool done = false;
 byte[] bits = new byte[buffSize];

 while (!done)
 {
    bits = reader.ReadBytes(buffSize);
    if (bits.Length == 0)
    {
        done = true;
    }
    else
    {
        writerAs2.Write(bits);
    }
 }

Note that the file stream does not include HTTP headers.  However, the headers were obtained earlier when the page was first activated (procedure "Page_Load").

 string sHttpHeader = "";

 // Load ServerVariable collection into NameValueCollection object.
 NameValueCollection coll = Request.ServerVariables;
 // Get names of all keys into a string array.
 String[] arr1 = coll.AllKeys;
 for (int i = 0; i < arr1.Length; i++)
 {
    if (arr1[i].StartsWith("ALL_RAW"))
    {
        String[] arr2 = coll.GetValues(arr1[i]);
        for (int ii = 0; ii < arr2.Length; ii++)
        {
            if (ii != 0)
            {
                sHttpHeader = sHttpHeader + "<br>";
            }
            sHttpHeader = sHttpHeader + arr1[i] + ": " + arr2[ii];
        }
    }
 }

The binary file and HTTP headers are later combined into a mailDocument object variable (oMailDocument) when the LoadContent method is called in procedure "ProcMDN".

 if (oMailDoc.LoadContent(AS2FileName, sHttpHeader) != 1)
    {
        // "Failed to load AS2"
    }
    else
    {
        ...

The entire AS2 document in the oMailDoc object can be viewed by saving it to a file with the Save method.  Saving objects to a file is not necessary throughout this AS2 Server program, but is helpful for troubleshooting.

 oMailDoc.Save(sSaveFilePath + "_MailDoc.bin");

 

Processing an AS2 document

In FREDI, the mailDocument consists of two message objects:  the subject message (mailMessage); and the message disposition notification (MDN).  The subject message would contain the EDI file, while the MDN message would have the MDN.  Whenever processing occurs in the subject message, the FREDI component will automatically be building the MDN message, if there was a request for it.

Processing can be controlled with the Option method.  For example, the line below disables automatic processing of the message when the AS2 document was loaded at the LoadContent method of the oMailDoc object.

 oMailDoc.set_Option(MailDocOptionIDConstants.MailDocOpt_Auto, 0);

And the line below, stops any processing beyond the point where the content (EDI file) gets to be processed.  Basically, processing stops up to where the content type can be determined.  For example, once the file type is known to be an EDI X12, UN/EDIFACT or XML file), processing does not go any further.

 oMailDoc.set_Option(MailDocOptionIDConstants.MailDocOpt_EnableEdi, 0);

The reason for wanting to determine the file type content, and not automatically processing them through, is so that files can be managed separately depending on their type.  In this example, the files are saved with different extensions according to their type, and their appropriate SEF files are loaded.

 //Get body from message so that we can extract the file from it.
 mailBody oMailBody = oMessage.GetBody();

 //Get the content type of the EDI message so that it can be processed appropriately
 string sTypeMessage = oMessage.get_HeaderFieldValue("Content-Type");

 if (sTypeMessage.ToLower() == "application/edi-x12") //EDI is X12
 {

    //Export the EDI part of the message into a file
    oMailBody.Export(sSaveFilePath + ".X12");

    //Load all pertaining X12 SEF files here.
    oEdiDoc.LoadSchema(sPath + "SEF/sample_850_X12-4010.SEF", SchemaTypeIDConstants.Schema_Standard_Exchange_Format);
    oEdiDoc.LoadSchema(sPath + "SEF/sample_837_X098.SEF", SchemaTypeIDConstants.Schema_Standard_Exchange_Format);
 }

 else if (sTypeMessage.ToLower() == "application/edifact") //EDI is EDIFACT
 {

    //Export the EDI part of the message into a file
    oMailBody.Export(sSaveFilePath + ".edi");

    //UNA segment allowed
    oEdiDoc.set_Option(DocumentOptionIDConstants.OptDocument_ServiceSegment, 1);

    //Load all pertaining EDIFACT SEF files here.
    oEdiDoc.LoadSchema(sPath + "SEF/sample_ORDERS_S93A.SEF", SchemaTypeIDConstants.Schema_Standard_Exchange_Format);
 }

The file content is obtained by first extracting the message for the AS2 document.  This is accomplished by doing the following:

 mailMessage oMessage = oMailDoc.GetMessageContent();

At this point, the state of the message can be encrypted, signed, or encoded in base64, or just plain text.  The process method can process the message at any of these states just by calling the line:

 oMailDoc.Process();

However, to break the process into steps would make it easier to troubleshoot the program should a problem occur during the process of "unwrapping" the message.

 // It's not necessary to process in steps, but good for troubleshooting.
 // To process in steps, use the ProcessStep method.
 string sContentTransferEncoding = oMessage.get_HeaderFieldValue("Content-Transfer-Encoding").ToLower();

 // If the file was encoded in base64, this step will decode it only.
 if (sContentTransferEncoding == "base64")
 {
    oMessage.ProcessStep();
    oMessage.Save(sSaveFilePath + "_MessageAfterBase64Process.bin");
 }

 sContentTransferEncoding = oMessage.get_HeaderFieldValue("Content-Transfer-Encoding").ToLower();

 // If the file was encrypted, this step will decrypt it only.
 if (sContentTransferEncoding == "binary")
 {
    oMessage.ProcessStep();
    oMessage.Save(sSaveFilePath + "_MessageAfterBinaryProcessProcess.bin");
 }

 // Do all other processes to get to the Content Type of the file.
 oMailDoc.Process();
 oMailDoc.Save(sSaveFilePath + "_ContentType.bin");

Note that when the message is getting unwrapped, the header information of the message will change to describe the message at its present state.  Some headers may even be excluded from the original state of the message like the "Receipt-delivery-option".  So the values of this particular header were saved before any processing was initiated because they will later be needed when sending MDN asynchronously.

 string urlMDN = oMessage.get_HeaderFieldValue("Receipt-delivery-option");
 mailInternetURI oReceiptURI = oMessage.GetHeaderTypeURI("Receipt-delivery-option");

Also note that the certificate names, encryption and assurance algorithms are not specified in the program when deciphering the message because those information are in the message itself.  What needs to be specified however, is where the certificates are located.

 //security settings.  Use the eSecurity Console Utility to view where the certificates are located.
 ediSecurities oSecurities = oEdiDoc.GetSecurities();
 oSecurities.DefaultProviderName = "Microsoft Strong Cryptographic Provider";
 oSecurities.DefaultCertSystemStoreLocation = "LocalMachine";
 oSecurities.DefaultCertSystemStoreName = "My";
 oSecurities.DefaultKeyContainer = "test_cert_key";

The certificates for the AS2 Server have to be stored in the "LocalMachine" folder in the "Machine Key Sets" certificate store because ASP applications do not use an interactive login account when accessing certificates as opposed to desktop applications, which locate their certificates in the "CurrentUser" folder in the "User Key Sets" certificate store.   Therefore, in the AS2 Server program, the "MachineKeySet" option must be enabled.

 //Use certificates in Machine Key Sets Certificate Store. Enable this when certificates are installed on servers.
 oEdiDoc.set_Option(DocumentOptionIDConstants.OptDocument_MachineKeySet, 1);

 

Message Dispostion Notification (MDN)

When an AS2 file is loaded into the mailDocument object, it checks the file for the presence of the "Disposition-Notification-To" header. If the header exist then an MDN has been requested, and the FREDI component internally builds the MDN object while the mailDocument object is being processed.  The MDN object must have earlier been instantiated with the following command:

  mailMessage oMDN = oMailDoc.GetMDN();

If the "Disposition-Notification-To" header does not exist, then the MDN object will be empty. 

Because the FREDI component already manages the MDN request, the AS2 server program does not have to check the AS2 message if an MDN was requested or not.  However, it still has to check for the presence of the "Receipt-delivery-option" header to see if the MDN should be sent synchronously or asynchronously.  If the "Receipt-delivery-option" header exists then the MDN has to be sent to the destination entered in the header using a different connection than the connection that was used to deliver the AS2 document.  (This is an asynchronous acknowledgment.)  The AS2 server can use HTTP, HTTPS, FTP and SMTP connection to send the MDN asynchronously.

Note that at the point where the program checks the "Receipt-delivery-option" header, the MDN is well formed and can already be sent to acknowledge the AS2 message; but our AS2 Server example program takes the additional step of validating the EDI file and including the results in the MDN.  The additional steps encompass:

  • Setting the EnableEDI option to true of the oMailDoc object so that when the process method get's executed, the message content is unwrapped completely to the point where only the EDI file exist in the message, and is then handed on to the EDI document object (oEdiDoc).

     oMailDoc.set_Option(MailDocOptionIDConstants.MailDocOpt_EnableEdi, 1);
     oMailDoc.set_Option(MailDocOptionIDConstants.MailDocOpt_Auto, 1);

     oMailDoc.Process()
  • Iterating through each segment of the EDI document so that they can get validated

     ediDataSegment oSegment = oEdiDoc.FirstDataSegment;
     while (oSegment != null)
     {
        ediDataSegment.Set(ref oSegment, oEdiDoc.NextDataSegment);
     }
  • Refreshing the MDN so that the results of the EDI validation gets included in the MDN

    oMailDoc.RefreshMDN()

When sending the MDN synchronously over HTTP, the MDN headers have to be added to the response headers...

 mailHeaders oHeaders = oMDN.GetHeaders();
 mailHeader oHeader = null;
 for (int i = 1; i <= oHeaders.Count; i++)
 {
    mailHeader.Set(ref oHeader, oHeaders.GetHeaderByIndex(i));
    Response.AddHeader(oHeader.Name, oHeader.Value);
 }

...while the body is sent in binary using the BinaryWrite method after saving it to a file.

 mailMessages oMdnMsgs = new mailMessages();

 // Get the MDN message only (not the headers)
 oMdnMsgs = oMDN.GetMessages();

 //save the message to a file so that we can read it back as binary
 oMdnMsgs.Export(sSaveFilePath + ".msgs.mdn");

 FileStream oMdnFs = new FileStream(sSaveFilePath + ".msgs.mdn", FileMode.Open);
 BinaryReader oMdnReader = new BinaryReader(oMdnFs);

 //read the generated .msgs.mdn file and write it back to the response stream
 int buffSize = 512;
 bool done = false;
 byte[] bits = new byte[buffSize];

 while (!done)
 {
    bits = oMdnReader.ReadBytes(buffSize);
    if (bits.Length == 0)
    {
         done = true;
    }
    else
    {
         Response.BinaryWrite(bits);
    }
 }
Setting up the As2 Server

1.  Install a FREDI Runtime Server license on the server where you will be deploying the AS2 Server program.

2.  Modify the AS2 Server program, and enter the appropriate values to:  (Use the eSecurity Console Utility to view where the certificates are located.)

      oSecurities.DefaultProviderName = ";
      oSecurities.DefaultCertSystemStoreLocation = "";
      oSecurities.DefaultCertSystemStoreName = "";
      oSecurities.DefaultKeyContainer = "";

      ... and edit the web.config file
            - Enter the appropriate paths for variables: uploadDirectory, processDirectory, sef
            - Set impersonate to true, then use an administrator user account, or create a user account that has access to at least the following:
                 - Framework EDI components (path and registry)
                 - EDI temp files, and all folders of AS2 server application
                 - private and public keys in certificate stores

3.  Explicitly specify the platform to be x86 or x64 before compiling the AS2 Server program.

4.  Publish the AS2 Server program into IIS as an application.
      - e.g. localhost, "Default Web Site/As2ServerNet/"
      - Check the box "Mark as IIS application on destination"

5.  Create the following sub directories under the AS2 Server application:
      - uploadDirectory - to contain files of the actual body and HTTP headers of documents that were received by the AS2 Server.
      - processDirectory - to contain the files in different format stages when the AS2 message was being processed.
      - sef - to contain SEF files that are for EDI files expected to be received and processed.
      - cert - to contain public certificate files.

6.  Edit permission of system account "NETWORK SERVICE" to allow for modification

7.  Verify that the platform of the application pool of the AS2 Server program is consistent with the platform of the program itself.  If the program platform is x64, disable the 32-bit application setting of the pool by going to the Advanced Settings of the Application Pool then set the “Enable 32-bit Application” field to false.

8.  Obtain SEF files for the EDI files you're expecting to receive, and copy them to the SEF folder under AS2 Sever application folder.

9a.  Purchase a class 3 SSL certificate for your server, and install it.  Then share its public key with your trading partners so that they can use it to encrypt files that they will be sending to the AS2 Server.  The paired private key (not shared, but installed on the server), will be used for decrypting the files, as well as for signing signed MDN's.

 b.  Obtain your trading partners' public keys from them, and install them on the server so that the AS2 server application can verify the signature in the signed messages it receives.

10.  Configure your server to allow HTTP posts to the AS2 Server application..  Check your firewall and router.


View entire source code

To download the complete program, click here.

 

    Click here to download a trial version of the Framework EDI