Personix.Mediator.Middleware 2.0.0

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

Personix.Mediator.Middleware

NuGet License: MIT

A mediator whose pipeline steps are ordinary ASP.NET Core middleware. Send a request, it travels the real middleware chain and arrives at your endpoint method, and you get the response back — with no server, no port and no socket anywhere behind it.

Your production middleware runs unchanged. It never learns there is no server.

Install

dotnet add package Personix.Mediator.Middleware

Usage

Register the pipeline the way you register anything else:

services.AddMiddlewareMediator(cfg => cfg.Configure(app =>
{
    app.UseRouting();
    app.UseMiddleware<CorrelationIdMiddleware>();   // your real middleware
    app.UseEndpoints(endpoints => endpoints.MapGet("/orders/{id:int}", (int id) => $"order {id}"));
}));

Then inject IMiddlewareSender and dispatch:

var response = await sender.SendAsync(SimulationHttpRequest.Get("/orders/42"));

Console.WriteLine(response.StatusCode);      // 200
Console.WriteLine(response.BodyAsString());  // order 42

Configure receives a real IApplicationBuilder, so it takes the same UseMiddleware, UseRouting and MapGet calls your Program.cs does. The same configuration method can feed both a real host and this one, which means there is no second definition to drift out of step.

Writing a step

There is nothing to learn. A step is ASP.NET Core middleware, in either activation style, with no reference to this package:

// convention-activated
public sealed class CorrelationIdMiddleware(RequestDelegate next)
{
    public async Task InvokeAsync(HttpContext context, ILogger<CorrelationIdMiddleware> logger)
    {
        context.Items["correlation-id"] = Guid.NewGuid().ToString("n");
        await next(context);
    }
}

// factory-activated
public sealed class BudgetMiddleware(IBudget budget) : IMiddleware
{
    public async Task InvokeAsync(HttpContext context, RequestDelegate next)
    {
        if (!budget.TryConsume())
        {
            context.Response.StatusCode = StatusCodes.Status429TooManyRequests;
            return;                      // short-circuits: nothing further down runs
        }

        await next(context);
    }
}

Both work, including IMiddlewareFactory activation, HttpContext.Items, RequestServices, endpoint routing with route-value binding, and Response.OnStarting callbacks.

Isolation: one instance per application

IMiddlewareSender is scoped, and publishes the scope it was resolved from as the request's RequestServices. Open one scope per simulated application and each gets its own scoped state and its own middleware instances, with no view of any other — while they all share one composed pipeline.

using var shop = provider.CreateScope();
using var warehouse = provider.CreateScope();

await shop.ServiceProvider.GetRequiredService<IMiddlewareSender>().SendAsync(request);
Lifetime Meaning
Singleton shared by every instance — stateless infrastructure
Scoped one per application instance — where an app's own mutable state belongs

Keys and scopes answer different questions and compose: AddMiddlewareMediator("shop", …) distinguishes one kind of application, a scope distinguishes one instance.

What you get without asking for it

A web server does a good deal around the pipeline that no middleware ever requests, and middleware is written expecting all of it. This package does the same, so a step that works under Kestrel works here:

  • An exception that escapes the pipeline becomes a 500, rather than reaching the caller. In a simulation, one application crashing must not take down the one that called it.
  • traceparent and baggage continue the caller's trace. Activity.Current is set for the duration of the dispatch, under the name Microsoft.AspNetCore.Hosting.HttpRequestIn that tracing tools already look for, so a call from one simulated instance to another shows up as one trace.
  • Log lines carry RequestId and RequestPath. With a thousand instances in one process, this is what makes a log readable.
  • Metrics and diagnostic events under the usual names — http.server.request.duration, http.server.active_requests, BeginRequest, EndRequest, UnhandledException.
  • Responses are framed: Content-Length computed from the body, plus Date and Server. A declared length that disagrees with the body is refused rather than sent.
  • Requests are identified as instance:sequence, the shape a server uses for connection:sequence.

Each is skipped when nothing is listening, so none of it costs anything you are not using.

Simulated time

Date and the recorded durations come from the registered TimeProvider. A simulation that advances in steps of its own registers one and its responses are stamped with its time rather than the machine's:

services.AddSingleton<TimeProvider>(mySimulationClock);
services.AddMiddlewareMediator(cfg => cfg.Configure(ConfigurePipeline));

Why it scales

Composing the chain reads every registration and activates every convention-based middleware — and that happens once, on first dispatch. Afterwards a dispatch costs one HttpContext.

Measurement Result
Opening 2 000 instances 1.5 ms
Memory per idle instance ~144 bytes
8 000 dispatches through routing and two middlewares 74 ms

Relationship to Personix.Mediator

Siblings, not layers. Personix.Mediator dispatches typed requests to typed handlers; this one dispatches HTTP-shaped requests through middleware. Neither depends on the other, and this package is separate precisely so that consumers of the plain mediator — console, MAUI, Blazor WebAssembly — are not forced to carry the ASP.NET Core shared framework.

License

MIT © Personix

Product Compatible and additional computed target framework versions.
.NET net11.0 is compatible. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net11.0

    • No dependencies.

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
2.0.0 118 8/30/2026
1.0.0 96 8/30/2026

2.0.0 — performs the work a web host does around every request: an escaping exception becomes a 500 instead of reaching the caller, traceparent and baggage continue the caller's trace, log lines carry RequestId and RequestPath, responses are framed with Content-Length, Date and Server, and metrics and diagnostic events are published under the usual names. Timestamps and durations come from the registered TimeProvider.