PTSC.ImportExcel 1.4.0

dotnet add package PTSC.ImportExcel --version 1.4.0
                    
NuGet\Install-Package PTSC.ImportExcel -Version 1.4.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="PTSC.ImportExcel" Version="1.4.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="PTSC.ImportExcel" Version="1.4.0" />
                    
Directory.Packages.props
<PackageReference Include="PTSC.ImportExcel" />
                    
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 PTSC.ImportExcel --version 1.4.0
                    
#r "nuget: PTSC.ImportExcel, 1.4.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 PTSC.ImportExcel@1.4.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=PTSC.ImportExcel&version=1.4.0
                    
Install as a Cake Addin
#tool nuget:?package=PTSC.ImportExcel&version=1.4.0
                    
Install as a Cake Tool

PTSC.ImportExcel

A high-performance Excel/DataTable import library for .NET 8 with:

  • 4 Import Strategies: Override, Grid, OnlyInsert, OnlyUpdate
  • Validation Pipeline: Chain of Responsibility with 9+ built-in rules
  • Fluent Builder API: Clean configuration chaining
  • Batch SQL Execution: Auto-splitting for SQL Server 2100 param limit
  • Error Observer: Structured error collection with Observer pattern
  • Progress Reporting: Real-time progress via abstracted reporter
  • Zero app-specific dependencies: No IClientContext, no ProgressServer, no EF Core — only abstractions

Dependencies

Package Purpose
Microsoft.AspNetCore.App (FrameworkReference) SignalR (IHubContext<THub>), IHttpContextAccessor
Microsoft.Data.SqlClient SQL Server connectivity
Microsoft.Extensions.DependencyInjection.Abstractions DI registration

No dependencies on: EF Core, or any app-specific interfaces (IClientContext, ProgressServer, etc.).

Quick Start

1. Install

dotnet add package PTSC.ImportExcel

2. Register in Program.cs

Minimal — connection string only (no SignalR progress)
using PTSC.ImportExcel;

builder.Services.AddImportExcel(
    builder.Configuration.GetConnectionString("DefaultConnection")
);
With SignalR progress — pass your Hub type
using PTSC.ImportExcel;

builder.Services.AddImportExcel<ChatHub>(
    builder.Configuration.GetConnectionString("DefaultConnection")
);
Full — SignalR progress + user context + column config
using PTSC.ImportExcel;
using Microsoft.AspNetCore.Http;

// ── Step 1: Register ImportUserContext factory (BEFORE AddImportExcel) ──
// This must be registered first so the package's TryAddTransient<ImportUserContext>
// detects it and skips the default (empty) registration.
builder.Services.AddTransient<ImportUserContext>(sp =>
{
    var httpContextAccessor = sp.GetRequiredService<IHttpContextAccessor>();
    var ctx = new ImportUserContext();
    var httpContext = httpContextAccessor.HttpContext;
    if (httpContext != null)
    {
        // Read user/session info from your app's HttpContext.Items, Claims, etc.
        ctx.UserName     = httpContext.Items["UserSession"]?.ToString() ?? "";
        ctx.DataCode     = httpContext.Items["DataCode"]?.ToString() ?? "";
        ctx.ProjectCode  = httpContext.Items["ProjectCode"]?.ToString() ?? "";
        ctx.FacilityCode = httpContext.Items["FacilityCode"]?.ToString() ?? "";
        ctx.Language     = httpContext.Items["Language"]?.ToString() ?? "";
        ctx.ConnectionId = httpContext.Items["ConnectionId"]?.ToString() ?? "";
    }
    return ctx;
});

// ── Step 2: Register ImportExcel with SignalR + column config ──
builder.Services.AddImportExcel<ChatHub>(
    builder.Configuration.GetConnectionString("DefaultConnection"),
    columns => columns
        .SetColumnId("Id")
        .SetColumnCreatedBy("CreatebyUser")
        .SetColumnCreatedDate("DateCreated", "CreateDate")
        .SetColumnModifiedBy("LastedModifyUser", "LastModifiedbyUser")
        .SetColumnModifiedDate("LastedModifyDate", "LastModifiedDate")
        .SetColumnFromClientContext("DataCode", "ProjectCode", "FacilityCode")
);

Important: Register ImportUserContext factory before calling AddImportExcel. The package uses TryAddTransient<ImportUserContext>() internally — it will skip its default (empty) registration if it finds yours already registered.

3. Built-in SignalR Progress Reporter (optional)

When using AddImportExcel<THub>(...), the library auto-registers ImportProgressReporter<THub>. When using AddImportExcel(...) without a Hub type, a NullProgressReporter (no-op) is registered instead.

  • Reads ConnectionId from ImportUserContext.ConnectionId
  • Sends progress via IHubContext<THub>.Clients.Client(connectionId).SendAsync("OnRunProgressBar", ...)
  • Includes percent deduplication (only sends when percent changes)

4. Use in your service/controller

var result = _importExcel.Override.CreateInstance(dataTable, typeof(MyModel))
    .WithPrimaryKeys("ProjectCode", "TagNo")
    .WithNotAllowNulls("Name", "Code")
    .WithMaxLength("Name", 100)
    .WithUnique("Email")
    .WithUpdateSystemField(false)
    .WithTransaction(transaction)
    .GetResult();

// Check errors (typed)
if (result.ErrorCollector.HasErrors)
{
    foreach (var error in result.ErrorCollector.Errors)
    {
        Console.WriteLine($"Row {error.RowIndex}: {error.ErrorMessage}");
    }
}

// Execute SQL
await _importToDatabase.Execute(result);

Abstractions

ImportExcelCore depends only on these abstractions — no concrete app classes:

ImportUserContext (POCO)

Simple data carrier resolved per-request via factory delegate at DI registration time. No interface needed — the library receives a populated POCO, not an abstraction to implement.

public class ImportUserContext
{
    public string UserName { get; set; } = "";
    public string DataCode { get; set; } = "";
    public string ProjectCode { get; set; } = "";
    public string FacilityCode { get; set; } = "";
    public string Language { get; set; } = "";
    public string ConnectionId { get; set; } = "";  // SignalR connection ID for progress
}

How it works: Register a Transient<ImportUserContext> factory before calling AddImportExcel. The factory runs per-request, reads user info from your app (e.g., IHttpContextAccessorHttpContext.Items), and returns a populated ImportUserContext. The engine never touches HttpContext or IClientContext directly. The ConnectionId is used by the built-in ImportProgressReporter<THub> to send SignalR progress messages.

IImportProgressReporter (Interface)

Built-in ImportProgressReporter<THub> handles progress via SignalR automatically. You only need to implement this interface if you want custom behavior (e.g., console logging, file output).

public interface IImportProgressReporter
{
    Task SendAsync(string message = "");
    Task SendAsync(string message, int current, int total);
}

ImportSystemColumnConfig (Fluent Config)

Configure system column names that the engine auto-populates during import.

columns => columns
    .SetColumnId("Id")                                        // Primary key column
    .SetColumnCreatedBy("CreatebyUser")                       // Created-by user column
    .SetColumnCreatedDate("DateCreated", "CreateDate")        // Created date columns
    .SetColumnModifiedBy("LastedModifyUser", "LastModifiedbyUser")
    .SetColumnModifiedDate("LastedModifyDate", "LastModifiedDate")
    .SetColumnFromClientContext("DataCode", "ProjectCode", "FacilityCode")  // Auto-filled from ImportUserContext

Custom Validation Rules

public class EmailFormatRule : IImportColumnValidationRule
{
    public void Validate(DataColumnChangeEventArgs e)
    {
        if (e.Column.ExtendedProperties.ContainsKey("RuleEmail"))
        {
            if (!e.ProposedValue.ToString().Contains("@"))
                throw new Exception("Invalid email format");
        }
    }
}

Architecture

PTSC.ImportExcel/
├── Abstractions/            ← ImportUserContext, IImportProgressReporter, ImportSystemColumnConfig
├── ImportProgressReporter   ← Built-in SignalR progress reporter (generic <THub>)
├── ImportSqlHelper   ← Built-in SQL utilities (no external dependencies)
├── Engine/           ← Core processing pipeline
├── Strategy/         ← 4 import strategies
├── Validation/       ← Validation pipeline
│   ├── Rules/        ← Built-in rules
│   └── Error/        ← Error collection (Observer pattern)
└── Extensions/       ← Fluent builder + type helpers

Decoupled Dependencies

The library has been systematically decoupled from all app-specific dependencies:

Before (app-coupled) After (abstracted) How
IClientContext ImportUserContext POCO + factory delegate at DI registration
ProgressServer (SignalR) ImportProgressReporter<THub> Built-in — just pass your Hub type to AddImportExcel<THub>(...)
IUnitOfMaster / IUnitOfSlave (EF Core) IImportDbConnection Direct SqlConnection via connection string
IImportSystemColumns ImportSystemColumnConfig Fluent builder config at DI registration
IImportSqlHelper (SHARED) ImportSqlHelper (built-in) Internal static helpers, zero external refs
IConfiguration Connection string param Passed directly to AddImportExcel(...)

License Key

The library requires a valid license key at registration time. Without a valid key, AddPTSCImportExcel throws InvalidOperationException at startup.

Generate a key (admin only)

using PTSC.ImportExcel.License;

// Never expires
string key = ImportLicenseGenerator.GenerateKey("PTSC_PMS_WEB_ENTERPRISE");

// Expires on a specific date
string key = ImportLicenseGenerator.GenerateKey("MyProject", expiresAt: new DateTime(2026, 12, 31));

Key format

{base64url_payload}.{base64url_signature} — HMAC-SHA256 signed.

Payload contains:

  • Subject — project/client identifier
  • ExpiresAt — expiry date (null = never)
  • Issuer — "PTSCMC"

Validation behavior

Scenario Result
No key provided InvalidOperationException at startup
Invalid/tampered key InvalidOperationException at startup
Expired key InvalidOperationException with expiry date
Valid key ImportLicenseInfo registered as singleton in DI

Publish to NuGet

# 1. Pack (Release mode)
dotnet pack PTSC.ImportExcel/PTSC.ImportExcel.csproj -c Release

# 2. Push to nuget.org
dotnet nuget push PTSC.ImportExcel/nupkg/PTSC.ImportExcel.1.0.0.nupkg \
    --api-key YOUR_NUGET_API_KEY \
    --source https://api.nuget.org/v3/index.json

# OR push to a private GitHub Packages feed
dotnet nuget push PTSC.ImportExcel/nupkg/PTSC.ImportExcel.1.0.0.nupkg \
    --api-key YOUR_GITHUB_PAT \
    --source https://nuget.pkg.github.com/PTSCMC/index.json
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.4.0 107 4/14/2026
1.3.1 111 4/2/2026
1.3.0 100 4/1/2026
1.2.0 109 3/27/2026