TCIS.Pluggable.Engine.AspNetCore
1.0.0-rc.19
dotnet add package TCIS.Pluggable.Engine.AspNetCore --version 1.0.0-rc.19
NuGet\Install-Package TCIS.Pluggable.Engine.AspNetCore -Version 1.0.0-rc.19
<PackageReference Include="TCIS.Pluggable.Engine.AspNetCore" Version="1.0.0-rc.19" />
<PackageVersion Include="TCIS.Pluggable.Engine.AspNetCore" Version="1.0.0-rc.19" />
<PackageReference Include="TCIS.Pluggable.Engine.AspNetCore" />
paket add TCIS.Pluggable.Engine.AspNetCore --version 1.0.0-rc.19
#r "nuget: TCIS.Pluggable.Engine.AspNetCore, 1.0.0-rc.19"
#:package TCIS.Pluggable.Engine.AspNetCore@1.0.0-rc.19
#addin nuget:?package=TCIS.Pluggable.Engine.AspNetCore&version=1.0.0-rc.19&prerelease
#tool nuget:?package=TCIS.Pluggable.Engine.AspNetCore&version=1.0.0-rc.19&prerelease
TCIS.Pluggable.Engine.AspNetCore
Lets a plugin expose its own HTTP endpoints — Minimal APIs, gRPC services or SignalR hubs — without the Platform host knowing they exist.
Split out of
TCIS.Pluggable.Engineon purpose: a Worker Service, a console app or a Hangfire host can use the pipeline engine without dragging in the whole of ASP.NET Core.
Table of contents
| Section | Contents |
|---|---|
| 1 | Registration |
| 2 | Exposing endpoints from a plugin |
| 3 | How mapping works |
| 4 | Routing conventions |
| 5 | Pitfalls |
1. Registration
dotnet add package TCIS.Pluggable.Engine.AspNetCore
var app = builder.Build();
app.UseTCISCore();
app.UseRequestTimeouts();
app.MapControllers();
app.MapPluggableEndpoints(); // <- maps endpoints declared by loaded modules
app.Run();
The namespace stays
TCIS.Pluggable.Engine.Moduleseven though the types now live in a separate assembly — existing code only needs to add the package reference, not touch itsusingdirectives.
2. Exposing endpoints from a plugin
Implement IPluggableEndpointModule — it extends ISiteModule, so one class both registers services and maps routes:
public sealed class CatLaiModule : IPluggableEndpointModule
{
public string SiteCode => "CATLAI";
public void RegisterServices(IServiceCollection services, IConfiguration configuration)
{
services.AddScoped<ICustomsGateway, CatLaiCustomsGateway>();
}
public void MapEndpoints(IEndpointRouteBuilder endpoints)
{
var group = endpoints.MapGroup("/api/v1/sites/catlai")
.RequireAuthorization()
.WithTags("CatLai");
group.MapPost("/customs/webhook", async (
CustomsCallback payload,
ICustomsGateway gateway,
CancellationToken ct) =>
{
await gateway.HandleCallbackAsync(payload, ct);
return Results.Accepted();
});
}
}
This is the sanctioned way to add a port-specific integration — a customs webhook, a partner callback, a scale-house feed — without touching the Platform routing table.
3. How mapping works
MapPluggableEndpoints() walks IModuleRegistry, and for every module that implements IPluggableEndpointModule it calls MapEndpoints during startup.
That means:
- Only modules actually loaded for this host contribute endpoints. A plugin the host never passed to
AddSitePluginsmaps nothing. - Mapping happens once at startup, not per request. There is no routing cost at runtime.
- A Platform module can implement the interface too, if the endpoint is genuinely standard business.
4. Routing conventions
Endpoints coming from plugins are still public API, so Chapter 1 applies in full. In particular:
| Rule | Meaning here |
|---|---|
API-001 / API-002 |
Plural nouns, kebab-case paths |
API-008 |
Version in the path — /api/v1/... |
API-011 |
Wrap responses in the standard envelope |
API-023 |
Deny by default — call RequireAuthorization() on the group |
Prefix plugin routes with the site (/api/v1/sites/catlai/...) so two ports cannot collide on the same path when several small ports share one deployment. Nothing enforces this automatically; a collision would surface as an ambiguous-route exception at startup, which is loud but late.
5. Pitfalls
| # | Pitfall | Consequence |
|---|---|---|
| 1 | Business logic inside MapEndpoints |
Same defect as a fat controller — untestable and unreachable from gRPC or a consumer. Delegate to a handler or pipeline |
| 2 | Forgetting RequireAuthorization() |
An open endpoint that no Platform review would ever see, because it lives in plugin code |
| 3 | Two plugins mapping the same route | Ambiguous-route exception at startup — prefix by site to avoid it |
| 4 | Calling MapPluggableEndpoints() before authentication middleware |
Authorization metadata is present but never evaluated |
| 5 | Using this package in a Worker Service | Unnecessary — the whole point of the split is that non-web hosts do not need it |
| 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 (>= 9.0.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 9.0.0)
- TCIS.Pluggable.Abstractions (>= 1.0.0-rc.19)
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-rc.19 | 35 | 8/13/2026 |
| 1.0.0-rc.18 | 31 | 8/13/2026 |
| 1.0.0-rc.17 | 30 | 8/13/2026 |
| 1.0.0-rc.16 | 40 | 8/13/2026 |
| 1.0.0-rc.15 | 41 | 8/12/2026 |
| 1.0.0-rc.14 | 42 | 8/12/2026 |
| 1.0.0-rc.13 | 41 | 8/11/2026 |
| 1.0.0-rc.12 | 50 | 8/10/2026 |