Scenario bases Question
- Get link
- X
- Other Apps
For this scenario, Middleware is the correct choice — not an Action Filter.
Why? Because the requirement is:
- Inspect every incoming HTTP request
- Check for a custom header
- Short-circuit the pipeline early
- Redirect before MVC/controllers execute
That is exactly what ASP.NET Core middleware is designed for.
Why Middleware is Better Than Action Filters
Middleware Runs Earlier in the Pipeline
Middleware executes before the request reaches MVC.
Pipeline flow:
Request
↓
Middleware
↓
Routing
↓
MVC / Controllers
↓
Action Filters
↓
Controller Action
So if the header is missing:
- Middleware can stop processing immediately
- No controller instantiation
- No model binding
- No action filters
- Better performance
Action Filters Are Too Late
Action Filters only execute:
- After routing
- Inside MVC
- Before/after controller actions
Meaning:
- The request has already entered MVC
- Resources have already been allocated
- Controllers are already involved
That violates the requirement:
“without hitting the controller”
So Action Filters are not ideal for this type of global low-level request validation.
Correct Solution Using Middleware
Step 1 — Create Custom Middleware
public class HeaderValidationMiddleware
{
private readonly RequestDelegate _next;
public HeaderValidationMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context)
{
// Check for custom header
if (!context.Request.Headers.ContainsKey("X-App-Maintenance"))
{
// Redirect immediately
context.Response.Redirect("/maintenance");
return; // Stop pipeline execution
}
// Continue to next middleware
await _next(context);
}
}
Step 2 — Register Middleware
In Program.cs:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.UseMiddleware<HeaderValidationMiddleware>();
app.MapControllers();
app.Run();
What Happens Internally
When a request arrives:
- Middleware executes first
- Header is checked
-
If missing:
- Response redirects
- Pipeline stops
- Controller is never reached
This is called short-circuiting the request pipeline.
Why This Is Architecturally Correct
Middleware is ideal for:
- Authentication
- Logging
- CORS
- Rate limiting
- Request validation
- Global request interception
- Maintenance mode
- Header inspection
These are all cross-cutting concerns.
Action Filters are better for:
- MVC-specific logic
- Model validation
- Action execution timing
- Result modification
- Authorization tied to controllers/actions
Middleware vs Action Filter Comparison
| Feature | Middleware | Action Filter |
|---|---|---|
| Runs before MVC | ✅ Yes | ❌ No |
| Can stop request early | ✅ Yes | ⚠️ Limited |
| Works globally | ✅ Excellent | ⚠️ MVC only |
| Access to raw HTTP pipeline | ✅ Full | ❌ Partial |
| Performance efficient | ✅ Better | ❌ More overhead |
| Controller-independent | ✅ Yes | ❌ No |
| Ideal for header validation | ✅ Perfect | ❌ Not ideal |
For this scenario, the key is understanding:
- How often the data changes
- Whether the service holds state
- Avoiding lifetime mismatches (captive dependencies)
Scenario Breakdown
You have:
PriceCalculator
↓ depends on
CurrencyExchange
↓ calls external API
Exchange rates change every minute
The important detail is:
Exchange rate data is dynamic and frequently updated.
That strongly affects the DI lifetime choice.
Recommended Lifetimes
| Service | Recommended Lifetime |
|---|---|
| PriceCalculator | Transient or Scoped |
| CurrencyExchange | Scoped or Transient |
Best Practical Choice
CurrencyExchange → Scoped
services.AddScoped<ICurrencyExchange, CurrencyExchange>();
Why Scoped?
A scoped service:
- Lives for a single HTTP request
- Avoids repeatedly calling the external API within the same request
- Still refreshes data on the next request
- Prevents stale singleton data
This is usually the best balance.
PriceCalculator → Transient
services.AddTransient<IPriceCalculator, PriceCalculator>();
Why Transient?
PriceCalculator:
- Usually contains no state
- Just performs calculations
- Lightweight business logic service
Transient is ideal because:
- Fresh instance each time
- No shared mutable state
- Cheap to create
Why NOT Singleton for CurrencyExchange?
A Singleton would:
- Be created once for the entire application lifetime
- Potentially cache stale exchange rates forever
- Share outdated data across all users
Example problem:
App starts at 9:00 AM
USD/EUR = 0.91
At 10:30 AM:
Real rate = 0.95
Singleton still returns 0.91
That is dangerous for financial systems.
Captive Dependency Problem
A major DI mistake is:
Singleton → depends on Scoped
This is called a captive dependency.
Example:
services.AddSingleton<PriceCalculator>();
services.AddScoped<CurrencyExchange>();
Why this is bad:
- Singleton lives forever
- Scoped service lives per request
- Singleton captures the first scoped instance permanently
Result:
- Incorrect behavior
- Memory leaks
- Stale request data
- Runtime exceptions in ASP.NET Core
ASP.NET Core may throw:
Cannot consume scoped service from singleton
Correct Lifetime Rules
A service can safely depend on:
| Service Lifetime | Can Depend On |
|---|---|
| Singleton | Singleton only |
| Scoped | Scoped or Singleton |
| Transient | Any lifetime |
Recommended Architecture
Option 1 (Most Common)
services.AddTransient<IPriceCalculator, PriceCalculator>();
services.AddScoped<ICurrencyExchange, CurrencyExchange>();
This works because:
- Transient can depend on Scoped
- Exchange rates stay consistent during one request
- Data refreshes naturally across requests
Alternative: Singleton + Internal Cache Refresh
Sometimes CurrencyExchange may be:
services.AddSingleton<ICurrencyExchange, CurrencyExchange>();
BUT only if:
- It manages its own thread-safe cache
- Refreshes rates periodically
- Uses background services/timers
- Is designed for concurrency
Example:
Singleton service
→ refreshes exchange rates every 60 seconds
→ stores cache safely
This is more advanced and suitable for:
- High-performance systems
- Heavy API traffic
- Centralized caching
But it requires careful thread safety.
3.
- Get link
- X
- Other Apps
Comments
Post a Comment