ProgressSharp 1.0.0

dotnet add package ProgressSharp --version 1.0.0
                    
NuGet\Install-Package ProgressSharp -Version 1.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="ProgressSharp" Version="1.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="ProgressSharp" Version="1.0.0" />
                    
Directory.Packages.props
<PackageReference Include="ProgressSharp" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add ProgressSharp --version 1.0.0
                    
#r "nuget: ProgressSharp, 1.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package ProgressSharp@1.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=ProgressSharp&version=1.0.0
                    
Install as a Cake Addin
#tool nuget:?package=ProgressSharp&version=1.0.0
                    
Install as a Cake Tool

ProgressSharp

NuGet Build Status License

A production-grade Progress/OpenEdge integration library for .NET 8+ that provides strongly-typed GET/POST operations against Progress tasks using POCO models + LINQ, eliminating hard-coded DataTable columns and providing comprehensive telemetry, resilience, and testing capabilities.

Features

๐Ÿš€ Strongly-Typed Operations - Use POCOs with attributes instead of DataTable manipulation
๐ŸŽฏ LINQ Integration - Project EF queries directly to Progress DTOs
๐Ÿ›ก๏ธ Production-Ready - Built-in resilience policies, telemetry, and structured logging
๐Ÿงช Testable - Comprehensive abstractions and fake implementations
๐Ÿ“Š Observable - OpenTelemetry integration with detailed metrics
โšก High Performance - Cached reflection and optimized mapping
๐Ÿ”ง Flexible Configuration - Support for multiple environments and connection pooling

Quick Start

Installation

dotnet add package ProgressSharp

Configuration

// Program.cs or Startup.cs
services.AddProgressSharp(Configuration);
// appsettings.json
{
  "Progress": {
    "AppServer": "AppServer://your-server:5162/asbroker1",
    "Username": "your-username",
    "Password": "your-password", 
    "Env": "production",
    "CallTimeout": "00:01:00",
    "PoolSize": 10,
    "EnableCircuitBreaker": true,
    "MaxRetryAttempts": 3
  }
}

Define Your DTOs

[ProTable("ttSabBankResponse")]
public sealed class SabBankResponseDto
{
    [ProField("Module")]       public string? Module { get; init; }
    [ProField("Action")]       public string? Action { get; init; }
    [ProField("ReferCode")]    public string? ReferCode { get; init; }
    [ProField("SerialNo")]     public int SerialNo { get; init; }
    [ProField("PaidAmt")]      public decimal PaidAmt { get; init; }
    [ProField("SABStatus")]    public string? SabStatus { get; init; }
    [ProField("IMSStatus")]    public string? ImsStatus { get; init; }
    [ProField("CreatedOn")]    public DateTime CreatedOn { get; init; }
}

[ProTable("ttCriteria")]
public sealed class SettlementCriteria
{
    [ProField("StatusCode")] public string? StatusCode { get; init; }
    [ProField("FromDate")]   public DateTime? FromDate { get; init; }
    [ProField("ToDate")]     public DateTime? ToDate { get; init; }
}

Use the Gateway

public class SettlementService
{
    private readonly IProgressGateway _progressGateway;

    public SettlementService(IProgressGateway progressGateway)
    {
        _progressGateway = progressGateway;
    }

    // GET operation with criteria
    public async Task<SabBankResponseDto[]> GetSettlementsAsync(string statusCode)
    {
        var criteria = new SettlementCriteria { StatusCode = statusCode };
        
        var result = await _progressGateway.GetAsync<SabBankResponseDto[]>(
            taskName: "GetSettlements",
            criteria: criteria);

        if (result.HasErrors)
        {
            throw new InvalidOperationException(
                $"Progress task failed: {string.Join(", ", result.Errors.Select(e => e.Description))}");
        }

        return result.Data ?? Array.Empty<SabBankResponseDto>();
    }

    // POST operation with data
    public async Task<int> UpdateIMSAsync(IEnumerable<SabBankResponseDto> settlements)
    {
        var settlementsArray = settlements.ToArray();
        
        var result = await _progressGateway.ExecuteAsync<SabBankResponseDto[], SabBankResponseDto[]>(
            taskName: "UpdateIMSStatus",
            input: settlementsArray);

        if (result.HasErrors)
        {
            throw new InvalidOperationException(
                $"IMS update failed: {string.Join(", ", result.Errors.Select(e => e.Description))}");
        }

        return result.Data?.Count(r => r.ImsStatus == "S") ?? 0;
    }
}

LINQ Integration

Replace hard-coded DataTable manipulation with strongly-typed LINQ projections:

// Before: Manual DataTable manipulation
var dataTable = new DataTable("ttSabBankResponse");
// ... add columns manually ...
foreach (var settlement in settlements)
{
    var row = dataTable.NewRow();
    row["Module"] = settlement.Source;
    row["ReferCode"] = settlement.ReferCode;
    // ... set all fields manually ...
    dataTable.Rows.Add(row);
}

// After: LINQ projection to Progress DTO
var settlements = await dbContext.Set<Settlement>()
    .Where(s => s.Status == targetStatus)
    .Select(s => new SabBankResponseDto
    {
        Module = s.Source,
        Action = "",
        ReferCode = s.ReferCode,
        SerialNo = s.SerialNo,
        PaidAmt = s.DueAmt,
        SabStatus = s.Status,
        SabMessage = s.BbDescription ?? s.BStatusDetail,
        BankRefNo = s.BBankReference,
        BatchId = s.BSequenceNum,
        IbanNo = s.Iban,
        BankSubmitDate = s.BBankUploadDate,
        PayDate = s.BbCreditValueDate.SafeDateTime(),
        CreatedOn = s.CreatedDate
    })
    .ToArrayAsync();

Error Handling

ProgressSharp provides comprehensive error handling with structured notifications:

var result = await progressGateway.ExecuteAsync<Input, Output>(taskName, input);

if (result.HasErrors)
{
    // Handle errors gracefully
    foreach (var error in result.Errors)
    {
        logger.LogError("Progress error {Code}: {Description}", error.Code, error.Description);
    }
    
    // Still process partial results if available
    if (result.Data != null)
    {
        // Handle partial success
    }
}

// Process informational notifications
foreach (var info in result.Information)
{
    logger.LogInformation("Progress info {Code}: {Description}", info.Code, info.Description);
}

Advanced Features

Multi-Table Payloads

public class MultiTablePayload
{
    [ProTable("ttHeader")]
    public HeaderDto Header { get; set; }
    
    [ProTable("ttDetails")]
    public List<DetailDto> Details { get; set; }
}

Custom Type Converters

// Automatic handling of:
// - Nullable types
// - Enums (by name or value)
// - DateTime/DateOnly/TimeOnly
// - Guid, byte[], custom types
// - Culture-invariant numeric conversion

Telemetry & Observability

// Automatic OpenTelemetry integration
services.AddOpenTelemetry()
    .WithTracing(builder => builder.AddSource("ProgressSharp"));

// Structured logging with correlation
// Activity tags: progress.task, progress.env, dataset.tables, rows.in/out

Testing

// Use fake implementation for testing
services.AddProgressSharpFake();

// Or mock the gateway
var mockGateway = new Mock<IProgressGateway>();
mockGateway.Setup(x => x.ExecuteAsync<Input, Output>(It.IsAny<string>(), It.IsAny<Input>(), It.IsAny<CancellationToken>()))
    .ReturnsAsync(new ProgressResult<Output> { Data = expectedOutput, Notifications = Array.Empty<Notification>() });

Performance

  • Cached Reflection: Mapping metadata cached on first use
  • Connection Pooling: Configurable connection pool size
  • Streaming: Large datasets processed without intermediate materialization
  • Memory Efficient: Minimal allocations during mapping operations

Resilience

Built-in Polly policies provide:

  • Timeout: Configurable per-operation timeouts
  • Retry: Exponential backoff with jitter for transient failures
  • Circuit Breaker: Fail-fast when Progress server is unavailable
  • Bulkhead: Resource isolation between different operations

Migration Guide

From Manual DataTable Code

// OLD: Manual DataTable manipulation
public DataSet CreateInputDataSet(IEnumerable<Settlement> settlements)
{
    var dataSet = new DataSet();
    var table = new DataTable("ttSabBankResponse");
    
    table.Columns.Add("Module", typeof(string));
    table.Columns.Add("ReferCode", typeof(string));
    // ... 15+ more columns ...
    
    foreach (var settlement in settlements)
    {
        var row = table.NewRow();
        row["Module"] = settlement.Source;
        row["ReferCode"] = settlement.ReferCode;
        // ... 15+ more assignments ...
        table.Rows.Add(row);
    }
    
    dataSet.Tables.Add(table);
    return dataSet;
}

// NEW: Strongly-typed with ProgressSharp
public async Task<int> UpdateSettlementsAsync(IEnumerable<Settlement> settlements)
{
    var dtos = settlements.Select(s => new SabBankResponseDto
    {
        Module = s.Source,
        ReferCode = s.ReferCode,
        // ... strongly-typed mapping ...
    }).ToArray();

    var result = await _progressGateway.ExecuteAsync<SabBankResponseDto[], SabBankResponseDto[]>(
        "UpdateIMSStatus", dtos);

    return result.Data?.Count(r => r.ImsStatus == "S") ?? 0;
}

Supported Types

  • Primitives: string, int, long, decimal, double, bool, byte[]
  • Date/Time: DateTime, DateTime?, DateOnly, TimeOnly
  • Other: Guid, Enum (by name or value)
  • Collections: T[], List<T>, IEnumerable<T>, etc.
  • Nullables: All nullable value types supported

Requirements

  • .NET 8.0 or later
  • Progress OpenEdge .NET Client (for production use)
  • C# 12 language features

Contributing

We welcome contributions! Please see CONTRIBUTING.md for guidelines.

License

This project is licensed under the MIT License - see the LICENSE file for details.

Samples

Support


ProgressSharp - Making Progress/OpenEdge integration in .NET simple, type-safe, and production-ready.

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.0 146 9/3/2025