Ad Code

✨🎆 JOIN MERN, JAVA, PYTHON, AI, DEVOPS, SALESFORCE Courses 🎆✨

Get 100% Placement Oriented Program CLICK to new more info click

What is Middleware in ASP.NET Core MVC | Explain Middleware in depth

What is Middleware in ASP.NET Core MVC:

 

Middleware is a fundamental component in ASP.NET Core's request processing pipeline. Understanding it is essential for customizing how HTTP requests and responses are handled in your application.

Middleware can process the request before next() and process the response after next() returns.

Easy formula

Request → Before → next() → Endpoint → After → Response


REQUEST ↓

        Middleware
        ┌─────────────┐
        │   BEFORE    │
        └──────┬──────┘
               ↓
            next()
               ↓
        Next Middleware
               ↓
           Endpoint
               ↓
        ┌─────────────┐
        │    AFTER    │
        └─────────────┘

             RESPONSE ↑

Each middleware component:

  1. Receives the HTTP request.

  2. Performs some processing.

  3. Optionally calls the next middleware in the pipeline.

  4. Optionally modifies the HTTP response.

Simple definition

Middleware is a piece of software that sits between the client request and the final response and can inspect, modify, or stop the request/response.


CreateBuilder()
      ↓
Configure Services
      ↓
builder.Build()
      ↓
Configure Middleware
      ↓
Map Endpoints
      ↓ 

app.Run() 


CreateBuilder() creates a WebApplicationBuilder object.

Think of it as:

"Prepare everything required to build my ASP.NET Core application."

It automatically sets up many things for you:

WebApplicationBuilder
       |
       |-- Configuration
       |-- Dependency Injection
       |-- Logging
       |-- Environment
       |-- Web Server settings
       |
       ↓
   Build Application

ASP.NET Core Middleware — Short Explanation

1. Use() — Middleware that can continue

app.Use(async (context, next) =>
{
    Console.WriteLine("Before");

    await next();

    Console.WriteLine("After");
});
  • Use() creates middleware.
  • It can execute code before and after the next middleware.
  • next() sends the request to the next middleware.

Remember: Use() → Continue


2. next() — Go to next middleware

await next();

Means:

"Execute the next middleware in the pipeline."

Without next():

Middleware 1
     ↓
    STOP ❌

With next():

Middleware 1
     ↓
Middleware 2
     ↓
Endpoint

Remember: next() → Next


3. Run() — Terminal middleware

app.Run(async context =>
{
    await context.Response.WriteAsync("Hello");
});
  • Run() is terminal middleware.
  • It does not call next().
  • Pipeline stops here.
M1 → M2 → Run()
             ↓
            STOP

Remember: Run() → Stop


4. Map() — Branch the pipeline

app.Map("/admin", adminApp =>
{
    adminApp.Run(async context =>
    {
        await context.Response.WriteAsync("Admin Area");
    });
});

If URL is:

/admin

the request goes into this branch.

                Request
                   ↓
                 Map()
                /     \
          /admin       Other
             ↓           ↓
          Admin       Main Pipeline

Remember: Map() → Branch


MiddlewarePurposeMandatory?
UseHttpsRedirection()HTTP → HTTPS❌ No
UseStaticFiles()CSS, JS, images❌ No
UseRouting()Routing❌ Not always explicitly required in modern minimal hosting
UseAuthentication()Identify logged-in user❌ Only if authentication is used
UseAuthorization()Check permissions❌ Only if authorization is used
MapControllerRoute()Map MVC controller routes✅ Required for conventional MVC routing
UseExceptionHandler()Global exception handling❌ Recommended
UseCors()Cross-origin requests❌ Only if CORS is needed

Recommended Middleware Order

var app = builder.Build();

app.UseExceptionHandler("/Home/Error");

app.UseHttpsRedirection();

app.UseStaticFiles();

app.UseRouting();

app.UseAuthentication();

app.UseAuthorization();

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

app.Run();

Flow

HTTP Request
     ↓
Exception Handling
     ↓
HTTPS Redirection
     ↓
Static Files
     ↓
Routing
     ↓
Authentication
     ↓
Authorization
     ↓
Controller / Action
     ↓
HTTP Response

Why this order?

OrderMiddlewarePurpose
1UseExceptionHandler()Catch errors
2UseHttpsRedirection()Redirect HTTP → HTTPS
3UseStaticFiles()Serve CSS/JS/images
4UseRouting()Select endpoint
5UseAuthentication()Identify user
6UseAuthorization()Check permission
7MapControllerRoute()Map MVC endpoint


Most important table

MethodMeaningContinue?
Use()Normal middleware✅ Yes, using next()
next()Go to next middleware➡️ Yes
Run()Terminal middleware❌ No
Map()Branch pipeline based on path🔀 Branch

Think of it as a series of checkpoints:

Browser
   ↓
Middleware 1
   ↓
Middleware 2
   ↓
Middleware 3
   ↓
Controller / Endpoint
   ↓
Middleware 3
   ↓
Middleware 2
   ↓
Middleware 1
   ↓
Browser


var builder = WebApplication.CreateBuilder(args); var app = builder.Build(); app.Use(async (context, next) => { Console.WriteLine("M1 - Before"); await next(); Console.WriteLine("M1 - After"); }); app.Use(async (context, next) => { Console.WriteLine("M2 - Before"); await next(); Console.WriteLine("M2 - After"); }); app.Run(async context => { Console.WriteLine("Endpoint"); await context.Response.WriteAsync("Hello World"); });

. Basic Middleware

For example:

app.Use(async (context, next) =>
{
    Console.WriteLine("Middleware 1 - Before");

    await next();

    Console.WriteLine("Middleware 1 - After");
});

Here:

await next();

means:

2. Middleware Order is VERY Important

Suppose we have:

app.Use(async (context, next) =>
{
    Console.WriteLine("A - Before");

    await next();

    Console.WriteLine("A - After");
});

app.Use(async (context, next) =>
{
    Console.WriteLine("B - Before");

    await next();

    Console.WriteLine("B - After");
});

app.Use(async (context, next) =>
{
    Console.WriteLine("C - Before");

    await next();

    Console.WriteLine("C - After");
});

When a request comes:

Request
   ↓
A
   ↓
B
   ↓
C
   ↓
Endpoint

But the response comes back in reverse:

Endpoint
   ↓
C
   ↓
B
   ↓
A
   ↓
Response

Therefore the console output will be:

A - Before
B - Before
C - Before

C - After
B - After
A - After

Pass the request to the next middleware in the pipeline.


3. Why does "After" execute in reverse?

Because of:

await next();

For example:

app.Use(async (context, next) =>
{
    Console.WriteLine("A Before");

    await next();

    Console.WriteLine("A After");
});

Execution doesn't finish at:

await next();

Instead, it temporarily goes to the next middleware.

So:

A Before
   ↓
    await next()
   ↓
B Before
   ↓
 await next()
   ↓
C Before
   ↓
Endpoint
   ↓
C After
   ↓
B After
   ↓
A After

This is similar to a stack: the last middleware entered is the first one to finish. 


4. Real ASP.NET Core Middleware Order

A typical application may have:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();

var app = builder.Build();

app.UseHttpsRedirection();

app.UseStaticFiles();

app.UseRouting();

app.UseAuthentication();

app.UseAuthorization();

app.MapControllers();

app.Run();

The order matters.

Conceptually:

Browser
   ↓
HTTPS Redirection
   ↓
Static Files
   ↓
Routing
   ↓
Authentication
   ↓
Authorization
   ↓
Controller
   ↓
Response


5. UseAuthentication() vs UseAuthorization()

This is a very important example of middleware ordering.

Usually:

app.UseAuthentication();
app.UseAuthorization();

Authentication asks:

Who are you?

Authorization asks:

Are you allowed to access this resource?

Therefore:

Authentication
       ↓
Who is the user?
       ↓
Authorization
       ↓
Is this user allowed?

You generally shouldn't put Authorization before Authentication.


6. Routing Order

Consider:

app.UseRouting();

app.UseAuthentication();

app.UseAuthorization();

app.MapControllers();

Routing determines which endpoint matches the request.

For example:

GET /api/student/10

Routing determines:

StudentController
      ↓
GetStudent(10)

Then authentication/authorization can operate with the selected endpoint's metadata.


7. Use() vs Run()

This is another important middleware concept.

Use()

Use() can call the next middleware:

app.Use(async (context, next) =>
{
    Console.WriteLine("Before");

    await next();

    Console.WriteLine("After");
});

Run()

Run() is terminal middleware.

app.Run(async context =>
{
    await context.Response.WriteAsync("Hello");
});

It doesn't have next.

Therefore:

A
 ↓
B
 ↓
Run()
 ↓
STOP


8. Example of Run()

app.Use(async (context, next) =>
{
    Console.WriteLine("Middleware 1");

    await next();
});

app.Run(async context =>
{
    await context.Response.WriteAsync("Hello");
});

app.Use(async (context, next) =>
{
    Console.WriteLine("Middleware 3");

    await next();
});

What happens?

Middleware 1
     ↓
Run()
     ↓
Response

Middleware in ASP.NET Core Pipeline

The middleware pipeline is configured in Startup.cs (or Program.cs in .NET 6+).

For .NET Core 3.1/ASP.NET Core MVC:


public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { // Built-in middleware examples app.UseDeveloperExceptionPage(); // 1. Show developer error page app.UseStaticFiles(); // 2. Serve static files (CSS, JS) app.UseRouting(); // 3. Enable routing app.UseAuthentication(); // 4. Handle authentication app.UseAuthorization(); // 5. Handle authorization app.UseEndpoints(endpoints => // 6. Match request to controller/action { endpoints.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}"); }); }

🔹 How Middleware Works
Every middleware is a function that takes in: HttpContext A RequestDelegate (the next middleware) Each middleware can decide: To process and pass to the next middleware. To short-circuit the pipeline.

Example of custom middleware: public class MyCustomMiddleware { private readonly RequestDelegate _next; public MyCustomMiddleware(RequestDelegate next) { _next = next; } public async Task InvokeAsync(HttpContext context) { // Before next middleware Console.WriteLine("Request: " + context.Request.Path); await _next(context); // Call the next middleware // After next middleware Console.WriteLine("Response: " + context.Response.StatusCode); } } To register it in the pipeline: public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { app.UseMiddleware<MyCustomMiddleware>();
} 🔹 Middleware Lifecycle (Request to Response) Incoming Request: Passes through all middleware in the order they are registered. Terminal Middleware: Ends the request without calling next (e.g., app.Run()). Response: Travels backward through middleware, allowing each to act on the response. 🔹 Built-in Middleware Examples Middleware Purpose UseStaticFiles() Serves static files like CSS, JS, images UseRouting() Enables routing system UseAuthentication() Handles user authentication UseAuthorization() Checks user permissions UseExceptionHandler() Centralized error handling UseCors() Enables CORS (Cross-Origin Resource Sharing) UseSession() Enables session state UseEndpoints() Maps routes to endpoints (controllers)

🔹 When to Use Middleware?
Use middleware when: You want to log request/response. You want to perform actions globally, not tied to a specific controller. You want to modify requests or responses. You want to implement cross-cutting concerns like CORS, caching, auth, etc.


Example of Middleware to Create LoggerFile:

namespace FirstWebProject.Services
{
    public class MyCustomMiddleware
    {
        private readonly RequestDelegate _next;
        private readonly string path;
        private static readonly SemaphoreSlim _lock = new SemaphoreSlim(1, 1);
        public MyCustomMiddleware(RequestDelegate next)
        {
            _next = next;

            path = Path.Combine(
                Directory.GetCurrentDirectory(),
                "mylogger.txt"
            );
        }

        public async Task InvokeAsync(HttpContext context)
        {
            // Before next middleware
            Console.WriteLine(
                "Request: " + context.Request.Path
            );


            await WriteLogAsync(
               $"REQUEST : {context.Request.Path}  Time : {DateTime.Now}"
           );

            // Call next middleware
            await _next(context);


            // After next middleware
            Console.WriteLine(
                "Response Status: " + context.Response.StatusCode
            );


            await WriteLogAsync(
                $"RESPONSE : {context.Request.Path} Status : 
{context.Response.StatusCode} Time : {DateTime.Now}" ); } private async Task WriteLogAsync(string message) { await _lock.WaitAsync(); try { await File.AppendAllTextAsync( path, message + Environment.NewLine ); } finally { _lock.Release(); } } } }

1. Create IpRestrictionMiddleware.cs

namespace FirstWebProject.Services
{
public class IpRestrictionMiddleware
{
private readonly RequestDelegate _next;

// Allowed IP addresses
private readonly string[] allowedIps =
{
"127.0.0.1",
"::1",
"192.168.1.10"
};


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


public async Task InvokeAsync(HttpContext context)
{
string userIp = context.Connection.RemoteIpAddress?
.ToString();


Console.WriteLine("User IP : " + userIp);


if (!allowedIps.Contains(userIp))
{
context.Response.StatusCode = StatusCodes.Status403Forbidden;

await context.Response.WriteAsync(
"Access Denied - IP Address Not Allowed"
);

return;
}


await _next(context);
}
}
}

2. Register middleware in Program.cs

Add before your routes:

app.UseMiddleware<IpRestrictionMiddleware>();

app.UseRouting();

app.UseAuthorization();

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

Testing

If your IP is:

192.168.1.10

Output:

User IP : 192.168.1.10

Allowed

Any other IP:

User IP : 45.23.11.90

403 Forbidden
Access Denied - IP Address Not Allowed

For real login page restriction

If you want to restrict only /Account/Login:

public async Task InvokeAsync(HttpContext context)
{
if(context.Request.Path == "/Account/Login")
{
string ip = context.Connection.RemoteIpAddress?.ToString();

if(!allowedIps.Contains(ip))
{
context.Response.StatusCode = 403;
await context.Response.WriteAsync(
"Login blocked from this IP"
);
return;
}
}

await _next(context);



}

Middleware Short-Circuiting

Short-circuiting means:

A middleware stops the request from going to the next middleware.

Normally:

Request
   ↓
Middleware 1
   ↓ next()
Middleware 2
   ↓ next()
Middleware 3
   ↓
Response

Short-circuit example

app.Use(async (context, next) =>
{
    Console.WriteLine("Middleware 1");

    await context.Response.WriteAsync("Request stopped!");

    // No next()
});

Because there is no await next():

Request
   ↓
Middleware 1
   ↓
Response
   ❌
Middleware 2 is NOT executed

Real-world example

app.Use(async (context, next) =>
{
    if (!context.Request.Query.ContainsKey("token"))
    {
        context.Response.StatusCode = 401;
        await context.Response.WriteAsync("Token Required");
        return; // Short-circuit
    }

    await next();
});

If token is missing:

Request
   ↓
Check Token
   ↓
Token Missing
   ↓
401 Response
   ❌ STOP

If token exists:

Request
   ↓
Check Token
   ↓
next()
   ↓
Next Middleware



Post a Comment

0 Comments