ProgressSharp 1.0.0
dotnet add package ProgressSharp --version 1.0.0
NuGet\Install-Package ProgressSharp -Version 1.0.0
<PackageReference Include="ProgressSharp" Version="1.0.0" />
<PackageVersion Include="ProgressSharp" Version="1.0.0" />
<PackageReference Include="ProgressSharp" />
paket add ProgressSharp --version 1.0.0
#r "nuget: ProgressSharp, 1.0.0"
#:package ProgressSharp@1.0.0
#addin nuget:?package=ProgressSharp&version=1.0.0
#tool nuget:?package=ProgressSharp&version=1.0.0
ProgressSharp
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
- Console Sample - Basic GET/POST operations
- Web API Sample - REST API with Swagger documentation
Support
- ๐ Documentation
- ๐ Issue Tracker
- ๐ฌ Discussions
ProgressSharp - Making Progress/OpenEdge integration in .NET simple, type-safe, and production-ready.
Product | Versions 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. |
-
net8.0
- Microsoft.Extensions.Configuration.Abstractions (>= 8.0.0)
- Microsoft.Extensions.Logging.Abstractions (>= 8.0.0)
- Microsoft.Extensions.Options.ConfigurationExtensions (>= 8.0.0)
- Polly (>= 8.6.3)
- Polly.Core (>= 8.6.3)
- System.Configuration.ConfigurationManager (>= 9.0.8)
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 |