.NET - FullstackCodingGuy/Developer-Fundamentals GitHub Wiki
read
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️⃣ 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
- 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
- Middleware components process requests in a sequential order.
- Can short-circuit requests (e.g., authentication, caching).
- Configured in
Program.cs
withapp.UseMiddleware<>
.
app.Use(async (context, next) =>
{
Console.WriteLine($"Request: {context.Request.Method} {context.Request.Path}");
await next(); // Pass request to next middleware
});
Middleware | Purpose |
---|---|
UseExceptionHandler | Global error handling |
UseRouting | Determines request path |
UseAuthentication | Handles JWT, OAuth, etc. |
UseAuthorization | Verifies user permissions |
UseRateLimiter | Throttles requests |
- 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! 😊
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.
read
- https://timdeschryver.dev/blog/the-decorator-pattern-using-nets-dependency-injection
- https://medium.com/@anderson.buenogod/mastering-the-decorator-pattern-in-c-net-8-advanced-use-cases-and-best-practices-378974abe9be
Approach 1



Approach 2

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


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.
-
record
types are designed to be immutable by default. - Unlike
class
, properties cannot be changed after object creation unless explicitly marked asmutable
.
public record Person(string Name, int Age);
var person1 = new Person("Alice", 30);
// person1.Age = 31; // ❌ Compilation Error: Properties are readonly by default.
- 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;
}
}
- 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)
-
record
supports awith
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.
- 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.
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 |
public record Product(string Name, decimal Price);
- Auto-generates properties with
get
accessors. - Provides a built-in
ToString()
, equality, andwith
expression support.
public record Product
{
public string Name { get; init; } = "";
public decimal Price { get; init; }
}
-
init
makes properties settable only during initialization.
- 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.
✅ 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
.
❌ Avoidrecord
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? 🚀
read
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:
- Keycloak already provides OIDC-based authentication.
- Ensure access tokens are validated correctly.
- 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"));
- Enable MFA in Keycloak to require OTP-based authentication.
- Go to Authentication → Required Actions → Configure OTP.
- Limit token permissions using Keycloak’s client scopes.
- Under Clients → my-dotnet-api → Client Scopes, define:
read:expenses
write:expenses
- Enforce HTTPS by redirecting HTTP traffic:
app.UseHttpsRedirection();
- 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" } }; });
- 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"); }); });
- Reduce token expiration time to 15 minutes in Keycloak:
- Go to Realm Settings → Tokens → Access Token Lifespan → 15m.
- Enable refresh token rotation to prevent token reuse.
- Configure Keycloak to revoke refresh tokens upon logout.
- 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); }
- Store hashed passwords with bcrypt:
using BCrypt.Net; string hashedPassword = BCrypt.HashPassword("mypassword"); bool isValid = BCrypt.Verify("mypassword", hashedPassword);
- Use Azure Key Vault or AWS Secrets Manager to store connection strings securely.
- Always use Entity Framework Core with parameterized queries:
var expense = dbContext.Expenses .Where(e => e.Category == category) .FirstOrDefault();
- 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); } }
- 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();
- 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; } }; });
-
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
- 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"
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. 🚀
read
To convert your Expense Management API into a multi-tenant SaaS solution, follow these key strategies:
- 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
- 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
- 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
Each incoming request should be identified and mapped to the correct tenant.
-
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) => _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>();
-
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); }
}
- Use Entity Framework Core with a dynamic
DbContext
per tenant.
public class ExpenseDbContext : DbContext
{
private readonly IHttpContextAccessor _httpContextAccessor;
public ExpenseDbContext(DbContextOptions<ExpenseDbContext> 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<string, string>
{
{ "tenantA", "Data Source=expense_tenantA.db" },
{ "tenantB", "Data Source=expense_tenantB.db" }
};
return connections.TryGetValue(tenant, out var conn) ? conn : "Data Source=default.db";
}
}
builder.Services.AddDbContext<ExpenseDbContext>(options =>
options.UseSqlite("Data Source=default.db"));
Modify services and repositories to filter by TenantID in shared schema models.
public class ExpenseService
{
private readonly ExpenseDbContext _dbContext;
private readonly IHttpContextAccessor _httpContextAccessor;
public ExpenseService(ExpenseDbContext dbContext, IHttpContextAccessor httpContextAccessor)
{
_dbContext = dbContext;
_httpContextAccessor = httpContextAccessor;
}
public async Task<List<Expense>> GetExpensesAsync()
{
var tenantId = _httpContextAccessor.HttpContext?.Items["Tenant"]?.ToString();
return await _dbContext.Expenses.Where(e => e.TenantID == tenantId).ToListAsync();
}
}
- Create a Realm per Tenant in Keycloak.
- Use OIDC Claims to Identify Tenant.
var tenantId = User.Claims.FirstOrDefault(c => c.Type == "tenant")?.Value;
services.AddAuthorization(options =>
{
options.AddPolicy("TenantPolicy", policy =>
policy.RequireClaim("tenant"));
});
Use in-memory caching for performance.
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<List<Category>> GetCategoriesAsync()
{
var tenant = _httpContextAccessor.HttpContext?.Items["Tenant"]?.ToString();
if (_cache.TryGetValue($"categories_{tenant}", out List<Category> categories))
{
return categories;
}
categories = await _dbContext.Categories.Where(c => c.TenantID == tenant).ToListAsync();
_cache.Set($"categories_{tenant}", categories, TimeSpan.FromMinutes(10));
return categories;
}
}
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 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
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? 🚀😃
read
- Lord ChatGPT
- https://code-maze.com/aspnetcore-register-multiple-interface-implementations/
- https://learn.microsoft.com/en-us/aspnet/core/security/docker-compose-https?view=aspnetcore-9.0
- https://www.yogihosting.com/docker-https-aspnet-core/
- https://andrewlock.net/8-ways-to-set-the-urls-for-an-aspnetcore-app/