Claude Agent Skill · by Affaan M

Dotnet Patterns

Install Dotnet Patterns skill for Claude Code from affaan-m/everything-claude-code.

Works with Paperclip

How Dotnet Patterns fits into a Paperclip company.

Dotnet Patterns drops into any Paperclip agent that handles this kind of work. Assign it to a specialist inside a pre-configured PaperclipOrg company and the skill becomes available on every heartbeat — no prompt engineering, no tool wiring.

S
SaaS FactoryPaired

Pre-configured AI company — 18 agents, 18 skills, one-time purchase.

$27$59
Explore pack
Source file
SKILL.md321 lines
Expand
---name: dotnet-patternsdescription: Idiomatic C# and .NET patterns, conventions, dependency injection, async/await, and best practices for building robust, maintainable .NET applications.origin: ECC--- # .NET Development Patterns Idiomatic C# and .NET patterns for building robust, performant, and maintainable applications. ## When to Activate - Writing new C# code- Reviewing C# code- Refactoring existing .NET applications- Designing service architectures with ASP.NET Core ## Core Principles ### 1. Prefer Immutability Use records and init-only properties for data models. Mutability should be an explicit, justified choice. ```csharp// Good: Immutable value objectpublic sealed record Money(decimal Amount, string Currency); // Good: Immutable DTO with init setterspublic sealed class CreateOrderRequest{    public required string CustomerId { get; init; }    public required IReadOnlyList<OrderItem> Items { get; init; }} // Bad: Mutable model with public setterspublic class Order{    public string CustomerId { get; set; }    public List<OrderItem> Items { get; set; }}``` ### 2. Explicit Over Implicit Be clear about nullability, access modifiers, and intent. ```csharp// Good: Explicit access modifiers and nullabilitypublic sealed class UserService{    private readonly IUserRepository _repository;    private readonly ILogger<UserService> _logger;     public UserService(IUserRepository repository, ILogger<UserService> logger)    {        _repository = repository ?? throw new ArgumentNullException(nameof(repository));        _logger = logger ?? throw new ArgumentNullException(nameof(logger));    }     public async Task<User?> FindByIdAsync(Guid id, CancellationToken cancellationToken)    {        return await _repository.FindByIdAsync(id, cancellationToken);    }}``` ### 3. Depend on Abstractions Use interfaces for service boundaries. Register via DI container. ```csharp// Good: Interface-based dependencypublic interface IOrderRepository{    Task<Order?> FindByIdAsync(Guid id, CancellationToken cancellationToken);    Task<IReadOnlyList<Order>> FindByCustomerAsync(string customerId, CancellationToken cancellationToken);    Task AddAsync(Order order, CancellationToken cancellationToken);} // Registrationbuilder.Services.AddScoped<IOrderRepository, SqlOrderRepository>();``` ## Async/Await Patterns ### Proper Async Usage ```csharp// Good: Async all the way, with CancellationTokenpublic async Task<OrderSummary> GetOrderSummaryAsync(    Guid orderId,    CancellationToken cancellationToken){    var order = await _repository.FindByIdAsync(orderId, cancellationToken)        ?? throw new NotFoundException($"Order {orderId} not found");     var customer = await _customerService.GetAsync(order.CustomerId, cancellationToken);     return new OrderSummary(order, customer);} // Bad: Blocking on asyncpublic OrderSummary GetOrderSummary(Guid orderId){    var order = _repository.FindByIdAsync(orderId, CancellationToken.None).Result; // Deadlock risk    return new OrderSummary(order);}``` ### Parallel Async Operations ```csharp// Good: Concurrent independent operationspublic async Task<DashboardData> LoadDashboardAsync(CancellationToken cancellationToken){    var ordersTask = _orderService.GetRecentAsync(cancellationToken);    var metricsTask = _metricsService.GetCurrentAsync(cancellationToken);    var alertsTask = _alertService.GetActiveAsync(cancellationToken);     await Task.WhenAll(ordersTask, metricsTask, alertsTask);     return new DashboardData(        Orders: await ordersTask,        Metrics: await metricsTask,        Alerts: await alertsTask);}``` ## Options Pattern Bind configuration sections to strongly-typed objects. ```csharppublic sealed class SmtpOptions{    public const string SectionName = "Smtp";     public required string Host { get; init; }    public required int Port { get; init; }    public required string Username { get; init; }    public bool UseSsl { get; init; } = true;} // Registrationbuilder.Services.Configure<SmtpOptions>(    builder.Configuration.GetSection(SmtpOptions.SectionName)); // Usage via injectionpublic class EmailService(IOptions<SmtpOptions> options){    private readonly SmtpOptions _smtp = options.Value;}``` ## Result Pattern Return explicit success/failure instead of throwing for expected failures. ```csharppublic sealed record Result<T>{    public bool IsSuccess { get; }    public T? Value { get; }    public string? Error { get; }     private Result(T value) { IsSuccess = true; Value = value; }    private Result(string error) { IsSuccess = false; Error = error; }     public static Result<T> Success(T value) => new(value);    public static Result<T> Failure(string error) => new(error);} // Usagepublic async Task<Result<Order>> PlaceOrderAsync(CreateOrderRequest request){    if (request.Items.Count == 0)        return Result<Order>.Failure("Order must contain at least one item");     var order = Order.Create(request);    await _repository.AddAsync(order, CancellationToken.None);    return Result<Order>.Success(order);}``` ## Repository Pattern with EF Core ```csharppublic sealed class SqlOrderRepository : IOrderRepository{    private readonly AppDbContext _db;     public SqlOrderRepository(AppDbContext db) => _db = db;     public async Task<Order?> FindByIdAsync(Guid id, CancellationToken cancellationToken)    {        return await _db.Orders            .Include(o => o.Items)            .AsNoTracking()            .FirstOrDefaultAsync(o => o.Id == id, cancellationToken);    }     public async Task<IReadOnlyList<Order>> FindByCustomerAsync(        string customerId,        CancellationToken cancellationToken)    {        return await _db.Orders            .Where(o => o.CustomerId == customerId)            .OrderByDescending(o => o.CreatedAt)            .AsNoTracking()            .ToListAsync(cancellationToken);    }     public async Task AddAsync(Order order, CancellationToken cancellationToken)    {        _db.Orders.Add(order);        await _db.SaveChangesAsync(cancellationToken);    }}``` ## Middleware and Pipeline ```csharp// Custom middlewarepublic sealed 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);        }    }}``` ## Minimal API Patterns ```csharp// Organized with route groupsvar orders = app.MapGroup("/api/orders")    .RequireAuthorization()    .WithTags("Orders"); orders.MapGet("/{id:guid}", async (    Guid id,    IOrderRepository repository,    CancellationToken cancellationToken) =>{    var order = await repository.FindByIdAsync(id, cancellationToken);    return order is not null        ? TypedResults.Ok(order)        : TypedResults.NotFound();}); orders.MapPost("/", async (    CreateOrderRequest request,    IOrderService service,    CancellationToken cancellationToken) =>{    var result = await service.PlaceOrderAsync(request, cancellationToken);    return result.IsSuccess        ? TypedResults.Created($"/api/orders/{result.Value!.Id}", result.Value)        : TypedResults.BadRequest(result.Error);});``` ## Guard Clauses ```csharp// Good: Early returns with clear validationpublic async Task<ProcessResult> ProcessPaymentAsync(    PaymentRequest request,    CancellationToken cancellationToken){    ArgumentNullException.ThrowIfNull(request);     if (request.Amount <= 0)        throw new ArgumentOutOfRangeException(nameof(request.Amount), "Amount must be positive");     if (string.IsNullOrWhiteSpace(request.Currency))        throw new ArgumentException("Currency is required", nameof(request.Currency));     // Happy path continues here without nesting    var gateway = _gatewayFactory.Create(request.Currency);    return await gateway.ChargeAsync(request, cancellationToken);}``` ## Anti-Patterns to Avoid | Anti-Pattern | Fix ||---|---|| `async void` methods | Return `Task` (except event handlers) || `.Result` or `.Wait()` | Use `await` || `catch (Exception) { }` | Handle or rethrow with context || `new Service()` in constructors | Use constructor injection || `public` fields | Use properties with appropriate accessors || `dynamic` in business logic | Use generics or explicit types || Mutable `static` state | Use DI scoping or `ConcurrentDictionary` || `string.Format` in loops | Use `StringBuilder` or interpolated string handlers |