Xpandables.AspNetCore.Composition
10.0.2
dotnet add package Xpandables.AspNetCore.Composition --version 10.0.2
NuGet\Install-Package Xpandables.AspNetCore.Composition -Version 10.0.2
<PackageReference Include="Xpandables.AspNetCore.Composition" Version="10.0.2" />
<PackageVersion Include="Xpandables.AspNetCore.Composition" Version="10.0.2" />
<PackageReference Include="Xpandables.AspNetCore.Composition" />
paket add Xpandables.AspNetCore.Composition --version 10.0.2
#r "nuget: Xpandables.AspNetCore.Composition, 10.0.2"
#:package Xpandables.AspNetCore.Composition@10.0.2
#addin nuget:?package=Xpandables.AspNetCore.Composition&version=10.0.2
#tool nuget:?package=Xpandables.AspNetCore.Composition&version=10.0.2
π AspNetCore.Composition
MEF-driven middleware composition and DI helpers for ASP.NET Core apps.
π Overview
AspNetCore.Composition adds two focused capabilities to ASP.NET Core:
- Lazy DI: register
Lazy<T>once withAddXLazyResolved()so services can defer expensive or optional dependencies. - MEF/assembly composition: load middleware/configuration exported from plugin assemblies via
UseXServiceExports()or applyIUseServiceimplementations from known assemblies withUseXServices(...).
Everything is built for .NET 10, trimming, and native AOT friendliness.
β¨ Key Features
- β³ Lazy<T> DI β
AddXLazyResolved()registersLazy<>to resolve from the container on first access. - π§© MEF exports β
UseXServiceExports()discoversIUseServiceExporttypes in plugin folders usingExportOptions(path, search pattern, recurse). - π§ Assembly-based wiring β
UseXServices(params Assembly[])instantiates and runs allIUseServiceimplementations for simple module composition.
π¦ Installation
dotnet add package AspNetCore.Composition
Or via NuGet Package Manager:
Install-Package AspNetCore.Composition
π Quick Start
Register Lazy<T>
using Microsoft.Extensions.DependencyInjection;
var builder = WebApplication.CreateBuilder(args);
// Enable Lazy<T> injection across the app
builder.Services.AddXLazyResolved();
// Example service consuming lazy dependencies
public sealed class InvoiceProcessor(
Lazy<IPaymentGateway> payment,
Lazy<IEmailSender> email)
{
public async Task ProcessAsync(Invoice invoice, CancellationToken ct)
{
if (invoice.RequiresPayment)
{
await payment.Value.ChargeAsync(invoice.Total, ct);
}
if (invoice.SendReceipt)
{
await email.Value.SendReceiptAsync(invoice, ct);
}
}
}
Apply MEF Exports (plugins)
using Microsoft.AspNetCore.Builder;
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
// Load middleware exports from the current directory (default) or a plugins folder
app.UseXServiceExports(options =>
{
options.Path = Path.Combine(AppContext.BaseDirectory, "plugins");
options.SearchPattern = "*.Plugin.dll";
options.SearchSubDirectories = true;
});
app.Run();
// In a plugin assembly
[Export(typeof(IUseServiceExport))]
public sealed class LoggingExport : IUseServiceExport
{
public void UseServices(WebApplication app)
{
app.UseMiddleware<RequestLoggingMiddleware>();
app.UseMiddleware<CorrelationMiddleware>();
}
}
Apply Assembly Services (no MEF)
var app = builder.Build();
// Scan known assemblies for IUseService implementations and run them
app.UseXServices(typeof(Program).Assembly, typeof(MyFeatureAssembly).Assembly);
app.Run();
// Example in a referenced assembly
public sealed class CorsSetup : IUseService
{
public void UseServices(WebApplication app)
{
app.UseCors("default");
}
}
π MEF Service Exports
UseXServiceExports scans assemblies in a directory using ExportOptions and executes each IUseServiceExport implementation.
ExportOptions
| Property | Type | Description |
|---|---|---|
Path |
string |
Base directory to scan (defaults to entry assembly directory). |
SearchPattern |
string |
File pattern to load (defaults to *.dll). |
SearchSubDirectories |
bool |
Recurse into subdirectories when true. |
Example: multi-tenant plugin folder
app.UseXServiceExports(options =>
{
options.Path = "/var/app/tenants/tenant-a/plugins";
options.SearchPattern = "TenantA.*.dll";
});
Each discovered export can wire middleware, endpoints, or other pipeline concerns.
β³ Lazy Service Resolution
AddXLazyResolved() registers Lazy<> so instances resolve from DI on first useβhelpful for expensive or optional dependencies and for breaking dependency cycles.
builder.Services.AddXLazyResolved();
public sealed class AuditWriter(Lazy<IAuditSink> sink)
{
public Task WriteAsync(AuditEntry entry, CancellationToken ct) =>
sink.Value.WriteAsync(entry, ct);
}
ποΈ Complete Example
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
var builder = WebApplication.CreateBuilder(args);
// Lazy<T> support for deferred dependencies
builder.Services.AddXLazyResolved();
var app = builder.Build();
app.UseHttpsRedirection();
// Load all plugin exports from /extensions
app.UseXServiceExports(options =>
{
options.Path = Path.Combine(AppContext.BaseDirectory, "extensions");
options.SearchPattern = "*.dll";
options.SearchSubDirectories = false;
});
// Apply in-assembly middleware modules
app.UseXServices(typeof(Program).Assembly);
app.Run();
// In a separate module assembly
public sealed class ObservabilityModule : IUseService
{
public void UseServices(WebApplication app)
{
app.UseMiddleware<RequestTimingMiddleware>();
app.UseMiddleware<TracingMiddleware>();
}
}
β Best Practices
- Keep
IUseServiceExportimplementations small and focused (logging, auth, multi-tenant customizations). - Use
SearchPatternto avoid loading unintended assemblies from plugin folders. - Prefer
IUseServicefor first-party modules you reference directly; use MEF exports for optional/external plugins. - Add
AddXLazyResolved()once at startup so all services can opt into deferred resolution.
π Related Packages
| Package | Description |
|---|---|
| AspNetCore.Net | Minimal API routing and metadata helpers |
| System.Composition | MEF composition utilities backing the export pipeline |
| AspNetCore.Results | Result pattern integration for HTTP responses |
π License
Apache License 2.0 - Copyright Β© Kamersoft 2025
Contributions welcome at Xpandables.Net on GitHub.
π AspNetCore.Net
ASP.NET Core Minimal API Infrastructure β Modular endpoint routing, lazy service resolution, JSON configuration, and MEF-based service exports for building clean, organized minimal APIs.
π Overview
AspNetCore.Net provides infrastructure components for building well-organized ASP.NET Core minimal APIs. The library offers modular endpoint routing via IMinimalEndpointRoute, lazy dependency injection support, JSON serializer configuration helpers, and MEF (Managed Extensibility Framework) integration for plugin-based service registration.
Built for .NET 10 with C# 14 extension members, this package promotes separation of concerns and clean architecture in minimal API applications.
β¨ Key Features
- π£οΈ
IMinimalEndpointRouteβ Interface for modular minimal API endpoint registration with service and middleware hooks - π§
MinimalRouteBuilderβ Wrapper aroundIEndpointRouteBuilderwith automatic filter application - β³ Lazy Resolution β
Lazy<T>dependency injection support viaAddXLazyResolved() - π JSON Configuration β Register
JsonSerializerOptionsas a singleton withAddXJsonSerializerOptions() - π― Route Metadata β Fluent API for endpoint metadata (
Produces200OK,Produces400BadRequest, etc.) - π MEF Integration β
IUseServiceExportfor plugin-based middleware configuration - βοΈ Minimal Support Options β Conditional endpoint filtering and configuration
π¦ Installation
[](https://www.nuget.org/packages/AspNetCore.Net)
[](https://dotnet.microsoft.com/)
[](LICENSE)
> **ASP.NET Core Minimal API Infrastructure** β Modular endpoint routing, lazy service resolution, JSON configuration, and MEF-based service exports for building clean, organized minimal APIs.
---
## π Overview
`AspNetCore.Net` provides infrastructure components for building well-organized ASP.NET Core minimal APIs. The library offers modular endpoint routing via `IMinimalEndpointRoute`, lazy dependency injection support, JSON serializer configuration helpers, and MEF (Managed Extensibility Framework) integration for plugin-based service registration.
Built for .NET 10 with C# 14 extension members, this package promotes separation of concerns and clean architecture in minimal API applications.
### β¨ Key Features
- π£οΈ **`IMinimalEndpointRoute`** β Interface for modular minimal API endpoint registration with service and middleware hooks
- π§ **`MinimalRouteBuilder`** β Wrapper around `IEndpointRouteBuilder` with automatic filter application
- β³ **Lazy Resolution** β `Lazy<T>` dependency injection support via `AddXLazyResolved()`
- π **JSON Configuration** β Register `JsonSerializerOptions` as a singleton with `AddXJsonSerializerOptions()`
- π― **Route Metadata** β Fluent API for endpoint metadata (`Produces200OK`, `Produces400BadRequest`, etc.)
- π **MEF Integration** β `IUseServiceExport` for plugin-based middleware configuration
- βοΈ **Minimal Support Options** β Conditional endpoint filtering and configuration
---
## π¦ Installation
```bash
dotnet add package AspNetCore.Net
Or via NuGet Package Manager:
Install-Package AspNetCore.Net
π Quick Start
Service Registration
using Microsoft.Extensions.DependencyInjection;
var builder = WebApplication.CreateBuilder(args);
// Register minimal support with optional configuration
builder.Services.AddXMinimalSupport(options =>
{
// Optional: Configure endpoint predicate for conditional filter application
options.EndpointPredicate = endpoint =>
endpoint.RoutePattern.RawText?.StartsWith("/api") ?? false;
// Optional: Configure all endpoints
options.ConfigureEndpoint = builder =>
builder.RequireAuthorization();
});
// Register endpoint routes from assemblies
builder.Services.AddXMinimalEndpointRoutes(typeof(Program).Assembly);
// Register JSON serializer options as singleton
builder.Services.AddXJsonSerializerOptions();
// Register lazy resolution support
builder.Services.AddXLazyResolved();
var app = builder.Build();
// Use registered endpoint routes
app.UseXMinimalEndpointRoutes();
app.Run();
π£οΈ Modular Endpoint Routing
Define Endpoint Routes
The IMinimalEndpointRoute interface provides a clean way to organize endpoints with optional service registration and middleware configuration:
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Configuration;
public sealed class ProductEndpoints : IMinimalEndpointRoute
{
// Register services specific to this endpoint module
public void AddServices(IServiceCollection services, IConfiguration configuration)
{
services.AddScoped<IProductRepository, ProductRepository>();
services.AddScoped<IProductService, ProductService>();
}
// Configure middleware specific to this module
public void UseServices(WebApplication application)
{
// Add any middleware needed for this module
}
// Define the endpoints
public void AddRoutes(MinimalRouteBuilder app)
{
var group = app.MapGroup("/api/products")
.WithTags("Products");
group.MapGet("/", GetAllProducts)
.Produces200OK<IEnumerable<Product>>()
.Produces500InternalServerError();
group.MapGet("/{id:guid}", GetProductById)
.Produces200OK<Product>()
.Produces404NotFound();
group.MapPost("/", CreateProduct)
.Accepts<CreateProductRequest>()
.Produces201Created<Product>()
.Produces400BadRequest();
group.MapPut("/{id:guid}", UpdateProduct)
.Accepts<UpdateProductRequest>()
.Produces200OK<Product>()
.Produces404NotFound()
.Produces409Conflict();
group.MapDelete("/{id:guid}", DeleteProduct)
.Produces200OK()
.Produces404NotFound()
.Produces401Unauthorized();
}
private static async Task<IResult> GetAllProducts(
IProductService productService,
CancellationToken cancellationToken)
{
var products = await productService.GetAllAsync(cancellationToken);
return Results.Ok(products);
}
private static async Task<IResult> GetProductById(
Guid id,
IProductService productService,
CancellationToken cancellationToken)
{
var product = await productService.GetByIdAsync(id, cancellationToken);
return product is not null
? Results.Ok(product)
: Results.NotFound();
}
private static async Task<IResult> CreateProduct(
CreateProductRequest request,
IProductService productService,
CancellationToken cancellationToken)
{
var product = await productService.CreateAsync(request, cancellationToken);
return Results.Created($"/api/products/{product.Id}", product);
}
private static async Task<IResult> UpdateProduct(
Guid id,
UpdateProductRequest request,
IProductService productService,
CancellationToken cancellationToken)
{
var product = await productService.UpdateAsync(id, request, cancellationToken);
return product is not null
? Results.Ok(product)
: Results.NotFound();
}
private static async Task<IResult> DeleteProduct(
Guid id,
IProductService productService,
CancellationToken cancellationToken)
{
var deleted = await productService.DeleteAsync(id, cancellationToken);
return deleted ? Results.Ok() : Results.NotFound();
}
}
Register and Use
// In Program.cs
var builder = WebApplication.CreateBuilder(args);
// Register minimal support options
builder.Services.AddXMinimalSupport();
// Discover and register all IMinimalEndpointRoute implementations
builder.Services.AddXMinimalEndpointRoutes(typeof(Program).Assembly);
var app = builder.Build();
// Apply all registered endpoint routes
app.UseXMinimalEndpointRoutes();
app.Run();
π― Route Metadata Extensions
Fluent API for adding OpenAPI metadata to endpoints:
public void AddRoutes(MinimalRouteBuilder app)
{
// GET endpoint with success and error responses
app.MapGet("/api/users", GetUsers)
.Produces200OK<IEnumerable<User>>()
.Produces500InternalServerError();
// GET with path parameter
app.MapGet("/api/users/{id}", GetUserById)
.Produces200OK<User>()
.Produces404NotFound();
// POST with request body
app.MapPost("/api/users", CreateUser)
.Accepts<CreateUserRequest>()
.Produces201Created<User>()
.Produces400BadRequest();
// PUT with conflict handling
app.MapPut("/api/users/{id}", UpdateUser)
.Accepts<UpdateUserRequest>()
.Produces200OK<User>()
.Produces404NotFound()
.Produces409Conflict();
// DELETE with authorization
app.MapDelete("/api/users/{id}", DeleteUser)
.Produces200OK()
.Produces404NotFound()
.Produces401Unauthorized();
// Custom HTTP methods
app.MapMethods("/api/users/{id}/activate", ["PATCH"], ActivateUser)
.Produces200OK()
.Produces405MethodNotAllowed();
}
Available Metadata Extensions
| Extension | Status Code | Content Type |
|---|---|---|
Produces200OK() |
200 | application/json |
Produces200OK<T>() |
200 | application/json |
Produces201Created<T>() |
201 | application/json |
Produces400BadRequest() |
400 | application/problem+json |
Produces401Unauthorized() |
401 | application/problem+json |
Produces404NotFound() |
404 | application/problem+json |
Produces405MethodNotAllowed() |
405 | application/problem+json |
Produces409Conflict() |
409 | application/problem+json |
Produces500InternalServerError() |
500 | application/problem+json |
Accepts<T>() |
β | application/json |
βοΈ Minimal Support Options
Configure how filters and conventions are applied to endpoints:
builder.Services.AddXMinimalSupport(options =>
{
// Apply configuration only to endpoints matching this predicate
options.EndpointPredicate = endpoint =>
endpoint.RoutePattern.RawText?.StartsWith("/api") ?? false;
// Configure matching endpoints
options.ConfigureEndpoint = builder =>
{
builder.RequireAuthorization();
builder.WithOpenApi();
};
});
Options Properties
| Property | Type | Description |
|---|---|---|
EndpointPredicate |
Func<RouteEndpoint, bool>? |
Predicate to filter which endpoints receive configuration |
ConfigureEndpoint |
Action<IEndpointConventionBuilder>? |
Action to configure matching endpoints |
β³ Lazy Service Resolution
Defer service resolution until first access with Lazy<T> support:
// Register lazy support
builder.Services.AddXLazyResolved();
// Use in services - services are only resolved when accessed
public class OrderProcessor(
Lazy<IEmailService> emailService,
Lazy<IPaymentGateway> paymentGateway,
Lazy<IInventoryService> inventoryService)
{
public async Task ProcessOrderAsync(Order order, CancellationToken ct)
{
// Payment gateway resolved only when needed
if (order.RequiresPayment)
{
await paymentGateway.Value.ChargeAsync(order.Total, ct);
}
// Inventory service resolved only when needed
if (order.HasPhysicalItems)
{
await inventoryService.Value.ReserveItemsAsync(order.Items, ct);
}
// Email service resolved only when sending confirmation
await emailService.Value.SendOrderConfirmationAsync(order, ct);
}
}
Benefits
- Reduced startup time β Services not resolved until needed
- Conditional dependencies β Only resolve services that are actually used
- Circular dependency avoidance β Break circular dependency chains
π JSON Serializer Options
Register ASP.NET Core's configured JsonSerializerOptions as a singleton:
// In Program.cs
builder.Services.AddXJsonSerializerOptions();
// Use in services
public class DataExportService(JsonSerializerOptions jsonOptions)
{
public string ExportToJson<T>(T data) =>
JsonSerializer.Serialize(data, jsonOptions);
public async Task ExportToFileAsync<T>(T data, string path, CancellationToken ct)
{
await using var stream = File.Create(path);
await JsonSerializer.SerializeAsync(stream, data, jsonOptions, ct);
}
public T? ImportFromJson<T>(string json) =>
JsonSerializer.Deserialize<T>(json, jsonOptions);
}
This ensures consistent JSON serialization settings throughout your application by reusing the options configured in JsonOptions.
π MEF Service Exports
Use MEF (Managed Extensibility Framework) for plugin-based service registration:
Define a Service Export
using System.ComponentModel.Composition;
using Microsoft.AspNetCore.Builder;
[Export(typeof(IUseServiceExport))]
public class LoggingMiddlewareExport : IUseServiceExport
{
public void UseServices(WebApplication application)
{
// Configure middleware from an external plugin
application.UseMiddleware<RequestLoggingMiddleware>();
application.UseMiddleware<PerformanceLoggingMiddleware>();
}
}
Apply Service Exports
var app = builder.Build();
// Apply all MEF-exported IUseServiceExport implementations
app.UseXServiceExports();
// Or with custom options
app.UseXServiceExports(options =>
{
options.Directories = ["/plugins", "/extensions"];
options.SearchPattern = "*.Plugin.dll";
});
app.Run();
Assembly-Based Service Discovery
// Discover and apply IUseService implementations from assemblies
app.UseXServices(typeof(Program).Assembly, typeof(PluginAssembly).Assembly);
ποΈ Complete Example
using Microsoft.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Routing;
var builder = WebApplication.CreateBuilder(args);
// Configure services
builder.Services.AddXMinimalSupport(options =>
{
options.EndpointPredicate = endpoint =>
endpoint.RoutePattern.RawText?.StartsWith("/api") ?? false;
});
builder.Services.AddXMinimalEndpointRoutes(typeof(Program).Assembly);
builder.Services.AddXJsonSerializerOptions();
builder.Services.AddXLazyResolved();
// Add OpenAPI support
builder.Services.AddOpenApi();
var app = builder.Build();
// Configure pipeline
app.UseHttpsRedirection();
app.MapOpenApi();
// Apply endpoint routes
app.UseXMinimalEndpointRoutes();
app.Run();
// Endpoint module
public sealed class HealthEndpoints : IMinimalEndpointRoute
{
public void AddRoutes(MinimalRouteBuilder app)
{
app.MapGet("/health", () => Results.Ok(new { Status = "Healthy", Timestamp = DateTime.UtcNow }))
.Produces200OK()
.WithTags("Health");
app.MapGet("/health/ready", async (IServiceProvider services) =>
{
// Check dependencies
var dbContext = services.GetService<AppDbContext>();
if (dbContext is null)
return Results.Problem("Database not configured", statusCode: 503);
return Results.Ok(new { Status = "Ready" });
})
.Produces200OK()
.Produces500InternalServerError()
.WithTags("Health");
}
}
β Best Practices
β Do
- Implement
IMinimalEndpointRouteβ Organize endpoints into cohesive modules - Use
AddServicesoverride β Register module-specific services within the endpoint class - Apply
AddXLazyResolved()β For optional or expensive dependencies - Use route metadata extensions β Ensure consistent OpenAPI documentation
- Configure
MinimalSupportOptionsβ Apply cross-cutting concerns consistently
β Don't
- Register endpoints directly in Program.cs β Use
IMinimalEndpointRoutefor organization - Resolve services eagerly β Use
Lazy<T>for conditional dependencies - Duplicate JSON options β Use
AddXJsonSerializerOptions()for consistency - Mix endpoint registration approaches β Choose one pattern per application
π Related Packages
| Package | Description |
|---|---|
| AspNetCore.Results | Result pattern integration for HTTP responses |
| AspNetCore.AsyncPaged | Async paged enumerable HTTP streaming |
| System.Composition | MEF composition utilities |
π License
Apache License 2.0 - Copyright Β© Kamersoft 2025
Contributions welcome at Xpandables.Net on GitHub.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | 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. |
-
net10.0
- System.ComponentModel.Composition (>= 10.0.5)
- Xpandables.Composition (>= 10.0.2)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.