Skip to content

Import Package

Section: 16 — Packages
Last Updated: 2026-05-30
Scope: Microtec.Import — bulk data import via Excel and CSV


Overview

The Microtec.Import package provides a structured pipeline for importing large datasets from Excel (.xlsx) and CSV files into any ERP module. It handles validation, error reporting, and background processing so that import operations never block the HTTP request thread.

Microtec.Import.Domain          ← Domain models (IImportable, ImportJob)
Microtec.Import.Infrastructure  ← Excel/CSV engines, validation pipeline
Microtec.Import.Integration     ← Integration events, IImportPublicApi HTTP client

Package Structure

Microtec.Import.Domain

What's Included
IImportable — marker interface for entities that support bulk import
ImportJob — aggregate root tracking the lifecycle of one import operation
ImportRow — a single row from the uploaded file
ImportValidationResult — per-row validation outcome
ImportStatus enum: Pending, Processing, Completed, Failed, PartialSuccess

Microtec.Import.Infrastructure

CategoryWhat's Included
Main serviceIImportService, ImportService
Excel engineExcelImportEngine — uses ClosedXML; reads .xlsx
CSV engineCsvImportEngine — uses CsvHelper; handles encoding, BOM
ValidationImportValidationPipeline, IImportValidator<T>
Column mappingImportColumnAttribute — decorate DTO properties
BackgroundImportBackgroundService — hosted service that dequeues and processes jobs
StorageIImportFileStorage — saves uploaded files to Blob Storage

Microtec.Import.Integration

What's Included
ImportCompletedEvent — published when a job finishes (success or partial)
ImportFailedEvent — published when a job fails entirely
IImportPublicApi — typed HTTP client for services to check import status

How Import Works

The client polls GET /import/jobs/{jobId} or subscribes to ImportCompletedEvent to know when the job is done.


Setup

Service Registration

csharp
// Program.cs (Import.Apis service)
builder.Services.AddMicrotecImport(builder.Configuration);

This registers:

  • IImportService (scoped)
  • ImportBackgroundService (hosted service)
  • ExcelImportEngine and CsvImportEngine
  • IImportFileStorage → Azure Blob Storage adapter

Configuration

json
{
  "Import": {
    "StorageConnectionString": "keyvaultref:import--storage-connection",
    "ContainerName": "import-files",
    "MaxFileSizeMb": 50,
    "MaxRowsPerJob": 50000,
    "WorkerConcurrency": 3
  }
}

Implementing an Importable Entity

Step 1 — Create the import DTO

csharp
public class EmployeeImportDto : IImportable
{
    [ImportColumn("Employee Code", Required = true, MaxLength = 20)]
    public string Code { get; set; } = default!;

    [ImportColumn("Full Name (Arabic)", Required = true)]
    public string NameAr { get; set; } = default!;

    [ImportColumn("Full Name (English)", Required = true)]
    public string NameEn { get; set; } = default!;

    [ImportColumn("National ID", Required = true)]
    public string NationalId { get; set; } = default!;

    [ImportColumn("Department Code", Required = false)]
    public string? DepartmentCode { get; set; }

    [ImportColumn("Hire Date", Required = true, Format = "yyyy-MM-dd")]
    public DateTime HireDate { get; set; }

    [ImportColumn("Basic Salary", Required = true)]
    public decimal BasicSalary { get; set; }
}

Step 2 — Implement the validator

csharp
public class EmployeeImportValidator : IImportValidator<EmployeeImportDto>
{
    private readonly IRepository<Department> _departments;

    public EmployeeImportValidator(IRepository<Department> departments)
        => _departments = departments;

    public async Task<ImportValidationResult> ValidateAsync(
        EmployeeImportDto row, int rowNumber, CancellationToken ct)
    {
        var errors = new List<string>();

        if (row.BasicSalary <= 0)
            errors.Add("Basic salary must be greater than zero.");

        if (row.DepartmentCode is not null)
        {
            var exists = await _departments.AnyAsync(d => d.Code == row.DepartmentCode, ct);
            if (!exists)
                errors.Add($"Department '{row.DepartmentCode}' not found.");
        }

        return errors.Count > 0
            ? ImportValidationResult.Failure(rowNumber, errors)
            : ImportValidationResult.Success(rowNumber);
    }
}

Step 3 — Register the validator

csharp
// In your module's DI setup (Infrastructure layer)
services.AddScoped<IImportValidator<EmployeeImportDto>, EmployeeImportValidator>();

Step 4 — Implement the import handler

The ImportBackgroundService calls the registered handler after validation:

csharp
public class EmployeeImportHandler : IImportHandler<EmployeeImportDto>
{
    private readonly IUnitOfWork<HrDbContext> _unitOfWork;

    public async Task HandleAsync(
        IEnumerable<EmployeeImportDto> validRows,
        ImportJob job,
        CancellationToken ct)
    {
        var employees = validRows.Select(row => Employee.Create(
            row.Code, row.NameAr, row.NameEn, row.NationalId, row.HireDate, row.BasicSalary
        )).ToList();

        await _unitOfWork.Repository<Employee>().AddRangeAsync(employees, ct);
        await _unitOfWork.CompleteAsync(ct);
    }
}

API Endpoints (Import.Apis)

MethodPathDescription
POST/import/{entityType}Upload file, returns jobId
GET/import/jobs/{jobId}Get job status and progress
GET/import/jobs/{jobId}/errorsDownload error report (Excel)
GET/import/templates/{entityType}Download blank import template
DELETE/import/jobs/{jobId}Cancel a pending job

Upload example

bash
curl -X POST https://gateway.microtecstage.com/import/employees \
  -H "Authorization: Bearer <token>" \
  -F "file=@employees.xlsx"
# Response: { "jobId": "3f1a7c2e-...", "status": "Pending" }

Poll for completion

bash
curl https://gateway.microtecstage.com/import/jobs/3f1a7c2e-... \
  -H "Authorization: Bearer <token>"
# Response:
# {
#   "jobId": "3f1a7c2e-...",
#   "status": "PartialSuccess",
#   "totalRows": 1200,
#   "successRows": 1187,
#   "failedRows": 13,
#   "completedAt": "2026-05-30T09:45:12Z"
# }

Error Report

When a job completes with PartialSuccess or Failed, an Excel error report is generated automatically. Each failed row appears with:

  • Row number (matches the original file)
  • Original row data (all columns)
  • Error messages (one per line in the last column)

Download via GET /import/jobs/{jobId}/errors.


ImportColumn Attribute Reference

PropertyTypeDefaultDescription
NamestringExact column header in the Excel/CSV file
RequiredboolfalseFails validation if cell is empty
MaxLengthint?nullString length validation
Formatstring?nullDate format string (e.g. "yyyy-MM-dd")
AllowedValuesstring[]?nullValidates against an enum-like fixed set
TrimbooltrueTrim whitespace from string values

Integration Events

csharp
// Subscribe in your service's consumer registration
busConfig.AddConsumer<ImportCompletedEventHandler>();

public class ImportCompletedEventHandler : ConsumerBase<ImportCompletedEvent>
{
    public override async Task Consume(ConsumeContext<ImportCompletedEvent> context)
    {
        var ev = context.Message;
        // ev.JobId, ev.EntityType, ev.TenantId, ev.SuccessRows, ev.FailedRows
        // Notify the user via the Notification service if needed
    }
}

Troubleshooting

SymptomLikely CauseFix
Job stuck in ProcessingWorker crashed mid-jobCheck Import service logs; re-enqueue via admin endpoint
File too large errorExceeds MaxFileSizeMbCompress or split the file; raise limit in config if justified
All rows fail validationColumn headers don't matchDownload the import template; compare column names exactly
Encoding error on CSVFile is not UTF-8Re-save CSV as UTF-8 (with BOM) from Excel
Department not foundLookup data missingSeed departments before importing employees

Internal Documentation — Microtec Platform Team