Claude Code Plugins

Community-maintained marketplace

Feedback

moai-lang-csharp

@kivo360/quickhooks
0
0

C# best practices with .NET 8, ASP.NET Core, Entity Framework, and modern async programming for 2025

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name moai-lang-csharp
version 2.0.0
created Thu Nov 06 2025 00:00:00 GMT+0000 (Coordinated Universal Time)
updated Thu Nov 06 2025 00:00:00 GMT+0000 (Coordinated Universal Time)
status active
description C# best practices with .NET 8, ASP.NET Core, Entity Framework, and modern async programming for 2025
keywords csharp, programming, dotnet, aspnetcore, entityframework, backend, async
allowed-tools Read, Write, Edit, Bash, WebFetch, WebSearch

C# Development Mastery

Modern C# Development with 2025 Best Practices

Comprehensive C# development guidance covering .NET 8 applications, ASP.NET Core APIs, Entity Framework Core with modern async/await patterns, and cross-platform development using the latest tools and frameworks.

What It Does

Backend Development

  • Web API Development: ASP.NET Core with minimal APIs, controllers, and modern routing
  • Database Integration: Entity Framework Core with LINQ, migrations, and performance optimization
  • Microservices: gRPC, message queuing, distributed systems patterns
  • Real-time Communication: SignalR, WebSockets with async/await
  • Testing: xUnit, Moq, FluentAssertions with integration testing

Cross-Platform Development

  • Desktop Applications: WPF, MAUI, WinUI 3 for Windows and cross-platform
  • Mobile Applications: .NET MAUI for iOS, Android, Windows
  • Console Applications: Modern CLI apps with DI and configuration
  • Background Services: .NET Core Workers, Hosted Services

Cloud Integration

  • Azure Integration: Azure Functions, App Service, Blob Storage
  • Docker & Kubernetes: Containerization and orchestration
  • DevOps: CI/CD pipelines, health checks, monitoring
  • Performance: Profiling, optimization, memory management

When to Use

Perfect Scenarios

  • Building REST APIs and microservices with ASP.NET Core
  • Developing enterprise applications with Entity Framework Core
  • Creating cross-platform mobile apps with .NET MAUI
  • Implementing real-time applications with SignalR
  • Building cloud-native applications with Azure integration
  • Developing high-performance backend services
  • Creating modern desktop applications with WPF/MAUI

Common Triggers

  • "Create C# web API"
  • "Build ASP.NET Core application"
  • "Set up Entity Framework Core"
  • "Implement async/await patterns"
  • "Optimize C# performance"
  • "Test C# application"
  • "C# best practices"

Tool Version Matrix (2025-11-06)

Core .NET

  • .NET: 8.0 (current LTS) / 9.0 Preview
  • C#: 12.0 (current) / 13.0 Preview
  • Package Managers: NuGet, .NET CLI
  • Runtime: .NET 8.0 LTS

Web Frameworks

  • ASP.NET Core: 8.0 - Web framework
  • Entity Framework Core: 8.0 - ORM framework
  • Blazor: 8.0 - Web UI framework
  • SignalR: 8.0 - Real-time communication
  • gRPC: 2.57.x - High-performance RPC

Testing Tools

  • xUnit: 2.6.x - Testing framework
  • Moq: 4.20.x - Mocking framework
  • FluentAssertions: 6.12.x - Assertion library
  • Bogus: 35.5.x - Test data generation
  • Microsoft.AspNetCore.Mvc.Testing: 8.0 - Integration testing

Development Tools

  • Visual Studio 2022: 17.10+
  • Visual Studio Code: C# Dev Kit extension
  • Rider: 2024.2+
  • .NET CLI: 8.0.400+

Database Tools

  • SQL Server: 2022 / Azure SQL
  • PostgreSQL: 16.x
  • SQLite: 3.45.x
  • MongoDB: 7.0.x (with MongoDB.Driver)

Ecosystem Overview

Package Management

# Create new projects
dotnet new webapi -n MyApi
dotnet new mvc -n MyMvcApp
dotnet new maui -n MyMauiApp
dotnet new worker -n MyWorkerService
dotnet new classlib -n MyLibrary

# Add packages
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
dotnet add package Microsoft.Extensions.Caching.StackExchangeRedis
dotnet add package Swashbuckle.AspNetCore
dotnet add package xunit

# Build and run
dotnet build
dotnet run --project MyApi.csproj
dotnet test

# Global tools
dotnet tool install --global dotnet-ef
dotnet tool install --global dotnet-aspnet-codegenerator

Project Structure (2025 Best Practice)

MyDotNetSolution/
├── src/
│   ├── MyApi/                    # Web API project
│   │   ├── Controllers/          # API controllers
│   │   ├── Endpoints/            # Minimal API endpoints
│   │   ├── Services/             # Business logic services
│   │   ├── Models/               # Data models and DTOs
│   │   ├── Data/                 # Data access layer
│   │   ├── Configuration/        # Configuration classes
│   │   ├── Filters/              # Action filters and middleware
│   │   └── Program.cs           # Application entry point
│   ├── MyCore/                  # Core business logic
│   │   ├── Entities/            # Domain entities
│   │   ├── Interfaces/          # Service interfaces
│   │   ├── ValueObjects/        # Value objects
│   │   └── Enums/               # Enumerations
│   ├── MyInfrastructure/        # Infrastructure concerns
│   │   ├── Persistence/         # Database implementations
│   │   ├── ExternalServices/    # External API clients
│   │   ├── Messaging/           # Message queue implementations
│   │   └── Caching/             # Cache implementations
│   └── MyTests/                 # Test projects
│       ├── Unit/                # Unit tests
│       ├── Integration/         # Integration tests
│       └── Functional/          # Functional tests
├── tests/                       # Additional test projects
├── docs/                        # Documentation
├── docker/                      # Docker configurations
├── .github/workflows/           # GitHub Actions
├── Directory.Build.props        # Solution-wide MSBuild properties
└── MySolution.sln               # Solution file

Modern Development Patterns

C# 12.0 Language Features

// Primary constructors for classes
public class UserService(
    IUserRepository userRepository,
    IEmailService emailService,
    ILogger<UserService> logger) : IUserService
{
    // Fields are automatically created from constructor parameters
    public async Task<User> CreateUserAsync(CreateUserRequest request, CancellationToken cancellationToken = default)
    {
        logger.LogInformation("Creating user with username: {Username}", request.Username);
        
        var user = new User(request.Username, request.Email);
        
        await userRepository.AddAsync(user, cancellationToken);
        await emailService.SendWelcomeEmailAsync(user.Email, cancellationToken);
        
        logger.LogInformation("User created successfully with ID: {UserId}", user.Id);
        return user;
    }
}

// Collection expressions
public class DataProcessor
{
    public int[] ProcessNumbers(IEnumerable<int> numbers)
    {
        return numbers
            .Where(n => n > 0)
            .OrderByDescending(n => n)
            .Take(10)
            .ToArray();
    }
    
    public List<string> GetDefaultPermissions()
    {
        return ["read", "write", "delete"]; // Collection expression
    }
}

// Required members
public class CreateUserRequest
{
    public required string Username { get; init; }
    public required string Email { get; init; }
    public string? FirstName { get; init; }
    public string? LastName { get; init; }
}

// Raw string literals
public class EmailService
{
    private readonly string _welcomeEmailTemplate = """
        Welcome to our platform!

        Hello {FirstName} {LastName},

        Thank you for registering with us. Your account has been created successfully.

        Best regards,
        The Team
        """;
    
    public async Task SendWelcomeEmailAsync(string email, string? firstName = null, string? lastName = null)
    {
        var emailBody = _welcomeEmailTemplate
            .Replace("{FirstName}", firstName ?? "User")
            .Replace("{LastName}", lastName ?? "");
        
        // Send email logic
    }
}

// Using aliases for numeric types
using Age = int;
using UserId = System.Guid;
using Price = decimal;

public class User
{
    public UserId Id { get; set; }
    public required string Username { get; set; }
    public Age Age { get; set; }
    public Price AccountBalance { get; set; }
}

// List patterns
public class NotificationService
{
    public string ProcessMessage(string[] messageParts)
    {
        return messageParts switch
        {
            [] => "Empty message",
            [var single] => $"Single part: {single}",
            [var first, var second] => $"Two parts: {first}, {second}",
            [var first, .. var middle, var last] => $"Multiple parts: {first} ... {last}",
            _ => "Complex message"
        };
    }
}

ASP.NET Core 8.0 Minimal APIs

var builder = WebApplication.CreateBuilder(args);

// Add services
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddDbContext<AppDbContext>(options =>
    options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
builder.Services.AddScoped<IUserRepository, UserRepository>();
builder.Services.AddScoped<IUserService, UserService>();
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"]!))
        };
    });

builder.Services.AddRateLimiter(options =>
{
    options.AddPolicy("Default", context =>
        RateLimitPartition.GetSlidingWindowLimiter(
            partitionKey: context.Connection.RemoteIpAddress?.ToString() ?? "anonymous",
            factory: _ => new SlidingWindowRateLimiterOptions
            {
                PermitLimit = 100,
                Window = TimeSpan.FromMinutes(1),
                SegmentsPerWindow = 2
            }));
});

var app = builder.Build();

// Middleware pipeline
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
app.UseRateLimiter();

// API Endpoint Groups
var userGroup = app.MapGroup("/api/users")
    .RequireAuthorization()
    .AddEndpointFilter<ValidationFilter>();

// Minimal API endpoints
userGroup.MapGet("/", async (IUserService userService, CancellationToken cancellationToken) =>
{
    var users = await userService.GetAllUsersAsync(cancellationToken);
    return Results.Ok(users);
})
    .WithName("GetAllUsers")
    .WithOpenApi();

userGroup.MapGet("/{id:guid}", async (Guid id, IUserService userService, CancellationToken cancellationToken) =>
{
    var user = await userService.GetUserByIdAsync(id, cancellationToken);
    return user is not null ? Results.Ok(user) : Results.NotFound();
})
    .WithName("GetUserById")
    .WithOpenApi();

userGroup.MapPost("/", async (CreateUserRequest request, IUserService userService, 
    IValidator<CreateUserRequest> validator, CancellationToken cancellationToken) =>
{
    var validationResult = await validator.ValidateAsync(request, cancellationToken);
    if (!validationResult.IsValid)
    {
        return Results.ValidationProblem(validationResult.ToDictionary());
    }

    var user = await userService.CreateUserAsync(request, cancellationToken);
    return Results.Created($"/api/users/{user.Id}", user);
})
    .WithName("CreateUser")
    .WithOpenApi()
    .RequireRateLimiting("Default");

userGroup.MapPut("/{id:guid}", async (Guid id, UpdateUserRequest request, 
    IUserService userService, CancellationToken cancellationToken) =>
{
    var user = await userService.UpdateUserAsync(id, request, cancellationToken);
    return user is not null ? Results.Ok(user) : Results.NotFound();
})
    .WithName("UpdateUser")
    .WithOpenApi();

userGroup.MapDelete("/{id:guid}", async (Guid id, IUserService userService, 
    CancellationToken cancellationToken) =>
{
    var success = await userService.DeleteUserAsync(id, cancellationToken);
    return success ? Results.NoContent() : Results.NotFound();
})
    .WithName("DeleteUser")
    .WithOpenApi();

// Real-time endpoints
var chatGroup = app.MapGroup("/api/chat")
    .RequireAuthorization();

app.MapHub<ChatHub>("/chatHub");

app.Run();

// Custom endpoint filters
public class ValidationFilter : IEndpointFilter
{
    public async ValueTask<object?> InvokeAsync(EndpointFilterInvocationContext context, EndpointFilterDelegate next)
    {
        // Add global validation logic here
        return await next(context);
    }
}

Entity Framework Core 8.0 Patterns

// Domain entities with value objects
public class User
{
    public Guid Id { get; private set; }
    public string Username { get; private set; } = string.Empty;
    public Email Email { get; private set; } = null!;
    public UserProfile Profile { get; private set; } = null!;
    public IReadOnlyCollection<UserRole> Roles => _roles.AsReadOnly();
    
    private readonly List<UserRole> _roles = new();
    
    // Private constructor for EF Core
    private User() { }
    
    public User(string username, Email email, UserProfile profile)
    {
        Id = Guid.NewGuid();
        Username = username;
        Email = email;
        Profile = profile;
        CreatedAt = DateTime.UtcNow;
    }
    
    public void UpdateProfile(UserProfile newProfile)
    {
        Profile = newProfile;
        UpdatedAt = DateTime.UtcNow;
    }
    
    public void AddRole(UserRole role)
    {
        if (!_roles.Contains(role))
        {
            _roles.Add(role);
            UpdatedAt = DateTime.UtcNow;
        }
    }
    
    public DateTime CreatedAt { get; private set; }
    public DateTime? UpdatedAt { get; private set; }
}

// Value object
public readonly record struct Email(string Value)
{
    public static Email Create(string email)
    {
        if (!Regex.IsMatch(email, @"^[^@\s]+@[^@\s]+\.[^@\s]+$"))
            throw new ArgumentException("Invalid email format", nameof(email));
        
        return new Email(email.ToLowerInvariant());
    }
}

// EF Core configuration
public class AppDbContext : DbContext
{
    public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }
    
    public DbSet<User> Users => Set<User>();
    public DbSet<UserRole> UserRoles => Set<UserRole>();
    public DbSet<Role> Roles => Set<Role>();
    
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<User>(builder =>
        {
            builder.HasKey(u => u.Id);
            builder.Property(u => u.Username).IsRequired().HasMaxLength(50);
            builder.Property(u => u.Email)
                .HasConversion(
                    email => email.Value,
                    value => Email.Create(value))
                .IsRequired()
                .HasMaxLength(100);
            
            builder.OwnsOne(u => u.Profile, profile =>
            {
                profile.Property(p => p.FirstName).HasMaxLength(50);
                profile.Property(p => p.LastName).HasMaxLength(50);
                profile.Property(p => p.Bio).HasMaxLength(500);
            });
            
            builder.Property(u => u.CreatedAt).HasDefaultValueSql("GETUTCDATE()");
            
            builder.HasMany(u => u.Roles)
                .WithMany(r => r.Users)
                .UsingEntity<UserRole>(
                    join => join.HasOne<Role>().WithMany(),
                    join => join.HasOne<User>().WithMany());
        });
        
        modelBuilder.Entity<Role>(builder =>
        {
            builder.HasKey(r => r.Id);
            builder.Property(r => r.Name).IsRequired().HasMaxLength(50);
            
            builder.HasData(
                new Role { Id = 1, Name = "Admin" },
                new Role { Id = 2, Name = "User" },
                new Role { Id = 3, Name = "Moderator" });
        });
    }
    
    public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
    {
        UpdateTimestamps();
        return await base.SaveChangesAsync(cancellationToken);
    }
    
    private void UpdateTimestamps()
    {
        var entries = ChangeTracker
            .Entries()
            .Where(e => e.Entity is User && (e.State == EntityState.Added || e.State == EntityState.Modified));
        
        foreach (var entry in entries)
        {
            var user = (User)entry.Entity;
            
            if (entry.State == EntityState.Added)
            {
                user.CreatedAt = DateTime.UtcNow;
            }
            else
            {
                user.UpdatedAt = DateTime.UtcNow;
            }
        }
    }
}

// Repository pattern with async/await
public interface IUserRepository
{
    Task<User?> GetByIdAsync(Guid id, CancellationToken cancellationToken = default);
    Task<User?> GetByUsernameAsync(string username, CancellationToken cancellationToken = default);
    Task<User?> GetByEmailAsync(Email email, CancellationToken cancellationToken = default);
    Task<IReadOnlyList<User>> GetAllAsync(int page = 1, int pageSize = 20, 
        CancellationToken cancellationToken = default);
    Task<User> AddAsync(User user, CancellationToken cancellationToken = default);
    Task<User> UpdateAsync(User user, CancellationToken cancellationToken = default);
    Task<bool> DeleteAsync(Guid id, CancellationToken cancellationToken = default);
    Task<bool> ExistsAsync(Guid id, CancellationToken cancellationToken = default);
}

public class UserRepository : IUserRepository
{
    private readonly AppDbContext _context;
    
    public UserRepository(AppDbContext context)
    {
        _context = context;
    }
    
    public async Task<User?> GetByIdAsync(Guid id, CancellationToken cancellationToken = default)
    {
        return await _context.Users
            .Include(u => u.Roles)
            .FirstOrDefaultAsync(u => u.Id == id, cancellationToken);
    }
    
    public async Task<User?> GetByUsernameAsync(string username, CancellationToken cancellationToken = default)
    {
        return await _context.Users
            .Include(u => u.Roles)
            .FirstOrDefaultAsync(u => u.Username == username, cancellationToken);
    }
    
    public async Task<User?> GetByEmailAsync(Email email, CancellationToken cancellationToken = default)
    {
        return await _context.Users
            .Include(u => u.Roles)
            .FirstOrDefaultAsync(u => u.Email == email, cancellationToken);
    }
    
    public async Task<IReadOnlyList<User>> GetAllAsync(int page = 1, int pageSize = 20, 
        CancellationToken cancellationToken = default)
    {
        return await _context.Users
            .Include(u => u.Roles)
            .OrderBy(u => u.Username)
            .Skip((page - 1) * pageSize)
            .Take(pageSize)
            .AsNoTracking()
            .ToListAsync(cancellationToken);
    }
    
    public async Task<User> AddAsync(User user, CancellationToken cancellationToken = default)
    {
        await _context.Users.AddAsync(user, cancellationToken);
        await _context.SaveChangesAsync(cancellationToken);
        return user;
    }
    
    public async Task<User> UpdateAsync(User user, CancellationToken cancellationToken = default)
    {
        _context.Users.Update(user);
        await _context.SaveChangesAsync(cancellationToken);
        return user;
    }
    
    public async Task<bool> DeleteAsync(Guid id, CancellationToken cancellationToken = default)
    {
        var user = await GetByIdAsync(id, cancellationToken);
        if (user is null) return false;
        
        _context.Users.Remove(user);
        await _context.SaveChangesAsync(cancellationToken);
        return true;
    }
    
    public async Task<bool> ExistsAsync(Guid id, CancellationToken cancellationToken = default)
    {
        return await _context.Users.AnyAsync(u => u.Id == id, cancellationToken);
    }
}

Modern Async Patterns

// Service layer with proper async handling
public class UserService : IUserService
{
    private readonly IUserRepository _userRepository;
    private readonly IEmailService _emailService;
    private readonly ILogger<UserService> _logger;
    private readonly IMemoryCache _cache;
    
    public UserService(
        IUserRepository userRepository,
        IEmailService emailService,
        ILogger<UserService> logger,
        IMemoryCache cache)
    {
        _userRepository = userRepository;
        _emailService = emailService;
        _logger = logger;
        _cache = cache;
    }
    
    public async Task<User> CreateUserAsync(CreateUserRequest request, 
        CancellationToken cancellationToken = default)
    {
        // Validate input
        var email = Email.Create(request.Email);
        
        // Check for existing user
        var existingUser = await _userRepository.GetByEmailAsync(email, cancellationToken);
        if (existingUser is not null)
        {
            throw new UserAlreadyExistsException($"User with email {email.Value} already exists");
        }
        
        // Create new user
        var profile = new UserProfile(request.FirstName, request.LastName, request.Bio);
        var user = new User(request.Username, email, profile);
        
        // Save to database
        var createdUser = await _userRepository.AddAsync(user, cancellationToken);
        
        // Send welcome email (fire and forget)
        _ = Task.Run(async () =>
        {
            try
            {
                await _emailService.SendWelcomeEmailAsync(email, profile.FirstName, profile.LastName);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Failed to send welcome email to {Email}", email.Value);
            }
        });
        
        // Cache the user
        _cache.Set($"user_{createdUser.Id}", createdUser, TimeSpan.FromMinutes(30));
        
        return createdUser;
    }
    
    public async Task<User?> GetUserByIdAsync(Guid id, CancellationToken cancellationToken = default)
    {
        // Try cache first
        if (_cache.TryGetValue($"user_{id}", out User? cachedUser))
        {
            return cachedUser;
        }
        
        // Fetch from database
        var user = await _userRepository.GetByIdAsync(id, cancellationToken);
        
        // Cache if found
        if (user is not null)
        {
            _cache.Set($"user_{id}", user, TimeSpan.FromMinutes(30));
        }
        
        return user;
    }
    
    public async Task<IReadOnlyList<User>> GetAllUsersAsync(int page = 1, int pageSize = 20,
        CancellationToken cancellationToken = default)
    {
        return await _userRepository.GetAllAsync(page, pageSize, cancellationToken);
    }
    
    public async Task<User?> UpdateUserAsync(Guid id, UpdateUserRequest request,
        CancellationToken cancellationToken = default)
    {
        var user = await _userRepository.GetByIdAsync(id, cancellationToken);
        if (user is null) return null;
        
        var updatedProfile = new UserProfile(
            request.FirstName ?? user.Profile.FirstName,
            request.LastName ?? user.Profile.LastName,
            request.Bio ?? user.Profile.Bio);
        
        user.UpdateProfile(updatedProfile);
        
        var updatedUser = await _userRepository.UpdateAsync(user, cancellationToken);
        
        // Update cache
        _cache.Set($"user_{id}", updatedUser, TimeSpan.FromMinutes(30));
        
        return updatedUser;
    }
    
    public async Task<bool> DeleteUserAsync(Guid id, CancellationToken cancellationToken = default)
    {
        var success = await _userRepository.DeleteAsync(id, cancellationToken);
        
        if (success)
        {
            // Remove from cache
            _cache.Remove($"user_{id}");
        }
        
        return success;
    }
    
    // Parallel operations example
    public async Task<UserProfileSummary> GetUserProfileSummaryAsync(Guid userId,
        CancellationToken cancellationToken = default)
    {
        var userTask = GetUserByIdAsync(userId, cancellationToken);
        var postsTask = GetUserPostsCountAsync(userId, cancellationToken);
        var followersTask = GetUserFollowersCountAsync(userId, cancellationToken);
        
        await Task.WhenAll(userTask, postsTask, followersTask);
        
        var user = await userTask;
        if (user is null) throw new UserNotFoundException(userId);
        
        var postsCount = await postsTask;
        var followersCount = await followersTask;
        
        return new UserProfileSummary
        {
            User = user,
            PostsCount = postsCount,
            FollowersCount = followersCount
        };
    }
    
    private async Task<int> GetUserPostsCountAsync(Guid userId, CancellationToken cancellationToken = default)
    {
        // Simulate external API call
        await Task.Delay(100, cancellationToken);
        return Random.Shared.Next(0, 100);
    }
    
    private async Task<int> GetUserFollowersCountAsync(Guid userId, CancellationToken cancellationToken = default)
    {
        // Simulate external API call
        await Task.Delay(150, cancellationToken);
        return Random.Shared.Next(0, 1000);
    }
}

// Background service with async patterns
public class UserCleanupService : BackgroundService
{
    private readonly ILogger<UserCleanupService> _logger;
    private readonly IUserRepository _userRepository;
    private readonly TimeSpan _cleanupInterval = TimeSpan.FromHours(1);
    
    public UserCleanupService(
        ILogger<UserCleanupService> logger,
        IUserRepository userRepository)
    {
        _logger = logger;
        _userRepository = userRepository;
    }
    
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            try
            {
                _logger.LogInformation("Starting user cleanup process");
                
                await CleanupInactiveUsersAsync(stoppingToken);
                
                _logger.LogInformation("User cleanup process completed");
            }
            catch (OperationCanceledException)
            {
                _logger.LogInformation("User cleanup service was cancelled");
                break;
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error during user cleanup process");
            }
            
            await Task.Delay(_cleanupInterval, stoppingToken);
        }
    }
    
    private async Task CleanupInactiveUsersAsync(CancellationToken cancellationToken)
    {
        const int batchSize = 100;
        var cutoffDate = DateTime.UtcNow.AddDays(-365);
        
        var inactiveUsers = await _userRepository.GetInactiveUsersBeforeAsync(cutoffDate, cancellationToken);
        
        foreach (var batch in inactiveUsers.Chunk(batchSize))
        {
            await Parallel.ForEachAsync(batch, cancellationToken, async (user, ct) =>
            {
                try
                {
                    await _userRepository.DeleteAsync(user.Id, ct);
                    _logger.LogInformation("Deleted inactive user {UserId} ({Username})", user.Id, user.Username);
                }
                catch (Exception ex)
                {
                    _logger.LogError(ex, "Failed to delete user {UserId}", user.Id);
                }
            });
        }
    }
}

Performance Considerations

Memory Management

// Efficient data structures and memory usage
public class DataProcessor
{
    // Use Span<T> for zero-allocation string processing
    public static bool IsValidEmail(ReadOnlySpan<char> email)
    {
        var atIdx = email.IndexOf('@');
        if (atIdx <= 0 || atIdx == email.Length - 1) return false;
        
        var dotIdx = email.LastIndexOf('.');
        if (dotIdx <= atIdx + 1 || dotIdx == email.Length - 1) return false;
        
        return true;
    }
    
    // Use ArrayPool for temporary arrays
    public static byte[] ProcessLargeData(ReadOnlySpan<byte> data)
    {
        var buffer = ArrayPool<byte>.Shared.Rent(data.Length * 2);
        
        try
        {
            // Process data into buffer
            var processedLength = ProcessDataInternal(data, buffer);
            
            var result = new byte[processedLength];
            buffer.AsSpan(0, processedLength).CopyTo(result);
            
            return result;
        }
        finally
        {
            ArrayPool<byte>.Shared.Return(buffer);
        }
    }
    
    private static int ProcessDataInternal(ReadOnlySpan<byte> source, Span<byte> destination)
    {
        // Processing logic
        return Math.Min(source.Length * 2, destination.Length);
    }
}

// Efficient LINQ usage with immediate execution when needed
public class UserRepository
{
    private readonly AppDbContext _context;
    
    public UserRepository(AppDbContext context)
    {
        _context = context;
    }
    
    // Use AsNoTracking for read-only queries
    public async Task<IReadOnlyList<User>> GetActiveUsersAsync(int page = 1, int pageSize = 20,
        CancellationToken cancellationToken = default)
    {
        return await _context.Users
            .AsNoTracking() // No change tracking for read-only
            .Where(u => u.IsActive)
            .OrderBy(u => u.Username)
            .Select(u => new UserDto // Project only needed fields
            {
                Id = u.Id,
                Username = u.Username,
                Email = u.Email.Value,
                CreatedAt = u.CreatedAt
            })
            .Skip((page - 1) * pageSize)
            .Take(pageSize)
            .ToListAsync(cancellationToken);
    }
    
    // Use compiled queries for frequently executed queries
    private static readonly Func<AppDbContext, Guid, Task<User?>> GetUserByIdCompiled =
        EF.CompileAsyncQuery((AppDbContext context, Guid id) =>
            context.Users
                .Include(u => u.Roles)
                .FirstOrDefaultAsync(u => u.Id == id));
    
    public async Task<User?> GetUserByIdAsync(Guid id, CancellationToken cancellationToken = default)
    {
        return await GetUserByIdCompiled(_context, id);
    }
    
    // Batch operations for better performance
    public async Task UpdateUsersLastLoginAsync(IEnumerable<Guid> userIds,
        CancellationToken cancellationToken = default)
    {
        const int batchSize = 1000;
        
        foreach (var batch in userIds.Chunk(batchSize))
        {
            await _context.Users
                .Where(u => batch.Contains(u.Id))
                .ExecuteUpdateAsync(setters => setters
                    .SetProperty(u => u.LastLoginAt, DateTime.UtcNow), cancellationToken);
        }
    }
}

// Memory-efficient caching
public class CacheService
{
    private readonly IMemoryCache _cache;
    private readonly Timer _cleanupTimer;
    
    public CacheService(IMemoryCache cache)
    {
        _cache = cache;
        
        // Set up periodic cleanup
        _cleanupTimer = new Timer(CleanupExpiredEntries, null, 
            TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(5));
    }
    
    public async Task<T?> GetOrCreateAsync<T>(string key, Func<Task<T>> factory,
        TimeSpan? absoluteExpiration = null, TimeSpan? slidingExpiration = null)
    {
        if (_cache.TryGetValue(key, out T? cachedValue))
        {
            return cachedValue;
        }
        
        var value = await factory();
        
        var options = new MemoryCacheEntryOptions
        {
            AbsoluteExpirationRelativeToNow = absoluteExpiration,
            SlidingExpiration = slidingExpiration,
            Size = 1 // Enable size-based eviction
        };
        
        _cache.Set(key, value, options);
        return value;
    }
    
    private void CleanupExpiredEntries(object? state)
    {
        // MemoryCache automatically removes expired entries, but we can
        // implement additional cleanup logic here if needed
        _cache.Compact(0.95); // Remove 5% of least recently used items
    }
    
    public void Remove(string key)
    {
        _cache.Remove(key);
    }
    
    public void Clear()
    {
        _cache.Compact(1.0); // Remove all entries
    }
}

Database Performance

// Optimized database operations
public class OptimizedDbContext : DbContext
{
    private readonly ILogger<OptimizedDbContext> _logger;
    
    public OptimizedDbContext(DbContextOptions<OptimizedDbContext> options,
        ILogger<OptimizedDbContext> logger) : base(options)
    {
        _logger = logger;
    }
    
    // Use connection resiliency
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.EnableRetryOnFailure(
            maxRetryCount: 3,
            maxRetryDelay: TimeSpan.FromSeconds(30),
            errorNumbersToAdd: null);
        
        // Configure sensitive data logging for development
        optionsBuilder.EnableSensitiveDataLogging(false);
        optionsBuilder.EnableDetailedErrors(false);
        
        // Set command timeout
        optionsBuilder.UseSqlServer(sqlOptions =>
        {
            sqlOptions.CommandTimeout(30);
            sqlOptions.EnableRetryOnFailure();
        });
    }
    
    // Batch operations
    public async Task BulkInsertAsync<T>(IEnumerable<T> entities, 
        CancellationToken cancellationToken = default) where T : class
    {
        const int batchSize = 1000;
        
        foreach (var batch in entities.Chunk(batchSize))
        {
            await Set<T>().AddRangeAsync(batch, cancellationToken);
            await SaveChangesAsync(cancellationToken);
            
            // Clear change tracker for memory efficiency
            ChangeTracker.Clear();
        }
    }
    
    // Optimized queries with proper indexing hints
    public async Task<IReadOnlyList<User>> SearchUsersAsync(string searchTerm,
        int page = 1, int pageSize = 20, CancellationToken cancellationToken = default)
    {
        var normalizedSearch = searchTerm.Trim().ToUpperInvariant();
        
        return await Users
            .AsNoTracking()
            .Where(u => EF.Functions.ILike(u.Username, $"%{normalizedSearch}%") ||
                       EF.Functions.ILike(u.Profile.FirstName, $"%{normalizedSearch}%") ||
                       EF.Functions.ILike(u.Profile.LastName, $"%{normalizedSearch}%"))
            .OrderBy(u => u.Username)
            .Select(u => new UserDto // Project to DTO for less data transfer
            {
                Id = u.Id,
                Username = u.Username,
                Email = u.Email.Value,
                FullName = $"{u.Profile.FirstName} {u.Profile.LastName}",
                CreatedAt = u.CreatedAt
            })
            .Skip((page - 1) * pageSize)
            .Take(pageSize)
            .ToListAsync(cancellationToken);
    }
    
    // Use raw SQL for performance-critical operations
    public async Task<int> DeleteOldRecordsAsync(DateTime cutoffDate,
        CancellationToken cancellationToken = default)
    {
        var sql = """
            DELETE FROM UserLogs 
            WHERE CreatedAt < @cutoffDate
            """;
        
        return await Database.ExecuteSqlRawAsync(sql,
            new SqlParameter("@cutoffDate", cutoffDate));
    }
    
    public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
    {
        try
        {
            return await base.SaveChangesAsync(cancellationToken);
        }
        catch (DbUpdateConcurrencyException ex)
        {
            _logger.LogWarning(ex, "Concurrency conflict detected");
            throw new ConcurrencyException("Data was modified by another user", ex);
        }
        catch (DbUpdateException ex)
        {
            _logger.LogError(ex, "Database update failed");
            throw new DataUpdateException("Failed to save changes to database", ex);
        }
    }
}

// Connection pooling and transaction management
public class UnitOfWork : IUnitOfWork, IDisposable
{
    private readonly OptimizedDbContext _context;
    private IDbContextTransaction? _transaction;
    
    public UnitOfWork(OptimizedDbContext context)
    {
        _context = context;
    }
    
    public async Task BeginTransactionAsync(IsolationLevel isolationLevel = IsolationLevel.ReadCommitted,
        CancellationToken cancellationToken = default)
    {
        _transaction = await _context.Database.BeginTransactionAsync(isolationLevel, cancellationToken);
    }
    
    public async Task CommitAsync(CancellationToken cancellationToken = default)
    {
        try
        {
            await _context.SaveChangesAsync(cancellationToken);
            await _transaction?.CommitAsync(cancellationToken)!;
        }
        catch
        {
            await RollbackAsync(cancellationToken);
            throw;
        }
    }
    
    public async Task RollbackAsync(CancellationToken cancellationToken = default)
    {
        if (_transaction != null)
        {
            await _transaction.RollbackAsync(cancellationToken);
        }
    }
    
    public void Dispose()
    {
        _transaction?.Dispose();
        _context.Dispose();
    }
}

HTTP Client Performance

// Optimized HTTP client with connection pooling and circuit breaker
public class OptimizedHttpClient
{
    private readonly HttpClient _httpClient;
    private readonly ILogger<OptimizedHttpClient> _logger;
    
    public OptimizedHttpClient(IHttpClientFactory httpClientFactory,
        ILogger<OptimizedHttpClient> logger)
    {
        _httpClient = httpClientFactory.CreateClient("OptimizedClient");
        _logger = logger;
    }
    
    public async Task<T?> GetAsync<T>(string uri, CancellationToken cancellationToken = default)
    {
        try
        {
            var response = await _httpClient.GetAsync(uri, cancellationToken);
            response.EnsureSuccessStatusCode();
            
            return await response.Content.ReadFromJsonAsync<T>(cancellationToken: cancellationToken);
        }
        catch (HttpRequestException ex)
        {
            _logger.LogError(ex, "HTTP request failed for URI: {Uri}", uri);
            throw;
        }
    }
    
    public async Task<T?> PostAsync<T>(string uri, T data, CancellationToken cancellationToken = default)
    {
        try
        {
            var response = await _httpClient.PostAsJsonAsync(uri, data, cancellationToken);
            response.EnsureSuccessStatusCode();
            
            return await response.Content.ReadFromJsonAsync<T>(cancellationToken: cancellationToken);
        }
        catch (HttpRequestException ex)
        {
            _logger.LogError(ex, "HTTP POST request failed for URI: {Uri}", uri);
            throw;
        }
    }
}

// HTTP client configuration in Program.cs
builder.Services.AddHttpClient("OptimizedClient", client =>
{
    client.BaseAddress = new Uri("https://api.example.com/");
    client.Timeout = TimeSpan.FromSeconds(30);
    client.DefaultRequestHeaders.Add("User-Agent", "MyApp/1.0");
})
.ConfigurePrimaryHttpMessageHandler(() => new SocketsHttpHandler
{
    PooledConnectionLifetime = TimeSpan.FromMinutes(5),
    PooledConnectionIdleTimeout = TimeSpan.FromMinutes(1),
    MaxConnectionsPerServer = 100
})
.AddPolicyHandler(GetRetryPolicy())
.AddPolicyHandler(GetCircuitBreakerPolicy());

static IAsyncPolicy<HttpResponseMessage> GetRetryPolicy()
{
    return HttpPolicyExtensions
        .HandleTransientHttpError()
        .OrResult(msg => msg.StatusCode == System.Net.HttpStatusCode.TooManyRequests)
        .WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));
}

static IAsyncPolicy<HttpResponseMessage> GetCircuitBreakerPolicy()
{
    return HttpPolicyExtensions
        .HandleTransientHttpError()
        .CircuitBreakerAsync(5, TimeSpan.FromSeconds(30));
}

Testing Strategy

xUnit Configuration

// Test project setup with dependency injection
public class TestFixture : IDisposable
{
    public ServiceProvider ServiceProvider { get; }
    public AppDbContext DbContext { get; }
    
    public TestFixture()
    {
        var services = new ServiceCollection();
        
        // Configure in-memory database
        services.AddDbContext<AppDbContext>(options =>
            options.UseInMemoryDatabase(Guid.NewGuid().ToString()));
        
        // Register services
        services.AddScoped<IUserRepository, UserRepository>();
        services.AddScoped<IUserService, UserService>();
        services.AddScoped<IEmailService, MockEmailService>();
        
        // Configure logging
        services.AddLogging(builder =>
            builder.SetMinimumLevel(LogLevel.Warning));
        
        ServiceProvider = services.BuildServiceProvider();
        DbContext = ServiceProvider.GetRequiredService<AppDbContext>();
        
        // Seed test data
        SeedTestData();
    }
    
    private void SeedTestData()
    {
        var users = new List<User>
        {
            new User("admin", Email.Create("admin@example.com"),
                new UserProfile("Admin", "User", "System administrator")),
            new User("user1", Email.Create("user1@example.com"),
                new UserProfile("Regular", "User", "Regular user"))
        };
        
        DbContext.Users.AddRange(users);
        DbContext.SaveChanges();
    }
    
    public void Dispose()
    {
        DbContext.Dispose();
        ServiceProvider.Dispose();
    }
}

// Integration tests with WebApplicationFactory
public class UserIntegrationTests : IClassFixture<WebApplicationFactory<Program>>
{
    private readonly WebApplicationFactory<Program> _factory;
    private readonly HttpClient _client;
    
    public UserIntegrationTests(WebApplicationFactory<Program> factory)
    {
        _factory = factory.WithWebHostBuilder(builder =>
        {
            builder.ConfigureServices(services =>
            {
                // Configure test services
                services.Remove(services.SingleOrDefault(
                    d => d.ServiceType == typeof(DbContextOptions<AppDbContext>)));
                
                services.AddDbContext<AppDbContext>(options =>
                    options.UseInMemoryDatabase("TestDb"));
                
                // Replace email service with mock
                services.AddScoped<IEmailService, MockEmailService>();
            });
        });
        
        _client = _factory.CreateClient();
    }
    
    [Fact]
    public async Task CreateUser_ValidInput_ReturnsCreated()
    {
        // Arrange
        var request = new CreateUserRequest
        {
            Username = "newuser",
            Email = "newuser@example.com",
            FirstName = "New",
            LastName = "User"
        };
        
        // Act
        var response = await _client.PostAsJsonAsync("/api/users", request);
        
        // Assert
        response.EnsureSuccessStatusCode();
        
        var user = await response.Content.ReadFromJsonAsync<UserDto>();
        Assert.NotNull(user);
        Assert.Equal("newuser", user!.Username);
        Assert.Equal("newuser@example.com", user.Email);
    }
    
    [Fact]
    public async Task GetUsers_ReturnsUserList()
    {
        // Act
        var response = await _client.GetAsync("/api/users");
        
        // Assert
        response.EnsureSuccessStatusCode();
        
        var users = await response.Content.ReadFromJsonAsync<List<UserDto>>();
        Assert.NotNull(users);
        Assert.True(users!.Count >= 2); // At least the seeded users
    }
    
    [Fact]
    public async Task GetUser_ExistingId_ReturnsUser()
    {
        // Arrange
        var createResponse = await _client.PostAsJsonAsync("/api/users", new CreateUserRequest
        {
            Username = "testuser",
            Email = "test@example.com",
            FirstName = "Test",
            LastName = "User"
        });
        
        var createdUser = await createResponse.Content.ReadFromJsonAsync<UserDto>();
        Assert.NotNull(createdUser);
        
        // Act
        var response = await _client.GetAsync($"/api/users/{createdUser!.Id}");
        
        // Assert
        response.EnsureSuccessStatusCode();
        
        var user = await response.Content.ReadFromJsonAsync<UserDto>();
        Assert.NotNull(user);
        Assert.Equal(createdUser!.Id, user!.Id);
    }
}

Unit Testing Patterns

public class UserServiceTests : IClassFixture<TestFixture>
{
    private readonly TestFixture _fixture;
    private readonly IUserService _userService;
    private readonly Mock<IEmailService> _mockEmailService;
    
    public UserServiceTests(TestFixture fixture)
    {
        _fixture = fixture;
        _userService = fixture.ServiceProvider.GetRequiredService<IUserService>();
        _mockEmailService = new Mock<IEmailService>();
        _userService = new UserService(
            _fixture.ServiceProvider.GetRequiredService<IUserRepository>(),
            _mockEmailService.Object,
            Mock.Of<ILogger<UserService>>(),
            Mock.Of<IMemoryCache>());
    }
    
    [Fact]
    public async Task CreateUser_ValidRequest_ReturnsUser()
    {
        // Arrange
        var request = new CreateUserRequest
        {
            Username = "newuser",
            Email = "newuser@example.com",
            FirstName = "New",
            LastName = "User"
        };
        
        _mockEmailService
            .Setup(s => s.SendWelcomeEmailAsync(It.IsAny<Email>(), It.IsAny<string>(), It.IsAny<string>()))
            .Returns(Task.CompletedTask);
        
        // Act
        var result = await _userService.CreateUserAsync(request);
        
        // Assert
        Assert.NotNull(result);
        Assert.Equal("newuser", result.Username);
        Assert.Equal("newuser@example.com", result.Email.Value);
        
        _mockEmailService.Verify(
            s => s.SendWelcomeEmailAsync(It.Is<Email>(e => e.Value == "newuser@example.com"), "New", "User"),
            Times.Once);
    }
    
    [Fact]
    public async Task CreateUser_DuplicateEmail_ThrowsException()
    {
        // Arrange
        var request = new CreateUserRequest
        {
            Username = "duplicate",
            Email = "admin@example.com", // Already exists in seed data
            FirstName = "Duplicate",
            LastName = "User"
        };
        
        // Act & Assert
        var exception = await Assert.ThrowsAsync<UserAlreadyExistsException>(
            () => _userService.CreateUserAsync(request));
        
        Assert.Contains("already exists", exception.Message);
        
        _mockEmailService.Verify(
            s => s.SendWelcomeEmailAsync(It.IsAny<Email>(), It.IsAny<string>(), It.IsAny<string>()),
            Times.Never);
    }
    
    [Theory]
    [InlineData("test@example.com", true)]
    [InlineData("invalid-email", false)]
    [InlineData("test@.com", false)]
    [InlineData("@example.com", false)]
    public void EmailValidation_ValidatesEmailCorrectly(string email, bool expectedValid)
    {
        // Act
        var isValid = EmailValidator.IsValid(email);
        
        // Assert
        Assert.Equal(expectedValid, isValid);
    }
    
    [Fact]
    public async Task UpdateUser_ValidRequest_UpdatesUser()
    {
        // Arrange
        var existingUser = await _userService.GetUserByIdAsync(Guid.Parse("00000000-0000-0000-0000-000000000001"));
        Assert.NotNull(existingUser);
        
        var updateRequest = new UpdateUserRequest
        {
            FirstName = "Updated",
            LastName = "Name",
            Bio = "Updated bio"
        };
        
        // Act
        var result = await _userService.UpdateUserAsync(existingUser!.Id, updateRequest);
        
        // Assert
        Assert.NotNull(result);
        Assert.Equal("Updated", result!.Profile.FirstName);
        Assert.Equal("Name", result.Profile.LastName);
        Assert.Equal("Updated bio", result.Profile.Bio);
    }
}

// Performance testing
public class PerformanceTests : IClassFixture<TestFixture>
{
    private readonly TestFixture _fixture;
    
    public PerformanceTests(TestFixture fixture)
    {
        _fixture = fixture;
    }
    
    [Fact]
    public async Task BulkInsert_PerformanceTest()
    {
        // Arrange
        const int userCount = 1000;
        var users = Enumerable.Range(1, userCount)
            .Select(i => new User($"user{i}", Email.Create($"user{i}@example.com"),
                new UserProfile($"User{i}", $"Test{i}", $"Bio for user {i}")))
            .ToList();
        
        var stopwatch = Stopwatch.StartNew();
        
        // Act
        await _fixture.DbContext.BulkInsertAsync(users);
        
        stopwatch.Stop();
        
        // Assert
        Assert.True(stopwatch.ElapsedMilliseconds < 5000, // Should complete within 5 seconds
            $"Bulk insert took {stopwatch.ElapsedMilliseconds}ms, expected less than 5000ms");
        
        var dbUsers = await _fixture.DbContext.Users.CountAsync();
        Assert.True(dbUsers >= userCount, $"Expected at least {userCount} users, got {dbUsers}");
    }
}

API Testing

// API contract testing
public class ApiContractTests : IClassFixture<WebApplicationFactory<Program>>
{
    private readonly WebApplicationFactory<Program> _factory;
    private readonly HttpClient _client;
    
    public ApiContractTests(WebApplicationFactory<Program> factory)
    {
        _factory = factory;
        _client = _factory.CreateClient();
    }
    
    [Fact]
    public async Task GetUsers_ReturnsCorrectContract()
    {
        // Act
        var response = await _client.GetAsync("/api/users");
        
        // Assert
        response.EnsureSuccessStatusCode();
        Assert.Equal("application/json; charset=utf-8", response.Content.Headers.ContentType?.ToString());
        
        var content = await response.Content.ReadAsStringAsync();
        Assert.NotEmpty(content);
        
        // Validate JSON schema
        var users = JsonSerializer.Deserialize<List<UserDto>>(content);
        Assert.NotNull(users);
    }
    
    [Fact]
    public async Task CreateUser_ValidInput_ReturnsCorrectContract()
    {
        // Arrange
        var request = new
        {
            Username = "newuser",
            Email = "newuser@example.com",
            FirstName = "New",
            LastName = "User"
        };
        
        // Act
        var response = await _client.PostAsJsonAsync("/api/users", request);
        
        // Assert
        Assert.Equal(HttpStatusCode.Created, response.StatusCode);
        Assert.Equal("application/json; charset=utf-8", response.Content.Headers.ContentType?.ToString());
        
        var content = await response.Content.ReadAsStringAsync();
        var user = JsonSerializer.Deserialize<UserDto>(content, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
        
        Assert.NotNull(user);
        Assert.Equal("newuser", user!.Username);
        Assert.Equal("newuser@example.com", user.Email);
        Assert.NotNull(user.Id);
    }
    
    [Fact]
    public async Task GetUsers_WithoutAuthentication_ReturnsUnauthorized()
    {
        // Act
        var response = await _client.GetAsync("/api/users/protected-endpoint");
        
        // Assert
        Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
    }
}

Security Best Practices

Input Validation and Sanitization

// Data annotations for validation
public class CreateUserRequest
{
    [Required(ErrorMessage = "Username is required")]
    [StringLength(50, MinimumLength = 3, ErrorMessage = "Username must be between 3 and 50 characters")]
    [RegularExpression(@"^[a-zA-Z0-9_]+$", ErrorMessage = "Username can only contain letters, numbers, and underscores")]
    public string Username { get; init; } = string.Empty;
    
    [Required(ErrorMessage = "Email is required")]
    [EmailAddress(ErrorMessage = "Invalid email format")]
    [StringLength(100, ErrorMessage = "Email must be less than 100 characters")]
    public string Email { get; init; } = string.Empty;
    
    [StringLength(50, ErrorMessage = "First name must be less than 50 characters")]
    [RegularExpression(@"^[a-zA-Z\s]+$", ErrorMessage = "First name can only contain letters and spaces")]
    public string? FirstName { get; init; }
    
    [StringLength(50, ErrorMessage = "Last name must be less than 50 characters")]
    [RegularExpression(@"^[a-zA-Z\s]+$", ErrorMessage = "Last name can only contain letters and spaces")]
    public string? LastName { get; init; }
    
    [StringLength(500, ErrorMessage = "Bio must be less than 500 characters")]
    [RegularExpression(@"^[^<>]*$", ErrorMessage = "Bio cannot contain HTML tags")]
    public string? Bio { get; init; }
}

// Custom validation attributes
public class StrongPasswordAttribute : ValidationAttribute
{
    public StrongPasswordAttribute() : base("Password must be at least 8 characters and contain uppercase, lowercase, digit, and special character")
    {
    }
    
    protected override ValidationResult? IsValid(object? value, ValidationContext validationContext)
    {
        if (value is string password)
        {
            if (password.Length < 8)
            {
                return new ValidationResult("Password must be at least 8 characters long");
            }
            
            if (!Regex.IsMatch(password, @"[A-Z]"))
            {
                return new ValidationResult("Password must contain at least one uppercase letter");
            }
            
            if (!Regex.IsMatch(password, @"[a-z]"))
            {
                return new ValidationResult("Password must contain at least one lowercase letter");
            }
            
            if (!Regex.IsMatch(password, @"\d"))
            {
                return new ValidationResult("Password must contain at least one digit");
            }
            
            if (!Regex.IsMatch(password, @"[!@#$%^&*(),.?""{}|<>]"))
            {
                return new ValidationResult("Password must contain at least one special character");
            }
            
            return ValidationResult.Success;
        }
        
        return new ValidationResult("Password is required");
    }
}

// HTML sanitization
public static class HtmlSanitizer
{
    private static readonly HtmlSanitizer _sanitizer = new HtmlSanitizer()
        .AllowTags("p", "br", "strong", "em", "u", "ol", "ul", "li")
        .AllowAttributes("class").OnAllTags()
        .RemoveAttributes("style", "onclick", "onload");
    
    public static string Sanitize(string? html)
    {
        if (string.IsNullOrEmpty(html))
        {
            return string.Empty;
        }
        
        return _sanitizer.Sanitize(html);
    }
}

Authentication and Authorization

// JWT authentication configuration
public static class AuthenticationConfiguration
{
    public static IServiceCollection AddJwtAuthentication(this IServiceCollection services,
        IConfiguration configuration)
    {
        var jwtSettings = configuration.GetSection("JwtSettings");
        var key = Encoding.UTF8.GetBytes(jwtSettings["Secret"]!);
        
        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 = jwtSettings["Issuer"],
                ValidAudience = jwtSettings["Audience"],
                IssuerSigningKey = new SymmetricSecurityKey(key),
                ClockSkew = TimeSpan.Zero
            };
        });
        
        return services;
    }
}

// JWT service
public interface IJwtService
{
    string GenerateToken(User user);
    ClaimsPrincipal? GetPrincipalFromToken(string token);
}

public class JwtService : IJwtService
{
    private readonly IConfiguration _configuration;
    private readonly string _secret;
    private readonly string _issuer;
    private readonly string _audience;
    
    public JwtService(IConfiguration configuration)
    {
        _configuration = configuration;
        _secret = _configuration["JwtSettings:Secret"]!;
        _issuer = _configuration["JwtSettings:Issuer"]!;
        _audience = _configuration["JwtSettings:Audience"]!;
    }
    
    public string GenerateToken(User user)
    {
        var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_secret));
        var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
        
        var claims = new[]
        {
            new Claim(JwtRegisteredClaimNames.Sub, user.Id.ToString()),
            new Claim(JwtRegisteredClaimNames.Email, user.Email.Value),
            new Claim(JwtRegisteredClaimNames.Name, user.Username),
            new Claim(ClaimTypes.Name, user.Username),
            new Claim(ClaimTypes.Email, user.Email.Value),
            new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
            new Claim("role", string.Join(",", user.Roles.Select(r => r.Name)))
        };
        
        var token = new JwtSecurityToken(
            issuer: _issuer,
            audience: _audience,
            claims: claims,
            expires: DateTime.UtcNow.AddHours(24),
            signingCredentials: credentials);
        
        return new JwtSecurityTokenHandler().WriteToken(token);
    }
    
    public ClaimsPrincipal? GetPrincipalFromToken(string token)
    {
        try
        {
            var tokenHandler = new JwtSecurityTokenHandler();
            var key = Encoding.UTF8.GetBytes(_secret);
            
            var validationParameters = new TokenValidationParameters
            {
                ValidateIssuer = true,
                ValidateAudience = true,
                ValidateLifetime = true,
                ValidateIssuerSigningKey = true,
                ValidIssuer = _issuer,
                ValidAudience = _audience,
                IssuerSigningKey = new SymmetricSecurityKey(key),
                ClockSkew = TimeSpan.Zero
            };
            
            var principal = tokenHandler.ValidateToken(token, validationParameters, out var validatedToken);
            
            return principal;
        }
        catch
        {
            return null;
        }
    }
}

// Authorization policies
public static class AuthorizationConfiguration
{
    public static IServiceCollection AddAuthorizationPolicies(this IServiceCollection services)
    {
        services.AddAuthorization(options =>
        {
            options.AddPolicy("AdminOnly", policy =>
                policy.RequireRole("Admin"));
            
            options.AddPolicy("UserOrAdmin", policy =>
                policy.RequireRole("User", "Admin"));
            
            options.AddPolicy("CanManageUsers", policy =>
                policy.Requirements.Add(new CanManageUsersRequirement()));
            
            options.AddPolicy("MinimumAge", policy =>
                policy.Requirements.Add(new MinimumAgeRequirement(18)));
        });
        
        services.AddScoped<IAuthorizationHandler, CanManageUsersHandler>();
        services.AddScoped<IAuthorizationHandler, MinimumAgeHandler>();
        
        return services;
    }
}

// Custom authorization requirements
public class CanManageUsersRequirement : IAuthorizationRequirement { }

public class MinimumAgeRequirement : IAuthorizationRequirement
{
    public MinimumAgeRequirement(int minimumAge)
    {
        MinimumAge = minimumAge;
    }
    
    public int MinimumAge { get; }
}

// Authorization handlers
public class CanManageUsersHandler : AuthorizationHandler<CanManageUsersRequirement>
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
        CanManageUsersRequirement requirement)
    {
        if (context.User.IsInRole("Admin"))
        {
            context.Succeed(requirement);
        }
        
        return Task.CompletedTask;
    }
}

// Using authorization in controllers
[ApiController]
[Route("api/[controller]")]
[Authorize]
public class UsersController : ControllerBase
{
    [HttpGet]
    [AllowAnonymous]
    public async Task<IActionResult> GetUsers()
    {
        // Public endpoint
    }
    
    [HttpPost]
    [Authorize(Roles = "Admin")]
    public async Task<IActionResult> CreateUser()
    {
        // Admin only endpoint
    }
    
    [HttpDelete("{id}")]
    [Authorize(Policy = "CanManageUsers")]
    public async Task<IActionResult> DeleteUser(Guid id)
    {
        // Users who can manage users
    }
}

Security Headers and Middleware

// Security headers middleware
public class SecurityHeadersMiddleware
{
    private readonly RequestDelegate _next;
    
    public SecurityHeadersMiddleware(RequestDelegate next)
    {
        _next = next;
    }
    
    public async Task InvokeAsync(HttpContext context)
    {
        // Add security headers
        context.Response.Headers.Add("X-Content-Type-Options", "nosniff");
        context.Response.Headers.Add("X-Frame-Options", "DENY");
        context.Response.Headers.Add("X-XSS-Protection", "1; mode=block");
        context.Response.Headers.Add("Strict-Transport-Security", "max-age=31536000; includeSubDomains");
        context.Response.Headers.Add("Content-Security-Policy", "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'");
        context.Response.Headers.Add("Referrer-Policy", "strict-origin-when-cross-origin");
        
        await _next(context);
    }
}

// Rate limiting middleware
public class RateLimitingMiddleware
{
    private readonly RequestDelegate _next;
    private readonly IMemoryCache _cache;
    private readonly RateLimitOptions _options;
    
    public RateLimitingMiddleware(RequestDelegate next, IMemoryCache cache, IOptions<RateLimitOptions> options)
    {
        _next = next;
        _cache = cache;
        _options = options.Value;
    }
    
    public async Task InvokeAsync(HttpContext context)
    {
        var clientId = GetClientId(context);
        var cacheKey = $"rate_limit_{clientId}";
        
        var requestCount = await _cache.GetOrCreateAsync(cacheKey, async () =>
        {
            await Task.CompletedTask;
            return 1;
        }, new MemoryCacheEntryOptions
        {
            AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(1),
            SlidingExpiration = TimeSpan.FromSeconds(30)
        });
        
        if (requestCount > _options.MaxRequestsPerMinute)
        {
            context.Response.StatusCode = StatusCodes.Status429TooManyRequests;
            await context.Response.WriteAsync("Rate limit exceeded");
            return;
        }
        
        await _next(context);
    }
    
    private string GetClientId(HttpContext context)
    {
        // Try to get user ID from claims, otherwise use IP address
        var userId = context.User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
        return userId ?? context.Connection.RemoteIpAddress?.ToString() ?? "anonymous";
    }
}

public class RateLimitOptions
{
    public int MaxRequestsPerMinute { get; set; } = 100;
}

// Configure middleware in Program.cs
app.UseMiddleware<SecurityHeadersMiddleware>();
app.UseMiddleware<RateLimitingMiddleware>();

Integration Patterns

Entity Framework Core Integration

// Repository pattern with Unit of Work
public interface IUnitOfWork : IDisposable
{
    IUserRepository Users { get; }
    Task<int> SaveChangesAsync(CancellationToken cancellationToken = default);
    Task BeginTransactionAsync(CancellationToken cancellationToken = default);
    Task CommitAsync(CancellationToken cancellationToken = default);
    Task RollbackAsync(CancellationToken cancellationToken = default);
}

public class UnitOfWork : IUnitOfWork
{
    private readonly AppDbContext _context;
    private IDbContextTransaction? _transaction;
    
    public UnitOfWork(AppDbContext context)
    {
        _context = context;
        Users = new UserRepository(_context);
    }
    
    public IUserRepository Users { get; }
    
    public async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
    {
        return await _context.SaveChangesAsync(cancellationToken);
    }
    
    public async Task BeginTransactionAsync(CancellationToken cancellationToken = default)
    {
        _transaction = await _context.Database.BeginTransactionAsync(cancellationToken);
    }
    
    public async Task CommitAsync(CancellationToken cancellationToken = default)
    {
        try
        {
            await _context.SaveChangesAsync(cancellationToken);
            await _transaction?.CommitAsync(cancellationToken)!;
        }
        catch
        {
            await RollbackAsync(cancellationToken);
            throw;
        }
    }
    
    public async Task RollbackAsync(CancellationToken cancellationToken = default)
    {
        if (_transaction != null)
        {
            await _transaction.RollbackAsync(cancellationToken);
        }
    }
    
    public void Dispose()
    {
        _transaction?.Dispose();
        _context.Dispose();
    }
}

// Service with Unit of Work
public class UserService
{
    private readonly IUnitOfWork _unitOfWork;
    private readonly ILogger<UserService> _logger;
    
    public UserService(IUnitOfWork unitOfWork, ILogger<UserService> logger)
    {
        _unitOfWork = unitOfWork;
        _logger = logger;
    }
    
    public async Task<User> CreateUserAsync(CreateUserRequest request,
        CancellationToken cancellationToken = default)
    {
        await _unitOfWork.BeginTransactionAsync(cancellationToken);
        
        try
        {
            var user = new User(request.Username, Email.Create(request.Email),
                new UserProfile(request.FirstName, request.LastName, request.Bio));
            
            await _unitOfWork.Users.AddAsync(user, cancellationToken);
            await _unitOfWork.CommitAsync(cancellationToken);
            
            _logger.LogInformation("User created successfully with ID: {UserId}", user.Id);
            return user;
        }
        catch
        {
            await _unitOfWork.RollbackAsync(cancellationToken);
            throw;
        }
    }
}

Message Queue Integration

// RabbitMQ integration
public interface IMessagePublisher
{
    Task PublishAsync<T>(T message, string routingKey, CancellationToken cancellationToken = default);
}

public class RabbitMqPublisher : IMessagePublisher
{
    private readonly IConnection _connection;
    private readonly ILogger<RabbitMqPublisher> _logger;
    
    public RabbitMqPublisher(IConnection connection, ILogger<RabbitMqPublisher> logger)
    {
        _connection = connection;
        _logger = logger;
    }
    
    public async Task PublishAsync<T>(T message, string routingKey,
        CancellationToken cancellationToken = default)
    {
        using var channel = await _connection.CreateChannelAsync(cancellationToken);
        
        await channel.ExchangeDeclareAsync("app_exchange", ExchangeType.Direct, durable: true, 
            cancellationToken: cancellationToken);
        
        var messageBody = JsonSerializer.SerializeToUtf8Bytes(message);
        
        await channel.BasicPublishAsync("app_exchange", routingKey, true, new BasicProperties
        {
            ContentType = "application/json",
            DeliveryMode = DeliveryModes.Persistent
        }, messageBody, cancellationToken);
        
        _logger.LogInformation("Message published to routing key: {RoutingKey}", routingKey);
    }
}

// Message consumer
public interface IMessageConsumer<T>
{
    Task ConsumeAsync(T message, CancellationToken cancellationToken = default);
}

public class UserCreatedEventConsumer : BackgroundService
{
    private readonly IConnection _connection;
    private readonly IServiceScopeFactory _scopeFactory;
    private readonly ILogger<UserCreatedEventConsumer> _logger;
    
    public UserCreatedEventConsumer(IConnection connection, IServiceScopeFactory scopeFactory,
        ILogger<UserCreatedEventConsumer> logger)
    {
        _connection = connection;
        _scopeFactory = scopeFactory;
        _logger = logger;
    }
    
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        using var channel = await _connection.CreateChannelAsync(stoppingToken);
        
        await channel.QueueDeclareAsync("user_created_queue", durable: true, exclusive: false,
            autoDelete: false, cancellationToken: stoppingToken);
        
        await channel.QueueBindAsync("user_created_queue", "app_exchange", "user.created",
            cancellationToken: stoppingToken);
        
        var consumer = new AsyncEventingBasicConsumer(channel);
        consumer.ReceivedAsync += async (sender, args) =>
        {
            try
            {
                var message = JsonSerializer.Deserialize<UserCreatedEvent>(args.Body.ToArray());
                
                if (message != null)
                {
                    await HandleUserCreatedEvent(message, stoppingToken);
                }
                
                await channel.BasicAckAsync(args.DeliveryTag, false, stoppingToken);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error processing user created event");
                await channel.BasicNackAsync(args.DeliveryTag, false, true, stoppingToken);
            }
        };
        
        await channel.BasicConsumeAsync("user_created_queue", false, consumer, stoppingToken);
        
        _logger.LogInformation("User created event consumer started");
        
        // Keep the consumer running
        while (!stoppingToken.IsCancellationRequested)
        {
            await Task.Delay(TimeSpan.FromSeconds(1), stoppingToken);
        }
    }
    
    private async Task HandleUserCreatedEvent(UserCreatedEvent @event,
        CancellationToken cancellationToken = default)
    {
        using var scope = _scopeFactory.CreateScope();
        var emailService = scope.ServiceProvider.GetRequiredService<IEmailService>();
        
        await emailService.SendWelcomeEmailAsync(@event.Email, @event.FirstName, @event.LastName, cancellationToken);
        
        _logger.LogInformation("Processed user created event for user: {UserId}", @event.UserId);
    }
}

public record UserCreatedEvent(
    Guid UserId,
    string Email,
    string FirstName,
    string LastName,
    DateTime CreatedAt);

Modern Development Workflow

Project Configuration

<!-- Directory.Build.props -->
<Project>
  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <LangVersion>12.0</LangVersion>
    <GenerateDocumentationFile>true</GenerateDocumentationFile>
    <NoWarn>$(NoWarn);1591</NoWarn>
  </PropertyGroup>
  
  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.0" />
    <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
    <PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.0" />
    <PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
  </ItemGroup>
</Project>

<!-- MyApi.csproj -->
<Project Sdk="Microsoft.NET.Sdk.Web">
  <PropertyGroup>
    <AssemblyName>MyApi</AssemblyName>
    <RootNamespace>MyApi</RootNamespace>
  </PropertyGroup>
  
  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.0" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.0" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.0" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.0" />
    <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.0" />
    <PackageReference Include="Microsoft.AspNetCore.Authorization" Version="8.0.0" />
    <PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
    <PackageReference Include="FluentValidation.AspNetCore" Version="11.3.0" />
    <PackageReference Include="Serilog.AspNetCore" Version="8.0.0" />
    <PackageReference Include="Serilog.Sinks.Console" Version="5.0.0" />
    <PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
  </ItemGroup>
</Project>

Docker Configuration

# Multi-stage Dockerfile for .NET 8.0
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build-env
WORKDIR /app

# Copy csproj and restore dependencies
COPY *.csproj ./
RUN dotnet restore

# Copy everything else and build
COPY . ./
RUN dotnet publish -c Release -o out

# Build runtime image
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS runtime
WORKDIR /app
COPY --from=build-env /app/out .

# Create non-root user
RUN adduser --disabled-password --gecos '' appuser && chown -R appuser /app
USER appuser

EXPOSE 8080
ENV ASPNETCORE_URLS=http://+:8080
ENV ASPNETCORE_ENVIRONMENT=Production

ENTRYPOINT ["dotnet", "MyApi.dll"]

CI/CD Configuration

# .github/workflows/dotnet.yml
name: .NET CI/CD

on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main ]

env:
  DOTNET_VERSION: '8.0.x'
  SOLUTION_FILE: 'MySolution.sln'
  
jobs:
  test:
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v4
    
    - name: Setup .NET
      uses: actions/setup-dotnet@v4
      with:
        dotnet-version: ${{ env.DOTNET_VERSION }}
    
    - name: Cache dependencies
      uses: actions/cache@v3
      with:
        path: ~/.nuget/packages
        key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj') }}
        restore-keys: |
          ${{ runner.os }}-nuget-
    
    - name: Restore dependencies
      run: dotnet restore ${{ env.SOLUTION_FILE }}
    
    - name: Build
      run: dotnet build ${{ env.SOLUTION_FILE }} --no-restore --configuration Release
    
    - name: Run tests
      run: dotnet test ${{ env.SOLUTION_FILE }} --no-build --configuration Release --verbosity normal --collect:"XPlat Code Coverage"
    
    - name: Upload coverage to Codecov
      uses: codecov/codecov-action@v3
      with:
        file: '**/coverage.cobertura.xml'
    
    - name: Run security scan
      run: |
        dotnet tool install --global dotnet-ssh --version 0.2.0
        dotnet-ssh scan MyApi/bin/Release/net8.0/MyApi.dll
  
  build-and-deploy:
    needs: test
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    
    steps:
    - uses: actions/checkout@v4
    
    - name: Setup .NET
      uses: actions/setup-dotnet@v4
      with:
        dotnet-version: ${{ env.DOTNET_VERSION }}
    
    - name: Build and publish
      run: dotnet publish ${{ env.SOLUTION_FILE }} --configuration Release --output ./publish
    
    - name: Build Docker image
      run: |
        docker build -t myregistry.com/myapi:${{ github.sha }} .
        docker tag myregistry.com/myapi:${{ github.sha }} myregistry.com/myapi:latest
    
    - name: Push Docker image
      if: github.ref == 'refs/heads/main'
      run: |
        echo ${{ secrets.DOCKER_PASSWORD }} | docker login -u ${{ secrets.DOCKER_USERNAME }} --password-stdin
        docker push myregistry.com/myapi:${{ github.sha }}
        docker push myregistry.com/myapi:latest

Created by: MoAI Language Skill Factory
Last Updated: 2025-11-06
Version: 2.0.0
C# Target: .NET 8.0 LTS with C# 12.0 and modern ASP.NET Core patterns

This skill provides comprehensive C# development guidance with 2025 best practices, covering everything from modern ASP.NET Core APIs to Entity Framework Core optimization and cross-platform development with .NET MAUI.