SOLID Principles and Design Pattern.

What is the SOLID Principle?

SOLID is an acronym for five key principles of object-oriented design that help create clean, scalable, testable, and maintainable code. These principles are especially useful when building layered applications like ASP.NET Core Web APIs or MVC apps.

SOLID stands for:

  • S - Single Responsibility Principle
  • O - Open/Closed Principle
  • L - Liskov Substitution Principle
  • I - Interface Segregation Principle
  • D - Dependency Inversion Principle

Let's understand each point with an example:

1. Single Responsibility Principle (SRP)

Definition: A class should have only one reason to change.

In ASP.NET Core, separate business logic, data access, and controller logic.

❌ Bad Example (Violates SRP).

public class ProductService
{
    public void Save(Product p) { /* save to DB */ }
    public void SendEmail(Product p) { /* email logic */ }
}

✅ Good Example.
public class ProductService
{
    private readonly IProductRepository _repo;
    public ProductService(IProductRepository repo) => _repo = repo;
    
    public void Save(Product product) => _repo.Add(product);
}

public class EmailService
{
    public void SendEmail(Product p) { /* only email logic */ }
}

2. Open/Closed Principle (OCP)
Definition: Classes should be open for extension but closed for modification.
In ASP.NET Core, use interfaces and inheritance to extend behavior without changing existing code.

Example: Logging Different Formats
public interface ILoggerService
{
    void Log(string message);
}

public class FileLogger : ILoggerService
{
    public void Log(string message) { /* log to file */ }
}

public class DbLogger : ILoggerService
{
    public void Log(string message) { /* log to DB */ }
}

The system can now be extended with a new logger without modifying the existing services.

3. Liskov Substitution Principle (LSP)
Definition: Subclasses should be replaceable for their base class without breaking the app.
In ASP.NET Core, ensure service implementations follow expected behavior.

❌ Violates LSP
If one subclass throws NotImplementedException:
public class BrokenNotificationService : NotificationService
{
    public override void Send(string message)
    {
        throw new NotImplementedException(); // ❌ Violates LSP
    }
}

Good Example:
We have a base class, NotificationService, and two derived classes: EmailNotificationService and SmsNotificationService.

Base Class:
public abstract class NotificationService
{
    public abstract void Send(string message);
}

Derived Class:
public class EmailNotificationService : NotificationService
{
    public override void Send(string message)
    {
        Console.WriteLine($"Email sent: {message}");
    }
}

public class SmsNotificationService : NotificationService
{
    public override void Send(string message)
    {
        Console.WriteLine($"SMS sent: {message}");
    }
}

Usage (LSP in Action)
public class NotificationController
{
    private readonly NotificationService _service;

    public NotificationController(NotificationService service)
    {
        _service = service;
    }

    public void NotifyUser()
    {
        _service.Send("Hello, User!");
    }
}

You can now substitute EmailNotificationService or SmsNotificationService when injecting into NotificationController without changing any controller code:
var controller1 = new NotificationController(new EmailNotificationService());
controller1.NotifyUser(); // Output: Email sent: Hello, User!

var controller2 = new NotificationController(new SmsNotificationService());
controller2.NotifyUser(); // Output: SMS sent: Hello, User!
  • Inheritance + LSP = Child classes must work in place of their base class.
  • Avoid incomplete or broken implementations in subclasses.
  • In ASP.NET Core, always ensure services or controller dependencies fully implement expected behavior.

4. Interface Segregation Principle (ISP)
Definition: Don’t force classes to implement unnecessary methods.
In ASP.NET Core, use smaller, specific interfaces.

❌ Bad Interface
public interface IProductService
{
    void Add();
    void Update();
    void ExportToExcel(); // not every implementation needs this
}

✅ Good Design
public interface ICrudService
{
    void Add();
    void Update();
}

public interface IExportService
{
    void ExportToExcel();
}

5. Dependency Inversion Principle (DIP)
Definition: High-level modules should not depend on low-level modules. Instead, both should depend on abstractions.
In ASP.NET Core, this is implemented via dependency injection (DI).
Example:
public interface IEmailService
{
    void Send(string to, string message);
}

public class SmtpEmailService : IEmailService
{
    public void Send(string to, string message)
    {
        // logic to send email
    }
}

public class NotificationController : ControllerBase
{
    private readonly IEmailService _emailService;

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

    public IActionResult Notify()
    {
        _emailService.Send("user@example.com", "Hello!");
        return Ok();
    }
}

Applying SOLID principles in ASP.NET Core helps you build robust, clean, and future-proof applications. These principles make your code:
  • Easier to test
  • Simpler to maintain
  • More scalable and flexible
I hope you understood the SOLID Principle and use case now. Let's understand a few popular Design Patterns that we follow while writing ASP.NET Core Code.

Design Patterns

1. Singleton Pattern.

The Singleton Pattern ensures that a class has only one instance throughout the application’s lifetime and provides a global point of access to that instance.

When to Use Singleton in ASP.NET Core
  • Logging services
  • Configuration settings
  • Caching
  • Shared services (only if thread-safe and stateless)
Example: Think of Logger: You want one shared logger throughout your app rather than creating a new one every time.

Step 1: Create the Singleton Class
public class AppLogger
{
    // Static instance
    private static readonly AppLogger _instance = new AppLogger();

    // Private constructor
    private AppLogger() { }

    // Public static property to access the instance
    public static AppLogger Instance => _instance;

    // Sample method
    public void Log(string message)
    {
        Console.WriteLine($"[LOG] {DateTime.Now}: {message}");
    }
}

Usage in Code:
class Program
{
    static void Main(string[] args)
    {
        var logger1 = AppLogger.Instance;
        var logger2 = AppLogger.Instance;

        logger1.Log("Singleton is working!");
        logger2.Log("Same instance is used again.");

        Console.WriteLine(ReferenceEquals(logger1, logger2)); // Output: True
    }
}

2. Factory Pattern.

The Factory Pattern is a creational design pattern that provides an interface for creating objects in a superclass but allows subclasses to alter the type of objects that will be created.
In simpler terms: You let a Factory class decide which object to create based on input, instead of using new everywhere.

In short, "The Factory Pattern lets me abstract the creation of objects based on input or logic. Instead of using new everywhere, I call a Factory method, which decides what class to return. This keeps my code flexible and clean."

When to Use the Factory Pattern
  • When object creation logic is complex or repetitive
  • When you want to decouple object creation from your main logic
  • When the class to instantiate is determined at runtime

Example: You want to send different types of notifications — Email, SMS, or Push.

Step 1: Create a Common Interface.
public interface INotification
{
    void Send(string message);
}

Step 2: Create Concrete Implementations.
public class EmailNotification : INotification
{
    public void Send(string message)
    {
        Console.WriteLine("Email sent: " + message);
    }
}

public class SmsNotification : INotification
{
    public void Send(string message)
    {
        Console.WriteLine("SMS sent: " + message);
    }
}

public class PushNotification : INotification
{
    public void Send(string message)
    {
        Console.WriteLine("Push sent: " + message);
    }
}

Step 3: Create the Factory Class.
public class NotificationFactory
{
    public static INotification Create(string type)
    {
        return type.ToLower() switch
        {
            "email" => new EmailNotification(),
            "sms" => new SmsNotification(),
            "push" => new PushNotification(),
            _ => throw new ArgumentException("Invalid notification type"),
        };
    }
}

Step 4: Use the Factory in Your App.
class Program
{
    static void Main()
    {
        Console.Write("Enter notification type (email/sms/push): ");
        string type = Console.ReadLine();

        INotification notification = NotificationFactory.Create(type);
        notification.Send("Hello from Factory Pattern!");
    }
}

3. Repository Pattern.

The Repository Pattern is used to abstract the data access layer so your application code (like services or controllers) doesn't directly interact with the database.
This pattern helps:
  • Centralized database logic
  • Make the code more testable
  • Enforce Separation of Concerns (SoC)

Example:
Think of the repository as a middleman between your app and the database.
Instead of writing EF Core queries inside your controller, you use a repository.

Step 1: Define a Model.
public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
}

Step 2: Create the DbContext.
public class AppDbContext : DbContext
{
    public DbSet<Product> Products { get; set; }

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

Step 3: Create Repository Interface.
public interface IProductRepository
{
    IEnumerable<Product> GetAll();
    Product GetById(int id);
    void Add(Product product);
    void Update(Product product);
    void Delete(int id);
}

Step 4: Implement Repository.
public class ProductRepository : IProductRepository
{
    private readonly AppDbContext _context;
    public ProductRepository(AppDbContext context)
    {
        _context = context;
    }

    public IEnumerable<Product> GetAll() => _context.Products.ToList();

    public Product GetById(int id) => _context.Products.Find(id);

    public void Add(Product product)
    {
        _context.Products.Add(product);
        _context.SaveChanges();
    }

    public void Update(Product product)
    {
        _context.Products.Update(product);
        _context.SaveChanges();
    }

    public void Delete(int id)
    {
        var product = _context.Products.Find(id);
        if (product != null)
        {
            _context.Products.Remove(product);
            _context.SaveChanges();
        }
    }
}

Step 5: Register in DI (Program.cs).
builder.Services.AddScoped<IProductRepository, ProductRepository>();
builder.Services.AddDbContext<AppDbContext>(options =>
    options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));

Step 6: Use the Repository in the Controller.
[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
    private readonly IProductRepository _repo;

    public ProductsController(IProductRepository repo)
    {
        _repo = repo;
    }

    [HttpGet]
    public IActionResult Get() => Ok(_repo.GetAll());

    [HttpPost]
    public IActionResult Post(Product product)
    {
        _repo.Add(product);
        return CreatedAtAction(nameof(Get), new { id = product.Id }, product);
    }
}

Q: Why use the repository pattern?
Answer: "It abstracts the data access logic, helps me write unit tests easily, and makes the app follow SOLID principles, especially SRP and DIP."

Clean Architecture in ASP.NET Core.

Before understanding Clean Architecture, let's first understand Three-Tier Architecture, and then we will understand how Clean Architecture is better compared to Three-Tier.

Three-Tier Architecture.

Three-Tier Architecture is a software design pattern that separates an application into three logical and physical layers, each with a specific responsibility.

Flow of Data in Three-Tier:

User (UI Layer)
   ↓
Controller → Calls Service (BLL)
   ↓
Service → Calls Repository (DAL)
   ↓
Repository → Talks to Database

Clean Architecture.

Clean Architecture is a powerful software design pattern that helps you organize your application into loosely-coupled, highly maintainable layers. It was popularized by Robert C. Martin (Uncle Bob) and is especially useful in large enterprise-level applications like those built with ASP.NET Core.

Why Clean Architecture?

  • Separates concerns clearly
  • Promotes testability
  • Enables independent development of core logic and infrastructure
  • Improves scalability and flexibility
  • Encourages dependency inversion (core depends on nothing)

Core Principles of Clean Architecture

UI
  • Controllers, Views, View Models
  • Filters, Middleware

Core
  • Business Logic Services
  • Business Logic Interfaces
  • Data Transfer Objects (DTO)

Domain
  • Repository Interfaces
  • Entity Classes

Infrastructure
  • DbContext, Repositories
  • External API Calls


Clean Architecture vs Three-Tier Architecture
Feature Three-Tier Architecture Clean Architecture
Layer Focus Focuses on UI → BLL → DB separation Focuses on core business logic and dependency flow
Dependency Flow Flows UI → BLL → DAL Always flows inward, toward the domain layer
Coupling Higher coupling to frameworks (e.g., EF in BLL) Loosely coupled; core has no external dependencies
Testability Harder if BLL directly depends on infrastructure High testability; core logic is isolated
Flexibility Rigid – changing DB or UI may impact logic Flexible – DB/UI are pluggable
Domain-Centric No – business logic mixed with infrastructure Yes – everything revolves around business rules
Use in Microservices Not ideal Highly suitable

AppSettings and IConfiguration

What is appsettings.json?

appsettings.json is a configuration file used in ASP.NET Core to store key-value pairs like:

  • Database connection strings
  • API keys
  • Feature toggles
  • Logging levels
  • Custom settings

Example:

{  "AppSettings": {
    "AppName": "MyApp",
    "Version": "1.0"
  },
  "ConnectionStrings": {
    "DefaultConnection": "Server=.;Database=MyDb;Trusted_Connection=True;"
  }
}

What is IConfiguration?

IConfiguration is an interface provided by ASP.NET Core to access configuration data (like from appsettings.json, environment variables, etc.).
You can inject it into any class:

Example:
public class HomeController : Controller
{
    private readonly IConfiguration _config;

    public HomeController(IConfiguration config)
    {
        _config = config;
    }

    public IActionResult Index()
    {
        var appName = _config["AppSettings:AppName"];
        return Content($"App Name: {appName}");
    }
}

How to Bind Settings to a Class

You can also bind sections from appsettings.json to a class:

Step 1: Create a Class.
public class AppSettings
{
    public string AppName { get; set; }
    public string Version { get; set; }
}

Step 2: Register it in Program.cs
builder.Services.Configure<AppSettings>(
    builder.Configuration.GetSection("AppSettings"));

Step 3: Inject using IOptions<T>
public class HomeController : Controller
{
    private readonly AppSettings _settings;

    public HomeController(IOptions<AppSettings> options)
    {
        _settings = options.Value;
    }

    public IActionResult Index()
    {
        return Content($"App: {_settings.AppName}, Version: {_settings.Version}");
    }
}
ss

Enable Swagger and CROS in ASP.NET Core.

Swagger.

Swagger (OpenAPI) is a powerful tool that:

  • Generates interactive API documentation
  • Helps you test APIs right from the browser
  • Makes your API self-descriptive for frontend teams and third-party users

How to configure Swagger?

If not already installed, add the package:

dotnet add package Swashbuckle.AspNetCore

Add Swagger services before building the app:
Example:
var builder = WebApplication.CreateBuilder(args);

// Add services
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer(); // For minimal API support
builder.Services.AddSwaggerGen();           // 👈 Adds Swagger generator

var app = builder.Build();

// Use middleware
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();                        // 👈 Enables Swagger JSON
    app.UseSwaggerUI();                      // 👈 Enables Swagger UI
}

app.UseAuthorization();
app.MapControllers();
app.Run();

You can enhance your Swagger UI by providing API details:
builder.Services.AddSwaggerGen(options =>
{
    options.SwaggerDoc("v1", new OpenApiInfo
    {
        Title = "My API",
        Version = "v1",
        Description = "This is a sample Web API using Swagger.",
        Contact = new OpenApiContact
        {
            Name = "Probin Sah",
            Email = "probin@example.com"
        }
    });
});

What is CORS?

CORS (Cross-Origin Resource Sharing) is a security feature implemented by browsers that restricts web apps from making requests to a different domain than the one that served the web page.

Why is it needed?
By default, browsers block cross-origin requests to protect users. For example:
  • If your Angular app runs on http://localhost:4200
  • And your API runs on http://localhost:5000
The browser blocks requests from the Angular app to the API unless CORS is explicitly enabled.

How To Enable CORS in ASP.NET Core?

Step 1: Add CORS Services in Program.cs
builder.Services.AddCors(options =>
{
    options.AddPolicy("AllowFrontendApp", policy =>
    {
        policy
            .WithOrigins("http://localhost:4200") // Frontend URL
            .AllowAnyMethod()
            .AllowAnyHeader();
    });
});

Step 2: Use CORS Middleware
Place it before UseAuthorization():
var app = builder.Build();

app.UseCors("AllowFrontendApp"); // 👈 Apply the policy

app.UseAuthorization();
app.MapControllers();

app.Run();
  • CORS allows your frontend (from another origin) to call your backend API.
  • Without it, browsers block cross-origin calls for security.
  • Use .WithOrigins, .AllowAnyMethod, .AllowAnyHeader to control access.

Logging in ASP.NET Core.

Logging is a critical part of any application for monitoring, debugging, and troubleshooting. ASP.NET Core comes with a flexible and powerful built-in logging framework that allows you to plug in various logging providers to send logs to different destinations.

In this article, you'll learn about the built-in logging providers in ASP.NET Core, how they work, and when to use each one.

What Is a Logging Provider?

A logging provider is a component that processes and stores log messages. ASP.NET Core allows you to configure one or more providers that can write logs to:
  • Console
  • Debug window
  • Event log (Windows)
  • Files
  • External systems (like Seq, ELK, or Application Insights)

You can configure logging providers in Program.cs:
var builder = WebApplication.CreateBuilder(args);

// Add built-in providers
builder.Logging.ClearProviders(); // Optional: clears default providers
builder.Logging.AddConsole();
builder.Logging.AddDebug();
builder.Logging.AddEventSourceLogger();

var app = builder.Build();

You can also control log levels in appsettings.json:
"Logging": {
  "LogLevel": {
    "Default": "Information",
    "Microsoft": "Warning",
    "Microsoft.Hosting.Lifetime": "Information"
  }
}

What is ILogger?

ILogger<T> is ASP.NET Core’s built-in logging interface that allows you to log messages inside your classes (controllers, services, etc.).

It supports:
  • Different log levels (Information, Warning, Error, etc.)
  • Structured logging (log message + key-value pairs)
  • Logging to multiple providers (console, file, debug, etc.)

How to Use ILogger in ASP.NET Core

Step 1: Inject ILogger<T> in the Constructor.
public class ProductService
{
    private readonly ILogger<ProductService> _logger;

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

    public void GetProduct()
    {
        _logger.LogInformation("Fetching product...");
    }
}

Step 2: Register and Use the Service in the Controller.
[ApiController]
[Route("api/[controller]")]
public class ProductController : ControllerBase
{
    private readonly ProductService _service;

    public ProductController(ProductService service)
    {
        _service = service;
    }

    [HttpGet]
    public IActionResult Get()
    {
        _service.GetProduct();
        return Ok("Product fetched.");
    }
}
Note: ASP.NET Core automatically injects ILogger<T> using its built-in Dependency Injection.

You can log at different severity levels:
_logger.LogTrace("Trace log");
_logger.LogDebug("Debugging info");
_logger.LogInformation("General info");
_logger.LogWarning("Something might be wrong");
_logger.LogError("An error occurred");
_logger.LogCritical("Critical issue!");

Complete Example:
[ApiController]
[Route("api/[controller]")]
public class OrderController : ControllerBase
{
    private readonly ILogger<OrderController> _logger;

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

    [HttpGet("{id}")]
    public IActionResult GetOrder(int id)
    {
        _logger.LogInformation("Fetching order with ID {OrderId}", id);

        if (id <= 0)
        {
            _logger.LogWarning("Invalid order ID: {OrderId}", id);
            return BadRequest("Invalid ID");
        }

        return Ok($"Order #{id}");
    }
}

How To Use Serilog in ASP.NET Core?

Serilog is a powerful logging library for .NET that supports structured logging, file output, sinks like Seq, and is easy to plug into ASP.NET Core.
Here’s a step-by-step example of using Serilog in an ASP.NET Core Web API project:

Step 1: Install Serilog NuGet Packages
Run these commands in your terminal:
dotnet add package Serilog.AspNetCore
dotnet add package Serilog.Sinks.Console
dotnet add package Serilog.Sinks.File

Step 2: Configure Serilog in the Program.cs
Update your Program.cs to use Serilog before building the host:
using Serilog;

var builder = WebApplication.CreateBuilder(args);

// 🔹 Configure Serilog before building the app
Log.Logger = new LoggerConfiguration()
    .MinimumLevel.Information()
    .WriteTo.Console()
    .WriteTo.File("Logs/log-.txt", rollingInterval: RollingInterval.Day)
    .Enrich.FromLogContext()
    .CreateLogger();

// Replace default logger with Serilog
builder.Host.UseSerilog();

builder.Services.AddControllers();
var app = builder.Build();

app.UseAuthorization();
app.MapControllers();
app.Run();

Step 3: Inject and Use ILogger in a Controller
Serilog integrates with ILogger<T> automatically. No need to use Serilog types directly.
[ApiController]
[Route("api/[controller]")]
public class TestController : ControllerBase
{
    private readonly ILogger<TestController> _logger;

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

    [HttpGet("{id}")]
    public IActionResult Get(int id)
    {
        _logger.LogInformation("Request received for ID: {Id}", id);

        if (id <= 0)
        {
            _logger.LogWarning("Invalid ID: {Id}", id);
            return BadRequest("Invalid ID");
        }

        return Ok($"Item #{id}");
    }
}

Output Example (in Console or File)
[10:30:12 INF] Request received for ID: 5
[10:30:14 WRN] Invalid ID: -1

Authentication and Authorization in ASP.NET Core

What is Authentication?

Authentication is the process of verifying who the user is.

It ensures the user’s identity is valid, typically through:

  • Username/Password
  • Tokens (e.g., JWT)
  • Cookies
  • External providers (Google, Facebook, Azure AD)

Example: If you log in using your email and password, that’s authentication. If your credentials are correct, the server "knows" who you are.

2. What is Authorization?

Authorization is the process of determining what an authenticated user is allowed to do.

After you are authenticated:

  • Are you allowed to access this endpoint?
  • Do you have the required role?
  • Do you have permission to perform a certain action?

What happens when the user logs in?

  • User submits credentials via login form or API (e.g., /login)
  • Backend validates the credentials (e.g., against a database)
  • If valid, the server issues an authentication token (JWT or sets a cookie).
  • On the next request, the client sends the token or cookie back
  • The middleware validates it and sets the User. Identity
  • Controllers/actions protected by [Authorize] now recognize the user

Example: JWT Authentication (API-Based)

Step 1. Add JWT Authentication Packages.

dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer

Step 2: Configure Authentication in the Program.cs.
builder.Services.AddAuthentication("Bearer")
    .AddJwtBearer("Bearer", options =>
    {
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidIssuer = "your-app",
            ValidateAudience = true,
            ValidAudience = "your-client",
            ValidateIssuerSigningKey = true,
            IssuerSigningKey = new SymmetricSecurityKey(
                Encoding.UTF8.GetBytes("your-secret-key")),
            ValidateLifetime = true
        };
    });

builder.Services.AddAuthorization();

Then in middleware:
var app = builder.Build();

app.UseAuthentication();   // Must be before UseAuthorization
app.UseAuthorization();

app.MapControllers();

Step 3: Login Endpoint: Validate Credentials and Generate Token
[HttpPost("login")]
public IActionResult Login(LoginModel model)
{
    if (model.Username == "admin" && model.Password == "password") // DB check in real case
    {
        var claims = new[]
        {
            new Claim(ClaimTypes.Name, model.Username),
            new Claim(ClaimTypes.Role, "Admin")
        };

        var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("your-secret-key"));
        var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);

        var token = new JwtSecurityToken(
            issuer: "your-app",
            audience: "your-client",
            claims: claims,
            expires: DateTime.Now.AddHours(1),
            signingCredentials: creds);

        return Ok(new
        {
            token = new JwtSecurityTokenHandler().WriteToken(token)
        });
    }

    return Unauthorized("Invalid credentials");
}

Step 4: Client Sends JWT in Authorization Header
On every request to a protected API:
GET /api/secure-data
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...

Step 5: Use [Authorize] to Protect Routes
[Authorize]
[HttpGet("secure-data")]
public IActionResult GetSecureData()
{
    var username = User.Identity?.Name;
    return Ok($"This data is only visible to {username}");
}

How does it work behind the Scenes?

Step 1: Authentication Middleware checks the incoming request
Step 2: Look for a valid Authorization header (with JWT)
Step 3: If valid:
  • It decodes the token
  • Sets HttpContext.User with claims and identity
Step 4: Then, [Authorize] uses this info to allow or deny access

[Authorize] → Requires the user to be authenticated
[AllowAnonymous] → Bypasses authorization for that method

JWT Token Structure.

A JWT token is made of 3 parts:
  • HEADER
  • PAYLOAD
  • SIGNATURE
1. HEADER
The Header contains metadata about the token:
{
  "alg": "HS256",   // Algorithm used (HMAC SHA-256)
  "typ": "JWT"      // Token type
}

2. PAYLOAD (this is where all your data is)
This is the main body of the JWT. It contains:
  • iss Issuer – who created the token
  • aud Audience – who the token is for
  • exp Expiration time (Unix timestamp)
  • nbf Not before – token valid after this time
  • iat Issued at time
  • sub Subject – usually the user ID
  • jti Token ID – unique identifier
Example:
{
  "iss": "JwtAuthDemo",
  "aud": "JwtAuthClient",
  "exp": 1720542515,
  "sub": "123456"
}

Custom Claims (You define these!)
These are the claims you add for user info, roles, etc.

Example:
{
  "name": "admin",
  "email": "admin@example.com",
  "role": "Admin",
  "department": "HR"
}

ASP.NET Core reads the role claim automatically for [Authorize(Roles = "...")].

3. SIGNATURE
Signature is used to validate the token’s integrity using your secret key.
It's computed like this:
HMACSHA256(
  base64UrlEncode(header) + "." + base64UrlEncode(payload),
  secret-key
)

If someone tries to tamper with the payload, the signature won’t match anymore.

Note: JWT is not encrypted, just base64-encoded. Never store sensitive information like passwords in it.

Middleware in ASP.NET Core.

What is Middleware?

Middleware in ASP.NET Core is a software component that is executed on every HTTP request and response. Middleware components are chained together to form a request-processing pipeline.

Each middleware:
  • Can perform operations before and after the next component in the pipeline
  • Can short-circuit the pipeline (e.g., for authentication, error handling)
  • Is executed in the order registered in the Program.cs

Middleware Examples
ASP.NET Core has built-in middleware such as:
  • UseRouting() – for routing requests
  • UseAuthentication() – for checking identity
  • UseAuthorization() – for permission control
  • UseEndpoints() – for mapping controllers
Middleware executes in the order added — request goes top to bottom, response goes bottom to top.

Request Pipeline Flow
Here’s how a typical request moves through middleware:
Request
  
[Middleware 1]  [Middleware 2]  ...  [Middleware N]  Controller
                                                  
Response                     

Each middleware has a chance to:
  • Do something before passing the request forward
  • Do something after the next middleware has completed

How to Create a Custom Middleware.

Step 1: Create a class with a constructor accepting RequestDelegate
Step 2: Add InvokeAsync(HttpContext) method
Step 3: Call _next(context) inside the method
Step 4: Register it using app.UseMiddleware<YourMiddleware>()

Step 1: Create a Middleware Class
public class LoggingMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger<LoggingMiddleware> _logger;

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

    public async Task InvokeAsync(HttpContext context)
    {
        _logger.LogInformation($"Incoming request: {context.Request.Method} {context.Request.Path}");
        await _next(context);
        _logger.LogInformation($"Response: {context.Response.StatusCode}");
    }
}

Step 2: To keep Program.cs clean, create extension methods for your middleware.
public static class MiddlewareExtensions
{
    public static IApplicationBuilder UseLoggingMiddleware(this IApplicationBuilder app)
    {
        return app.UseMiddleware<LoggingMiddleware>();
    }
}

Step 3: Program.cs (or Startup.cs in older projects)
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.UseLoggingMiddleware(); // 👈 From your extension method

app.UseRouting();
app.UseAuthorization();
app.MapControllers();

app.Run();

📁 Recommended Middleware File Structure.

/YourProjectRoot

├── Program.cs
├── Startup.cs (if applicable)

├── Middleware/
   ├── LoggingMiddleware.cs
   ├── ExceptionHandlingMiddleware.cs
   └── RequestTimingMiddleware.cs

└── Extensions/
    └── MiddlewareExtensions.cs

When to Use Custom Middleware?

You might create custom middleware to:
  • Log requests and responses
  • Handle exceptions globally
  • Modify headers
  • Implement custom authentication or IP filtering
  • Measure performance/response time

Typical Middleware Order in Program.cs

Here is the recommended and logical order of middleware in most real-world ASP.NET Core applications:
var app = builder.Build();

app.UseExceptionHandler("/error");         // 1. Global exception handling
app.UseHsts();                             // 2. Enforce HTTPS strict transport (in production)
app.UseHttpsRedirection();                 // 3. Redirect HTTP to HTTPS
app.UseStaticFiles();                      // 4. Serve static files (JS, CSS, images)
app.UseRouting();                          // 5. Enable routing system
app.UseCors();                             // 6. Enable CORS if needed
app.UseAuthentication();                   // 7. Identity/authentication check
app.UseAuthorization();                    // 8. Role-based or policy-based authorization
app.UseMiddleware<CustomLoggingMiddleware>(); // 9. Your custom middlewares
app.UseEndpoints(endpoints =>              // 10. Connects routes to controllers
{
    endpoints.MapControllers();
});

app.Run();

Common Middleware Interview Question.

Q1. What is the difference between Use, Run, and Map?
  • Use: Adds middleware to the pipeline, can call next
  • Run: Terminal middleware, doesn't call next
  • Map: Branches pipeline based on request path

Q2. Can a middleware return a response without calling the next middleware?
Answer Tip: Yes. This is called short-circuiting. For example, authentication or CORS middleware might return a response early.

Q3. What happens if you don't call _next(context) in a middleware?
Answer Tip: The pipeline is terminated, and the request doesn't reach subsequent middleware or the controller.

Q4. How do you handle exceptions globally in middleware?
Answer: Use a custom exception-handling middleware or app.UseExceptionHandler().

DON'T MISS

Tech News
© all rights reserved
made with by AlgoLesson