.NET - FullstackCodingGuy/Developer-Fundamentals GitHub Wiki

Request Lifecycle in .NET 9

read

🌍 Request Lifecycle in .NET 9

The request lifecycle in a .NET 9 Minimal API / MVC application follows a series of steps from incoming request to response generation. Understanding this helps in performance tuning, debugging, and implementing middleware effectively.


✅ 1. Lifecycle Overview

🔄 Step-by-Step Flow

1️⃣ Client Sends Request → (Browser/Postman/Frontend App)
2️⃣ Kestrel Web Server Receives Request
3️⃣ Middleware Pipeline Processes Request
4️⃣ Routing Determines Controller/Minimal API
5️⃣ Model Binding & Validation (if applicable)
6️⃣ Controller Action / Minimal API Executes
7️⃣ Service Layer Handles Business Logic
8️⃣ Database Interaction Occurs (via Repository/EF Core)
9️⃣ Response Is Generated
🔟 Middleware Pipeline Handles Response
🔟 Response Is Sent Back to Client


✅ 2. Breakdown of Key Stages

🔹 Step 1: HTTP Request Reaches Kestrel

  • Kestrel is the built-in cross-platform web server in ASP.NET.
  • Listens for incoming HTTP requests and forwards them to the middleware pipeline.

🔹 Example:

GET https://localhost:5001/api/expenses

🔹 Step 2: Middleware Pipeline Execution

  • Middleware components process requests in a sequential order.
  • Can short-circuit requests (e.g., authentication, caching).
  • Configured in Program.cs with app.UseMiddleware<>.

🔧 Example Middleware (Logging)

app.Use(async (context, next) =>
{
    Console.WriteLine($"Request: {context.Request.Method} {context.Request.Path}");
    await next(); // Pass request to next middleware
});

Common Middleware in .NET 9

Middleware Purpose
UseExceptionHandler Global error handling
UseRouting Determines request path
UseAuthentication Handles JWT, OAuth, etc.
UseAuthorization Verifies user permissions
UseRateLimiter Throttles requests

🎯 Final Thoughts

  • Understanding the request lifecycle helps optimize performance.
  • Middleware placement is crucial for efficient request processing.
  • Asynchronous programming improves scalability.
  • Database optimizations prevent performance bottlenecks.

This ensures your .NET 9 API is efficient, scalable, and production-ready! 🚀 Let me know if you need further details! 😊

Conditionally Applying Decorators in .NET Dependency Injection?

read

In .NET DI, you can conditionally apply the Decorator Pattern based on configuration or runtime conditions using IServiceCollection.

public class LoggingExpenseServiceDecorator : IExpenseService
{
    private readonly IExpenseService _inner;
    private readonly ILogger<LoggingExpenseServiceDecorator> _logger;

    public LoggingExpenseServiceDecorator(IExpenseService inner, ILogger<LoggingExpenseServiceDecorator> logger)
    {
        _inner = inner;
        _logger = logger;
    }

    public async Task<string> ProcessExpenseAsync(decimal amount)
    {
        _logger.LogInformation($"Processing expense: {amount} USD");

        var result = await _inner.ProcessExpenseAsync(amount);

        _logger.LogInformation($"Expense processed successfully: {result}");

        return result;
    }
}



Conditionally Register the Decorator in Program.cs

var builder = WebApplication.CreateBuilder(args);
var services = builder.Services;

// Register the base service
services.AddScoped<IExpenseService, ExpenseService>();

// Conditionally apply the decorator
bool useLoggingDecorator = builder.Configuration.GetValue<bool>("UseLoggingDecorator");

if (useLoggingDecorator)
{
    services.AddScoped<IExpenseService>(provider =>
    {
        var innerService = provider.GetRequiredService<ExpenseService>();
        var logger = provider.GetRequiredService<ILogger<LoggingExpenseServiceDecorator>>();
        return new LoggingExpenseServiceDecorator(innerService, logger);
    });
}

var app = builder.Build();


Enable/Disable Decorator Using Configuration

{
  "UseLoggingDecorator": true
}


* Set to true → Decorator will be applied.
* Set to false → Only the base service (ExpenseService) will be used.

How to register multiple implementations for the same interface and resolve it?

read

Use of Decorator Pattern

Approach 1

image image image

Approach 2

image

Approach 3 - Using Keyed Services to Register Multiple Implementations of the Same Interface

image image

When to use record in c#

read

In C#, record is a reference type introduced in C# 9 that is primarily used for immutable data models. It provides several benefits over traditional class and struct types, especially when dealing with data-centric applications.


Why Use record in C#?

1️⃣ Immutable Data Modeling

  • record types are designed to be immutable by default.
  • Unlike class, properties cannot be changed after object creation unless explicitly marked as mutable.
public record Person(string Name, int Age);
var person1 = new Person("Alice", 30);
// person1.Age = 31; // ❌ Compilation Error: Properties are readonly by default.

2️⃣ Concise and Readable Syntax

  • Reduces boilerplate code compared to class.
  • Supports primary constructor syntax for easier initialization.
public record Employee(string Name, string Department);

Instead of writing:

public class Employee
{
    public string Name { get; }
    public string Department { get; }
public Employee(string name, string department)
{
    Name = name;
    Department = department;
}

}


3️⃣ Value-Based Equality (Unlike Class)

  • Unlike class, where equality is reference-based, record compares by value.
  • Two records with the same property values are considered equal.
var e1 = new Employee("Bob", "IT");
var e2 = new Employee("Bob", "IT");

Console.WriteLine(e1 == e2); // ✅ True (Value-based equality)

Whereas, with a class:

public class Employee
{
    public string Name { get; }
    public string Department { get; }
public Employee(string name, string department)
{
    Name = name;
    Department = department;
}

}

var c1 = new Employee("Bob", "IT"); var c2 = new Employee("Bob", "IT");

Console.WriteLine(c1 == c2); // ❌ False (Reference-based equality)


4️⃣ Built-in with Expression for Object Copying

  • record supports a with expression for making shallow copies with modifications.
var emp1 = new Employee("Alice", "HR");
var emp2 = emp1 with { Department = "Finance" }; // Copy with modified property

Console.WriteLine(emp1); // Employee { Name = Alice, Department = HR } Console.WriteLine(emp2); // Employee { Name = Alice, Department = Finance }

  • This avoids manually cloning objects in class-based designs.

5️⃣ Supports Inheritance

  • Unlike struct, record supports inheritance.
public record Person(string Name);
public record Employee(string Name, string Role) : Person(Name);
  • Useful when defining domain models with hierarchical relationships.

When to Use record?

Use Case record or class?
Immutable Data Models ✅ record
DTOs (Data Transfer Objects) ✅ record
Configuration Objects ✅ record
Entity Framework Models ❌ class (EF requires mutable properties)
Reference Equality (e.g., Cache, Dependency Injection) ❌ class

Types of record in C#

1️⃣ Positional Record (Primary Constructor)

public record Product(string Name, decimal Price);
  • Auto-generates properties with get accessors.
  • Provides a built-in ToString(), equality, and with expression support.

2️⃣ Classic Record (Like a Class)

public record Product
{
    public string Name { get; init; } = "";
    public decimal Price { get; init; }
}
  • init makes properties settable only during initialization.

3️⃣ Record Struct (record struct)

  • Introduced in C# 10 for value-type record behavior.
public record struct Point(int X, int Y);
  • More memory-efficient for small, frequently used structures.

Conclusion

✅ Use record when:

  • You want immutable and value-based equality behavior.
  • You need concise, readable code with less boilerplate.
  • You require pattern matching and efficient copying with with.
    ❌ Avoid record when:
  • You need mutable objects (prefer class).
  • You work with Entity Framework (EF requires mutable properties).

Would you like a specific example of record usage in an application, such as ASP.NET Core or a REST API? 🚀

Best Security Practices for Securing Your .NET 9 API

read

🔒 Best Security Practices for Securing Your .NET 9 API

To make your .NET 9 API more secure and reliable, you should implement multiple security best practices. Below are the key areas to focus on:


1️⃣ Secure Authentication & Authorization

Use OAuth2.0 / OpenID Connect (OIDC)

  • Keycloak already provides OIDC-based authentication.
  • Ensure access tokens are validated correctly.

Use Role-Based Authorization (RBAC)

  • Implement role-based access control (RBAC) to restrict sensitive endpoints.
  • Example in Program.cs:
    app.MapGet("/admin", () => "Admin Access Only!")
        .RequireAuthorization(policy => policy.RequireRole("admin"));
    

Enforce Multi-Factor Authentication (MFA)

  • Enable MFA in Keycloak to require OTP-based authentication.
  • Go to Authentication → Required Actions → Configure OTP.

Restrict Token Scope

  • Limit token permissions using Keycloak’s client scopes.
  • Under Clients → my-dotnet-api → Client Scopes, define:
    • read:expenses
    • write:expenses

2️⃣ Secure API Endpoints

Use HTTPS Everywhere

  • Enforce HTTPS by redirecting HTTP traffic:
    app.UseHttpsRedirection();
    

Use Rate Limiting to Prevent DDoS

  • Implement Rate Limiting with AspNetCoreRateLimit:
    dotnet add package AspNetCoreRateLimit
    
  • Configure in Program.cs:
    builder.Services.AddRateLimiting(options =>
    {
        options.GlobalRules = new List<RateLimitRule>
        {
            new RateLimitRule
            {
                Endpoint = "*",
                Limit = 100,
                Period = "1m"
            }
        };
    });
    

Disable Unnecessary HTTP Methods

  • Only allow required HTTP methods in CORS policy:
    builder.Services.AddCors(options =>
    {
        options.AddPolicy("AllowSpecificOrigins", policy =>
        {
            policy.WithOrigins("https://trustedclient.com")
                  .AllowMethods("GET", "POST", "PUT", "DELETE")
                  .AllowHeaders("Authorization", "Content-Type");
        });
    });
    

3️⃣ Secure Token Management

Use Short-Lived Access Tokens

  • Reduce token expiration time to 15 minutes in Keycloak:
    • Go to Realm Settings → Tokens → Access Token Lifespan15m.

Use Refresh Tokens with Rotation

  • Enable refresh token rotation to prevent token reuse.

Revoke Tokens on Logout

  • Configure Keycloak to revoke refresh tokens upon logout.

4️⃣ Secure Data Storage

Encrypt Sensitive Data

  • Use AES-256 encryption for storing sensitive data.
  • Example using System.Security.Cryptography:
    public static string EncryptData(string text, string key)
    {
        using var aes = Aes.Create();
        aes.Key = Encoding.UTF8.GetBytes(key);
        aes.GenerateIV();
        using var encryptor = aes.CreateEncryptor();
        var bytes = Encoding.UTF8.GetBytes(text);
        var encrypted = encryptor.TransformFinalBlock(bytes, 0, bytes.Length);
        return Convert.ToBase64String(encrypted);
    }
    

Use Hashing for Passwords

  • Store hashed passwords with bcrypt:
    using BCrypt.Net;
    string hashedPassword = BCrypt.HashPassword("mypassword");
    bool isValid = BCrypt.Verify("mypassword", hashedPassword);
    

Use Database Connection String Encryption

  • Use Azure Key Vault or AWS Secrets Manager to store connection strings securely.

5️⃣ Prevent Injection Attacks

Use Parameterized Queries

  • Always use Entity Framework Core with parameterized queries:
    var expense = dbContext.Expenses
        .Where(e => e.Category == category)
        .FirstOrDefault();
    

Sanitize User Input

  • Use FluentValidation to validate API requests:
    dotnet add package FluentValidation.AspNetCore
    
  • Example validation for expense creation:
    public class ExpenseValidator : AbstractValidator<ExpenseDto>
    {
        public ExpenseValidator()
        {
            RuleFor(e => e.Title).NotEmpty();
            RuleFor(e => e.Amount).GreaterThan(0);
        }
    }
    

6️⃣ Secure Logging & Monitoring

Use Serilog for Structured Logging

  • Install Serilog:
    dotnet add package Serilog.AspNetCore
    
  • Configure in Program.cs:
    Log.Logger = new LoggerConfiguration()
        .WriteTo.Console()
        .WriteTo.File("logs/log.txt", rollingInterval: RollingInterval.Day)
        .CreateLogger();
    

    builder.Host.UseSerilog();

Enable API Auditing

  • Log failed authentication attempts:
    builder.Services.AddAuthentication()
        .AddJwtBearer(options =>
        {
            options.Events = new JwtBearerEvents
            {
                OnAuthenticationFailed = context =>
                {
                    Log.Error("Authentication failed: {Error}", context.Exception.Message);
                    return Task.CompletedTask;
                }
            };
        });
    

7️⃣ Secure Deployment

Use Docker for Secure API Deployment

  • Create a Dockerfile for containerization:

    FROM mcr.microsoft.com/dotnet/aspnet:9.0
    COPY ./publish /app
    WORKDIR /app
    ENTRYPOINT ["dotnet", "ExpenseManager.dll"]
    
  • Run API securely with Docker:

    docker build -t expense-api .
    docker run -d -p 5000:80 --env ASPNETCORE_ENVIRONMENT=Production expense-api
    

Deploy with Kubernetes and Pod Security

  • Use Kubernetes Network Policies to restrict API traffic.
  • Use Secrets Management for storing credentials:
    kubectl create secret generic api-secrets \
      --from-literal=DB_CONNECTION_STRING="Server=db;Database=expense;User Id=admin;Password=securepassword"
    

🎯 Summary

Security Aspect Best Practice
Authentication Use OAuth2.0 / OIDC with Keycloak
Authorization Implement Role-Based Access Control (RBAC)
Rate Limiting Prevent DDoS attacks using rate limiting
CORS Protection Restrict origins, methods, and headers
Data Security Encrypt sensitive data & hash passwords
Injection Prevention Use parameterized queries & input validation
Logging & Monitoring Implement Serilog & audit failed auth attempts
Deployment Security Use Docker, Kubernetes, and Secrets Management

By implementing these practices, your .NET 9 API will be safer, more resilient, and production-ready. 🚀

Making Your .NET 9 API Multi-Tenant for a SaaS Application

read

🏢 Making Your .NET 9 API Multi-Tenant for a SaaS Application

To convert your Expense Management API into a multi-tenant SaaS solution, follow these key strategies:


1️⃣ Choose a Multi-Tenancy Strategy

🔹 Database per Tenant (Isolated Model)

  • Each tenant gets its own database.
  • Best for: High-security applications where data isolation is critical.
  • Example:
    Tenant A → Database: ExpenseDB_TenantA  
    Tenant B → Database: ExpenseDB_TenantB  
    

🔹 Schema per Tenant

  • A single database with separate schemas for each tenant.
  • Best for: Medium-scale apps with balanced performance and cost.
  • Example:
    ExpenseDB  
        ├── TenantA.Expenses  
        ├── TenantB.Expenses  
    

🔹 Shared Schema with Tenant ID (Single Database)

  • A single database, single schema, where all tenant data is differentiated using a TenantID column.
  • Best for: SaaS startups and cost-effective solutions.
  • Example:
    Expenses Table  
        ├── ID  
        ├── TenantID  
        ├── Title  
        ├── Amount  
    

2️⃣ Implement Tenant Identification

Each incoming request should be identified and mapped to the correct tenant.

Option 1: Subdomain-Based Tenant Identification

  • URL Format: tenantA.example.com, tenantB.example.com

  • Extract Tenant from Host Name in Middleware:

    public class TenantMiddleware
    {
        private readonly RequestDelegate _next;
    
    public TenantMiddleware(RequestDelegate next) =&gt; _next = next;
    
    public async Task Invoke(HttpContext context)
    {
        var host = context.Request.Host.Host;
        var tenant = host.Split('.')[0]; // Extract subdomain as tenant identifier
        context.Items["Tenant"] = tenant;
        await _next(context);
    }
    

    }

    Register Middleware in Program.cs:

    app.UseMiddleware<TenantMiddleware>();
    

Option 2: Header-Based Tenant Identification

  • Clients send a custom header with each request:

    GET /expenses
    Host: api.example.com
    X-Tenant-ID: tenantA
    

    Extract Tenant from Request Header:

    public class TenantMiddleware
    {
        private readonly RequestDelegate _next;
        public TenantMiddleware(RequestDelegate next) => _next = next;
    
    public async Task Invoke(HttpContext context)
    {
        if (context.Request.Headers.TryGetValue("X-Tenant-ID", out var tenantId))
        {
            context.Items["Tenant"] = tenantId.ToString();
        }
        await _next(context);
    }
    

    }


3️⃣ Dynamic Connection String per Tenant

  • Use Entity Framework Core with a dynamic DbContext per tenant.

Modify DbContext to Support Multi-Tenancy

public class ExpenseDbContext : DbContext
{
    private readonly IHttpContextAccessor _httpContextAccessor;
public ExpenseDbContext(DbContextOptions&lt;ExpenseDbContext&gt; options, IHttpContextAccessor httpContextAccessor)
    : base(options)
{
    _httpContextAccessor = httpContextAccessor;
}

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    var tenant = _httpContextAccessor.HttpContext?.Items["Tenant"]?.ToString();
    if (!string.IsNullOrEmpty(tenant))
    {
        var connectionString = GetConnectionStringForTenant(tenant);
        optionsBuilder.UseSqlite(connectionString);
    }
}

private string GetConnectionStringForTenant(string tenant)
{
    // Example logic, ideally store these in a secure config
    var connections = new Dictionary&lt;string, string&gt;
    {
        { "tenantA", "Data Source=expense_tenantA.db" },
        { "tenantB", "Data Source=expense_tenantB.db" }
    };

    return connections.TryGetValue(tenant, out var conn) ? conn : "Data Source=default.db";
}

}

Register DbContext with Dependency Injection

builder.Services.AddDbContext<ExpenseDbContext>(options =>
    options.UseSqlite("Data Source=default.db"));

4️⃣ Apply Multi-Tenancy to Business Logic

Modify services and repositories to filter by TenantID in shared schema models.

Example: Multi-Tenant Expense Service

public class ExpenseService
{
    private readonly ExpenseDbContext _dbContext;
    private readonly IHttpContextAccessor _httpContextAccessor;
public ExpenseService(ExpenseDbContext dbContext, IHttpContextAccessor httpContextAccessor)
{
    _dbContext = dbContext;
    _httpContextAccessor = httpContextAccessor;
}

public async Task&lt;List&lt;Expense&gt;&gt; GetExpensesAsync()
{
    var tenantId = _httpContextAccessor.HttpContext?.Items["Tenant"]?.ToString();
    return await _dbContext.Expenses.Where(e =&gt; e.TenantID == tenantId).ToListAsync();
}

}


5️⃣ Tenant-Specific Authentication Using Keycloak

  • Create a Realm per Tenant in Keycloak.
  • Use OIDC Claims to Identify Tenant.

Extract Tenant from Keycloak Token

var tenantId = User.Claims.FirstOrDefault(c => c.Type == "tenant")?.Value;

Modify Authentication Policy to Validate Tenant

services.AddAuthorization(options =>
{
    options.AddPolicy("TenantPolicy", policy =>
        policy.RequireClaim("tenant"));
});

6️⃣ Implement Per-Tenant Caching

Use in-memory caching for performance.

Cache Tenant-Specific Categories

public class CategoryService
{
    private readonly IMemoryCache _cache;
    private readonly ExpenseDbContext _dbContext;
    private readonly IHttpContextAccessor _httpContextAccessor;
public CategoryService(IMemoryCache cache, ExpenseDbContext dbContext, IHttpContextAccessor httpContextAccessor)
{
    _cache = cache;
    _dbContext = dbContext;
    _httpContextAccessor = httpContextAccessor;
}

public async Task&lt;List&lt;Category&gt;&gt; GetCategoriesAsync()
{
    var tenant = _httpContextAccessor.HttpContext?.Items["Tenant"]?.ToString();
    if (_cache.TryGetValue($"categories_{tenant}", out List&lt;Category&gt; categories))
    {
        return categories;
    }

    categories = await _dbContext.Categories.Where(c =&gt; c.TenantID == tenant).ToListAsync();
    _cache.Set($"categories_{tenant}", categories, TimeSpan.FromMinutes(10));
    return categories;
}

}


7️⃣ Deployment Strategy for Multi-Tenant SaaS

Use Docker with Dynamic Environments

Modify the Dockerfile to use tenant-based environments:

FROM mcr.microsoft.com/dotnet/aspnet:9.0
WORKDIR /app
COPY . /app
ENV TENANT_ID=tenantA
ENTRYPOINT ["dotnet", "ExpenseManager.dll"]

Deploy to Kubernetes with Multi-Tenant Pods

  • Deploy each tenant-specific API instance with separate databases.
  • Use Kubernetes namespaces for tenant isolation.
apiVersion: apps/v1
kind: Deployment
metadata:
  name: expense-api-tenantA
  namespace: tenantA
spec:
  template:
    spec:
      containers:
      - name: expense-api
        env:
        - name: TENANT_ID
          value: tenantA

🚀 Summary

Multi-Tenancy Feature Implementation
Tenant Identification Subdomain, Header, or Keycloak Token
Database Strategy Isolated DB, Schema, or Shared Table
Dynamic Connection Tenant-based DbContext
Multi-Tenant Caching IMemoryCache per Tenant
Authorization Tenant-based Claims with Keycloak
Deployment Kubernetes Namespace per Tenant

Would you like a detailed step-by-step guide for any of these implementations? 🚀😃

Minimal API - Examples

read

References

⚠️ **GitHub.com Fallback** ⚠️