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 :: 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 9.0 Hosting - HostForLIFE :: Using Switch-Case to Dynamically Load Several ASCX Pages into an ASPX Page

clock October 14, 2025 07:21 by author Peter

Pages in ASP.NET Web Forms frequently have to dynamically display various user controls (.ascx) in response to a condition (such as a query string parameter or menu selection). Using LoadControl and a switch-case structure, you may load each control dynamically at runtime rather than generating distinct pages for each control.

Scenario
Suppose you have multiple user controls:

  • Dashboard.ascx
  • NewClient.ascx
  • AddNewClient.ascx
  • masterList.ascx
  • …and many others

You want to load these controls into a placeholder ( ControlHolder ) in your Default.aspx page based on a numeric or string parameter.

Implementation Steps

1. Add a Placeholder in ASPX Page
In your ASPX page ( Default.aspx ), add a PlaceHolder control where dynamic controls will be loaded:
<asp:PlaceHolder ID="ControlHolder" runat="server"></asp:PlaceHolder>

This placeholder acts as a container for your user controls.

2. Load Controls Dynamically in Code-Behind
In your code-behind ( Default.aspx.cs ), you can use a switch-case statement to determine which .ascx control to load:
protected void Page_Load(object sender, EventArgs e)
{
    if (!IsPostBack)
    {
        string pageID = Request.QueryString["PageID"] != null ? Request.QueryString["PageID"].ToString() : ""; // Example: get value from URL ?PageID=1
        LoadUserControl(pageID);
    }
}


private void LoadUserControl(string pageID)
{
    switch (pageID)
    {
        case "1":
            Dashboard dashboared_ctrl = (Dashboard)LoadControl("Dashboard.ascx");
            ControlHolder.Controls.Add(dashboared_ctrl);
            break;

        case "2":
            NewClientmaster newclient_ctrl = (NewClientmaster)LoadControl("NewClient.ascx");
            ControlHolder.Controls.Add(newclient_ctrl);
            break;

        case "3":
            AddNewClientList addnewclient = (AddNewClientList)LoadControl("AddNewClient.ascx");
            ControlHolder.Controls.Add(addnewclient);
            break;

        case "4":
            UCCmasterList uccmaster_ctrl = (UCCmasterList)LoadControl("masterList.ascx");
            ControlHolder.Controls.Add(uccmaster_ctrl);
            break;

        case "5":
            AddClientList addclient_ctrl = (AddClientList)LoadControl("AddClientList.ascx");
            ControlHolder.Controls.Add(addclient_ctrl);
            break;

        case "6":
            ViewIPOMaster viewipo_master = (ViewIPOMaster)LoadControl("ViewMaster.ascx");
            ControlHolder.Controls.Add(viewipo_master);
            break;

        case "7":
            AddIPOMaster addipomastr = (AddIPOMaster)LoadControl("AddMaster.ascx");
            ControlHolder.Controls.Add(addipomastr);
            break;

        case "8":
            AddIPO addipo = (AddIPO)LoadControl("AddIPO.ascx");
            ControlHolder.Controls.Add(addipo);
            break;

        case "9":
            UpdateExchangeIPO updateEx = (UpdateExchangeIPO)LoadControl("Update.ascx");
            ControlHolder.Controls.Add(updateEx);
            break;

        case "10":
            OrderList orderlist_ctrl = (OrderList)LoadControl("OrderList.ascx");
            ControlHolder.Controls.Add(orderlist_ctrl);
            break;

        case "11":
            UPIReport upireport_ctrl = (UPIReport)LoadControl("Report.ascx");
            ControlHolder.Controls.Add(upireport_ctrl);
            break;

        case "12":
            IPOSettingsOffline offlinesetting_ctrl = (IPOSettingsOffline)LoadControl("Settings.ascx");
            ControlHolder.Controls.Add(offlinesetting_ctrl);
            break;

        case "13":
            OfflineOrders offlinelist_ctrl = (OfflineOrders)LoadControl("Orders.ascx");
            ControlHolder.Controls.Add(offlinelist_ctrl);
            break;

        case "14":
            IPOAllotment ipoallotment_ctrl = (IPOAllotment)LoadControl("Allotment.ascx");
            ControlHolder.Controls.Add(ipoallotment_ctrl);
            break;

        case "15":
            ExchangePage ExchangePage_ctrl = (ExchangePage)LoadControl("ExchangePage.ascx");
            ControlHolder.Controls.Add(ExchangePage_ctrl);
            break;

        case "16":
            ViewIssue ViewIssue_ctrl = (ViewIssue)LoadControl("ViewIssue.ascx");
            ControlHolder.Controls.Add(ViewIssue_ctrl);
            break;

        case "17":
            ViewIPO ViewIPO_ctrl = (ViewIPO)LoadControl("ViewIPO.ascx");
            ControlHolder.Controls.Add(ViewIPO_ctrl);
            break;

        default:
            // Optionally show a default control or message
            ControlHolder.Controls.Add(new LiteralControl("<p>Select a valid option</p>"));
            break;
    }
}


3. How It Works

  • Get the Page Identifier: You can use QueryString , a DropDownList , or any parameter to determine which control to load.
  • Switch-Case Logic: Each case corresponds to a specific .ascx control. The control is loaded dynamically and added to the placeholder.
  • Type Casting: (Dashboard)LoadControl("Dashboard.ascx") casts the loaded control to the correct class so you can access its public properties or methods.
  • Adding to Placeholder: ControlHolder.Controls.Add(dashboared_ctrl) adds the control to the page dynamically at runtime.

4. Advantages of this Approach

  • Single ASPX page for multiple views: Reduces code duplication.
  • Dynamic content loading: Only loads the control needed based on user interaction.
  • Maintainability: Adding new controls is simple: just add a new case in the switch.
  • Encapsulation: Each user control handles its own UI and logic.

5. Optional Enhancements

  • Use QueryString or Session for persistence.
  • Default control: Show a welcome dashboard if no PageID is provided.
  • Error handling: Wrap LoadControl in try-catch to prevent runtime errors.
  • Refactor using Dictionary : For large numbers of controls, you can use a dictionary mapping instead of long switch-case.

Example URL to Load Controls
Default.aspx?PageID=1   -> Loads Dashboard.ascx
Default.aspx?PageID=2   -> Loads NewClientmaster.ascx
Default.aspx?PageID=10  -> Loads OrderList.ascx


Conclusion
An effective technique to dynamically load several user controls into a single ASPX page is to utilize switch-case with LoadControl in ASP.NET Web Forms. This makes your program modular and maintainable.

HostForLIFE ASP.NET Core 9.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 9.0 Hosting - HostForLIFE :: In.NET 8, Dapper and Entity Framework Core (EF Core) work together to combat PostgreSQL

clock October 7, 2025 08:38 by author Peter

I’ll show two approaches you can choose from:

  • Approach A (recommended for most reads/writes): Use EF Core for schema, migrations, and most CRUD; use Dapper (Npgsql) for fast raw/complex queries. (Dapper uses its own IDbConnection.)
  • Approach B (when you need a single transaction across EF + Dapper): Reuse the DbConnection /transaction inside DbContext so Dapper executes on the same connection/transaction as EF Core.

Key libraries used: Dapper , Npgsql (Postgres ADO.NET driver), and Npgsql.EntityFrameworkCore.PostgreSQL (EF Core provider).

1. Create project & add packages

dotnet new webapi -n DapperEfPgDemo
cd DapperEfPgDemo

dotnet add package Dapper
dotnet add package Npgsql
dotnet add package Npgsql.EntityFrameworkCore.PostgreSQL
dotnet add package Microsoft.EntityFrameworkCore.Design

(Install dotnet-ef tool if you need CLI migrations: dotnet tool install --global dotnet-ef .)

2. Connection string (appsettings.json)

{
  "ConnectionStrings": {
    "DefaultConnection": "Host=localhost;Port=5432;Database=dapperefpg;Username=postgres;Password=your_password"
  }
}

3. POCO models
Models/Product.cs
public class Product
{
    public int Id { get; set; }
    public string Name { get; set; } = null!;
    public decimal Price { get; set; }
    public int CategoryId { get; set; }
}

Models/Category.cs
public class Category
{
    public int Id { get; set; }
    public string Name { get; set; } = null!;
}

4. EF Core DbContext
Data/AppDbContext.cs
using Microsoft.EntityFrameworkCore;

public class AppDbContext : DbContext
{
    public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) {}

    public DbSet<Product> Products { get; set; } = null!;
    public DbSet<Category> Categories { get; set; } = null!;
}

Register DbContext in Program.cs (below).

5. Program.cs (DI & EF + optional Dapper factory)
Program.cs (minimal)
using Npgsql;
using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);

var conn = builder.Configuration.GetConnectionString("DefaultConnection");

// EF Core
builder.Services.AddDbContext<AppDbContext>(options =>
    options.UseNpgsql(conn)
);

// OPTIONAL: register an IDbConnection factory for Dapper (Approach A)
builder.Services.AddScoped<System.Data.IDbConnection>(_ => new NpgsqlConnection(conn));

// register repositories, controllers, swagger...
builder.Services.AddScoped<IProductRepository, ProductRepository>();

builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();
app.UseSwagger();
app.UseSwaggerUI();
app.MapControllers();
app.Run();

You can either inject IDbConnection (a new NpgsqlConnection per scope) for Dapper, or use the DbContext ’s connection (shown below) when you need transaction sharing. npgsql.org

6. Create the DB (migrations)
dotnet ef migrations add InitialCreate
dotnet ef database update

(Use EF migrations for schema management — easier than hand-written SQL for most workflows.) npgsql.org

7. Repositories — two patterns
Pattern A — Dapper using its own IDbConnection (no shared transaction)

Good when you only need quick reads/writes and don't need EF & Dapper to share a single transaction.

Repositories/ProductDapperRepository.cs
using Dapper;
using System.Data;

public class ProductDapperRepository : IProductRepository
{
    private readonly IDbConnection _db; // injected NpgsqlConnection (scoped)

    public ProductDapperRepository(IDbConnection db) => _db = db;

    public async Task<IEnumerable<Product>> GetAllAsync()
    {
        var sql = "SELECT id AS Id, name AS Name, price AS Price, categoryid AS CategoryId FROM products";
        if (_db.State != ConnectionState.Open) await _db.OpenAsync();
        return await _db.QueryAsync<Product>(sql);
    }

    public async Task<Product?> GetByIdAsync(int id)
    {
        var sql = "SELECT id, name, price, categoryid FROM products WHERE id = @Id";
        if (_db.State != ConnectionState.Open) await _db.OpenAsync();
        return await _db.QueryFirstOrDefaultAsync<Product>(sql, new { Id = id });
    }

    // Create/Update/Delete can use ExecuteAsync / ExecuteScalarAsync etc.
}


This is the simplest pattern and performs well for read-heavy, SQL-optimized queries.

  • Pattern B — Use EF DbContext's connection so EF & Dapper share the same connection/transaction
  • Use this when you need both EF and Dapper operations in the same transaction (commit/rollback together).

Repositories/ProductCombinedRepository.cs
using Dapper;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Storage;
using System.Data;

public class ProductCombinedRepository
{
    private readonly AppDbContext _db;

    public ProductCombinedRepository(AppDbContext db) => _db = db;

    // Dapper read using DbContext's connection (don't dispose the connection EF owns)
    public async Task<IEnumerable<Product>> GetAllWithDapperAsync()
    {
        var conn = _db.Database.GetDbConnection(); // returns DbConnection
        if (conn.State != ConnectionState.Open) await conn.OpenAsync();

        var sql = "SELECT id AS Id, name AS Name, price AS Price, categoryid AS CategoryId FROM products";
        // Dapper's QueryAsync accepts DbConnection since it implements IDbConnection
        return await conn.QueryAsync<Product>(sql);
    }

    // Example: an operation that uses Dapper + EF in one transaction
    public async Task<int> CreateProductAndLogAsync(Product product, string auditMessage)
    {
        // Start EF transaction
        await using var efTx = await _db.Database.BeginTransactionAsync();
        try
        {
            // 1) run a Dapper insert on the same connection/transaction
            var conn = _db.Database.GetDbConnection();
            if (conn.State != ConnectionState.Open) await conn.OpenAsync();

            var insertSql = @"
                INSERT INTO products (name, price, categoryid)
                VALUES (@Name, @Price, @CategoryId)
                RETURNING id;
            ";

            // Get underlying ADO.NET transaction to pass to Dapper
            var dbTransaction = efTx.GetDbTransaction(); // IDbTransaction
            var newId = await conn.ExecuteScalarAsync<int>(insertSql, product, transaction: dbTransaction);

            // 2) use EF Core to write to some tracked audit entity (or any other EF operation)
            var audit = new AuditLog { Message = auditMessage, ProductId = newId, CreatedAt = DateTime.UtcNow };
            _db.Add(audit);
            await _db.SaveChangesAsync();

            await efTx.CommitAsync();
            return newId;
        }
        catch
        {
            await efTx.RollbackAsync();
            throw;
        }
    }
}


Notes
Database.GetDbConnection() returns the DbConnection currently used by EF — do not dispose it if EF created it. Open it if closed.

BeginTransactionAsync() returns an IDbContextTransaction ; you can call .GetDbTransaction() to retrieve the underlying ADO.NET DbTransaction and pass it to Dapper. This is how you make Dapper run in the same DB transaction as EF.

8. Example controller (using combined repo)
Controllers/ProductsController.cs
[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
    private readonly ProductCombinedRepository _repo;

    public ProductsController(ProductCombinedRepository repo) => _repo = repo;

    [HttpGet]
    public async Task<IActionResult> GetAll() => Ok(await _repo.GetAllWithDapperAsync());

    [HttpPost]
    public async Task<IActionResult> Create([FromBody] Product p)
    {
        var id = await _repo.CreateProductAndLogAsync(p, $"Created via API at {DateTime.UtcNow:O}");
        return CreatedAtAction(nameof(GetAll), new { id }, new { Id = id });
    }
}


9. Important best-practices & gotchas

  • Use EF Core for schema/migrations and domain mapping; use Dapper for raw/complex queries or performance hotspots. This combination is common in production.
  • If you obtain the connection from DbContext.Database.GetDbConnection() do NOT dispose it (EF owns it). Just open it when needed.
  • To share a transaction: call BeginTransaction() / BeginTransactionAsync() on the DbContext , get the DbTransaction via GetDbTransaction() , and pass it to your Dapper calls (via the transaction: parameter). That keeps EF & Dapper in the same DB transaction.
  • Parameterize SQL — Dapper supports anonymous parameter objects (always avoid string concatenation to prevent SQL injection).
  • Unit testing: EF InMemory provider doesn't run raw SQL — tests that use Dapper need a real Postgres (or testcontainer).Connection pooling: Npgsql pools connections; opening/closing is cheap. Prefer short-lived commands or let EF manage the connection lifetime when sharing.

10. Quick checklist / commands

  • Add packages: Dapper , Npgsql , Npgsql.EntityFrameworkCore.PostgreSQL , Microsoft.EntityFrameworkCore.Design .
  • Add AppDbContext and model DbSet s.
  • builder.Services.AddDbContext<AppDbContext>(options => options.UseNpgsql(conn));
  • Optionally register IDbConnection factory ( new NpgsqlConnection(conn) ) if you prefer separate Dapper connections.
  • dotnet ef migrations add InitialCreate → dotnet ef database update .

References/reading

  • Dapper (NuGet / repo).
  • Npgsql EF Core provider & docs.
  • EF Core transactions / using external DbTransaction (how to share a transaction with ADO.NET ).
  • Practical tutorial: Using EF Core + Dapper together (example & transaction patterns).
  • Note on GetDbConnection() disposal rules.

HostForLIFE ASP.NET Core 9.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 9.0 Hosting - HostForLIFE :: Understanding Filters in Minimal API with .NET 9.0

clock October 2, 2025 08:14 by author Peter

Minimal APIs gained popularity right away when they were released in.NET 6 because they made web services speedy and light.  Nevertheless, previous iterations lacked a suitable method for managing routine tasks like as permission, logging, and validation without having to repeat the same code. This is where Endpoint Filters come in; they were first introduced in.NET 7, got better in.NET 8, and are now much better in.NET 9, offering more control over the request pipeline, grouping options, and ease of use.

This article will examine filters in Minimal APIs with.NET 9 and show you how to utilize them efficiently while adhering to current best practices.

What is New in .NET 9 for Filters?
The core API remains the same, but .NET 9 brings several improvements that make filters easier and more powerful to use:

  • Better dependency injection (DI) and setup for IEndpointFilterFactory
  • Support for filter composition and grouping
  • Smarter type inference for inline filters
  • More consistent behavior between MapGroup and individual endpoints

If you have used filters in .NET 7 or 8, they’ll still work — but in .NET 9, you’ll enjoy cleaner code, stronger typing, and simpler configuration.

What Are Filters?

Filters let you add custom logic into the request pipeline of a Minimal API endpoint — both before and after the handler runs.
You can think of them as lightweight middleware but applied only to specific endpoints or endpoint groups.

They’re great for handling common tasks like:

  • Validation
  • Authentication / Authorization
  • Logging / Metrics
  • Exception Handling
  • Response Wrapping
  • The source code can be downloaded from GitHub.


The tools that I have used here

1. .NET 9.0
2. Visual Studio 2026 Insider


Defining Filters in .NET 9
You can create filters in two main ways:

  • Inline Filters: Write quick, simple logic directly next to the endpoint.
  • Reusable Filters: Create separate classes that implement IEndpointFilter for cleaner, reusable code.

Let us look at both.

Inline Filters
The simplest way to add a filter is inline using AddEndpointFilter

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.MapGet("/inlinefilters", (string name) => $"Hello, {name}!")
   .AddEndpointFilter(async (context, next) =>
   {
       Console.WriteLine("Before endpoint logic");

       var result = await next(context);

       Console.WriteLine("After endpoint logic");
       return result;
   });
app.Run();

How It Works

  • You receive a context containing arguments, HttpContext, and more.
  • You can run code before or after calling next(context).
  • You can modify the result or even stop the request early.

Reusable Filters (IEndpointFilter)
When logic needs to be reused or tested, define a class-based filter:
public class LoggingFilter : IEndpointFilter
{
    public async ValueTask<object?> InvokeAsync(
        EndpointFilterInvocationContext context,
        EndpointFilterDelegate next)
    {
        Console.WriteLine($"Incoming: {context.HttpContext.Request.Path}");

        var result = await next(context);

        Console.WriteLine($"Completed: {context.HttpContext.Response.StatusCode}");

        return result;

    }
}


Attach to the endpoint as below:
// Using a reusable filter
app.MapGet("/reusefilter", (string name) => $"Hello, {name}!")
   .AddEndpointFilter<WebApplication1.Filters.LoggingFilter>();

Practical Use Cases
Here are some real-world examples of how filters can be used in .NET 9 projects:

Validation Filter
Perform model validation before hitting your endpoint logic.
public class ValidationFilter<UserDto> : IEndpointFilter
{
    public async ValueTask<object?> InvokeAsync(
        EndpointFilterInvocationContext context,
        EndpointFilterDelegate next)
    {
        var model = context.Arguments.OfType<UserDto>().FirstOrDefault();
        if (model == null)
        {
            return Results.BadRequest("Invalid request payload.");
        }

        // Use reflection to check for UserName, Name, and Age properties
        var type = typeof(UserDto);
        var nameProp = type.GetProperty("Name");
        var ageProp = type.GetProperty("Age");

        var name = nameProp?.GetValue(model) as string;
        var ageValue = ageProp?.GetValue(model);
        int age = 0;
        if (ageValue != null && int.TryParse(ageValue.ToString(), out var parsedAge))
        {
            age = parsedAge;
        }

        if (string.IsNullOrWhiteSpace(name) || age <= 0)
        {
            return Results.BadRequest("Invalid request payload. Name and Age are required and Age must be greater than 0.");
        }

        var validationResults = new List<ValidationResult>();
        bool isValid = Validator.TryValidateObject(
            model, new ValidationContext(model), validationResults, true);

        if (!isValid)
        {
            var errors = validationResults.ToDictionary(
                r => r.MemberNames.FirstOrDefault() ?? "Unknown",
                r => new[] { r.ErrorMessage ?? "Validation error" });

            return Results.ValidationProblem(errors);
        }

        // Await the next delegate in the pipeline
        return await next(context);
    }
}


Attach to endpoint as below
// Using a validation filter
app.MapPost("/validationfilter", (WebApplication1.Models.UserDto user) => Results.Ok($"User {user.Name} of age {user.Age} created."))
   .AddEndpointFilter<ValidationFilter<WebApplication1.Models.UserDto>>();


Authorization Filter
Implement role-based access without repeating logic.
public class RoleFilter : IEndpointFilter
 {
     private readonly string _role;
     public RoleFilter(string role) => _role = role;

     public async ValueTask<object?> InvokeAsync(
         EndpointFilterInvocationContext context,
         EndpointFilterDelegate next)
     {
         var user = context.HttpContext.User;

         if (!user.Identity?.IsAuthenticated ?? false)
             return Results.Unauthorized();

         if (!user.IsInRole(_role))
             return Results.Forbid();

         return await next(context);
     }
 }


Attach to the endpoint:
//using role based filter
app.MapGet("/rolefilter", () => "Welcome, Admin")
   .AddEndpointFilterFactory((factoryContext, next) =>
       (context) =>
       {
           var filter = new RoleFilter("Admin");
           return filter.InvokeAsync(context, next);
       });


Exception Handler Filter
Centralize error handling at the endpoint or group level.
public class ExceptionFilter : IEndpointFilter
{
    public async ValueTask<object?> InvokeAsync(
        EndpointFilterInvocationContext context,
        EndpointFilterDelegate next)
    {
        try
        {
            return await next(context);
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Exception: {ex.Message}");
            return Results.Problem("Something went wrong, please try again.");
        }
    }
}


Apply at a group level in .NET 9:
public class ExceptionFilter : IEndpointFilter
{
    public async ValueTask<object?> InvokeAsync(
        EndpointFilterInvocationContext context,
        EndpointFilterDelegate next)
    {
        try
        {
            return await next(context);
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Exception: {ex.Message}");
            return Results.Problem("Something went wrong, please try again.");
        }
    }
}


Attach to the endpoint:
// Using a global exception handling filter
var api = app.MapGroup("/api")
      .AddEndpointFilter<ExceptionFilter>();

api.MapGet("/exceptionfilter", () => { throw new Exception("Test exception"); });


Conclusion
.NET 9 filters Common activities like validation, logging, and error handling can be easily handled in one location with minimal APIs. They guarantee that shared logic remains constant throughout your application while keeping your endpoint code clear and concise. For greater control, you can apply filters to particular endpoints or groups without introducing the intricacy of global middleware. Filters aid in maintaining readable, well-structured, and reusable code as your application expands. To put it briefly, they're an excellent method for creating dependable, clear, and stable minimal APIs. Happy Coding!

HostForLIFE ASP.NET Core 9.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