WbizCoreX 1.2.0

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

WbizCoreX

WbizCoreX is the shared foundation library for the Wbiz ERP services.

It standardizes the pieces that are currently duplicated across procurement, inventory, approval, and accounting:

  • tenant-aware current user context
  • centralized scoped authorization from authenticated token context
  • middleware that resolves user context from authenticated JWT claims or external token validation
  • approval service client with automatic bearer-token forwarding
  • base controller, service, and repository abstractions
  • EF Core base DbContext that stamps tenant and audit fields
  • global exception middleware with a consistent API response shape

Package Structure

  • Auth: current-user context, token helpers, validation abstractions
  • Authorization: scoped authorization service and tenant-product access validation
  • Approval: typed approval client and DTOs
  • Firs: typed FIRS e-invoicing service client, IRN generation, and DTOs
  • Audit: producer SDK for publishing signed, sanitized audit events to Service Bus — a different concern from Data's entity-level stamping below
  • Data: base entities and EF Core DbContext support
  • Repositories: generic repository contract and EF implementation
  • Services: base application service
  • Mvc: base controller
  • Middleware: current-user context and exception middleware
  • Localization: request-localization registration and culture-fallback string helpers
  • Json: UTC timestamp serialization convention for API payloads
  • DependencyInjection: registration and pipeline extensions

Intended Usage

Register shared infrastructure in each service. For production microservices, the preferred path is local JWT validation plus shared current-user/scoped-authorization handling:

builder.Services.AddAuthentication()
    .AddJwtBearer();

builder.Services.AddWbizCoreX();
builder.Services.AddWbizScopedAuthorization<WBizDbContext>();

Use the middleware after UseAuthentication() so WbizCoreX can hydrate CurrentUserContext from the authenticated principal:

app.UseWbizCoreXExceptionHandling();
app.UseAuthentication();
app.UseWbizCurrentUserContext();
app.UseAuthorization();

Use the shared authorizer inside application services instead of repeating token product-scope checks:

var authorizationFailure = await scopedAuthorizationService.AuthorizeAsync<object>(new ScopedAuthorizationRequest
{
    EnforceScopedAuthorization = true,
    RequiredPermissions = ["materials.read"],
    ForbiddenPermissionMessage = "You do not have permission to view materials."
});

if (authorizationFailure is not null)
{
    return authorizationFailure;
}

The shared authorization check validates tenant-product access with the token's product_id and tenant scope, while keeping product_code as descriptive metadata on the resolved user context.

If a service wants the convenience wrapper, it can still register the shared foundation:

builder.Services.AddWbizServiceFoundation(builder.Configuration, options =>
{
    options.BypassApprovalWebhook = true;
});

Use AddWbizHttpTokenValidation(...) only for services that cannot validate JWTs locally and must rely on an external token-validation endpoint. It is optional compatibility support, not the preferred production path.

If a service wants lower-level control, it can still register the pieces individually:

builder.Services.AddWbizCoreX();
builder.Services.AddWbizApprovalClient(builder.Configuration);

Build controllers and services on the shared base types:

public sealed class MaterialRequisitionService : ApplicationServiceBase
{
    public MaterialRequisitionService(ICurrentUserContextAccessor currentUserContextAccessor)
        : base(currentUserContextAccessor)
    {
    }
}

public sealed class MaterialRequisitionController : WbizControllerBase
{
    public MaterialRequisitionController(ICurrentUserContextAccessor currentUserContextAccessor)
        : base(currentUserContextAccessor)
    {
    }
}

Register the typed FIRS e-invoicing client for services that submit invoices to FIRS:

builder.Services.AddWbizFirsServiceClient(builder.Configuration);

The client binds FirsService configuration (validating that BaseUrl is an absolute URL), forwards the current user's bearer token, and exposes IRN generation plus submission-response reading through IFirsServiceClient.

Audit

Register the audit-event producer client for services that publish audit events to the audit service over Service Bus:

builder.Services.AddWbizAuditClient(builder.Configuration);

The client binds the Audit configuration section (ServiceName, FullyQualifiedNamespace, TopicName, SigningKey, Enabled), validates that ServiceName, FullyQualifiedNamespace, TopicName, and SigningKey are all set when Enabled is true, and exposes IAuditPublisher for application code to call. Each published event is sanitized (secrets and JWT/Bearer-shaped values redacted) and HMAC-signed with SigningKey before being sent. The signature covers ServiceName, TenantId, EventId, and EventTimeUtc — it authenticates the producer (proves the message came from a holder of that service's SigningKey), not the full event body, so a valid signature is not proof that ActionCode, ResultStatus, Context, Attributes, or other fields went unaltered in transit. When Enabled is false (or the section is absent), a no-op publisher is registered instead, so services can adopt this incrementally without breaking when unconfigured.

This is unrelated to Data's IAuditableEntity/AuditableEntityBase, which stamp CreatedBy/ModifiedUtc on EF Core entities on save — Audit publishes discrete business-audit events to the separate audit service, it doesn't touch entity persistence.

JSON Timestamps

Register the UTC timestamp convention so every instant a service returns is self-describing on the wire:

builder.Services.AddWbizUtcTimestamps();

SQL Server's datetime2 carries no offset, so values read back through EF Core or Dapper arrive with DateTimeKind.Unspecified. System.Text.Json then writes them with no timezone suffix - "2026-08-04T12:11:00" - and a browser parses that as local time, so every timestamp is displayed shifted by the viewer's offset. This registration writes any property whose name ends in Utc with an explicit Z and full datetime2(7) tick precision (yyyy-MM-dd'T'HH:mm:ss.fffffff'Z'), and reads incoming values back as UTC.

The Utc name suffix is the opt-in, and properties without it are left alone deliberately: stamping a calendar date such as an invoice's issue date as UTC midnight would show the previous day to a viewer west of Greenwich. A member carrying its own [JsonConverter] is also left untouched, since that is a deliberate choice by the DTO's author. The suffix is matched on the CLR member name rather than the serialized name, so a [JsonPropertyName] rename or a naming policy does not change which properties are treated as instants.

The extension configures both ConfigureHttpJsonOptions (minimal APIs) and MVC's JsonOptions, composing onto whatever resolver is already configured instead of mutating the shared instance. Services that build their own JsonSerializerOptions can apply the same behaviour directly through the UtcTimestampJson.MarkUtcTimestamps resolver modifier.

Localization

Register localization and request-culture handling (English/French, resolved from the Accept-Language header), mirroring the BaseQuc localization shape:

builder.Services.AddWbizLocalizationManagement();

Apply it early in the pipeline, before UseRouting():

app.UseWbizLocalization();

Inject IStringLocalizer<TMarker> for a host-defined marker class backed by host-owned .resx files. Use the GetLocalizedMessage extension to look up a key for the active culture and fall back to the host default culture (default "en") instead of surfacing the raw key when a translation is missing:

var message = localizer.GetLocalizedMessage("Materials.NotFound");

This repository currently contains the shared library only. The next step is to migrate procurement and approval first, since both already duplicate the same user-context and approval-integration concerns.

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 is compatible.  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 is compatible.  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.2.0 99 8/19/2026
1.1.0 142 8/11/2026
1.0.3 205 7/3/2026
1.0.2 173 5/1/2026
1.0.1 273 3/17/2026
1.0.0 108 3/15/2026