Claude Skill

dotnet-aspnet-core

Build, debug, modernize, or review ASP.NET Core applications with correct hosting, middleware, security, configuration, logging, and deployment patterns on current .NET.

LLM Mart · 0 points · 0 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download postpartum-genushyacinthus29-dotnet-skills-skills_dotnet-aspnet-core-bfa4ebd.zip · 585 KB
Part of postpartum-genushyacinthus29/dotnet-skills — 80 skills

Install

skills CLI npx skills add https://github.com/Postpartum-genushyacinthus29/dotnet-skills/tree/main/skills/dotnet-aspnet-core
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install postpartum-genushyacinthus29-dotnet-skills@llmmart
Git git clone https://github.com/Postpartum-genushyacinthus29/dotnet-skills.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole postpartum-genushyacinthus29/dotnet-skills collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

ASP.NET Core

Trigger On

  • working on ASP.NET Core apps, services, or middleware
  • changing auth, routing, configuration, hosting, or deployment behavior
  • deciding between ASP.NET Core sub-stacks such as Blazor, Minimal APIs, or controller APIs
  • debugging request pipeline issues
  • modernizing legacy ASP.NET to ASP.NET Core

Documentation

References

  • patterns.md - Detailed middleware patterns, security patterns, configuration patterns, DI patterns, error handling patterns, and logging patterns
  • anti-patterns.md - Common ASP.NET Core mistakes including HttpClient misuse, async anti-patterns, configuration errors, DI issues, middleware ordering problems, and security vulnerabilities

Workflow

  1. Detect the real hosting shape first:

    • top-level Program.cs structure
    • middleware order and registration
    • auth model (Identity, JWT, OAuth, cookies)
    • endpoint registrations and routing
  2. Follow the correct middleware order:

    ExceptionHandler → HttpsRedirection → Static Files → Routing
    → CORS → Authentication → Authorization → Rate Limiting
    → Response Caching → Custom Middleware → Endpoints
    
  3. Use built-in patterns correctly:

    • Prefer IOptions<T> / IOptionsSnapshot<T> for configuration
    • Use ILogger<T> for structured logging
    • Use IHttpClientFactory for HTTP clients (never new HttpClient())
    • Use IHostedService / BackgroundService for background work
  4. Route specialized work to specific skills:

    • UI and components → dotnet-blazor
    • Real-time → dotnet-signalr
    • RPC → dotnet-grpc
    • New HTTP APIs → dotnet-minimal-apis (prefer unless controllers needed)
    • Controller APIs → dotnet-web-api
  5. Validate with build, tests, and targeted endpoint checks.

Middleware Patterns

Correct Order Matters

var app = builder.Build();

app.UseExceptionHandler("/error");      // 1. Catch all exceptions
app.UseHsts();                          // 2. Security headers
app.UseHttpsRedirection();              // 3. HTTPS redirect
app.UseStaticFiles();                   // 4. Serve static files
app.UseRouting();                       // 5. Route matching
app.UseCors();                          // 6. CORS policy
app.UseAuthentication();                // 7. Who are you?
app.UseAuthorization();                 // 8. Can you access?
app.UseRateLimiter();                   // 9. Rate limiting
app.UseResponseCaching();               // 10. Response cache
app.MapControllers();                   // 11. Endpoints

Custom Middleware Pattern

public class RequestTimingMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger<RequestTimingMiddleware> _logger;

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

    public async Task InvokeAsync(HttpContext context)
    {
        var sw = Stopwatch.StartNew();
        await _next(context);
        _logger.LogInformation("Request {Path} completed in {Elapsed}ms",
            context.Request.Path, sw.ElapsedMilliseconds);
    }
}

Configuration Patterns

Strongly-Typed Options

// appsettings.json
{
  "EmailSettings": {
    "SmtpServer": "smtp.example.com",
    "Port": 587
  }
}

// Registration
builder.Services.Configure<EmailSettings>(
    builder.Configuration.GetSection("EmailSettings"));

// Usage
public class EmailService(IOptions<EmailSettings> options)
{
    private readonly EmailSettings _settings = options.Value;
}

Environment-Based Configuration

builder.Configuration
    .AddJsonFile("appsettings.json", optional: false)
    .AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json", optional: true)
    .AddEnvironmentVariables()
    .AddUserSecrets<Program>(optional: true);

Security Patterns

Authentication Setup

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true,
            ValidIssuer = builder.Configuration["Jwt:Issuer"],
            ValidAudience = builder.Configuration["Jwt:Audience"],
            IssuerSigningKey = new SymmetricSecurityKey(
                Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"]!))
        };
    });

Authorization Policies

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("AdminOnly", policy =>
        policy.RequireRole("Admin"));
    options.AddPolicy("MinAge18", policy =>
        policy.RequireClaim("Age", "18", "19", "20")); // simplified
});

Anti-Patterns to Avoid

Anti-Pattern Why It's Bad Better Approach
new HttpClient() Socket exhaustion IHttpClientFactory
Sync-over-async (Task.Result) Thread pool starvation await properly
Storing secrets in appsettings.json Security risk User Secrets, Key Vault
Catching all exceptions silently Hides bugs Use IExceptionHandler
async void in middleware Crashes process async Task
Missing HTTPS redirect Security risk UseHttpsRedirection()

Performance Best Practices

  1. Use async/await everywhere — avoid sync blocking calls
  2. Pool DbContext properly — use scoped lifetime
  3. Enable response compression — UseResponseCompression()
  4. Use output caching — UseOutputCache() for .NET 7+
  5. Profile with diagnostic tools — Visual Studio Diagnostic Tools, PerfView
  6. Avoid allocations in hot paths — use Span<T>, pooling

Deliver

  • production-credible ASP.NET Core code and config
  • a clear request pipeline and hosting story
  • verification that matches the affected endpoints and middleware
  • security headers and HTTPS configured correctly

Validate

  • middleware order is intentional and documented
  • security and configuration changes are explicit
  • endpoint behavior is covered by tests or smoke checks
  • no blocking calls in async context
  • secrets are not committed to source control
  • health checks are implemented for production readiness
Files (dotnet-skills)
  • references
    • anti-patterns.md 26.8 KB
      # ASP.NET Core Anti-Patterns Reference
      
      This document catalogs common mistakes and anti-patterns in ASP.NET Core development, with explanations of why they are problematic and how to fix them.
      
      ## Table of Contents
      
      - [HttpClient Anti-Patterns](#httpclient-anti-patterns)
      - [Async Anti-Patterns](#async-anti-patterns)
      - [Configuration and Secrets Anti-Patterns](#configuration-and-secrets-anti-patterns)
      - [Dependency Injection Anti-Patterns](#dependency-injection-anti-patterns)
      - [Middleware Anti-Patterns](#middleware-anti-patterns)
      - [Error Handling Anti-Patterns](#error-handling-anti-patterns)
      - [Security Anti-Patterns](#security-anti-patterns)
      - [Performance Anti-Patterns](#performance-anti-patterns)
      - [Controller Anti-Patterns](#controller-anti-patterns)
      - [Database and EF Core Anti-Patterns](#database-and-ef-core-anti-patterns)
      
      ---
      
      ## HttpClient Anti-Patterns
      
      ### Creating HttpClient Instances Directly
      
      **Anti-Pattern:**
      ```csharp
      public class WeatherService
      {
          public async Task<Weather> GetWeatherAsync(string city)
          {
              using var client = new HttpClient(); // BAD: Creates new socket each time
              var response = await client.GetAsync($"https://api.weather.com/{city}");
              return await response.Content.ReadFromJsonAsync<Weather>();
          }
      }
      ```
      
      **Problem:** Creating `HttpClient` directly leads to socket exhaustion. Each `HttpClient` instance opens new TCP connections and disposing it does not immediately release sockets (they stay in TIME_WAIT state).
      
      **Correct Approach:**
      ```csharp
      // Registration
      builder.Services.AddHttpClient<IWeatherService, WeatherService>(client =>
      {
          client.BaseAddress = new Uri("https://api.weather.com/");
      });
      
      // Service
      public class WeatherService : IWeatherService
      {
          private readonly HttpClient _client;
      
          public WeatherService(HttpClient client)
          {
              _client = client;
          }
      
          public async Task<Weather> GetWeatherAsync(string city)
          {
              var response = await _client.GetAsync(city);
              return await response.Content.ReadFromJsonAsync<Weather>();
          }
      }
      ```
      
      ### Using Static HttpClient Without Respecting DNS Changes
      
      **Anti-Pattern:**
      ```csharp
      public static class ApiClient
      {
          private static readonly HttpClient _client = new HttpClient
          {
              BaseAddress = new Uri("https://api.example.com/")
          };
      
          public static async Task<string> GetDataAsync()
          {
              return await _client.GetStringAsync("data");
          }
      }
      ```
      
      **Problem:** Static HttpClient instances cache DNS indefinitely. If the server's IP address changes, the client will continue to use the old IP.
      
      **Correct Approach:**
      ```csharp
      // Use IHttpClientFactory which handles DNS rotation
      builder.Services.AddHttpClient("api", client =>
      {
          client.BaseAddress = new Uri("https://api.example.com/");
      })
      .SetHandlerLifetime(TimeSpan.FromMinutes(5)); // Rotate handlers periodically
      ```
      
      ---
      
      ## Async Anti-Patterns
      
      ### Sync-Over-Async (Blocking on Async Code)
      
      **Anti-Pattern:**
      ```csharp
      public class UserController : ControllerBase
      {
          [HttpGet("{id}")]
          public IActionResult GetUser(int id)
          {
              // BAD: Blocking on async code
              var user = _userService.GetUserAsync(id).Result;
              // Or equally bad:
              var user2 = _userService.GetUserAsync(id).GetAwaiter().GetResult();
              // Or:
              var user3 = Task.Run(() => _userService.GetUserAsync(id)).Result;
      
              return Ok(user);
          }
      }
      ```
      
      **Problem:** Blocking on async code can cause deadlocks (especially with synchronization contexts) and wastes thread pool threads. The thread waits instead of being released to handle other requests.
      
      **Correct Approach:**
      ```csharp
      [HttpGet("{id}")]
      public async Task<IActionResult> GetUser(int id)
      {
          var user = await _userService.GetUserAsync(id);
          return Ok(user);
      }
      ```
      
      ### Async Void
      
      **Anti-Pattern:**
      ```csharp
      public class LoggingMiddleware
      {
          private readonly RequestDelegate _next;
      
          public LoggingMiddleware(RequestDelegate next)
          {
              _next = next;
          }
      
          public async void Invoke(HttpContext context) // BAD: async void
          {
              await LogRequestAsync(context);
              await _next(context);
          }
      }
      ```
      
      **Problem:** `async void` methods cannot be awaited, exceptions cannot be caught, and if an exception occurs it will crash the process.
      
      **Correct Approach:**
      ```csharp
      public async Task InvokeAsync(HttpContext context)
      {
          await LogRequestAsync(context);
          await _next(context);
      }
      ```
      
      ### Not Using ConfigureAwait in Library Code
      
      **Anti-Pattern (in library/shared code):**
      ```csharp
      public class DataService
      {
          public async Task<Data> GetDataAsync()
          {
              var result = await _repository.QueryAsync(); // May capture unnecessary context
              return ProcessData(result);
          }
      }
      ```
      
      **Problem:** In library code, not using `ConfigureAwait(false)` captures the synchronization context unnecessarily, which can cause performance issues and potential deadlocks when called from UI applications.
      
      **Correct Approach (for library code):**
      ```csharp
      public async Task<Data> GetDataAsync()
      {
          var result = await _repository.QueryAsync().ConfigureAwait(false);
          return ProcessData(result);
      }
      ```
      
      Note: In ASP.NET Core application code (controllers, services directly serving requests), this is less critical since ASP.NET Core does not have a synchronization context.
      
      ### Async Over Sync
      
      **Anti-Pattern:**
      ```csharp
      public Task<int> CalculateAsync(int value)
      {
          return Task.Run(() => Calculate(value)); // BAD: Wrapping sync in Task.Run
      }
      
      public Task SaveAsync(Data data)
      {
          Save(data);
          return Task.CompletedTask; // BAD: Fake async
      }
      ```
      
      **Problem:** Wrapping synchronous code in `Task.Run` wastes thread pool threads. Returning `Task.CompletedTask` after sync work is misleading and provides no benefit.
      
      **Correct Approach:**
      ```csharp
      // Either keep it synchronous if there's nothing to await
      public int Calculate(int value)
      {
          return value * 2;
      }
      
      // Or use truly async operations
      public async Task SaveAsync(Data data)
      {
          await _dbContext.SaveChangesAsync();
      }
      ```
      
      ---
      
      ## Configuration and Secrets Anti-Patterns
      
      ### Storing Secrets in appsettings.json
      
      **Anti-Pattern:**
      ```json
      {
        "ConnectionStrings": {
          "Database": "Server=prod.db.com;User=admin;Password=SuperSecret123!"
        },
        "Jwt": {
          "Key": "MySuperSecretKey12345"
        },
        "ThirdPartyApi": {
          "ApiKey": "sk-live-xxxxxxxxxxxxx"
        }
      }
      ```
      
      **Problem:** Secrets committed to source control are exposed to anyone with repository access. They persist in git history even if removed later.
      
      **Correct Approach:**
      ```csharp
      // Development: User Secrets
      // In terminal: dotnet user-secrets set "Jwt:Key" "development-key"
      
      // Production: Environment variables or secret stores
      builder.Configuration
          .AddEnvironmentVariables()
          .AddAzureKeyVault(new Uri("https://myvault.vault.azure.net/"),
              new DefaultAzureCredential());
      
      // appsettings.json should only contain non-sensitive defaults
      {
        "ConnectionStrings": {
          "Database": "" // Placeholder, real value from env/vault
        }
      }
      ```
      
      ### Hardcoding Configuration Values
      
      **Anti-Pattern:**
      ```csharp
      public class EmailService
      {
          public async Task SendAsync(string to, string subject, string body)
          {
              using var client = new SmtpClient("smtp.company.com", 587); // BAD: Hardcoded
              client.Credentials = new NetworkCredential("noreply@company.com", "password123");
              // ...
          }
      }
      ```
      
      **Correct Approach:**
      ```csharp
      public class EmailService
      {
          private readonly EmailSettings _settings;
      
          public EmailService(IOptions<EmailSettings> options)
          {
              _settings = options.Value;
          }
      
          public async Task SendAsync(string to, string subject, string body)
          {
              using var client = new SmtpClient(_settings.SmtpServer, _settings.Port);
              // ...
          }
      }
      ```
      
      ### Reading Configuration in Constructor
      
      **Anti-Pattern:**
      ```csharp
      public class ReportService
      {
          private readonly string _connectionString;
      
          public ReportService(IConfiguration configuration)
          {
              // BAD: Reading raw configuration in constructor
              _connectionString = configuration["ConnectionStrings:Reports"];
          }
      }
      ```
      
      **Problem:** Bypasses the options pattern, no validation, harder to test, service knows about configuration structure.
      
      **Correct Approach:**
      ```csharp
      public class ReportService
      {
          private readonly ReportSettings _settings;
      
          public ReportService(IOptions<ReportSettings> options)
          {
              _settings = options.Value;
          }
      }
      ```
      
      ---
      
      ## Dependency Injection Anti-Patterns
      
      ### Captive Dependency (Scoped in Singleton)
      
      **Anti-Pattern:**
      ```csharp
      builder.Services.AddSingleton<ICacheService, CacheService>();
      builder.Services.AddScoped<IUserContext, HttpUserContext>();
      
      public class CacheService : ICacheService
      {
          private readonly IUserContext _userContext; // BAD: Scoped in singleton
      
          public CacheService(IUserContext userContext)
          {
              _userContext = userContext; // This instance will be captured forever
          }
      }
      ```
      
      **Problem:** A scoped service injected into a singleton is "captured" and reused for all requests, leading to stale data, wrong user context, and thread safety issues.
      
      **Correct Approach:**
      ```csharp
      public class CacheService : ICacheService
      {
          private readonly IServiceScopeFactory _scopeFactory;
      
          public CacheService(IServiceScopeFactory scopeFactory)
          {
              _scopeFactory = scopeFactory;
          }
      
          public async Task<T> GetOrCreateAsync<T>(string key, Func<Task<T>> factory)
          {
              using var scope = _scopeFactory.CreateScope();
              var userContext = scope.ServiceProvider.GetRequiredService<IUserContext>();
              // Use userContext safely within this scope
          }
      }
      ```
      
      ### Service Locator Pattern
      
      **Anti-Pattern:**
      ```csharp
      public class OrderService
      {
          private readonly IServiceProvider _serviceProvider;
      
          public OrderService(IServiceProvider serviceProvider)
          {
              _serviceProvider = serviceProvider;
          }
      
          public async Task ProcessOrderAsync(Order order)
          {
              // BAD: Service locator hides dependencies
              var emailService = _serviceProvider.GetRequiredService<IEmailService>();
              var inventoryService = _serviceProvider.GetRequiredService<IInventoryService>();
              var paymentService = _serviceProvider.GetRequiredService<IPaymentService>();
          }
      }
      ```
      
      **Problem:** Hides dependencies, makes testing harder, class can request any service at any time, breaks explicit dependency declaration.
      
      **Correct Approach:**
      ```csharp
      public class OrderService
      {
          private readonly IEmailService _emailService;
          private readonly IInventoryService _inventoryService;
          private readonly IPaymentService _paymentService;
      
          public OrderService(
              IEmailService emailService,
              IInventoryService inventoryService,
              IPaymentService paymentService)
          {
              _emailService = emailService;
              _inventoryService = inventoryService;
              _paymentService = paymentService;
          }
      }
      ```
      
      ### Circular Dependencies
      
      **Anti-Pattern:**
      ```csharp
      public class ServiceA
      {
          public ServiceA(ServiceB serviceB) { }
      }
      
      public class ServiceB
      {
          public ServiceB(ServiceA serviceA) { } // BAD: Circular dependency
      }
      ```
      
      **Problem:** Container cannot resolve circular dependencies. This indicates a design problem.
      
      **Correct Approach:**
      ```csharp
      // Option 1: Extract shared logic into a third service
      public class SharedService { }
      public class ServiceA { public ServiceA(SharedService shared) { } }
      public class ServiceB { public ServiceB(SharedService shared) { } }
      
      // Option 2: Use lazy injection
      public class ServiceA
      {
          private readonly Lazy<ServiceB> _serviceB;
          public ServiceA(Lazy<ServiceB> serviceB) { _serviceB = serviceB; }
      }
      
      // Option 3: Use events/mediator pattern to decouple
      ```
      
      ---
      
      ## Middleware Anti-Patterns
      
      ### Wrong Middleware Order
      
      **Anti-Pattern:**
      ```csharp
      var app = builder.Build();
      
      app.MapControllers();              // BAD: Endpoints before auth
      app.UseAuthentication();
      app.UseAuthorization();
      app.UseRouting();                  // BAD: Routing after endpoints
      app.UseExceptionHandler("/error"); // BAD: Exception handler last
      ```
      
      **Problem:** Middleware order matters. Authentication before routing means route data is not available. Exception handler at the end will not catch exceptions from endpoints.
      
      **Correct Approach:**
      ```csharp
      app.UseExceptionHandler("/error"); // First - catches all exceptions
      app.UseHttpsRedirection();
      app.UseStaticFiles();
      app.UseRouting();
      app.UseAuthentication();           // After routing
      app.UseAuthorization();            // After authentication
      app.MapControllers();              // Last
      ```
      
      ### Blocking Operations in Middleware
      
      **Anti-Pattern:**
      ```csharp
      public class ValidationMiddleware
      {
          private readonly RequestDelegate _next;
      
          public ValidationMiddleware(RequestDelegate next)
          {
              _next = next;
          }
      
          public async Task InvokeAsync(HttpContext context)
          {
              // BAD: Synchronous blocking read
              using var reader = new StreamReader(context.Request.Body);
              var body = reader.ReadToEnd();
      
              // BAD: Blocking database call
              var isValid = _validator.Validate(body).Result;
      
              await _next(context);
          }
      }
      ```
      
      **Correct Approach:**
      ```csharp
      public async Task InvokeAsync(HttpContext context)
      {
          context.Request.EnableBuffering();
          using var reader = new StreamReader(context.Request.Body, leaveOpen: true);
          var body = await reader.ReadToEndAsync();
          context.Request.Body.Position = 0;
      
          var isValid = await _validator.ValidateAsync(body);
      
          await _next(context);
      }
      ```
      
      ### Not Calling Next Unintentionally
      
      **Anti-Pattern:**
      ```csharp
      public async Task InvokeAsync(HttpContext context)
      {
          if (SomeCondition(context))
          {
              await _next(context);
          }
          // BAD: If condition is false, pipeline stops silently
      }
      ```
      
      **Problem:** Pipeline stops without response, leading to hanging requests or empty responses.
      
      **Correct Approach:**
      ```csharp
      public async Task InvokeAsync(HttpContext context)
      {
          if (!SomeCondition(context))
          {
              context.Response.StatusCode = StatusCodes.Status400BadRequest;
              await context.Response.WriteAsJsonAsync(new { error = "Validation failed" });
              return; // Explicit short-circuit with response
          }
      
          await _next(context);
      }
      ```
      
      ---
      
      ## Error Handling Anti-Patterns
      
      ### Swallowing Exceptions
      
      **Anti-Pattern:**
      ```csharp
      public async Task<Order> GetOrderAsync(int id)
      {
          try
          {
              return await _repository.GetByIdAsync(id);
          }
          catch (Exception)
          {
              return null; // BAD: Exception swallowed, bug hidden
          }
      }
      ```
      
      **Problem:** Hides bugs, makes debugging impossible, caller does not know something went wrong.
      
      **Correct Approach:**
      ```csharp
      public async Task<Order?> GetOrderAsync(int id)
      {
          try
          {
              return await _repository.GetByIdAsync(id);
          }
          catch (SqlException ex) when (ex.Number == 2) // Specific exception
          {
              _logger.LogWarning(ex, "Database timeout while getting order {OrderId}", id);
              throw new ServiceUnavailableException("Database temporarily unavailable", ex);
          }
      }
      ```
      
      ### Exposing Exception Details to Users
      
      **Anti-Pattern:**
      ```csharp
      [HttpGet("{id}")]
      public async Task<IActionResult> GetUser(int id)
      {
          try
          {
              return Ok(await _userService.GetByIdAsync(id));
          }
          catch (Exception ex)
          {
              // BAD: Stack trace and internal details exposed
              return StatusCode(500, new
              {
                  error = ex.Message,
                  stackTrace = ex.StackTrace,
                  innerException = ex.InnerException?.Message
              });
          }
      }
      ```
      
      **Problem:** Exposes internal system details, potential security vulnerability, helps attackers understand system internals.
      
      **Correct Approach:**
      ```csharp
      // Use global exception handler with environment-aware responses
      public class GlobalExceptionHandler : IExceptionHandler
      {
          private readonly IHostEnvironment _environment;
          private readonly ILogger<GlobalExceptionHandler> _logger;
      
          public async ValueTask<bool> TryHandleAsync(
              HttpContext httpContext,
              Exception exception,
              CancellationToken cancellationToken)
          {
              _logger.LogError(exception, "Unhandled exception");
      
              var problemDetails = new ProblemDetails
              {
                  Status = StatusCodes.Status500InternalServerError,
                  Title = "An error occurred",
                  Detail = _environment.IsDevelopment() ? exception.Message : null
              };
      
              await httpContext.Response.WriteAsJsonAsync(problemDetails, cancellationToken);
              return true;
          }
      }
      ```
      
      ### Catch-All Without Logging
      
      **Anti-Pattern:**
      ```csharp
      try
      {
          await ProcessPaymentAsync(order);
      }
      catch (Exception)
      {
          // BAD: No logging, no context
          throw;
      }
      ```
      
      **Correct Approach:**
      ```csharp
      try
      {
          await ProcessPaymentAsync(order);
      }
      catch (PaymentException ex)
      {
          _logger.LogWarning(ex, "Payment failed for order {OrderId}: {Reason}",
              order.Id, ex.Reason);
          throw;
      }
      catch (Exception ex)
      {
          _logger.LogError(ex, "Unexpected error processing payment for order {OrderId}",
              order.Id);
          throw;
      }
      ```
      
      ---
      
      ## Security Anti-Patterns
      
      ### Missing HTTPS Redirection
      
      **Anti-Pattern:**
      ```csharp
      var app = builder.Build();
      app.MapControllers(); // BAD: No HTTPS enforcement
      app.Run();
      ```
      
      **Correct Approach:**
      ```csharp
      if (!app.Environment.IsDevelopment())
      {
          app.UseHsts();
      }
      app.UseHttpsRedirection();
      app.MapControllers();
      ```
      
      ### Disabled CORS (Allow All)
      
      **Anti-Pattern:**
      ```csharp
      builder.Services.AddCors(options =>
      {
          options.AddPolicy("AllowAll", policy =>
          {
              policy.AllowAnyOrigin()    // BAD: Any site can access
                    .AllowAnyMethod()
                    .AllowAnyHeader();
          });
      });
      ```
      
      **Correct Approach:**
      ```csharp
      builder.Services.AddCors(options =>
      {
          options.AddPolicy("Production", policy =>
          {
              policy.WithOrigins(
                      "https://app.example.com",
                      "https://admin.example.com")
                    .WithMethods("GET", "POST", "PUT", "DELETE")
                    .WithHeaders("Authorization", "Content-Type")
                    .AllowCredentials();
          });
      });
      ```
      
      ### SQL Injection via String Concatenation
      
      **Anti-Pattern:**
      ```csharp
      public async Task<User> GetUserAsync(string username)
      {
          // BAD: SQL injection vulnerability
          var sql = $"SELECT * FROM Users WHERE Username = '{username}'";
          return await _context.Users.FromSqlRaw(sql).FirstOrDefaultAsync();
      }
      ```
      
      **Correct Approach:**
      ```csharp
      public async Task<User> GetUserAsync(string username)
      {
          // Parameterized query
          return await _context.Users
              .FromSqlInterpolated($"SELECT * FROM Users WHERE Username = {username}")
              .FirstOrDefaultAsync();
      
          // Or use LINQ
          return await _context.Users
              .Where(u => u.Username == username)
              .FirstOrDefaultAsync();
      }
      ```
      
      ### Missing Authorization on Sensitive Endpoints
      
      **Anti-Pattern:**
      ```csharp
      [HttpDelete("{id}")]
      public async Task<IActionResult> DeleteUser(int id) // BAD: No authorization
      {
          await _userService.DeleteAsync(id);
          return NoContent();
      }
      ```
      
      **Correct Approach:**
      ```csharp
      [Authorize(Policy = "AdminOnly")]
      [HttpDelete("{id}")]
      public async Task<IActionResult> DeleteUser(int id)
      {
          await _userService.DeleteAsync(id);
          return NoContent();
      }
      ```
      
      ### Storing Passwords in Plain Text
      
      **Anti-Pattern:**
      ```csharp
      public async Task<User> CreateUserAsync(string email, string password)
      {
          var user = new User
          {
              Email = email,
              Password = password // BAD: Plain text password
          };
          await _context.SaveChangesAsync();
          return user;
      }
      ```
      
      **Correct Approach:**
      ```csharp
      // Use ASP.NET Core Identity or proper password hashing
      public async Task<User> CreateUserAsync(string email, string password)
      {
          var user = new User
          {
              Email = email,
              PasswordHash = _passwordHasher.HashPassword(null, password)
          };
          await _context.SaveChangesAsync();
          return user;
      }
      ```
      
      ---
      
      ## Performance Anti-Patterns
      
      ### Not Using Async I/O
      
      **Anti-Pattern:**
      ```csharp
      [HttpGet]
      public IActionResult GetReport()
      {
          // BAD: Synchronous database call blocks thread
          var data = _context.Reports.ToList();
          var file = File.ReadAllBytes("template.pdf"); // BAD: Sync file I/O
          return Ok(GenerateReport(data, file));
      }
      ```
      
      **Correct Approach:**
      ```csharp
      [HttpGet]
      public async Task<IActionResult> GetReport()
      {
          var data = await _context.Reports.ToListAsync();
          var file = await File.ReadAllBytesAsync("template.pdf");
          return Ok(GenerateReport(data, file));
      }
      ```
      
      ### Loading Entire Collections into Memory
      
      **Anti-Pattern:**
      ```csharp
      public async Task<decimal> GetTotalRevenueAsync()
      {
          // BAD: Loads all orders into memory
          var orders = await _context.Orders.ToListAsync();
          return orders.Sum(o => o.Total);
      }
      ```
      
      **Correct Approach:**
      ```csharp
      public async Task<decimal> GetTotalRevenueAsync()
      {
          // Computed in database
          return await _context.Orders.SumAsync(o => o.Total);
      }
      ```
      
      ### N+1 Query Problem
      
      **Anti-Pattern:**
      ```csharp
      public async Task<List<OrderDto>> GetOrdersAsync()
      {
          var orders = await _context.Orders.ToListAsync();
      
          return orders.Select(o => new OrderDto
          {
              Id = o.Id,
              // BAD: Each access triggers a separate query
              CustomerName = o.Customer.Name,
              Items = o.Items.Select(i => i.Name).ToList()
          }).ToList();
      }
      ```
      
      **Correct Approach:**
      ```csharp
      public async Task<List<OrderDto>> GetOrdersAsync()
      {
          return await _context.Orders
              .Include(o => o.Customer)
              .Include(o => o.Items)
              .Select(o => new OrderDto
              {
                  Id = o.Id,
                  CustomerName = o.Customer.Name,
                  Items = o.Items.Select(i => i.Name).ToList()
              })
              .ToListAsync();
      }
      ```
      
      ### Not Using Response Compression
      
      **Anti-Pattern:**
      ```csharp
      var app = builder.Build();
      app.MapControllers(); // BAD: Large responses sent uncompressed
      ```
      
      **Correct Approach:**
      ```csharp
      builder.Services.AddResponseCompression(options =>
      {
          options.EnableForHttps = true;
          options.Providers.Add<BrotliCompressionProvider>();
          options.Providers.Add<GzipCompressionProvider>();
      });
      
      builder.Services.Configure<BrotliCompressionProviderOptions>(options =>
      {
          options.Level = CompressionLevel.Fastest;
      });
      
      app.UseResponseCompression();
      app.MapControllers();
      ```
      
      ---
      
      ## Controller Anti-Patterns
      
      ### Fat Controllers
      
      **Anti-Pattern:**
      ```csharp
      [HttpPost]
      public async Task<IActionResult> CreateOrder(CreateOrderRequest request)
      {
          // BAD: Business logic in controller
          if (request.Items.Count == 0)
              return BadRequest("Order must have items");
      
          var customer = await _context.Customers.FindAsync(request.CustomerId);
          if (customer == null)
              return NotFound("Customer not found");
      
          var order = new Order { CustomerId = request.CustomerId };
      
          foreach (var item in request.Items)
          {
              var product = await _context.Products.FindAsync(item.ProductId);
              if (product == null)
                  return BadRequest($"Product {item.ProductId} not found");
      
              if (product.Stock < item.Quantity)
                  return BadRequest($"Insufficient stock for {product.Name}");
      
              product.Stock -= item.Quantity;
              order.Items.Add(new OrderItem
              {
                  ProductId = product.Id,
                  Quantity = item.Quantity,
                  Price = product.Price
              });
          }
      
          order.Total = order.Items.Sum(i => i.Price * i.Quantity);
          _context.Orders.Add(order);
          await _context.SaveChangesAsync();
      
          await _emailService.SendOrderConfirmationAsync(customer.Email, order);
      
          return CreatedAtAction(nameof(GetOrder), new { id = order.Id }, order);
      }
      ```
      
      **Correct Approach:**
      ```csharp
      [HttpPost]
      public async Task<IActionResult> CreateOrder(CreateOrderRequest request)
      {
          var result = await _orderService.CreateOrderAsync(request);
      
          return result.Match<IActionResult>(
              success => CreatedAtAction(nameof(GetOrder), new { id = success.OrderId }, success),
              notFound => NotFound(notFound.Message),
              validation => BadRequest(validation.Errors));
      }
      ```
      
      ### Not Using Action Filters
      
      **Anti-Pattern:**
      ```csharp
      [HttpPost]
      public async Task<IActionResult> Create(CreateRequest request)
      {
          // BAD: Manual validation in every action
          if (!ModelState.IsValid)
          {
              return BadRequest(ModelState);
          }
          // ...
      }
      ```
      
      **Correct Approach:**
      ```csharp
      // Configure globally
      builder.Services.Configure<ApiBehaviorOptions>(options =>
      {
          options.SuppressModelStateInvalidFilter = false; // Enable automatic validation
      });
      
      // Or use a filter
      public class ValidateModelAttribute : ActionFilterAttribute
      {
          public override void OnActionExecuting(ActionExecutingContext context)
          {
              if (!context.ModelState.IsValid)
              {
                  context.Result = new BadRequestObjectResult(context.ModelState);
              }
          }
      }
      ```
      
      ---
      
      ## Database and EF Core Anti-Patterns
      
      ### Using DbContext as Singleton
      
      **Anti-Pattern:**
      ```csharp
      builder.Services.AddSingleton<MyDbContext>(); // BAD: DbContext is not thread-safe
      ```
      
      **Problem:** `DbContext` is not thread-safe and tracks entities. Using it as singleton causes concurrency issues and memory leaks.
      
      **Correct Approach:**
      ```csharp
      builder.Services.AddDbContext<MyDbContext>(options =>
          options.UseSqlServer(connectionString));
      // Default is scoped lifetime
      ```
      
      ### Not Disposing DbContext (Manual Creation)
      
      **Anti-Pattern:**
      ```csharp
      public class ReportService
      {
          private readonly MyDbContext _context = new MyDbContext(); // BAD: Never disposed
      }
      ```
      
      **Correct Approach:**
      ```csharp
      public class ReportService
      {
          private readonly MyDbContext _context;
      
          public ReportService(MyDbContext context)
          {
              _context = context; // Injected, container manages lifetime
          }
      }
      ```
      
      ### Tracking Entities When Not Needed
      
      **Anti-Pattern:**
      ```csharp
      public async Task<List<ProductDto>> GetProductsAsync()
      {
          // BAD: Tracking enabled for read-only query
          var products = await _context.Products.ToListAsync();
          return products.Select(p => new ProductDto { Name = p.Name }).ToList();
      }
      ```
      
      **Correct Approach:**
      ```csharp
      public async Task<List<ProductDto>> GetProductsAsync()
      {
          return await _context.Products
              .AsNoTracking()
              .Select(p => new ProductDto { Name = p.Name })
              .ToListAsync();
      }
      ```
      
      ### Lazy Loading in Web Applications
      
      **Anti-Pattern:**
      ```csharp
      builder.Services.AddDbContext<MyDbContext>(options =>
          options.UseLazyLoadingProxies() // BAD: Hidden N+1 queries
                 .UseSqlServer(connectionString));
      ```
      
      **Problem:** Lazy loading causes unexpected database queries, N+1 problems, and makes it hard to track what data is being loaded.
      
      **Correct Approach:**
      ```csharp
      // Use explicit loading with Include/ThenInclude
      var orders = await _context.Orders
          .Include(o => o.Customer)
          .Include(o => o.Items)
              .ThenInclude(i => i.Product)
          .ToListAsync();
      
      // Or use projection
      var orderDtos = await _context.Orders
          .Select(o => new OrderDto
          {
              Id = o.Id,
              CustomerName = o.Customer.Name
          })
          .ToListAsync();
      ```
      
    • patterns.md 29.3 KB
      # ASP.NET Core Patterns Reference
      
      This document provides detailed patterns for middleware, security, and configuration in ASP.NET Core applications.
      
      ## Table of Contents
      
      - [Middleware Patterns](#middleware-patterns)
      - [Security Patterns](#security-patterns)
      - [Configuration Patterns](#configuration-patterns)
      - [Dependency Injection Patterns](#dependency-injection-patterns)
      - [Error Handling Patterns](#error-handling-patterns)
      - [Logging Patterns](#logging-patterns)
      
      ---
      
      ## Middleware Patterns
      
      ### Pipeline Architecture
      
      The ASP.NET Core request pipeline consists of middleware components that process requests in sequence. Each middleware can:
      - Handle the request and short-circuit the pipeline
      - Pass the request to the next middleware
      - Execute code before and after the next middleware
      
      ### Canonical Middleware Order
      
      ```csharp
      var app = builder.Build();
      
      // 1. Exception handling - must be first to catch all exceptions
      app.UseExceptionHandler("/error");
      
      // 2. HSTS - HTTP Strict Transport Security header
      app.UseHsts();
      
      // 3. HTTPS redirection - before any content is served
      app.UseHttpsRedirection();
      
      // 4. Static files - serve before routing for performance
      app.UseStaticFiles();
      
      // 5. Routing - must precede authentication/authorization
      app.UseRouting();
      
      // 6. CORS - after routing, before auth
      app.UseCors("PolicyName");
      
      // 7. Authentication - identify the user
      app.UseAuthentication();
      
      // 8. Authorization - verify access rights
      app.UseAuthorization();
      
      // 9. Rate limiting - protect endpoints
      app.UseRateLimiter();
      
      // 10. Response caching - cache authorized responses
      app.UseResponseCaching();
      
      // 11. Output caching (.NET 7+) - server-side caching
      app.UseOutputCache();
      
      // 12. Custom middleware - business logic
      app.UseMiddleware<CustomMiddleware>();
      
      // 13. Endpoints - terminal middleware
      app.MapControllers();
      app.MapRazorPages();
      app.MapBlazorHub();
      ```
      
      ### Class-Based Middleware Pattern
      
      ```csharp
      public class RequestTimingMiddleware
      {
          private readonly RequestDelegate _next;
          private readonly ILogger<RequestTimingMiddleware> _logger;
      
          public RequestTimingMiddleware(RequestDelegate next, ILogger<RequestTimingMiddleware> logger)
          {
              _next = next;
              _logger = logger;
          }
      
          public async Task InvokeAsync(HttpContext context)
          {
              var stopwatch = Stopwatch.StartNew();
      
              try
              {
                  await _next(context);
              }
              finally
              {
                  stopwatch.Stop();
                  _logger.LogInformation(
                      "Request {Method} {Path} completed in {ElapsedMs}ms with status {StatusCode}",
                      context.Request.Method,
                      context.Request.Path,
                      stopwatch.ElapsedMilliseconds,
                      context.Response.StatusCode);
              }
          }
      }
      
      // Extension method for clean registration
      public static class RequestTimingMiddlewareExtensions
      {
          public static IApplicationBuilder UseRequestTiming(this IApplicationBuilder builder)
          {
              return builder.UseMiddleware<RequestTimingMiddleware>();
          }
      }
      ```
      
      ### Convention-Based Middleware with Dependencies
      
      ```csharp
      public class TenantResolutionMiddleware
      {
          private readonly RequestDelegate _next;
      
          public TenantResolutionMiddleware(RequestDelegate next)
          {
              _next = next;
          }
      
          // Scoped services can be injected into InvokeAsync
          public async Task InvokeAsync(
              HttpContext context,
              ITenantService tenantService,
              ILogger<TenantResolutionMiddleware> logger)
          {
              var tenantId = context.Request.Headers["X-Tenant-Id"].FirstOrDefault();
      
              if (string.IsNullOrEmpty(tenantId))
              {
                  context.Response.StatusCode = StatusCodes.Status400BadRequest;
                  await context.Response.WriteAsJsonAsync(new { error = "Tenant header required" });
                  return;
              }
      
              var tenant = await tenantService.GetTenantAsync(tenantId);
              if (tenant is null)
              {
                  context.Response.StatusCode = StatusCodes.Status404NotFound;
                  await context.Response.WriteAsJsonAsync(new { error = "Tenant not found" });
                  return;
              }
      
              context.Items["Tenant"] = tenant;
              logger.LogDebug("Resolved tenant {TenantId}", tenantId);
      
              await _next(context);
          }
      }
      ```
      
      ### Inline Middleware for Simple Cases
      
      ```csharp
      // Use for simple, non-reusable middleware
      app.Use(async (context, next) =>
      {
          context.Response.Headers.Append("X-Content-Type-Options", "nosniff");
          await next(context);
      });
      
      // Terminal middleware (does not call next)
      app.Map("/health", app => app.Run(async context =>
      {
          context.Response.ContentType = "application/json";
          await context.Response.WriteAsJsonAsync(new { status = "healthy" });
      }));
      ```
      
      ### Conditional Middleware
      
      ```csharp
      // Apply middleware only for specific paths
      app.UseWhen(
          context => context.Request.Path.StartsWithSegments("/api"),
          appBuilder => appBuilder.UseMiddleware<ApiLoggingMiddleware>());
      
      // Branch the pipeline
      app.MapWhen(
          context => context.Request.Query.ContainsKey("debug"),
          appBuilder => appBuilder.UseMiddleware<DebugMiddleware>());
      ```
      
      ### Short-Circuiting Middleware
      
      ```csharp
      public class MaintenanceModeMiddleware
      {
          private readonly RequestDelegate _next;
          private readonly IOptions<MaintenanceOptions> _options;
      
          public MaintenanceModeMiddleware(RequestDelegate next, IOptions<MaintenanceOptions> options)
          {
              _next = next;
              _options = options;
          }
      
          public async Task InvokeAsync(HttpContext context)
          {
              if (_options.Value.IsEnabled)
              {
                  context.Response.StatusCode = StatusCodes.Status503ServiceUnavailable;
                  context.Response.Headers.RetryAfter = _options.Value.RetryAfterSeconds.ToString();
                  await context.Response.WriteAsJsonAsync(new
                  {
                      message = "Service under maintenance",
                      estimatedReturn = _options.Value.EstimatedReturn
                  });
                  return; // Short-circuit - do not call next
              }
      
              await _next(context);
          }
      }
      ```
      
      ---
      
      ## Security Patterns
      
      ### JWT Authentication Setup
      
      ```csharp
      builder.Services.AddAuthentication(options =>
      {
          options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
          options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
      })
      .AddJwtBearer(options =>
      {
          options.TokenValidationParameters = new TokenValidationParameters
          {
              ValidateIssuer = true,
              ValidateAudience = true,
              ValidateLifetime = true,
              ValidateIssuerSigningKey = true,
              ValidIssuer = builder.Configuration["Jwt:Issuer"],
              ValidAudience = builder.Configuration["Jwt:Audience"],
              IssuerSigningKey = new SymmetricSecurityKey(
                  Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"]!)),
              ClockSkew = TimeSpan.FromMinutes(1) // Reduce default 5-minute skew
          };
      
          options.Events = new JwtBearerEvents
          {
              OnAuthenticationFailed = context =>
              {
                  if (context.Exception is SecurityTokenExpiredException)
                  {
                      context.Response.Headers.Append("Token-Expired", "true");
                  }
                  return Task.CompletedTask;
              },
              OnTokenValidated = context =>
              {
                  // Add custom claims or perform additional validation
                  return Task.CompletedTask;
              }
          };
      });
      ```
      
      ### Cookie Authentication with Security Best Practices
      
      ```csharp
      builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
          .AddCookie(options =>
          {
              options.Cookie.Name = "AppAuth";
              options.Cookie.HttpOnly = true;
              options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
              options.Cookie.SameSite = SameSiteMode.Strict;
              options.ExpireTimeSpan = TimeSpan.FromHours(8);
              options.SlidingExpiration = true;
              options.LoginPath = "/auth/login";
              options.LogoutPath = "/auth/logout";
              options.AccessDeniedPath = "/auth/access-denied";
      
              options.Events = new CookieAuthenticationEvents
              {
                  OnValidatePrincipal = async context =>
                  {
                      // Validate user still exists and is active
                      var userService = context.HttpContext.RequestServices
                          .GetRequiredService<IUserService>();
                      var userId = context.Principal?.FindFirst(ClaimTypes.NameIdentifier)?.Value;
      
                      if (userId is null || !await userService.IsUserActiveAsync(userId))
                      {
                          context.RejectPrincipal();
                          await context.HttpContext.SignOutAsync();
                      }
                  }
              };
          });
      ```
      
      ### Policy-Based Authorization
      
      ```csharp
      builder.Services.AddAuthorization(options =>
      {
          // Simple role-based policy
          options.AddPolicy("AdminOnly", policy =>
              policy.RequireRole("Admin"));
      
          // Claim-based policy
          options.AddPolicy("VerifiedEmail", policy =>
              policy.RequireClaim("email_verified", "true"));
      
          // Multiple requirements (AND)
          options.AddPolicy("SeniorAdmin", policy =>
              policy.RequireRole("Admin")
                    .RequireClaim("experience_years", "5", "6", "7", "8", "9", "10"));
      
          // Custom requirement
          options.AddPolicy("MinimumAge", policy =>
              policy.Requirements.Add(new MinimumAgeRequirement(18)));
      
          // Resource-based authorization setup
          options.AddPolicy("DocumentOwner", policy =>
              policy.Requirements.Add(new DocumentOwnerRequirement()));
      });
      
      // Custom requirement
      public class MinimumAgeRequirement : IAuthorizationRequirement
      {
          public int MinimumAge { get; }
          public MinimumAgeRequirement(int minimumAge) => MinimumAge = minimumAge;
      }
      
      // Handler for custom requirement
      public class MinimumAgeHandler : AuthorizationHandler<MinimumAgeRequirement>
      {
          protected override Task HandleRequirementAsync(
              AuthorizationHandlerContext context,
              MinimumAgeRequirement requirement)
          {
              var birthDateClaim = context.User.FindFirst("birth_date");
              if (birthDateClaim is null)
              {
                  return Task.CompletedTask;
              }
      
              if (DateTime.TryParse(birthDateClaim.Value, out var birthDate))
              {
                  var age = DateTime.Today.Year - birthDate.Year;
                  if (birthDate.Date > DateTime.Today.AddYears(-age)) age--;
      
                  if (age >= requirement.MinimumAge)
                  {
                      context.Succeed(requirement);
                  }
              }
      
              return Task.CompletedTask;
          }
      }
      
      // Register handler
      builder.Services.AddSingleton<IAuthorizationHandler, MinimumAgeHandler>();
      ```
      
      ### Resource-Based Authorization
      
      ```csharp
      public class DocumentOwnerRequirement : IAuthorizationRequirement { }
      
      public class DocumentOwnerHandler : AuthorizationHandler<DocumentOwnerRequirement, Document>
      {
          protected override Task HandleRequirementAsync(
              AuthorizationHandlerContext context,
              DocumentOwnerRequirement requirement,
              Document resource)
          {
              var userId = context.User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
      
              if (userId == resource.OwnerId)
              {
                  context.Succeed(requirement);
              }
      
              return Task.CompletedTask;
          }
      }
      
      // Usage in controller
      public class DocumentsController : ControllerBase
      {
          private readonly IAuthorizationService _authorizationService;
      
          public DocumentsController(IAuthorizationService authorizationService)
          {
              _authorizationService = authorizationService;
          }
      
          [HttpPut("{id}")]
          public async Task<IActionResult> Update(int id, DocumentUpdateDto dto)
          {
              var document = await _repository.GetByIdAsync(id);
              if (document is null) return NotFound();
      
              var authResult = await _authorizationService.AuthorizeAsync(
                  User, document, "DocumentOwner");
      
              if (!authResult.Succeeded)
              {
                  return Forbid();
              }
      
              // Proceed with update
              return Ok();
          }
      }
      ```
      
      ### Security Headers Middleware
      
      ```csharp
      public class SecurityHeadersMiddleware
      {
          private readonly RequestDelegate _next;
      
          public SecurityHeadersMiddleware(RequestDelegate next)
          {
              _next = next;
          }
      
          public async Task InvokeAsync(HttpContext context)
          {
              // Prevent MIME type sniffing
              context.Response.Headers.XContentTypeOptions = "nosniff";
      
              // Prevent clickjacking
              context.Response.Headers.XFrameOptions = "DENY";
      
              // XSS protection (legacy browsers)
              context.Response.Headers["X-XSS-Protection"] = "1; mode=block";
      
              // Referrer policy
              context.Response.Headers["Referrer-Policy"] = "strict-origin-when-cross-origin";
      
              // Permissions policy
              context.Response.Headers["Permissions-Policy"] =
                  "accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()";
      
              // Content Security Policy (customize based on your needs)
              context.Response.Headers.ContentSecurityPolicy =
                  "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:;";
      
              await _next(context);
          }
      }
      ```
      
      ### CORS Configuration
      
      ```csharp
      builder.Services.AddCors(options =>
      {
          // Named policy for specific origins
          options.AddPolicy("AllowSpecificOrigins", policy =>
          {
              policy.WithOrigins(
                      "https://app.example.com",
                      "https://admin.example.com")
                  .AllowAnyHeader()
                  .AllowAnyMethod()
                  .AllowCredentials();
          });
      
          // Policy for API clients
          options.AddPolicy("ApiClients", policy =>
          {
              policy.WithOrigins(builder.Configuration.GetSection("Cors:AllowedOrigins").Get<string[]>()!)
                  .WithHeaders("Authorization", "Content-Type", "X-Requested-With")
                  .WithMethods("GET", "POST", "PUT", "DELETE")
                  .SetPreflightMaxAge(TimeSpan.FromMinutes(10));
          });
      });
      
      // Apply globally or per-endpoint
      app.UseCors("AllowSpecificOrigins");
      
      // Or per-controller
      [EnableCors("ApiClients")]
      public class ApiController : ControllerBase { }
      ```
      
      ### Rate Limiting (.NET 7+)
      
      ```csharp
      builder.Services.AddRateLimiter(options =>
      {
          options.RejectionStatusCode = StatusCodes.Status429TooManyRequests;
      
          // Fixed window limiter
          options.AddFixedWindowLimiter("fixed", opt =>
          {
              opt.Window = TimeSpan.FromMinutes(1);
              opt.PermitLimit = 100;
              opt.QueueLimit = 10;
              opt.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
          });
      
          // Sliding window limiter
          options.AddSlidingWindowLimiter("sliding", opt =>
          {
              opt.Window = TimeSpan.FromMinutes(1);
              opt.SegmentsPerWindow = 6;
              opt.PermitLimit = 100;
          });
      
          // Token bucket limiter
          options.AddTokenBucketLimiter("token", opt =>
          {
              opt.TokenLimit = 100;
              opt.ReplenishmentPeriod = TimeSpan.FromSeconds(10);
              opt.TokensPerPeriod = 10;
          });
      
          // Per-user rate limiting
          options.AddPolicy("per-user", context =>
          {
              var userId = context.User.FindFirst(ClaimTypes.NameIdentifier)?.Value
                  ?? context.Connection.RemoteIpAddress?.ToString()
                  ?? "anonymous";
      
              return RateLimitPartition.GetFixedWindowLimiter(
                  partitionKey: userId,
                  factory: _ => new FixedWindowRateLimiterOptions
                  {
                      Window = TimeSpan.FromMinutes(1),
                      PermitLimit = 60
                  });
          });
      
          options.OnRejected = async (context, token) =>
          {
              context.HttpContext.Response.ContentType = "application/json";
              await context.HttpContext.Response.WriteAsJsonAsync(new
              {
                  error = "Too many requests",
                  retryAfter = context.Lease.TryGetMetadata(MetadataName.RetryAfter, out var retryAfter)
                      ? retryAfter.TotalSeconds
                      : 60
              }, token);
          };
      });
      
      app.UseRateLimiter();
      
      // Apply to endpoints
      app.MapGet("/api/data", () => "data")
          .RequireRateLimiting("per-user");
      ```
      
      ---
      
      ## Configuration Patterns
      
      ### Strongly-Typed Options with Validation
      
      ```csharp
      public class EmailSettings
      {
          public const string SectionName = "Email";
      
          [Required]
          public string SmtpServer { get; set; } = string.Empty;
      
          [Range(1, 65535)]
          public int Port { get; set; } = 587;
      
          [Required, EmailAddress]
          public string FromAddress { get; set; } = string.Empty;
      
          public bool UseSsl { get; set; } = true;
      
          [Range(1, 300)]
          public int TimeoutSeconds { get; set; } = 30;
      }
      
      // Registration with validation
      builder.Services.AddOptions<EmailSettings>()
          .Bind(builder.Configuration.GetSection(EmailSettings.SectionName))
          .ValidateDataAnnotations()
          .ValidateOnStart(); // Fail fast on startup if invalid
      
      // Custom validation
      builder.Services.AddOptions<DatabaseSettings>()
          .Bind(builder.Configuration.GetSection("Database"))
          .Validate(settings =>
          {
              return !string.IsNullOrEmpty(settings.ConnectionString)
                  && settings.MaxPoolSize >= settings.MinPoolSize;
          }, "Database configuration is invalid")
          .ValidateOnStart();
      ```
      
      ### Options Interfaces Usage
      
      ```csharp
      public class EmailService
      {
          // IOptions<T> - singleton, read once at startup
          public EmailService(IOptions<EmailSettings> options)
          {
              var settings = options.Value;
          }
      }
      
      public class NotificationService
      {
          // IOptionsSnapshot<T> - scoped, re-reads config per request
          public NotificationService(IOptionsSnapshot<EmailSettings> options)
          {
              var settings = options.Value;
          }
      }
      
      public class BackgroundEmailProcessor : BackgroundService
      {
          // IOptionsMonitor<T> - singleton with change notifications
          private readonly IOptionsMonitor<EmailSettings> _optionsMonitor;
          private EmailSettings _currentSettings;
      
          public BackgroundEmailProcessor(IOptionsMonitor<EmailSettings> optionsMonitor)
          {
              _optionsMonitor = optionsMonitor;
              _currentSettings = optionsMonitor.CurrentValue;
      
              _optionsMonitor.OnChange(settings =>
              {
                  _currentSettings = settings;
                  // Handle configuration change
              });
          }
      }
      ```
      
      ### Multi-Environment Configuration
      
      ```csharp
      var builder = WebApplication.CreateBuilder(args);
      
      // Configuration sources (later sources override earlier ones)
      builder.Configuration
          .SetBasePath(builder.Environment.ContentRootPath)
          .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
          .AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json", optional: true, reloadOnChange: true)
          .AddJsonFile("appsettings.local.json", optional: true, reloadOnChange: true) // Git-ignored local overrides
          .AddEnvironmentVariables()
          .AddCommandLine(args);
      
      // Development-only sources
      if (builder.Environment.IsDevelopment())
      {
          builder.Configuration.AddUserSecrets<Program>();
      }
      
      // Production secret stores
      if (builder.Environment.IsProduction())
      {
          builder.Configuration.AddAzureKeyVault(
              new Uri($"https://{builder.Configuration["KeyVault:Name"]}.vault.azure.net/"),
              new DefaultAzureCredential());
      }
      ```
      
      ### Named Options Pattern
      
      ```csharp
      // appsettings.json
      {
        "Storage": {
          "Primary": {
            "ConnectionString": "...",
            "ContainerName": "primary"
          },
          "Backup": {
            "ConnectionString": "...",
            "ContainerName": "backup"
          }
        }
      }
      
      // Registration
      builder.Services.Configure<StorageSettings>("Primary",
          builder.Configuration.GetSection("Storage:Primary"));
      builder.Services.Configure<StorageSettings>("Backup",
          builder.Configuration.GetSection("Storage:Backup"));
      
      // Usage with IOptionsSnapshot
      public class StorageService
      {
          private readonly StorageSettings _primarySettings;
          private readonly StorageSettings _backupSettings;
      
          public StorageService(IOptionsSnapshot<StorageSettings> options)
          {
              _primarySettings = options.Get("Primary");
              _backupSettings = options.Get("Backup");
          }
      }
      ```
      
      ### Post-Configure and Configure All
      
      ```csharp
      // Post-configure runs after all Configure calls
      builder.Services.PostConfigure<EmailSettings>(settings =>
      {
          // Apply defaults or computed values
          if (string.IsNullOrEmpty(settings.FromAddress))
          {
              settings.FromAddress = "noreply@example.com";
          }
      });
      
      // Configure all instances of an options type
      builder.Services.ConfigureAll<StorageSettings>(settings =>
      {
          settings.TimeoutSeconds = 30; // Apply to all named instances
      });
      ```
      
      ---
      
      ## Dependency Injection Patterns
      
      ### Service Lifetimes
      
      ```csharp
      // Singleton - one instance for application lifetime
      builder.Services.AddSingleton<ICacheService, MemoryCacheService>();
      
      // Scoped - one instance per HTTP request
      builder.Services.AddScoped<IUserContext, HttpUserContext>();
      builder.Services.AddScoped<IUnitOfWork, EfUnitOfWork>();
      
      // Transient - new instance every time
      builder.Services.AddTransient<IEmailBuilder, EmailBuilder>();
      ```
      
      ### Factory Pattern Registration
      
      ```csharp
      // Simple factory
      builder.Services.AddScoped<IPaymentProcessor>(sp =>
      {
          var config = sp.GetRequiredService<IOptions<PaymentSettings>>().Value;
          return config.Provider switch
          {
              "Stripe" => new StripePaymentProcessor(config),
              "PayPal" => new PayPalPaymentProcessor(config),
              _ => throw new InvalidOperationException($"Unknown provider: {config.Provider}")
          };
      });
      
      // Named factory with keyed services (.NET 8+)
      builder.Services.AddKeyedScoped<IPaymentProcessor, StripePaymentProcessor>("stripe");
      builder.Services.AddKeyedScoped<IPaymentProcessor, PayPalPaymentProcessor>("paypal");
      
      // Usage
      public class CheckoutService([FromKeyedServices("stripe")] IPaymentProcessor stripeProcessor)
      {
          // ...
      }
      ```
      
      ### Decorator Pattern
      
      ```csharp
      // Manual decoration
      builder.Services.AddScoped<IRepository, SqlRepository>();
      builder.Services.Decorate<IRepository, CachingRepository>();
      builder.Services.Decorate<IRepository, LoggingRepository>();
      
      // Extension method implementation
      public static class ServiceCollectionExtensions
      {
          public static IServiceCollection Decorate<TInterface, TDecorator>(
              this IServiceCollection services)
              where TDecorator : TInterface
          {
              var descriptor = services.Single(d => d.ServiceType == typeof(TInterface));
      
              services.Add(new ServiceDescriptor(
                  typeof(TInterface),
                  sp =>
                  {
                      var inner = (TInterface)ActivatorUtilities.CreateInstance(
                          sp,
                          descriptor.ImplementationType!);
                      return ActivatorUtilities.CreateInstance<TDecorator>(sp, inner);
                  },
                  descriptor.Lifetime));
      
              services.Remove(descriptor);
              return services;
          }
      }
      ```
      
      ### HttpClientFactory Patterns
      
      ```csharp
      // Typed client
      builder.Services.AddHttpClient<IGitHubClient, GitHubClient>(client =>
      {
          client.BaseAddress = new Uri("https://api.github.com/");
          client.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
          client.DefaultRequestHeaders.Add("User-Agent", "MyApp/1.0");
      })
      .AddPolicyHandler(GetRetryPolicy())
      .AddPolicyHandler(GetCircuitBreakerPolicy());
      
      // Named client
      builder.Services.AddHttpClient("weather", client =>
      {
          client.BaseAddress = new Uri("https://api.weather.com/");
          client.Timeout = TimeSpan.FromSeconds(30);
      });
      
      // Resilience policies with Polly
      static IAsyncPolicy<HttpResponseMessage> GetRetryPolicy()
      {
          return HttpPolicyExtensions
              .HandleTransientHttpError()
              .WaitAndRetryAsync(3, retryAttempt =>
                  TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));
      }
      
      static IAsyncPolicy<HttpResponseMessage> GetCircuitBreakerPolicy()
      {
          return HttpPolicyExtensions
              .HandleTransientHttpError()
              .CircuitBreakerAsync(5, TimeSpan.FromSeconds(30));
      }
      ```
      
      ---
      
      ## Error Handling Patterns
      
      ### Global Exception Handler (.NET 8+)
      
      ```csharp
      public class GlobalExceptionHandler : IExceptionHandler
      {
          private readonly ILogger<GlobalExceptionHandler> _logger;
          private readonly IHostEnvironment _environment;
      
          public GlobalExceptionHandler(
              ILogger<GlobalExceptionHandler> logger,
              IHostEnvironment environment)
          {
              _logger = logger;
              _environment = environment;
          }
      
          public async ValueTask<bool> TryHandleAsync(
              HttpContext httpContext,
              Exception exception,
              CancellationToken cancellationToken)
          {
              var (statusCode, title) = exception switch
              {
                  ValidationException => (StatusCodes.Status400BadRequest, "Validation Error"),
                  NotFoundException => (StatusCodes.Status404NotFound, "Not Found"),
                  UnauthorizedAccessException => (StatusCodes.Status401Unauthorized, "Unauthorized"),
                  ForbiddenException => (StatusCodes.Status403Forbidden, "Forbidden"),
                  ConflictException => (StatusCodes.Status409Conflict, "Conflict"),
                  _ => (StatusCodes.Status500InternalServerError, "Internal Server Error")
              };
      
              _logger.LogError(exception, "Exception occurred: {Message}", exception.Message);
      
              var problemDetails = new ProblemDetails
              {
                  Status = statusCode,
                  Title = title,
                  Instance = httpContext.Request.Path,
                  Detail = _environment.IsDevelopment() ? exception.Message : null
              };
      
              if (exception is ValidationException validationException)
              {
                  problemDetails.Extensions["errors"] = validationException.Errors;
              }
      
              httpContext.Response.StatusCode = statusCode;
              await httpContext.Response.WriteAsJsonAsync(problemDetails, cancellationToken);
      
              return true;
          }
      }
      
      // Registration
      builder.Services.AddExceptionHandler<GlobalExceptionHandler>();
      builder.Services.AddProblemDetails();
      
      app.UseExceptionHandler();
      ```
      
      ### Problem Details Configuration
      
      ```csharp
      builder.Services.AddProblemDetails(options =>
      {
          options.CustomizeProblemDetails = context =>
          {
              context.ProblemDetails.Instance = context.HttpContext.Request.Path;
              context.ProblemDetails.Extensions["traceId"] = context.HttpContext.TraceIdentifier;
              context.ProblemDetails.Extensions["timestamp"] = DateTime.UtcNow;
      
              if (context.HttpContext.RequestServices.GetService<IHostEnvironment>()?.IsDevelopment() == true)
              {
                  context.ProblemDetails.Extensions["machineName"] = Environment.MachineName;
              }
          };
      });
      ```
      
      ---
      
      ## Logging Patterns
      
      ### Structured Logging with Serilog
      
      ```csharp
      builder.Host.UseSerilog((context, loggerConfig) =>
      {
          loggerConfig
              .ReadFrom.Configuration(context.Configuration)
              .Enrich.FromLogContext()
              .Enrich.WithMachineName()
              .Enrich.WithEnvironmentName()
              .Enrich.WithProperty("Application", "MyApp")
              .WriteTo.Console(new JsonFormatter())
              .WriteTo.Seq(context.Configuration["Seq:ServerUrl"]!);
      });
      
      // Request logging middleware
      app.UseSerilogRequestLogging(options =>
      {
          options.MessageTemplate =
              "HTTP {RequestMethod} {RequestPath} responded {StatusCode} in {Elapsed:0.0000}ms";
      
          options.EnrichDiagnosticContext = (diagnosticContext, httpContext) =>
          {
              diagnosticContext.Set("RequestHost", httpContext.Request.Host.Value);
              diagnosticContext.Set("UserAgent", httpContext.Request.Headers.UserAgent.ToString());
              diagnosticContext.Set("UserId", httpContext.User.FindFirst(ClaimTypes.NameIdentifier)?.Value);
          };
      });
      ```
      
      ### High-Performance Logging with Source Generators
      
      ```csharp
      public static partial class LogMessages
      {
          [LoggerMessage(
              EventId = 1001,
              Level = LogLevel.Information,
              Message = "Processing order {OrderId} for customer {CustomerId}")]
          public static partial void ProcessingOrder(
              ILogger logger,
              string orderId,
              string customerId);
      
          [LoggerMessage(
              EventId = 1002,
              Level = LogLevel.Warning,
              Message = "Order {OrderId} processing delayed, retry attempt {Attempt}")]
          public static partial void OrderProcessingDelayed(
              ILogger logger,
              string orderId,
              int attempt);
      
          [LoggerMessage(
              EventId = 1003,
              Level = LogLevel.Error,
              Message = "Failed to process order {OrderId}")]
          public static partial void OrderProcessingFailed(
              ILogger logger,
              string orderId,
              Exception exception);
      }
      
      // Usage
      public class OrderProcessor
      {
          private readonly ILogger<OrderProcessor> _logger;
      
          public OrderProcessor(ILogger<OrderProcessor> logger)
          {
              _logger = logger;
          }
      
          public async Task ProcessAsync(Order order)
          {
              LogMessages.ProcessingOrder(_logger, order.Id, order.CustomerId);
      
              try
              {
                  // Process order
              }
              catch (Exception ex)
              {
                  LogMessages.OrderProcessingFailed(_logger, order.Id, ex);
                  throw;
              }
          }
      }
      ```
      
      ### Correlation and Scoped Logging
      
      ```csharp
      public class CorrelationIdMiddleware
      {
          private readonly RequestDelegate _next;
      
          public CorrelationIdMiddleware(RequestDelegate next)
          {
              _next = next;
          }
      
          public async Task InvokeAsync(HttpContext context, ILogger<CorrelationIdMiddleware> logger)
          {
              var correlationId = context.Request.Headers["X-Correlation-Id"].FirstOrDefault()
                  ?? Guid.NewGuid().ToString();
      
              context.Response.Headers["X-Correlation-Id"] = correlationId;
      
              using (logger.BeginScope(new Dictionary<string, object>
              {
                  ["CorrelationId"] = correlationId,
                  ["RequestPath"] = context.Request.Path.ToString()
              }))
              {
                  await _next(context);
              }
          }
      }
      ```
      
  • SKILL.md 7.2 KB
    ---
    name: dotnet-aspnet-core
    version: "1.0.0"
    category: "Web"
    description: "Build, debug, modernize, or review ASP.NET Core applications with correct hosting, middleware, security, configuration, logging, and deployment patterns on current .NET."
    compatibility: "Requires an ASP.NET Core project or solution."
    ---
    
    # ASP.NET Core
    
    ## Trigger On
    
    - working on ASP.NET Core apps, services, or middleware
    - changing auth, routing, configuration, hosting, or deployment behavior
    - deciding between ASP.NET Core sub-stacks such as Blazor, Minimal APIs, or controller APIs
    - debugging request pipeline issues
    - modernizing legacy ASP.NET to ASP.NET Core
    
    ## Documentation
    
    - [ASP.NET Core Overview](https://learn.microsoft.com/en-us/aspnet/core/?view=aspnetcore-10.0)
    - [ASP.NET Core Middleware](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/middleware/?view=aspnetcore-10.0)
    - [ASP.NET Core Best Practices](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/best-practices?view=aspnetcore-10.0)
    - [Configuration in ASP.NET Core](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?view=aspnetcore-10.0)
    - [Authentication and Authorization](https://learn.microsoft.com/en-us/aspnet/core/security/?view=aspnetcore-10.0)
    
    ### References
    
    - [patterns.md](references/patterns.md) - Detailed middleware patterns, security patterns, configuration patterns, DI patterns, error handling patterns, and logging patterns
    - [anti-patterns.md](references/anti-patterns.md) - Common ASP.NET Core mistakes including HttpClient misuse, async anti-patterns, configuration errors, DI issues, middleware ordering problems, and security vulnerabilities
    
    ## Workflow
    
    1. **Detect the real hosting shape first:**
       - top-level `Program.cs` structure
       - middleware order and registration
       - auth model (Identity, JWT, OAuth, cookies)
       - endpoint registrations and routing
    
    2. **Follow the correct middleware order:**
       ```
       ExceptionHandler → HttpsRedirection → Static Files → Routing
       → CORS → Authentication → Authorization → Rate Limiting
       → Response Caching → Custom Middleware → Endpoints
       ```
    
    3. **Use built-in patterns correctly:**
       - Prefer `IOptions<T>` / `IOptionsSnapshot<T>` for configuration
       - Use `ILogger<T>` for structured logging
       - Use `IHttpClientFactory` for HTTP clients (never `new HttpClient()`)
       - Use `IHostedService` / `BackgroundService` for background work
    
    4. **Route specialized work to specific skills:**
       - UI and components → `dotnet-blazor`
       - Real-time → `dotnet-signalr`
       - RPC → `dotnet-grpc`
       - New HTTP APIs → `dotnet-minimal-apis` (prefer unless controllers needed)
       - Controller APIs → `dotnet-web-api`
    
    5. **Validate with build, tests, and targeted endpoint checks.**
    
    ## Middleware Patterns
    
    ### Correct Order Matters
    ```csharp
    var app = builder.Build();
    
    app.UseExceptionHandler("/error");      // 1. Catch all exceptions
    app.UseHsts();                          // 2. Security headers
    app.UseHttpsRedirection();              // 3. HTTPS redirect
    app.UseStaticFiles();                   // 4. Serve static files
    app.UseRouting();                       // 5. Route matching
    app.UseCors();                          // 6. CORS policy
    app.UseAuthentication();                // 7. Who are you?
    app.UseAuthorization();                 // 8. Can you access?
    app.UseRateLimiter();                   // 9. Rate limiting
    app.UseResponseCaching();               // 10. Response cache
    app.MapControllers();                   // 11. Endpoints
    ```
    
    ### Custom Middleware Pattern
    ```csharp
    public class RequestTimingMiddleware
    {
        private readonly RequestDelegate _next;
        private readonly ILogger<RequestTimingMiddleware> _logger;
    
        public RequestTimingMiddleware(RequestDelegate next, ILogger<RequestTimingMiddleware> logger)
        {
            _next = next;
            _logger = logger;
        }
    
        public async Task InvokeAsync(HttpContext context)
        {
            var sw = Stopwatch.StartNew();
            await _next(context);
            _logger.LogInformation("Request {Path} completed in {Elapsed}ms",
                context.Request.Path, sw.ElapsedMilliseconds);
        }
    }
    ```
    
    ## Configuration Patterns
    
    ### Strongly-Typed Options
    ```csharp
    // appsettings.json
    {
      "EmailSettings": {
        "SmtpServer": "smtp.example.com",
        "Port": 587
      }
    }
    
    // Registration
    builder.Services.Configure<EmailSettings>(
        builder.Configuration.GetSection("EmailSettings"));
    
    // Usage
    public class EmailService(IOptions<EmailSettings> options)
    {
        private readonly EmailSettings _settings = options.Value;
    }
    ```
    
    ### Environment-Based Configuration
    ```csharp
    builder.Configuration
        .AddJsonFile("appsettings.json", optional: false)
        .AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json", optional: true)
        .AddEnvironmentVariables()
        .AddUserSecrets<Program>(optional: true);
    ```
    
    ## Security Patterns
    
    ### Authentication Setup
    ```csharp
    builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
        .AddJwtBearer(options =>
        {
            options.TokenValidationParameters = new TokenValidationParameters
            {
                ValidateIssuer = true,
                ValidateAudience = true,
                ValidateLifetime = true,
                ValidateIssuerSigningKey = true,
                ValidIssuer = builder.Configuration["Jwt:Issuer"],
                ValidAudience = builder.Configuration["Jwt:Audience"],
                IssuerSigningKey = new SymmetricSecurityKey(
                    Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"]!))
            };
        });
    ```
    
    ### Authorization Policies
    ```csharp
    builder.Services.AddAuthorization(options =>
    {
        options.AddPolicy("AdminOnly", policy =>
            policy.RequireRole("Admin"));
        options.AddPolicy("MinAge18", policy =>
            policy.RequireClaim("Age", "18", "19", "20")); // simplified
    });
    ```
    
    ## Anti-Patterns to Avoid
    
    | Anti-Pattern | Why It's Bad | Better Approach |
    |--------------|--------------|-----------------|
    | `new HttpClient()` | Socket exhaustion | `IHttpClientFactory` |
    | Sync-over-async (`Task.Result`) | Thread pool starvation | `await` properly |
    | Storing secrets in `appsettings.json` | Security risk | User Secrets, Key Vault |
    | Catching all exceptions silently | Hides bugs | Use `IExceptionHandler` |
    | `async void` in middleware | Crashes process | `async Task` |
    | Missing HTTPS redirect | Security risk | `UseHttpsRedirection()` |
    
    ## Performance Best Practices
    
    1. **Use async/await everywhere** — avoid sync blocking calls
    2. **Pool DbContext properly** — use scoped lifetime
    3. **Enable response compression** — `UseResponseCompression()`
    4. **Use output caching** — `UseOutputCache()` for .NET 7+
    5. **Profile with diagnostic tools** — Visual Studio Diagnostic Tools, PerfView
    6. **Avoid allocations in hot paths** — use `Span<T>`, pooling
    
    ## Deliver
    
    - production-credible ASP.NET Core code and config
    - a clear request pipeline and hosting story
    - verification that matches the affected endpoints and middleware
    - security headers and HTTPS configured correctly
    
    ## Validate
    
    - middleware order is intentional and documented
    - security and configuration changes are explicit
    - endpoint behavior is covered by tests or smoke checks
    - no blocking calls in async context
    - secrets are not committed to source control
    - health checks are implemented for production readiness
    
  • skills-dotnet-v3.2.zip 567.2 KB · in bundle

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related