European ASP.NET 4.5 Hosting BLOG

BLOG about ASP.NET 4, ASP.NET 4.5 Hosting and Its Technology - Dedicated to European Windows Hosting Customer

European ASP.NET Core 10.0 Hosting - HostForLIFE :: The REPR Pattern in Contemporary.Net with Fast Endpoints

clock October 27, 2025 07:52 by author Peter

The HTTP layer becomes a thin adaptor, intentions are explicit, and invariants reside in the domain in mature.NET systems that converge on a feature-first structure. Request > Entity > Processor > Response is one name for that form. It is what those concepts typically result from when scaled across teams and time; it does not take the place of Clean Architecture or CQRS. In order to keep the surface modest and the business logic at the center, this article describes REPR as a fundamental design and shows how to express it precisely in modern.NET using FastEndpoints.

Clarity is the aim, with each use-case being represented by a slice made up of a consumer-shaped contract (Response), a truthful domain core (Entity), a use case script (Processor), and an intent (Request). The slice itself conveys the meaning, so incidental abstractions, controllers, and service layers disappear. As a result, the codebase reads by verbs instead of layers, which is the logical unit of reasoning for both engineering and products.

Why REPR Emerges from Clean Architecture and CQRS?
Clean Architecture pushes dependencies inward and centres decisions in the domain. CQRS separates reads and writes to sharpen intent. Yet both leave open the practical question: What is the smallest useful unit of a feature? REPR answers with a compact pipeline. The Request captures intent without leaking storage concerns. The Entity enforces business rules and invariants. The Processor orchestrates precisely what the use-case demands and nothing more. The Response returns a business outcome suitable for its consumer, not an accidental snapshot of internal state.

This alignment reduces cognitive load. A feature has a single entry and a single exit, and the domain is the only place allowed to change truth. Cross-cutting concerns become pipeline steps around the Processor instead of boilerplate inside it. Teams scale by owning sets of verbs rather than slices of a layered cake.

The Feature Slice: Directory Shape and Mental Model

A coherent slice contains only what it needs, co-located for discoverability. An example for approving a Submission (the same structure fits quotes, orders, pricing, or claims):
/Features/Submissions/Approve

ApproveSubmissionRequest.cs

ApproveSubmissionResponse.cs

ApproveSubmissionValidator.cs

ApproveSubmissionEndpoint.cs

ApproveSubmissionProcessor.cs

Submission.cs

ISubmissionRepository.cs

EfSubmissionRepository.cs


Each file is small and purposeful. The Endpoint adapts transport to intent. The Processor applies policy and coordinates domain work. The Entity owns invariants and protects truth. The repository is a narrow port that returns the shapes the domain needs.

The REPR Flow
The Request expresses what the caller wants. The Processor asks the Entity to perform domain operations under invariant checks. The Repository persists or projects as needed. The Response communicates the outcome in consumer terms. The HTTP or messaging edge is merely a conduit.

Request and Response: Intent and Outcome
Requests model intent; they are not transport bags or EF rows. Responses model what consumers need; they are not entities.
// Request > intent, not storage schema.

public sealed record ApproveSubmissionRequest(long SubmissionId, string ApprovedBy);

// Response > consumer-shaped business outcome.

public sealed record ApproveSubmissionResponse(

long SubmissionId,

DateTime ApprovedAtUtc,

string ApprovedBy,

string StatusMessage);


Names should read like language in the domain. Versioning the Response is an explicit contract decision, not an accident.

Entity: The Only Place Allowed to Change Truth
Entities own invariants. Processors never toggle fields directly; they call domain methods that enforce rules atomically.

public sealed class Submission(long id, DateTime createdAtUtc)

{

public long Id { get; } = id;

public DateTime CreatedAtUtc { get; } = createdAtUtc;

public bool IsApproved { get; private set; }

public DateTime? ApprovedAtUtc { get; private set; }

public string? ApprovedBy { get; private set; }

public Result Approve(string approver, DateTime nowUtc)

{

if (IsApproved)

return Result.Fail("Submission is already approved.");

if (string.IsNullOrWhiteSpace(approver))

return Result.Fail("Approver is required.");

IsApproved  = true;

ApprovedAtUtc = nowUtc;

ApprovedBy  = approver;

return Result.Ok();

}

}

A small, explicit domain method prevents entire categories of drift, such as direct flag mutation scattered across handlers.

Repository Port: Narrow and Purposeful

Commands require Entities to enforce invariants; queries typically require projections. A port keeps the Processor independent of storage concerns.

public interface ISubmissionRepository

{

Task<Submission?> GetAsync(long id, CancellationToken ct);

Task SaveAsync(Submission Submission, CancellationToken ct);

}


Implementation choices (EF Core, Dapper, external service) do not change the Processor surface. N+1 issues are solved inside the repository contract, not leaked into callers.

Processor: The Use-Case Script
The Processor composes the use-case: load, ask, change, persist, reply. It does not perform transport work or data access gymnastics; it orchestrates policy with the Entity.

public interface IClock { DateTime UtcNow { get; } }

public sealed class SystemClock : IClock { public DateTime UtcNow => DateTime.UtcNow; }

public sealed class ApproveSubmissionProcessor(ISubmissionRepository repo, IClock clock)

{

public async Task<Result<ApproveSubmissionResponse>> Handle(ApproveSubmissionRequest req, CancellationToken ct)

{

var Submission = await repo.GetAsync(req.SubmissionId, ct);

if (Submission is null)

return Result.Fail<ApproveSubmissionResponse>("Submission not found.");

var approved = Submission.Approve(req.ApprovedBy, clock.UtcNow);

if (approved.IsFailure)

return approved.Cast<ApproveSubmissionResponse>();

await repo.SaveAsync(Submission, ct);

return Result.Ok(new ApproveSubmissionResponse(

Submission.Id,

Submission.ApprovedAtUtc!.Value,

Submission.ApprovedBy!,

"Approved"));

}

}


This class should remain short. If it grows, the Entity likely needs a new domain operation to absorb rules.

FastEndpoints: The Precise HTTP Adapter
FastEndpoints models the HTTP edge with minimal ceremony. The Endpoint accepts the Request and delegates to the Processor, returning a Response or an error mapping.

using FastEndpoints;

public sealed class ApproveSubmissionEndpoint(ApproveSubmissionProcessor processor)

: Endpoint<ApproveSubmissionRequest, ApproveSubmissionResponse>

{

public override void Configure()

{

Post("/Submission/Submissions/{SubmissionId:long}/approve");

Version(1);

Summary(s =>

{

s.Summary = "Approve a Submission";

s.Description = "Approves a Submission if business rules allow.";

s.ExampleRequest = new ApproveSubmissionRequest(42, "Approver01");

});

}

public override async Task HandleAsync(ApproveSubmissionRequest req, CancellationToken ct)

{

var result = await processor.Handle(req, ct);

if (result.IsSuccess)

await SendOkAsync(result.Value, ct);

else

await SendErrorsAsync(400, result.Message);

}

}


The Endpoint remains transport-centric. It does not read data stores, compute business rules, or create hidden dependencies.

Validation: Polite at the Edge, Strict in the Core

Transport-level validation protects the Processor from malformed input, and invariants in the Entity protect the core from impossible states.
using FastEndpoints;

using FluentValidation;

public sealed class ApproveSubmissionValidator : Validator<ApproveSubmissionRequest>

{

public ApproveSubmissionValidator()

{

RuleLevelCascadeMode = CascadeMode.Stop;

RuleFor(r => r.SubmissionId).GreaterThan(0);

RuleFor(r => r.ApprovedBy).NotEmpty().MaximumLength(200);

}

}

This split is essential. It prevents duplication and avoids shifting business rules into validators where they cannot be enforced consistently.

Program Setup: Small Surface, Clear Wiring

Minimal registration with DI and FastEndpoints keeps the composition explicit and discoverable.
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddDbContext<MyDbContext>(...);

builder.Services.AddScoped<ISubmissionRepository, EfSubmissionRepository>();

builder.Services.AddScoped<ApproveSubmissionProcessor>();

builder.Services.AddSingleton<IClock, SystemClock>();

builder.Services.AddFastEndpoints();

builder.Services.AddFluentValidationAutoValidation();

builder.Services.AddSwaggerDoc();

var app = builder.Build();

app.UseAuthorization();

app.UseFastEndpoints();

app.UseOpenApi();

app.UseSwaggerUi3();

app.Run();


New slices add themselves by file convention; the application is composed of features rather than layers.

Query Slices: Reads Benefit Even More
Read models become explicit, projection-first, and immune to accidental IQueryable leakage. The Request captures query intent, the Processor (or Endpoint for simple reads) projects to a consumer shape.
public sealed record GetSubmissionsRequest(int Page = 1, int PageSize = 20);

public sealed record SubmissionSummary(long Id, bool IsApproved, DateTime CreatedAtUtc);

public sealed class GetSubmissionsEndpoint(MyDbContext db)

: Endpoint<GetSubmissionsRequest, IReadOnlyList<SubmissionSummary>>

{

public override void Configure()

{

Get("/uwSubmission/Submissions");

Version(1);

}

public override async Task HandleAsync(GetSubmissionsRequest req, CancellationToken ct)

{

if (req.Page <= 0 || req.PageSize is <= 0 or > 200)

{

await SendErrorsAsync(400, "Invalid paging");

return;

}

var data = await db.Submissions

.AsNoTracking()

.OrderByDescending(h => h.CreatedAtUtc)

.Skip((req.Page - 1) * req.PageSize)

.Take(req.PageSize)

.Select(h => new SubmissionSummary(h.Id, h.IsApproved, h.CreatedAtUtc))

.ToListAsync(ct);

await SendOkAsync(data, ct);

}

}


Read and write slices share the same mental model and directory shape, simplifying navigation and ownership.

Cross-Cutting Concerns: Pipeline, Not Boilerplate

In REPR, cross-cutting concerns belong around the Processor as pipeline steps or behaviours, not inside it. FastEndpoints supports pre/post processors at the HTTP edge; a mediator or composition pattern can provide behaviours around the Processor for logging, correlation, authorisation, idempotency, and resilience.

A simple correlation pre-processor:
public sealed class CorrelationPreProcessor<TReq, TRes>(

ILogger<CorrelationPreProcessor<TReq,TRes>> log)

: IPreProcessor<Endpoint<TReq,TRes>>

where TReq : notnull

{

public Task PreProcessAsync(Endpoint<TReq,TRes> ep, CancellationToken ct)

{

var cid = ep.HttpContext.TraceIdentifier;

ep.HttpContext.Items["cid"] = cid;

log.LogInformation("Handling {Request} cid={Cid}", typeof(TReq).Name, cid);

return Task.CompletedTask;

}

}


Applied globally or per endpoint, this keeps transport concerns at the edge and business concerns in the core.

Events and Outbox: Facts Out, Reliably

Clarity is the aim, with each use-case being represented by a slice made up of a consumer-shaped contract (Response), a truthful domain core (Entity), a use case script (Processor), and an intent (Request). The slice itself conveys the meaning, so incidental abstractions, controllers, and service layers disappear. As a result, the codebase reads by verbs instead of layers, which is the logical unit of reasoning for both engineering and products.

This pattern aligns neatly with REPR’s flow: intent in, invariant-checked state change, facts recorded, outcome returned.

Testing: Small, Fast, and Honest

REPR improves testability by shrinking surface areas. Entities are tested directly via domain methods. Processors are tested with fake repositories and clocks, asserting on Result<T> and domain state. Endpoints can be verified with FastEndpoints’ in-memory host where HTTP-level assurance is required, but business logic never depends on a web server to be testable.

A Processor test is concise and meaningful:
public async Task Approve_Sets_State_And_Returns_Response()

{

var now = new DateTime(2025, 10, 24, 12, 0, 0, DateTimeKind.Utc);

var clock = Substitute.For<IClock>(); clock.UtcNow.Returns(now);

var Submission = new Submission(42, now.AddDays(-1));

var repo = Substitute.For<ISubmissionRepository>();

repo.GetAsync(42, Arg.Any<CancellationToken>()).Returns(Submission);

var processor = new ApproveSubmissionProcessor(repo, clock);

var result = await processor.Handle(new ApproveSubmissionRequest(42, "PK"), CancellationToken.None);

result.IsSuccess.ShouldBeTrue();

Submission.IsApproved.ShouldBeTrue();

Submission.ApprovedAtUtc.ShouldBe(now);

Submission.ApprovedBy.ShouldBe("PK");

await repo.Received(1).SaveAsync(Submission, Arg.Any<CancellationToken>());

}


The test demonstrates intent, rule enforcement, and persistence orchestration without scaffolding overhead.

Anti-Patterns to Avoid

A few pitfalls undermine the benefits:

  • Requests that mirror database rows rather than intent encourage accidental coupling and bloat.
  • Processors that toggle fields directly instead of calling domain methods bypass invariants.
  • Repositories that return IQueryable leak persistence concerns and invite N+1 issues outside the boundary.
  • Responses that expose Entities create fragile contracts and accidental internal coupling.
  • Generic “BaseRepository<T>” or “Service” classes that centralise convenience instead of domain language lead to anaemic designs.

REPR’s strength is clarity; resist abstractions that obscure intent.

Impact and Ownership

Eams use verbs and results to reason. When a slice of a story—Request, Processor, Response—with invariants visible in an entity, PRs examine it more quickly. Because logs and analytics match Request types and semantic processes rather than raw URLs, observability improves. Instead of mapping to controllers or horizontal layers, ownership maps to features, allowing for autonomous delivery free from unintentional contention.

REPR is not a framework and does not require a mediator, controller, or a particular storage engine. It is the minimal shape a serious codebase adopts when Clean Architecture and CQRS are applied with discipline and scaled across a product’s lifetime. Expressed with FastEndpoints, the HTTP edge becomes quiet and intention-based, and the domain regains custody of truth.

The practical move is simple: for the next non-trivial feature, create a slice named for the verb. Write the Request so the intent is explicit. Put rules in an Entity method. Keep the Processor short and orchestral. Return a Response that a consumer would actually want. Repeat until the architecture fades into the furniture and only the product remains visible.

HostForLIFE ASP.NET Core 10.0 Hosting

European Best, cheap and reliable ASP.NET Core 10.0 hosting with instant activation. HostForLIFE.eu is #1 Recommended Windows and ASP.NET hosting in European Continent. With 99.99% Uptime Guaranteed of Relibility, Stability and Performace. HostForLIFE.eu security team is constantly monitoring the entire network for unusual behaviour. We deliver hosting solution including Shared hosting, Cloud hosting, Reseller hosting, Dedicated Servers, and IT as Service for companies of all size.



European ASP.NET Core 10.0 Hosting - HostForLIFE :: Mastery of ASP.NET Core Middleware: Custom Auth, Pipeline Control, and Logging

clock October 23, 2025 08:35 by author Peter

Introduction to Middleware
What is Middleware?

Middleware in ASP.NET Core is software components that are assembled into an application pipeline to handle requests and responses. Each component:

  • Chooses whether to pass the request to the next component
  • Can perform work before and after the next component

// Simple middleware concept
app.Use(async (context, next) =>
{
    // Do work before the next middleware
    await next.Invoke();
    // Do work after the next middleware
});


Real-Life Analogy: Airport Security Check
Think of middleware as airport security checks:

  • Ticket Verification - Check if you have a valid ticket
  • Security Screening - Scan luggage and personal items
  • Passport Control - Verify travel documents
  • Boarding Gate - Final check before boarding

Each step can either pass you to the next or reject you entirely.

2. Understanding the Request Pipeline
Basic Pipeline Structure

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }

    app.UseRouting();
    app.UseAuthorization();
    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllers();
    });
}

Pipeline Flow Visualization

Request → Middleware 1 → Middleware 2 → ... → Middleware N → Endpoint → Response

Lifecycle Example: E-Commerce Request
// Program.cs - Complete pipeline setup
var builder = WebApplication.CreateBuilder(args);

// Add services
builder.Services.AddControllers();
builder.Services.AddAuthentication();
builder.Services.AddAuthorization();

var app = builder.Build();

// Configure pipeline
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseRequestLogging(); // Custom middleware
app.UsePerformanceMonitoring(); // Custom middleware

app.MapControllers();

app.Run();

3. Built-in Middleware Components
Essential Built-in Middleware

3.1 Static Files Middleware
// Serve static files like HTML, CSS, JavaScript, images
app.UseStaticFiles();

// Serve files from custom directory
app.UseStaticFiles(new StaticFileOptions
{
    FileProvider = new PhysicalFileProvider(
        Path.Combine(Directory.GetCurrentDirectory(), "MyStaticFiles")),
    RequestPath = "/StaticFiles"
});


3.2 Routing Middleware
app.UseRouting();

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllerRoute(
        name: "default",
        pattern: "{controller=Home}/{action=Index}/{id?}");

    endpoints.MapGet("/hello", async context =>
    {
        await context.Response.WriteAsync("Hello World!");
    });
});


3.3 Authentication & Authorization
app.UseAuthentication();
app.UseAuthorization();

3.4 CORS Middleware
// Add CORS service
builder.Services.AddCors(options =>
{
    options.AddPolicy("AllowAll",
        builder =>
        {
            builder.AllowAnyOrigin()
                   .AllowAnyMethod()
                   .AllowAnyHeader();
        });
});

// Use CORS middleware
app.UseCors("AllowAll");


4. Creating Custom Middleware
4.1 Inline Middleware

app.Use(async (context, next) =>
{
    var startTime = DateTime.UtcNow;

    // Call the next middleware
    await next();

    var endTime = DateTime.UtcNow;
    var duration = endTime - startTime;

    Console.WriteLine($"Request took: {duration.TotalMilliseconds}ms");
});


4.2 Class-Based Middleware
Basic Custom Middleware Class
// Custom logging middleware
public class RequestLoggingMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger<RequestLoggingMiddleware> _logger;

    public RequestLoggingMiddleware(RequestDelegate next, ILogger<RequestLoggingMiddleware> logger)
    {
        _next = next;
        _logger = logger;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        var startTime = DateTime.UtcNow;

        _logger.LogInformation($"Starting request: {context.Request.Method} {context.Request.Path}");

        try
        {
            await _next(context);
        }
        finally
        {
            var endTime = DateTime.UtcNow;
            var duration = endTime - startTime;

            _logger.LogInformation(
                $"Completed request: {context.Request.Method} {context.Request.Path} " +
                $"with status: {context.Response.StatusCode} in {duration.TotalMilliseconds}ms");
        }
    }
}

// Extension method for easy use
public static class RequestLoggingMiddlewareExtensions
{
    public static IApplicationBuilder UseRequestLogging(this IApplicationBuilder builder)
    {
        return builder.UseMiddleware<RequestLoggingMiddleware>();
    }
}


4.3 Factory-Based Middleware
// Factory-activated middleware
public class TimingMiddleware : IMiddleware
{
    private readonly ILogger<TimingMiddleware> _logger;

    public TimingMiddleware(ILogger<TimingMiddleware> logger)
    {
        _logger = logger;
    }

    public async Task InvokeAsync(HttpContext context, RequestDelegate next)
    {
        var stopwatch = Stopwatch.StartNew();

        _logger.LogInformation($"Starting request: {context.Request.Path}");

        await next(context);

        stopwatch.Stop();
        _logger.LogInformation($"Request {context.Request.Path} completed in {stopwatch.ElapsedMilliseconds}ms");
    }
}

// Register in DI container
builder.Services.AddTransient<TimingMiddleware>();


5. Real-World Middleware Examples
5.1 API Key Authentication Middleware

// API Key Authentication Middleware
public class ApiKeyMiddleware
{
    private readonly RequestDelegate _next;
    private readonly IConfiguration _configuration;
    private const string API_KEY_HEADER = "X-API-Key";

    public ApiKeyMiddleware(RequestDelegate next, IConfiguration configuration)
    {
        _next = next;
        _configuration = configuration;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        // Skip API key check for certain paths
        if (context.Request.Path.StartsWithSegments("/public"))
        {
            await _next(context);
            return;
        }

        if (!context.Request.Headers.TryGetValue(API_KEY_HEADER, out var extractedApiKey))
        {
            context.Response.StatusCode = 401;
            await context.Response.WriteAsync("API Key is missing");
            return;
        }

        var validApiKeys = _configuration.GetSection("ValidApiKeys").Get<List<string>>();

        if (!validApiKeys.Contains(extractedApiKey))
        {
            context.Response.StatusCode = 401;
            await context.Response.WriteAsync("Invalid API Key");
            return;
        }

        await _next(context);
    }
}

// Extension method
public static class ApiKeyMiddlewareExtensions
{
    public static IApplicationBuilder UseApiKeyAuthentication(this IApplicationBuilder builder)
    {
        return builder.UseMiddleware<ApiKeyMiddleware>();
    }
}

5.2 Request/Response Logging Middleware
// Detailed request/response logging middleware
public class DetailedLoggingMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger<DetailedLoggingMiddleware> _logger;

    public DetailedLoggingMiddleware(RequestDelegate next, ILogger<DetailedLoggingMiddleware> logger)
    {
        _next = next;
        _logger = logger;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        // Log request
        await LogRequest(context);

        // Copy original response body stream
        var originalBodyStream = context.Response.Body;

        using var responseBody = new MemoryStream();
        context.Response.Body = responseBody;

        try
        {
            await _next(context);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "An unhandled exception occurred");
            throw;
        }
        finally
        {
            // Log response
            await LogResponse(context, responseBody, originalBodyStream);
        }
    }

    private async Task LogRequest(HttpContext context)
    {
        context.Request.EnableBuffering();

        var request = $"{context.Request.Method} {context.Request.Path}{context.Request.QueryString}";
        var headers = string.Join(", ", context.Request.Headers.Select(h => $"{h.Key}: {h.Value}"));

        _logger.LogInformation($"Request: {request}");
        _logger.LogDebug($"Headers: {headers}");

        if (context.Request.ContentLength > 0)
        {
            using var reader = new StreamReader(context.Request.Body, Encoding.UTF8,
                leaveOpen: true);
            var body = await reader.ReadToEndAsync();
            context.Request.Body.Position = 0;

            _logger.LogDebug($"Request Body: {body}");
        }
    }

    private async Task LogResponse(HttpContext context, MemoryStream responseBody, Stream originalBodyStream)
    {
        responseBody.Position = 0;
        var responseText = await new StreamReader(responseBody).ReadToEndAsync();
        responseBody.Position = 0;

        _logger.LogInformation($"Response: {context.Response.StatusCode}");
        _logger.LogDebug($"Response Body: {responseText}");

        await responseBody.CopyToAsync(originalBodyStream);
        context.Response.Body = originalBodyStream;
    }
}

5.3 Rate Limiting Middleware
// Simple rate limiting middleware
public class RateLimitingMiddleware
{
    private readonly RequestDelegate _next;
    private readonly Dictionary<string, List<DateTime>> _requestLog = new();
    private readonly object _lockObject = new object();
    private readonly int _maxRequests = 100;
    private readonly TimeSpan _timeWindow = TimeSpan.FromMinutes(1);

    public RateLimitingMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        var clientIp = context.Connection.RemoteIpAddress?.ToString() ?? "unknown";

        if (IsRateLimited(clientIp))
        {
            context.Response.StatusCode = 429; // Too Many Requests
            await context.Response.WriteAsync("Rate limit exceeded. Please try again later.");
            return;
        }

        await _next(context);
    }

    private bool IsRateLimited(string clientIp)
    {
        lock (_lockObject)
        {
            var now = DateTime.UtcNow;

            if (!_requestLog.ContainsKey(clientIp))
            {
                _requestLog[clientIp] = new List<DateTime>();
            }

            // Remove old requests outside the time window
            _requestLog[clientIp].RemoveAll(time => now - time > _timeWindow);

            // Check if over limit
            if (_requestLog[clientIp].Count >= _maxRequests)
            {
                return true;
            }

            // Log this request
            _requestLog[clientIp].Add(now);
            return false;
        }
    }
}

5.4 Correlation ID Middleware
// Correlation ID middleware for request tracking
public class CorrelationIdMiddleware
{
    private readonly RequestDelegate _next;
    private const string CORRELATION_ID_HEADER = "X-Correlation-ID";

    public CorrelationIdMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task InvokeAsync(HttpContext context, ILogger<CorrelationIdMiddleware> logger)
    {
        var correlationId = GetOrCreateCorrelationId(context);

        // Add correlation ID to response headers
        context.Response.OnStarting(() =>
        {
            context.Response.Headers[CORRELATION_ID_HEADER] = correlationId;
            return Task.CompletedTask;
        });

        // Add to logger scope
        using (logger.BeginScope("{CorrelationId}", correlationId))
        {
            await _next(context);
        }
    }

    private string GetOrCreateCorrelationId(HttpContext context)
    {
        if (context.Request.Headers.TryGetValue(CORRELATION_ID_HEADER, out var correlationId))
        {
            return correlationId!;
        }

        return Guid.NewGuid().ToString();
    }
}

6. Middleware Ordering and Execution
Critical Middleware Order

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    // 1. Exception/Error Handling
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/Error");
        app.UseHsts();
    }

    // 2. HTTPS Redirection
    app.UseHttpsRedirection();

    // 3. Static Files
    app.UseStaticFiles();

    // 4. Routing
    app.UseRouting();

    // 5. Custom Middleware (Authentication, Logging, etc.)
    app.UseCorrelationId();
    app.UseRequestLogging();
    app.UseApiKeyAuthentication();

    // 6. CORS
    app.UseCors("AllowAll");

    // 7. Authentication & Authorization
    app.UseAuthentication();
    app.UseAuthorization();

    // 8. Endpoints
    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllers();
        endpoints.MapRazorPages();
    });
}


Ordering Impact Example
// Wrong order - Authentication won't work properly
app.UseEndpoints(endpoints => { /* ... */ });
app.UseAuthentication(); // This won't be called for endpoint requests

// Correct order
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints => { /* ... */ });

7. Advanced Middleware Patterns
7.1 Conditional Middleware

// Conditional middleware based on request path
public class ConditionalMiddleware
{
    private readonly RequestDelegate _next;
    private readonly string _pathStartsWith;

    public ConditionalMiddleware(RequestDelegate next, string pathStartsWith)
    {
        _next = next;
        _pathStartsWith = pathStartsWith;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        if (context.Request.Path.StartsWithSegments(_pathStartsWith))
        {
            // Custom logic for specific paths
            context.Items["CustomFeature"] = "Enabled";
        }

        await _next(context);
    }
}

// Usage with multiple conditions
app.MapWhen(context => context.Request.Path.StartsWithSegments("/api"),
    apiApp =>
    {
        apiApp.UseMiddleware<ApiSpecificMiddleware>();
        apiApp.UseRouting();
        apiApp.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
        });
    });

7.2 Branching Middleware Pipeline
// Branching for different pipeline configurations
app.Map("/admin", adminApp =>
{
    adminApp.UseMiddleware<AdminAuthenticationMiddleware>();
    adminApp.UseRouting();
    adminApp.UseEndpoints(endpoints =>
    {
        endpoints.MapControllerRoute(
            name: "admin",
            pattern: "{controller=Home}/{action=Index}/{id?}");
    });
});

app.Map("/api", apiApp =>
{
    apiApp.UseMiddleware<ApiKeyMiddleware>();
    apiApp.UseRouting();
    apiApp.UseEndpoints(endpoints =>
    {
        endpoints.MapControllers();
    });
});

7.3 Middleware with Configuration
// Configurable middleware
public class ConfigurableMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ConfigurableMiddlewareOptions _options;

    public ConfigurableMiddleware(RequestDelegate next, IOptions<ConfigurableMiddlewareOptions> options)
    {
        _next = next;
        _options = options.Value;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        if (_options.EnableFeature && context.Request.Path.StartsWithSegments(_options.ApplyToPath))
        {
            // Apply middleware logic
            await ApplyCustomLogic(context);
        }

        await _next(context);
    }

    private async Task ApplyCustomLogic(HttpContext context)
    {
        // Custom implementation
        context.Items["CustomFeatureApplied"] = true;
    }
}

public class ConfigurableMiddlewareOptions
{
    public bool EnableFeature { get; set; }
    public string ApplyToPath { get; set; } = "/api";
}

// Registration
builder.Services.Configure<ConfigurableMiddlewareOptions>(options =>
{
    options.EnableFeature = true;
    options.ApplyToPath = "/secure";
});


8. Error Handling Middleware
Global Exception Handling Middleware

// Comprehensive error handling middleware
public class GlobalExceptionHandlerMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger<GlobalExceptionHandlerMiddleware> _logger;
    private readonly IWebHostEnvironment _env;

    public GlobalExceptionHandlerMiddleware(
        RequestDelegate next,
        ILogger<GlobalExceptionHandlerMiddleware> logger,
        IWebHostEnvironment env)
    {
        _next = next;
        _logger = logger;
        _env = env;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        try
        {
            await _next(context);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "An unhandled exception occurred");
            await HandleExceptionAsync(context, ex);
        }
    }

    private async Task HandleExceptionAsync(HttpContext context, Exception exception)
    {
        context.Response.ContentType = "application/json";

        var errorResponse = new ErrorResponse
        {
            Success = false,
            Error = "An unexpected error occurred"
        };

        switch (exception)
        {
            case UnauthorizedAccessException:
                context.Response.StatusCode = StatusCodes.Status401Unauthorized;
                errorResponse.Error = "Unauthorized access";
                break;

            case KeyNotFoundException:
                context.Response.StatusCode = StatusCodes.Status404NotFound;
                errorResponse.Error = "Resource not found";
                break;

            case ValidationException vex:
                context.Response.StatusCode = StatusCodes.Status400BadRequest;
                errorResponse.Error = "Validation failed";
                errorResponse.Details = vex.Errors;
                break;

            default:
                context.Response.StatusCode = StatusCodes.Status500InternalServerError;
                if (_env.IsDevelopment())
                {
                    errorResponse.Details = exception.ToString();
                }
                break;
        }

        var json = JsonSerializer.Serialize(errorResponse, new JsonSerializerOptions
        {
            PropertyNamingPolicy = JsonNamingPolicy.CamelCase
        });

        await context.Response.WriteAsync(json);
    }
}

public class ErrorResponse
{
    public bool Success { get; set; }
    public string Error { get; set; } = string.Empty;
    public object? Details { get; set; }
}


Custom Exception Classes
// Custom exception for business logic
public class BusinessException : Exception
{
    public string ErrorCode { get; }

    public BusinessException(string errorCode, string message) : base(message)
    {
        ErrorCode = errorCode;
    }
}

public class ValidationException : Exception
{
    public Dictionary<string, string[]> Errors { get; }

    public ValidationException(Dictionary<string, string[]> errors)
        : base("Validation failed")
    {
        Errors = errors;
    }
}


9. Performance Optimization
Response Caching Middleware

// Custom response caching middleware
public class ResponseCachingMiddleware
{
    private readonly RequestDelegate _next;
    private readonly IMemoryCache _cache;
    private readonly ILogger<ResponseCachingMiddleware> _logger;

    public ResponseCachingMiddleware(
        RequestDelegate next,
        IMemoryCache cache,
        ILogger<ResponseCachingMiddleware> logger)
    {
        _next = next;
        _cache = cache;
        _logger = logger;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        var cacheKey = GenerateCacheKey(context.Request);

        if (context.Request.Method == "GET" && _cache.TryGetValue(cacheKey, out byte[]? cachedResponse))
        {
            _logger.LogInformation($"Serving cached response for {context.Request.Path}");
            context.Response.ContentType = "application/json";
            context.Response.ContentLength = cachedResponse?.Length ?? 0;
            await context.Response.Body.WriteAsync(cachedResponse!);
            return;
        }

        var originalBodyStream = context.Response.Body;
        using var responseBody = new MemoryStream();
        context.Response.Body = responseBody;

        await _next(context);

        if (context.Response.StatusCode == 200)
        {
            var responseData = responseBody.ToArray();

            // Cache the response
            var cacheOptions = new MemoryCacheEntryOptions()
                .SetAbsoluteExpiration(TimeSpan.FromMinutes(5))
                .SetSlidingExpiration(TimeSpan.FromMinutes(1));

            _cache.Set(cacheKey, responseData, cacheOptions);

            _logger.LogInformation($"Cached response for {context.Request.Path}");
        }

        await responseBody.CopyToAsync(originalBodyStream);
    }

    private string GenerateCacheKey(HttpRequest request)
    {
        return $"{request.Path}{request.QueryString}";
    }
}


Performance Monitoring Middleware
// Performance monitoring middleware
public class PerformanceMonitoringMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger<PerformanceMonitoringMiddleware> _logger;

    public PerformanceMonitoringMiddleware(
        RequestDelegate next,
        ILogger<PerformanceMonitoringMiddleware> logger)
    {
        _next = next;
        _logger = logger;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        var stopwatch = Stopwatch.StartNew();
        var startMemory = GC.GetTotalMemory(false);

        try
        {
            await _next(context);
        }
        finally
        {
            stopwatch.Stop();
            var endMemory = GC.GetTotalMemory(false);
            var memoryUsed = endMemory - startMemory;

            _logger.LogInformation(
                $"Performance - Path: {context.Request.Path}, " +
                $"Duration: {stopwatch.ElapsedMilliseconds}ms, " +
                $"Memory: {memoryUsed} bytes, " +
                $"Status: {context.Response.StatusCode}");

            // Log warning for slow requests
            if (stopwatch.ElapsedMilliseconds > 1000)
            {
                _logger.LogWarning(
                    $"Slow request detected: {context.Request.Path} " +
                    $"took {stopwatch.ElapsedMilliseconds}ms");
            }
        }
    }
}


10. Testing Middleware
Unit Testing Middleware

// Unit tests for custom middleware
public class RequestLoggingMiddlewareTests
{
    [Fact]
    public async Task InvokeAsync_LogsRequestInformation()
    {
        // Arrange
        var loggerMock = new Mock<ILogger<RequestLoggingMiddleware>>();
        var nextMock = new Mock<RequestDelegate>();

        var middleware = new RequestLoggingMiddleware(nextMock.Object, loggerMock.Object);

        var context = new DefaultHttpContext();
        context.Request.Method = "GET";
        context.Request.Path = "/api/test";

        // Act
        await middleware.InvokeAsync(context);

        // Assert
        nextMock.Verify(next => next(context), Times.Once);
        // Verify logging calls
    }

    [Fact]
    public async Task InvokeAsync_WhenException_LogsError()
    {
        // Arrange
        var loggerMock = new Mock<ILogger<RequestLoggingMiddleware>>();
        var nextMock = new Mock<RequestDelegate>();
        nextMock.Setup(next => next(It.IsAny<HttpContext>()))
                .ThrowsAsync(new Exception("Test exception"));

        var middleware = new RequestLoggingMiddleware(nextMock.Object, loggerMock.Object);
        var context = new DefaultHttpContext();

        // Act & Assert
        await Assert.ThrowsAsync<Exception>(() => middleware.InvokeAsync(context));

        // Verify error was logged
    }
}

// Integration testing
public class MiddlewareIntegrationTests : IClassFixture<WebApplicationFactory<Program>>
{
    private readonly WebApplicationFactory<Program> _factory;

    public MiddlewareIntegrationTests(WebApplicationFactory<Program> factory)
    {
        _factory = factory;
    }

    [Fact]
    public async Task CustomMiddleware_AddsCorrelationHeader()
    {
        // Arrange
        var client = _factory.CreateClient();

        // Act
        var response = await client.GetAsync("/api/test");

        // Assert
        response.Headers.Contains("X-Correlation-ID").Should().BeTrue();
    }
}

11. Best Practices and Alternatives
Middleware Best Practices


11.1 Do's and Don'ts
Do:

  • Keep middleware focused and single-purpose
  • Use dependency injection properly
  • Handle exceptions appropriately
  • Consider performance implications
  • Use appropriate logging levels

Don't:

  • Put business logic in middleware
  • Block the pipeline unnecessarily
  • Ignore async/await patterns
  • Forget to call next() when needed

11.2 Performance Considerations
// Good - Async all the way
public async Task InvokeAsync(HttpContext context)
{
    await SomeAsyncOperation();
    await _next(context);
}

// Bad - Mixing sync and async
public async Task InvokeAsync(HttpContext context)
{
    SomeSyncOperation(); // Blocks thread
    await _next(context);
}

Alternatives to Custom Middleware
11.3 Action Filters
// Use action filters for controller-specific logic
public class LogActionFilter : IActionFilter
{
    private readonly ILogger<LogActionFilter> _logger;

    public LogActionFilter(ILogger<LogActionFilter> logger)
    {
        _logger = logger;
    }

    public void OnActionExecuting(ActionExecutingContext context)
    {
        _logger.LogInformation($"Executing action: {context.ActionDescriptor.DisplayName}");
    }

    public void OnActionExecuted(ActionExecutedContext context)
    {
        _logger.LogInformation($"Executed action: {context.ActionDescriptor.DisplayName}");
    }
}


11.4 Endpoint Filters
// Endpoint filters for minimal APIs
app.MapGet("/api/products", () =>
{
    return Results.Ok(products);
})
.AddEndpointFilter<LoggingEndpointFilter>();

public class LoggingEndpointFilter : IEndpointFilter
{
    public async ValueTask<object?> InvokeAsync(
        EndpointFilterInvocationContext context,
        EndpointFilterDelegate next)
    {
        Console.WriteLine($"Before: {context.HttpContext.Request.Path}");
        var result = await next(context);
        Console.WriteLine($"After: {context.HttpContext.Request.Path}");

        return result;
    }
}


Complete Real-World Example: E-Commerce Pipeline
// Complete e-commerce application pipeline
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    // Exception handling
    app.UseGlobalExceptionHandler();

    // Security
    app.UseHsts();
    app.UseHttpsRedirection();

    // Performance
    app.UseResponseCompression();
    app.UseResponseCaching();

    // Static files
    app.UseStaticFiles();

    // Routing
    app.UseRouting();

    // Custom middleware
    app.UseCorrelationId();
    app.UseRequestLogging();
    app.UsePerformanceMonitoring();

    // Authentication & Authorization
    app.UseAuthentication();
    app.UseAuthorization();

    // Rate limiting for API
    app.MapWhen(context => context.Request.Path.StartsWithSegments("/api"),
        apiApp =>
        {
            apiApp.UseRateLimiting();
            apiApp.UseApiKeyAuthentication();
        });

    // Endpoints
    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllers();
        endpoints.MapRazorPages();

        // Health check
        endpoints.MapHealthChecks("/health");
    });
}

HostForLIFE ASP.NET Core 10.0 Hosting

European Best, cheap and reliable ASP.NET Core 10.0 hosting with instant activation. HostForLIFE.eu is #1 Recommended Windows and ASP.NET hosting in European Continent. With 99.99% Uptime Guaranteed of Relibility, Stability and Performace. HostForLIFE.eu security team is constantly monitoring the entire network for unusual behaviour. We deliver hosting solution including Shared hosting, Cloud hosting, Reseller hosting, Dedicated Servers, and IT as Service for companies of all size.

 



European ASP.NET Core 9.0 Hosting - HostForLIFE :: Runtime Intelligence Unlocked: A Formal Investigation of Reflection in.NET

clock October 20, 2025 07:25 by author Peter

Programs can examine and work with their own metadata at runtime thanks to the potent.NET reflection feature. It serves as the foundation for numerous complex programming scenarios, such as runtime code generation, late binding, and dynamic type discovery. This article offers a thorough rundown of Reflection in.NET, including its essential elements, real-world uses, and recommended usage guidelines for security and effectiveness.

What Is Reflection?
Reflection is the ability of a program to examine and manipulate its own structure and behavior. In the .NET ecosystem, this is facilitated by the System.Reflection namespace, which provides classes and methods to access metadata about assemblies, modules, types, members, and parameters.

Reflection allows developers to:

  • Discover type information at runtime
  • Instantiate objects dynamically
  • Invoke methods and access properties or fields
  • Explore custom attributes and metadata
  • Key Components of Reflection

1. Assembly Inspection
The Assembly class enables loading and inspecting assemblies. Developers can retrieve types, modules, and referenced assemblies using methods like Assembly.GetTypes() and Assembly.GetReferencedAssemblies().

2. Type Discovery
The Type class is central to Reflection. It provides metadata about classes, interfaces, enums, and other types. Common methods include:
GetMethods(), GetProperties(), GetFields()
IsClass, IsInterface, IsGenericType

3. Member Invocation

Reflection allows invoking members dynamically using:

  • MethodInfo.Invoke() for methods
  • PropertyInfo.GetValue() and SetValue() for properties
  • FieldInfo.GetValue() and SetValue() for fields

4. Custom Attributes
Reflection can retrieve custom attributes applied to types or members using Attribute.GetCustomAttributes() or MemberInfo.GetCustomAttributes().
Practical Applications

Reflection is widely used in scenarios such as:

  • Dependency Injection Frameworks: Discovering and injecting services at runtime.
  • ORMs (Object-Relational Mappers): Mapping database tables to classes dynamically.
  • Unit Testing Tools: Discovering and executing test methods.
  • Serialization Libraries: Inspecting object graphs for custom serialization.

Performance Considerations
While Reflection is powerful, it comes with performance overhead due to runtime type resolution and security checks. Best practices include:

  • Caching Type, MethodInfo, and PropertyInfo objects
  • Avoiding frequent reflection calls in performance-critical paths
  • Using compiled expressions (Expression<T>) or delegates for repeated invocations

Security Implications
Reflection can bypass access modifiers and invoke private members, which may pose security risks. Developers should:

  • Avoid exposing sensitive internals via Reflection
  • Use BindingFlags judiciously to limit scope
  • Validate inputs when using Reflection in public APIs

Alternatives and Enhancements
With the advent of .NET Core and .NET 5+, alternatives like System.Text.Json and source generators offer compile-time metadata access, reducing reliance on Reflection. Additionally, the TypeInfo class in .NET Core provides a more granular API for type inspection.

Reflection in .NET empowers developers with runtime introspection and dynamic behavior, making it indispensable for frameworks, libraries, and tooling. However, its use should be balanced with performance and security considerations. By understanding its architecture and applying best practices, developers can harness Reflection to build flexible and intelligent applications.

HostForLIFE ASP.NET Core 10.0 Hosting

European Best, cheap and reliable ASP.NET hosting with instant activation. HostForLIFE.eu is #1 Recommended Windows and ASP.NET hosting in European Continent. With 99.99% Uptime Guaranteed of Relibility, Stability and Performace. HostForLIFE.eu security team is constantly monitoring the entire network for unusual behaviour. We deliver hosting solution including Shared hosting, Cloud hosting, Reseller hosting, Dedicated Servers, and IT as Service for companies of all size.

 



European ASP.NET Core 10.0 Hosting - HostForLIFE :: SOLID Principles: A Comprehensive Guide to Practical.NET Projects

clock July 8, 2025 08:24 by author Peter

Single Responsibility Principle (SRP)
// One class = One job

Every class should have only one reason to change. This means no mixing database logic with business logic or API response formatting with validation rules.

Real-World .NET Example
// Bad: Doing too much
public class UserManager
{
    public void Register(User user) { /* logic */ }
    public void SaveToDb(User user) { /* DB logic */ }
    public void SendWelcomeEmail(User user) { /* Email logic */ }
}
// Good: SRP Applied
public class UserService
{
    public void Register(UserDto dto) { /* business rules */ }
}
public class UserRepository
{
    public void Save(User user) { /* DB only */ }
}
public class EmailSender
{
    public void SendWelcomeEmail(User user) { /* Email only */ }
}

Open/Closed Principle (OCP)
Open for extension, closed for modification

Your code should allow new behavior without changing existing code. This avoids regressions and makes it easier to add future features.

Real-World Example
public interface IDiscountStrategy
{
    decimal ApplyDiscount(decimal price);
}
public class HolidayDiscount : IDiscountStrategy
{
    public decimal ApplyDiscount(decimal price) => price * 0.9m;
}
public class NoDiscount : IDiscountStrategy
{
    public decimal ApplyDiscount(decimal price) => price;
}

By injecting IDiscountStrategy, you can add new discounts without changing your OrderService.

Liskov Substitution Principle (LSP)
Subtypes must be substitutable for their base types

Any derived class should be replaceable for its parent without breaking functionality.

Anti-Pattern
public class Rectangle
{
    public virtual void SetWidth(int width) { }
}
public class Square : Rectangle
{
    public override void SetWidth(int width)
    {
        throw new NotImplementedException(); // LSP Violation
    }
}

Instead, design with interfaces or composition when inheritance breaks behavior.

Interface Segregation Principle (ISP)
Keep interfaces small and focused

Clients shouldn't be forced to implement methods they don’t use. This avoids bloated and confusing code.

Better Design
public interface ILoginService
{
    void Login(string user, string password);
}
public interface IRegistrationService
{
    void Register(UserDto dto);
}

Instead of forcing everything into a single IUserService, split interfaces by responsibility.

Dependency Inversion Principle (DIP)
Depend on abstractions, not concrete classes

High-level modules (like OrderService) should depend on interfaces, not low-level classes like SqlOrderRepository.

Real-World Example
public interface IEmailService
{
    void SendEmail(string to, string message);
}
public class EmailService : IEmailService
{
    public void SendEmail(string to, string message)
    {
        // SMTP logic
    }
}
public class NotificationManager
{
    private readonly IEmailService _emailService;

    public NotificationManager(IEmailService emailService)
    {
        _emailService = emailService;
    }

    public void Notify(string msg)
    {
        _emailService.SendEmail("[email protected]", msg);
    }
}


In Program.cs
services.AddScoped<IEmailService, EmailService>();

How to Apply SOLID Principles Efficiently While Working?

  • Start with interfaces, then build implementations.
  • Don’t mix responsibilities: Separate service, repository, controller.
  • Write unit tests: If a class is hard to test, it’s probably violating SOLID.
  • Utilize design patterns:such as Strategy, Factory, and Mediator, to simplify SOLID principles.
  • Think like Lego: Compose functionality from small, testable bricks.


Final Thoughts
Mastering SOLID is like upgrading your dev brain. You’ll not only write clean and testable code but also scale faster, debug smarter, and survive enterprise-level chaos like a pro.
Want feedback on how SOLID your current codebase is? Drop a sample, and let's refactor it together!

Happy coding, and stay SOLID.

HostForLIFE ASP.NET Core 10.0 Hosting

European Best, cheap and reliable ASP.NET hosting with instant activation. HostForLIFE.eu is #1 Recommended Windows and ASP.NET hosting in European Continent. With 99.99% Uptime Guaranteed of Relibility, Stability and Performace. HostForLIFE.eu security team is constantly monitoring the entire network for unusual behaviour. We deliver hosting solution including Shared hosting, Cloud hosting, Reseller hosting, Dedicated Servers, and IT as Service for companies of all size.



European ASP.NET Core 10.0 Hosting - HostForLIFE :: Wolverine's Modern CQRS and Messaging in.NET

clock July 1, 2025 08:44 by author Peter

You have most likely used libraries like MediatR, MassTransit, or CAP to handle commands and messages at some time during your.NET journey. Although each has advantages, wiring and configuration can occasionally impede the development of features alone. I recently learned about the JasperFx team's command and message bus library, Wolverine. It combines scheduled jobs, communications, CQRS, and a robust inbox/outbox into a single, streamlined solution. What's the best part? It's enjoyable to work with, quick, and clean.

This post will show you how to use Wolverine to manage commands, publish events, and schedule background messages without the need for a database or web API by guiding you through a straightforward console application. 

Why Wolverine?
Wolverine aims to simplify distributed application architecture by combining.

  • Command handling (like MediatR).
  • Asynchronous and scheduled messaging.
  • Built-in middleware and behaviors.
  • Optional durable inbox/outbox with Marten (PostgreSQL).

Instead of bolting together multiple libraries, Wolverine gives you a unified model with batteries included.
The Example: Console-Based User Registration

We’ll build a console app that,

  • Registers a user via a command.
  • Sends a welcome email (simulated).
  • Schedules a follow-up reminder message.

Everything runs in-process using Wolverine's local message bus.

Step 1. Create the Project

Project Folder Structure
WolverineConsoleDemo/
├── Program.cs
├── WolverineConsoleDemo.csproj
├── Messages/
│   ├── RegisterUser.cs
│   ├── UserRegistered.cs
│   └── RemindAdmin.cs
├── Handlers/
│   ├── RegisterUserHandler.cs
│   ├── UserRegisteredHandler.cs
│   └── RemindAdminHandler.cs


dotnet new console -n WolverineConsoleDemo
cd WolverineConsoleDemo

dotnet add package WolverineFx


Step 2. Define Messages and Handlers
RegisterUser.cs
public record RegisterUser(string Email, string FullName);

UserRegistered.cs
public record UserRegistered(string Email);

RemindAdmin.cs
public record RemindAdmin(string Email);

Step 3. Implement Handlers
RegisterUserHandler.cs

using Wolverine;
using WolverineConsoleDemo.Messages;
namespace WolverineConsoleDemo.Handlers
{
    public class RegisterUserHandler
    {
        /// <summary>
        /// Handles the specified command.
        /// </summary>
        /// <param name="command">The command.</param>
        /// <param name="context">The context.</param>
        public static async Task Handle(RegisterUser command, IMessageContext context)
        {
            Console.ForegroundColor = ConsoleColor.Green;
            Console.WriteLine($"Registered: {command.FullName}");
            Console.ResetColor();
            await context.PublishAsync(new UserRegistered(command.Email));
            await context.ScheduleAsync(new RemindAdmin(command.Email), TimeSpan.FromSeconds(10));
        }
    }
}


UserRegisteredHandler.cs
using WolverineConsoleDemo.Messages;
namespace WolverineConsoleDemo.Handlers
{
    public class UserRegisteredHandler
    {
        /// <summary>
        /// Handles the specified event.
        /// </summary>
        /// <param name="event">The event.</param>
        /// <returns></returns>
        public static Task Handle(UserRegistered @event)
        {
            Console.ForegroundColor = ConsoleColor.Blue;
            Console.WriteLine($"Welcome email sent to {@event.Email}");
            Console.ResetColor();
            return Task.CompletedTask;
        }
    }
}


RemindAdminHandler.cs
using WolverineConsoleDemo.Messages;
namespace WolverineConsoleDemo.Handlers
{
    public class RemindAdminHandler
    {
        /// <summary>
        /// Handles the specified MSG.
        /// </summary>
        /// <param name="msg">The MSG.</param>
        /// <returns></returns>
        public static Task Handle(RemindAdmin msg)
        {
            Console.ForegroundColor = ConsoleColor.Yellow;
            Console.WriteLine($"Admin reminded for: {msg.Email}");
            Console.ResetColor();
            return Task.CompletedTask;
        }
    }
}

Step 4. Configure Wolverine in the Program.cs
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Wolverine;
using WolverineConsoleDemo.Messages;
var builder = Host.CreateApplicationBuilder(args);
// suppress hosting lifetime logs
builder.Logging.AddFilter("Microsoft.Hosting.Lifetime", LogLevel.None);
builder.Services.AddWolverine(opts =>
{
    opts.PublishMessage<RegisterUser>().ToLocalQueue("users");
    opts.PublishMessage<UserRegistered>().ToLocalQueue("emails");
    opts.PublishMessage<RemindAdmin>().ToLocalQueue("admin");
});
using var host = builder.Build();
// Start the host before using Wolverine
await host.StartAsync();
var bus = host.Services.GetRequiredService<IMessageBus>();
await bus.InvokeAsync(new RegisterUser("[email protected]", "Test User"));
Console.WriteLine("Press any key to exit...");
Console.ReadKey();
// Optional: gracefully shut down
await host.StopAsync();


Sample Console Output
This output demonstrates how Wolverine utilizes color-coded messages to distinguish each part of the registration process clearly.


Conclusion
Wolverine brings a fresh, powerful approach to building message-driven systems in .NET. With minimal setup and zero boilerplate, we’ve seen how easy it is to.

  • Create and handle commands.
  • Publish and respond to events.
  • Schedule background tasks.
  • Run everything in-process without external queues or services.

This lightweight console app demonstrates that you don’t need heavy infrastructure to adopt clean, decoupled architectures. Whether you’re prototyping a feature, building core business logic, or moving toward microservices, Wolverine gives you the right tools with a developer-friendly experience. If you're tired of wiring up multiple libraries to do what Wolverine does out of the box, give it a try in your next project. You might be surprised how much simpler your code becomes.

HostForLIFE ASP.NET Core 10.0 Hosting

European Best, cheap and reliable ASP.NET hosting with instant activation. HostForLIFE.eu is #1 Recommended Windows and ASP.NET hosting in European Continent. With 99.99% Uptime Guaranteed of Relibility, Stability and Performace. HostForLIFE.eu security team is constantly monitoring the entire network for unusual behaviour. We deliver hosting solution including Shared hosting, Cloud hosting, Reseller hosting, Dedicated Servers, and IT as Service for companies of all size.



European ASP.NET Core 10.0 Hosting - HostForLIFE :: ASP.NET Core Service Lifetimes: Transient, Scoped, and Singleton

clock June 26, 2025 07:56 by author Peter

Dependency Injection (DI) is a potent feature in ASP.NET Core that aids programmers in creating loosely linked, tested, and maintainable applications. Understanding how services are handled over time is a crucial component of this system, and this is where Service Lifetimes enter the picture.

A Service Lifetime determines how long an instance of a service will live once it's created by the built-in IoC (Inversion of Control) container.

When a service is registered in the Program.cs (or Startup.cs in older versions), we must specify its lifetime. This decision directly affects.

  • When the service instance is created
  • How long will it be reused
  • Whether it's shared across requests, users, or components

"How long will the instance of the service live?" and "When will a new instance be created?"

There are 3 built-in lifetimes.

Lifetime Description
Transient New instance every time it is requested.
Scoped One instance per HTTP request (or scope).
Singleton One instance for the entire lifetime of the application..

Before diving into service lifetimes, it's very important to understand the core concepts.

Before diving into service lifetimes, it's very important to understand the core concepts.

  • Dependency Injection (DI)
  • Inversion of Control (IoC)

What is Dependency Injection (DI)?
Dependency Injection is a design pattern used to provide an object’s dependencies from the outside, instead of creating them inside the object itself.
Dependency = Another class or object that your class needs to function.
Injection = Supplying those dependencies externally.

Imagine you're a chef who needs ingredients to cook.

  • Without DI, the chef goes out to the farm, harvests the vegetables, and then cooks.
  • With DI, someone delivers the fresh ingredients to the chef so they can focus on cooking.

DI = Someone gives you what you need. You don’t fetch it yourself.
Without DI (tight coupling)
public class Car
{
    private Engine _engine = new Engine();  // Car creates its own engine

    public void Drive()
    {
        _engine.Start();
    }
}


Problem

  • Hard to test
  • Can’t swap the Engine with the electric engine easily
  • Tight coupling

With DI (loose coupling)
public class Car
{
    private readonly IEngine _engine;

    public Car(IEngine engine)  // Engine is "injected" from outside
    {
        _engine = engine;
    }

    public void Drive()
    {
        _engine.Start();
    }
}


Now we can,

  • Inject any IEngine implementation
  • Test easily (mock the engine)
  • Change logic without touching the Car class

What is Inversion of Control (IoC)?
Inversion of Control is a broader principle. It means that the control of creating and managing objects is inverted — from the class to a container or framework. In simpler terms.
Instead of our code controlling how things are created, the framework (like ASP.NET Core) does it for us.

Example of IoC
We register services in a container.
services.AddScoped<ICarService, CarService>();

Then our controller.
public class HomeController : Controller
{
    private readonly ICarService _carService;

    public HomeController(ICarService carService) // Injected by framework
    {
        _carService = carService;
    }
}


We didn’t create CarService the IoC container did and gave it to us!

Relationship Between DI and IoC

Concept Meaning Who Manages Creation
IoC Inverting object creation/control Framework/Container
DI A way to implement IoC by injecting dependencies The framework injects what you need

So, Dependency Injection is a technique used to achieve Inversion of Control.

  • IoC: Framework creates and manages the object
  • DI: The Controller gets the service it needs without creating it

Why Are Service Lifetimes Important?
Choosing the correct service lifetime is essential for,

  • Memory management: Avoid unnecessary object creation
  • Performance optimization: Reuse objects when appropriate
  • Thread safety prevents state corruption in multi-user scenarios
  • Correct behavior, especially when services maintain data like a database context
  • Prevents data leaks by isolating instances.
  • Promotes cleaner architecture via proper dependency reuse.

Types of Service Lifetimes in .NET Core
.NET provides three main service lifetimes.

  • Transient: A new instance is created every time the service is requested.
  • Scoped: A single instance is created per HTTP request and shared within that request.
  • Singleton: A single instance is created once and shared across the entire application.

Choosing the correct lifetime ensures better performance, resource management, and application behavior.

Where Are Lifetimes Defined?
Lifetimes are defined when registering services in the Program.cs file.
builder.Services.AddTransient<IMyService, MyService>();   // Transient
builder.Services.AddScoped<IMyService, MyService>();      // Scoped
builder.Services.AddSingleton<IMyService, MyService>();   // Singleton

Service Lifetime Differences in ASP.NET Core

Feature / Criteria Transient Scoped Singleton
Object Creation Every time it is requested Once per HTTP request Once for the entire application
Shared Between Never shared Shared within one request Shared across all requests and components
Reuse Behavior New instance for every injection Same instance reused during one request Same instance reused everywhere
Lifetime Ends Immediately after use End of the HTTP request When the app shuts down
Thread-Safety Required No (short-lived) No (request-specific) Yes (used by multiple threads)
Testing Use Case Best for isolated tests Good for per-request logic Good for app-level state
Best For Lightweight, stateless services Entity Framework DbContext, per-user logic Logging, Caching, Configuration
Common Pitfall High object creation cost Cannot be injected into a Singleton Memory leaks or shared state issues

Best Practices for Choosing Service Lifetimes in .NET

Scenario Recommended Lifetime Real Example Explanation
Stateless utility like a formatter/calculator Transient PriceFormatterService Creates a new instance each time — ideal for small, stateless services like currency formatting or calculations.
Database access, user session services Scoped ApplicationDbContext, UserService Ensures consistency within a single HTTP request — needed for services handling DB transactions or per-user data.
Global logger, configuration, and caching Singleton LoggerService, ConfigProvider Created once and reused across the app — perfect for shared logic like logging or reading global settings.

What Happens If We Choose the Wrong Lifetime?

1. Scoped Service Injected into Singleton

  • Problem: Scoped services (like DbContext) are created per request. A Singleton lives for the entire app.
  • What Happens: .NET throws a runtime error InvalidOperationException.
  • Why: A Singleton can't depend on something that changes per request.

2. Storing Large Data in a Singleton

  • Problem: Singleton services stay alive for the app's entire lifetime.
  • What Happens: If you store large data (e.g., thousands of records), it causes memory leaks or slow performance.
  • Why: Data is never released from memory.

3. Using Transient for Expensive or Stateful Services

  • Problem: Transient creates a new instance every time.
  • What Happens: Extra memory usage, duplicate work (like database connections or API calls), and inconsistent behavior.
  • Why: Data/state is not shared between calls.

4. Expecting Transient or Scoped to Act Like Singleton

  • Problem: Assuming the service "remembers" data across requests or injections.ASP.NET Core Service Lifetimes: Transient, Scoped, and Singleton
  • What Happens: Data is lost or reset unexpectedly.
  • Why: Scoped lasts only one request, and Transient lasts only one use.

Conclusion
Understanding Service Lifetimes in .NET is essential for building efficient, scalable, and maintainable applications. By mastering Dependency Injection and properly choosing between Transient, Scoped, and Singleton lifetimes, you can control how services behave, improve app performance, and avoid bugs related to memory leaks or shared state. Always start with Transient unless your scenario calls for Scoped or Singleton. Use Scoped for request-based operations like database access, and use Singleton cautiously for shared logic like configuration or logging, while ensuring thread safety. With the right service lifetime choices and a solid understanding of DI and IoC, your ASP.NET Core applications will be more testable, modular, and cleanly architected.

HostForLIFE ASP.NET Core 10.0 Hosting

European Best, cheap and reliable ASP.NET hosting with instant activation. HostForLIFE.eu is #1 Recommended Windows and ASP.NET hosting in European Continent. With 99.99% Uptime Guaranteed of Relibility, Stability and Performace. HostForLIFE.eu security team is constantly monitoring the entire network for unusual behaviour. We deliver hosting solution including Shared hosting, Cloud hosting, Reseller hosting, Dedicated Servers, and IT as Service for companies of all size.



European ASP.NET Core 10.0 Hosting - HostForLIFE :: Monitoring of Entity Framework Changes

clock June 23, 2025 09:10 by author Peter

We shall study Entity Framework Change Tracking in this article. Now, let's get going.

Comprehending Change Tracking

EF Core uses a technology called change tracking to monitor modifications made to your entities. This aids EF Core in figuring out which SQL commands must be run in order to maintain database changes.

Example
using (var context = new AppDbContext())
  {
      var employee = context.Employees.FirstOrDefault(e => e.EmployeeId == 101);
      employee.City = "Mumbai";

      // EF Core tracks the change in the City property.
  }


Managing the State of Entities
EF Core tracks the state of each entity (Added, Unchanged, Modified, Deleted) to determine the operations to be performed during SaveChanges.
using (var context = new AppDbContext())
  {
      var employee = context.Employees.FirstOrDefault(e => e.EmployeeId == 101);
      context.Entry(employee).State = EntityState.Modified;
      context.SaveChanges();

      // The state is set to Modified, prompting and UPDATE command
  }

Using No-Tracking Queries
No-tracking queries improve performance for read-only operations by not tracking the entities in the context. This reduces memory usage and speeds up query execution.
using (var context = new AppDbContext())
  {
      var employees = context.Employees.AsNoTracking().ToList();

      // No change tracking is performed, ideal for read-only operations
  }


Benefits of No-Tracking Queries

  • Performance: Faster queries and lower memory footprint.
  • Scalability: Better for large-scale read operations.
  • Simplicity: Reduces overhead when changes to entities are not needed


using (var context = new AppDbContext())
  {
      var employees = context.Employees.AsNoTracking().FirstOrDefault(e => e.EmployeeId == 101);

      // Efficiently retrieves the post without tracking it's state
  }


Summary

  • Change Tracking: Tracks changes to determine necessary database operations.
  • Entity State Management: Manages entity states for accurate database updates.
  • No-Tracking Queries: Optimizes performance for read-only operations.

In this article, I have tried to cover how to handle Entity Framework Change Tracking.

HostForLIFE ASP.NET Core 10.0 Hosting

European Best, cheap and reliable ASP.NET hosting with instant activation. HostForLIFE.eu is #1 Recommended Windows and ASP.NET hosting in European Continent. With 99.99% Uptime Guaranteed of Relibility, Stability and Performace. HostForLIFE.eu security team is constantly monitoring the entire network for unusual behaviour. We deliver hosting solution including Shared hosting, Cloud hosting, Reseller hosting, Dedicated Servers, and IT as Service for companies of all size.



European ASP.NET Core 10.0 Hosting - HostForLIFE :: LINQ's FirstOrDefault and SingleOrDefault: Important Distinctions and Applications

clock June 17, 2025 08:06 by author Peter

In this article, we explore the key differences between FirstOrDefault and SingleOrDefault in LINQ. You'll learn how each method behaves, the scenarios where they are best applied, and when to choose one over the other. By the end, you’ll gain clarity on how to use them effectively to write cleaner and more efficient LINQ queries in your C# applications. Understanding the correct usage of these methods helps you avoid common runtime errors, improve application performance, and maintain predictable code behavior. Always perform null checks when working with reference types, and use SingleOrDefault() cautiously in performance-critical sections of your code.

In this short and simple guide, we’ll explain the key differences, show you when to use each one, and help you avoid common mistakes in real-life coding situations.

What is FirstOrDefault()?
FirstOrDefault() returns the first element of a sequence or the default value (e.g., null for reference types) if no element is found.

Use Cases

  • When multiple results are expected, but you want just the first match.
  • When the sequence can be empty.
  • When performance is a concern and early exit is ideal.

Code Example
var firstProduct = products.FirstOrDefault(p => p.Price > 100);

What is SingleOrDefault()?
SingleOrDefault() returns exactly one element that matches the condition or the default value if no match is found. It throws an exception if more than one element is found.
var uniqueProduct = products.SingleOrDefault(p => p.Id == 101);

Use Cases

  • When you expect only one matching result.
  • Ideal for unique identifiers, like primary keys.
  • Use it when data integrity is important and duplicates are unexpected.

FirstOrDefault vs. SingleOrDefault

Feature FirstOrDefault SingleOrDefault
Returns First match or default Single match or default
Throws if multiple matches? ❌ No ✅ Yes
Use case First matching item Exactly one expected match
Performance Stops at the first match Scans all items to validate uniqueness


Performance Considerations

  • FirstOrDefault() stops evaluating after the first match — faster on large datasets.
  • SingleOrDefault() must scan the entire sequence to ensure there's only one match — slower if the collection is large.

Best Practices

  • Use SingleOrDefault(): only when data constraints guarantee that at most one element can satisfy the condition.
  • Prefer FirstOrDefault(): When multiple matches are possible and you only need the first occurrence, or can handle a missing result.
  • Always perform a null check: after using either method when working with reference types to prevent NullReferenceException.
  • Minimize the use of SingleOrDefault(): In performance-sensitive operations, it must evaluate the entire collection to ensure uniqueness.

Summary
In LINQ, both FirstOrDefault() and SingleOrDefault() are used to retrieve elements from a collection, but they serve different purposes. FirstOrDefault() returns the first matching element or a default value if none is found—ideal when multiple matches are possible. On the other hand, SingleOrDefault() ensures only one match exists and throws an exception if multiple results are found, making it suitable for unique identifiers or strict data constraints.

HostForLIFE ASP.NET Core 10.0 Hosting

European Best, cheap and reliable ASP.NET hosting with instant activation. HostForLIFE.eu is #1 Recommended Windows and ASP.NET hosting in European Continent. With 99.99% Uptime Guaranteed of Relibility, Stability and Performace. HostForLIFE.eu security team is constantly monitoring the entire network for unusual behaviour. We deliver hosting solution including Shared hosting, Cloud hosting, Reseller hosting, Dedicated Servers, and IT as Service for companies of all size.



European ASP.NET Core 10.0 Hosting - HostForLIFE :: How to Build ASP.NET Core Background Tasks?

clock June 10, 2025 08:23 by author Peter

A crucial prerequisite for contemporary ASP.NET Core applications is background processing, particularly for those that use microservices. Background services are crucial for tasks like email sending, Kafka message processing, cron-type processes, and managing lengthy workflows.

With the introduction of the Generic Host and robust improvements in .NET 6, 7, and 8, ASP.NET Core now provides powerful patterns through:

  • IHostedService
  • BackgroundService
  • Worker Service templates

This article dives deep into how to design, build, and optimize background services for production.

Worker Services vs Hosted Services: What’s the Difference?

Feature IHostedService BackgroundService Worker Service Template
Interface-based Yes Yes (abstracts IHostedService) Yes (project template-based)
Long-running support Needs manual implementation Provides an infinite loop (ExecuteAsync) Yes
Best for Short-lived or event-based tasks Long-running background jobs Standalone background apps

Setting Up a Worker Service Project

You can create a worker service using.
dotnet new worker -n EmailBackgroundService

This creates a ready-to-run worker template using the Generic Host.

The Program.cs in .NET 8+ is minimal.
var host = Host.CreateDefaultBuilder(args)
    .ConfigureServices((context, services) =>
    {
        services.AddHostedService<EmailSenderWorker>();
    })
    .Build();

await host.RunAsync();


Creating a Custom BackgroundService
Here’s a simple example of a background worker.
public class EmailSenderWorker : BackgroundService
{
    private readonly ILogger<EmailSenderWorker> _logger;

    public EmailSenderWorker(ILogger<EmailSenderWorker> logger)
    {
        _logger = logger;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            _logger.LogInformation("Sending queued emails at: {time}", DateTimeOffset.Now);
            await SendPendingEmails();
            await Task.Delay(TimeSpan.FromMinutes(5), stoppingToken);
        }
    }

    private Task SendPendingEmails()
    {
        // Implement actual email logic here
        return Task.CompletedTask;
    }
}

Advanced Hosted Services with IHostedService
When you need fine-grained control over start/stop behavior (e.g., for message queue consumers).
public class KafkaConsumerService : IHostedService
{
    public Task StartAsync(CancellationToken cancellationToken)
    {
        // Connect to Kafka and begin consuming
        return Task.Run(() => ConsumeMessages(cancellationToken), cancellationToken);
    }

    public Task StopAsync(CancellationToken cancellationToken)
    {
        // Cleanup, disconnect
        return Task.CompletedTask;
    }

    private void ConsumeMessages(CancellationToken cancellationToken)
    {
        // Poll Kafka and process messages
    }
}


Managing Multiple Background Services

You can register multiple services easily.
services.AddHostedService<EmailSenderWorker>();
services.AddHostedService<KafkaConsumerService>();
services.AddHostedService<DataSyncWorker>();


Each runs independently inside the host.

Handling Exceptions & Graceful Shutdown

To avoid crashing the entire app due to unhandled exceptions in workers.
try
{
    await SomeBackgroundTask();
}
catch (Exception ex)
{
    _logger.LogError(ex, "An error occurred.");
}


For graceful shutdown, ensure your workers respond to the CancellationToken from ExecuteAsync.

Dependency Injection in Background Services
Services are injected normally.
public class DataSyncWorker : BackgroundService
{
    private readonly IDataService _dataService;

    public DataSyncWorker(IDataService dataService)
    {
        _dataService = dataService;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        await _dataService.SyncExternalData();
    }
}

Scheduling and Cron-like Jobs
For scheduled tasks, consider:

  • Quartz.NET for CRON jobs.
  • Hangfire for background jobs with retry support.
  • Or a Timer-based service with Task.Delay.

Example using the Cronos package.
var cron = CronExpression.Parse("0 */5 * * * *");
var next = cron.GetNextOccurrence(DateTimeOffset.Now);

Logging, Metrics & Observability
Integrate with:

  • Serilog / Seq for structured logging
  • Prometheus for metrics
  • OpenTelemetry for traces and spans

Production Tips
Always handle exceptions gracefully

  • Use scoped services inside BackgroundService via IServiceScopeFactory
  • Monitor using health checks (IHealthCheck)
  • Separate retry logic (e.g., use Polly)
  • Set max concurrency (e.g., in queues) to prevent overload

Conclusion
ASP.NET Core’s Hosted Services and Worker Services provide powerful primitives for executing background tasks in a clean, reliable, and scalable way. Whether you're building message consumers, email dispatchers, or data processors, background services allow you to decouple, scale, and manage your application’s background logic like a pro. Happy coding!

HostForLIFE ASP.NET Core 10.0 Hosting

European Best, cheap and reliable ASP.NET hosting with instant activation. HostForLIFE.eu is #1 Recommended Windows and ASP.NET hosting in European Continent. With 99.99% Uptime Guaranteed of Relibility, Stability and Performace. HostForLIFE.eu security team is constantly monitoring the entire network for unusual behaviour. We deliver hosting solution including Shared hosting, Cloud hosting, Reseller hosting, Dedicated Servers, and IT as Service for companies of all size.



About HostForLIFE

HostForLIFE is European Windows Hosting Provider which focuses on Windows Platform only. We deliver on-demand hosting solutions including Shared hosting, Reseller Hosting, Cloud Hosting, Dedicated Servers, and IT as a Service for companies of all sizes.

We have offered the latest Windows 2019 Hosting, ASP.NET 5 Hosting, ASP.NET MVC 6 Hosting and SQL 2019 Hosting.


Month List

Tag cloud

Sign in