Documentation

C# / .NET SDK

Using ExactEDI in .NET applications, including ASP.NET and streaming patterns.

Complete guide for using ExactEDI in .NET applications.

Installation

ExactEDI is distributed through your customer portal — it is not published to nuget.org. Download the ExactEDI.X.Y.Z.nupkg package from the portal, then install it from a local feed:

# From the directory containing the downloaded .nupkg
dotnet add package ExactEDI --source .

Alternatively, point your project at a local NuGet feed in nuget.config:

<configuration>
  <packageSources>
    <add key="exactedi-local" value="/path/to/portal-downloads" />
  </packageSources>
</configuration>

Then dotnet add package ExactEDI will resolve from that local source.

Requirements:

  • .NET 6.0+ or .NET Standard 2.1
  • Windows, Linux, or macOS (x64, ARM64)
  • A valid ExactEDI license file from your customer portal

Quick Start

using ExactEDI;

// Analyze a file
using var result = ExactEDI.AnalyzeFile("claims.edi");

Console.WriteLine(
quot;Transactions: {result.TransactionCount}"); Console.WriteLine(
quot;Valid: {result.IsValid}"); // Access PHI-safe facts foreach (var tx in result.Transactions) { Console.WriteLine(
quot;Type: {tx.Type}"); Console.WriteLine(
quot;Claim ID: {tx.ClaimId}"); Console.WriteLine(
quot;Total Charge: ${tx.TotalCharge:F2}"); }

Static Methods

ExactEDI.AnalyzeFile()

Analyze an X12 file with default options.

using var result = ExactEDI.AnalyzeFile("claims.edi");

ExactEDI.AnalyzeString()

Analyze X12 content from a string.

string content = File.ReadAllText("claims.edi");
using var result = ExactEDI.AnalyzeString(content);

ExactEDI.AnalyzeMemory()

Analyze X12 content from bytes.

byte[] data = File.ReadAllBytes("claims.edi");
using var result = ExactEDI.AnalyzeMemory(data);

ExactEDI.Version

Console.WriteLine(ExactEDI.Version);  // "1.0.0"

ExactEDI.LicenseStatus

Console.WriteLine(ExactEDI.LicenseStatus);  // "Professional"

ExactEDI.SetLicensePath()

ExactEDI.SetLicensePath("/path/to/license.lic");

Analyzer Class

For more control, use the Analyzer class:

using var analyzer = new Analyzer
{
    StrictValidation = true,
    ExtractFacts = true,
    MaxTransactions = 100,
    ContinueOnError = true
};

using var result = analyzer.AnalyzeFile("claims.edi");

Properties

PropertyTypeDefaultDescription
StrictValidationbooltrueEnforce envelope validation
ExtractFactsbooltrueExtract PHI-safe facts
MaxTransactionsulong0Limit transactions (0 = unlimited)
ContinueOnErrorbooltrueContinue after errors

Methods

MethodDescription
AnalyzeFile(path)Analyze file
AnalyzeString(data)Analyze string
AnalyzeMemory(byte[])Analyze byte array
AnalyzeMemory(ReadOnlySpan<byte>)Analyze span
ValidateFile(path)Validate only
AnalyzeFileStreaming(path, callback)Stream large files
AnalyzeFileAsync(path, ct?)Analyze file asynchronously
AnalyzeStringAsync(data, ct?)Analyze string asynchronously
AnalyzeMemoryAsync(bytes, ct?)Analyze byte array asynchronously
ValidateFileAsync(path, ct?)Validate only, asynchronously
AnalyzeFileStreamingAsync(path, callback, ct?)Stream large files asynchronously

Async API

Every analysis operation on Analyzer has an async counterpart that returns Task<T> and accepts a CancellationToken. The async methods offload the synchronous native parse to a thread-pool thread so the calling thread — a UI thread, an ASP.NET request thread — is not blocked for the duration of a parse. They perform no network I/O and produce results identical to the synchronous methods.

using var analyzer = new Analyzer();

// Async file analysis
using var result = await analyzer.AnalyzeFileAsync("claims.edi");

// With cancellation (e.g. a timeout, or HttpContext.RequestAborted)
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));
using var result2 = await analyzer.AnalyzeFileAsync("claims.edi", cts.Token);

// Every operation has an async form
using var r3 = await analyzer.AnalyzeStringAsync(ediContent);
using var r4 = await analyzer.AnalyzeMemoryAsync(ediBytes);
var vstats = await analyzer.ValidateFileAsync("claims.edi");
var sstats = await analyzer.AnalyzeFileStreamingAsync("large.edi",
    (json, type) => true);

For the one-shot methods, cancellation is honored before the native parse begins. For AnalyzeFileStreamingAsync, cancellation is cooperative and honored mid-stream — the stream stops at the next transaction boundary.

The native engine itself is synchronous and deterministic; these methods are thread-offload wrappers, not an asynchronous parser.


AnalysisResult

The result object contains all analysis output. Always dispose with using.

using var result = ExactEDI.AnalyzeFile("claims.edi");

// Check validity
if (result.IsValid)
{
    Console.WriteLine("No validation errors");
}
else
{
    Console.WriteLine(
quot;{result.Stats.ErrorCount} errors"); } // Statistics Console.WriteLine(
quot;Bytes: {result.Stats.BytesProcessed}"); Console.WriteLine(
quot;Segments: {result.Stats.SegmentsParsed}"); Console.WriteLine(
quot;Transactions: {result.TransactionCount}"); Console.WriteLine(
quot;Time: {result.Stats.ElapsedSeconds:F3}s"); // Access transactions foreach (var tx in result.Transactions) { Console.WriteLine(
quot;{tx.Type}: {tx.ClaimId}"); } // Export to JSON string json = result.ToJson(); string factsJson = result.FactsJson(); // PHI-safe

Properties

PropertyTypeDescription
IsValidboolTrue if no errors
TransactionCountintNumber of transactions
TransactionsIReadOnlyList<TransactionFacts>Transaction facts
StatsAnalysisStatsProcessing statistics

Methods

MethodReturnsDescription
ToJson()stringFull JSON output
FactsJson()stringPHI-safe Facts JSON

AnalysisStats

var stats = result.Stats;

Console.WriteLine(
quot;Bytes: {stats.BytesProcessed}"); Console.WriteLine(
quot;Segments: {stats.SegmentsParsed}"); Console.WriteLine(
quot;Interchanges: {stats.InterchangeCount}"); Console.WriteLine(
quot;Groups: {stats.GroupCount}"); Console.WriteLine(
quot;Transactions: {stats.TransactionCount}"); Console.WriteLine(
quot;Errors: {stats.ErrorCount}"); Console.WriteLine(
quot;Warnings: {stats.WarningCount}"); Console.WriteLine(
quot;Time: {stats.ElapsedSeconds:F3}s");

TransactionFacts

Each transaction contains PHI-safe extracted facts.

foreach (var tx in result.Transactions)
{
    // Transaction identification
    Console.WriteLine(tx.Type);                // "837P", "835", etc.
    Console.WriteLine(tx.InterchangeControl);  // ISA control number
    Console.WriteLine(tx.GroupControl);        // GS control number
    Console.WriteLine(tx.TransactionControl);  // ST control number

    // Claim information
    Console.WriteLine(tx.ClaimId);
    Console.WriteLine(tx.ClaimStatus);         // 835 only
    Console.WriteLine(string.Join(", ", tx.ServiceDates));

    // Financial
    Console.WriteLine(
quot;Charge: ${tx.TotalCharge:F2}"); Console.WriteLine(
quot;Payment: ${tx.TotalPayment:F2}"); // Provider/Payer Console.WriteLine(tx.PayerId); Console.WriteLine(tx.PayerName); Console.WriteLine(tx.BillingProviderNpi); // Clinical (codes only, no PHI) Console.WriteLine(string.Join(", ", tx.ProcedureCodes)); Console.WriteLine(string.Join(", ", tx.DiagnosisCodes)); Console.WriteLine(tx.PlaceOfService); Console.WriteLine(tx.ServiceLineCount); // Export Console.WriteLine(tx.ToJson()); }

Supported Transactions

The TransactionType enum reports all of the following; ten also receive loop-aware structural validation.

TypeDescriptionLoop-aware validation
837PProfessional claimYes
837IInstitutional claim
837DDental claim
835Remittance adviceYes
270Eligibility inquiryYes
271Eligibility responseYes
276Claim status requestYes
277Claim status responseYes
277CAClaim acknowledgmentYes
278Health care services review (prior authorization)Yes
820Premium paymentYes
834Benefit enrollment
999Implementation acknowledgmentYes
TA1Interchange acknowledgment

TransactionType Enum

public enum TransactionType
{
    Unknown = 0,
    Claim837P = 1,
    Claim837I = 2,
    Claim837D = 3,
    Remittance835 = 4,
    ClaimStatus277 = 5,
    Acknowledgment999 = 6,
    Eligibility270 = 7,
    EligibilityResponse271 = 8,
    ClaimStatusRequest276 = 9,
    ClaimAcknowledgment277CA = 10,
    PriorAuthorization278 = 11,
    PremiumPayment820 = 12,
    BenefitEnrollment834 = 13,
    InterchangeAckTA1 = 14
}

Streaming Large Files

For files too large to fit in memory:

using var analyzer = new Analyzer();

var stats = analyzer.AnalyzeFileStreaming("large.edi", (json, type) =>
{
    Console.WriteLine(
quot;Processing {type}"); // Parse JSON using var doc = JsonDocument.Parse(json); var claimId = doc.RootElement.GetProperty("claim_id").GetString(); // Save to database, etc. SaveToDatabase(claimId, json); return true; // Continue (false to stop) }); Console.WriteLine(
quot;Processed {stats.TransactionCount} transactions");

Error Handling

try
{
    using var result = ExactEDI.AnalyzeFile("claims.edi");
    // Process result
}
catch (ExactEDIException ex)
{
    Console.WriteLine(
quot;EDI Error: {ex.Message}"); Console.WriteLine(
quot;Status: {ex.Status}"); } catch (FileNotFoundException) { Console.WriteLine("File not found"); }

ExactEDIException

public class ExactEDIException : Exception
{
    public Status Status { get; }
}

public enum Status
{
    Ok = 0,
    InvalidArgument,
    FileNotFound,
    FileRead,
    FileWrite,
    ParseFailed,
    ValidationFailed,
    OutOfMemory,
    License,
    Internal
}

ASP.NET Core Integration

Controller Example

[ApiController]
[Route("api/[controller]")]
public class EdiController : ControllerBase
{
    [HttpPost("analyze")]
    public IActionResult Analyze(IFormFile file)
    {
        if (file == null || file.Length == 0)
            return BadRequest("No file provided");

        using var stream = new MemoryStream();
        file.CopyTo(stream);

        try
        {
            using var result = ExactEDI.AnalyzeMemory(stream.ToArray());

            return Ok(new
            {
                Valid = result.IsValid,
                TransactionCount = result.TransactionCount,
                Transactions = result.Transactions.Select(tx => new
                {
                    tx.Type,
                    tx.ClaimId,
                    tx.TotalCharge,
                    tx.TotalPayment
                })
            });
        }
        catch (ExactEDIException ex)
        {
            return BadRequest(new { Error = ex.Message });
        }
    }
}

Minimal API Example

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapPost("/analyze", async (IFormFile file) =>
{
    using var stream = new MemoryStream();
    await file.CopyToAsync(stream);

    try
    {
        using var result = ExactEDI.AnalyzeMemory(stream.ToArray());
        return Results.Ok(new
        {
            result.IsValid,
            result.TransactionCount,
            Facts = result.FactsJson()
        });
    }
    catch (ExactEDIException ex)
    {
        return Results.BadRequest(new { Error = ex.Message });
    }
});

app.Run();

Dependency Injection

// Register as singleton (Analyzer is reusable)
builder.Services.AddSingleton<Analyzer>(sp =>
{
    return new Analyzer
    {
        StrictValidation = true,
        ExtractFacts = true
    };
});

// Use in controller
public class ClaimsController : ControllerBase
{
    private readonly Analyzer _analyzer;

    public ClaimsController(Analyzer analyzer)
    {
        _analyzer = analyzer;
    }

    [HttpPost]
    public IActionResult Process(IFormFile file)
    {
        // Use _analyzer
    }
}

Common Patterns

Filter by Transaction Type

using var result = ExactEDI.AnalyzeFile("mixed.edi");

// Get only 837 claims
var claims = result.Transactions
    .Where(tx => tx.Type.ToString().StartsWith("Claim837"))
    .ToList();

// Get only 835 remittances
var remittances = result.Transactions
    .Where(tx => tx.Type == TransactionType.Remittance835)
    .ToList();

Aggregate Financials

using var result = ExactEDI.AnalyzeFile("claims.edi");

var totalCharges = result.Transactions.Sum(tx => tx.TotalCharge);
var totalPayments = result.Transactions.Sum(tx => tx.TotalPayment);

Console.WriteLine(
quot;Total Charges: ${totalCharges:N2}"); Console.WriteLine(
quot;Total Payments: ${totalPayments:N2}");

Group by Type

using var result = ExactEDI.AnalyzeFile("mixed.edi");

var byType = result.Transactions
    .GroupBy(tx => tx.Type)
    .Select(g => new
    {
        Type = g.Key,
        Count = g.Count(),
        TotalCharge = g.Sum(tx => tx.TotalCharge)
    });

foreach (var group in byType)
{
    Console.WriteLine(
quot;{group.Type}: {group.Count} tx, ${group.TotalCharge:N2}"); }

LINQ to JSON

using System.Text.Json;

using var result = ExactEDI.AnalyzeFile("claims.edi");
var factsJson = result.FactsJson();

using var doc = JsonDocument.Parse(factsJson);
var transactions = doc.RootElement.GetProperty("transactions");

foreach (var tx in transactions.EnumerateArray())
{
    var type = tx.GetProperty("type").GetString();
    var charge = tx.GetProperty("total_charge").GetDecimal();
    Console.WriteLine(
quot;{type}: ${charge:N2}"); }

Batch Processing

var files = Directory.GetFiles("incoming", "*.edi");

// Sequential
foreach (var file in files)
{
    using var result = ExactEDI.AnalyzeFile(file);
    Console.WriteLine(
quot;{Path.GetFileName(file)}: {result.TransactionCount} tx"); } // Parallel Parallel.ForEach(files, file => { using var result = ExactEDI.AnalyzeFile(file); Console.WriteLine(
quot;{Path.GetFileName(file)}: {result.TransactionCount} tx"); });

Platform-Specific Notes

Native Library Loading

The NuGet package includes native libraries for all platforms:

  • runtimes/win-x64/native/exactedi.dll
  • runtimes/win-arm64/native/exactedi.dll
  • runtimes/linux-x64/native/libexactedi.so
  • runtimes/linux-arm64/native/libexactedi.so
  • runtimes/osx-x64/native/libexactedi.dylib
  • runtimes/osx-arm64/native/libexactedi.dylib

Self-Contained Deployment

Native libraries are automatically included in self-contained publishes:

dotnet publish -r win-x64 --self-contained
dotnet publish -r linux-x64 --self-contained

Performance Tips

  1. Reuse Analyzer instances - Create once, use many times
  2. Use streaming for files over 100MB
  3. Process in parallel with Parallel.ForEach
  4. Disable fact extraction if only validating
  5. Always dispose results with using

Troubleshooting

DllNotFoundException

The native library is missing. Ensure the NuGet package is properly installed and the correct runtime is available.

License errors

Console.WriteLine(ExactEDI.LicenseStatus);
ExactEDI.SetLicensePath("/path/to/license.lic");

Memory issues

Use streaming for large files or set MaxTransactions to limit processing.


See Also