Source profileQuality 75/100

affaan-m/ECC/skills/dotnet-patterns/SKILL.md

dotnet-patterns

Idiomatic C# and .NET patterns, conventions, dependency injection, async/await, and best practices for building robust, maintainable .NET applications.

Source repository stars
234,327
Declared platforms
0
Static risk flags
0
Last source update
2026-07-27
Source checked
2026-07-28

Decision brief

What it does—and where it fits

Idiomatic C and .NET patterns for building robust, performant, and maintainable applications.

Best for

    Not for

    • Tasks that require unconfirmed production actions or broad system permissions.
    • Environments where the pinned source and install steps cannot be inspected.

    Compatibility matrix

    Platform support, with evidence labels

    PlatformStatusEvidenceWhat to check
    CodexNot declaredNo explicit evidencePortability before use
    Claude CodeNot declaredNo explicit evidencePortability before use
    CursorNot declaredNo explicit evidencePortability before use
    Gemini CLINot declaredNo explicit evidencePortability before use
    Open the compatibility checker

    Installation

    Inspect first. Install second.

    The source command is displayed only when detected. A safe inspection prompt is always available so your agent can explain every action before execution.

    Source-detected install commandSource
    npx skills add https://github.com/affaan-m/ECC --skill "skills/dotnet-patterns"
    Safe inspection promptEditorial

    Inspect the Agent Skill "dotnet-patterns" from https://github.com/affaan-m/ECC/blob/4e973d3eaf92d97f8d2e2d8abb39d8bdc8711b38/skills/dotnet-patterns/SKILL.md at commit 4e973d3eaf92d97f8d2e2d8abb39d8bdc8711b38. List every install step, command, network request, credential, file read/write, external action, and rollback step. Explain whether it fits my task. Do not install or execute anything until I approve.

    Workflow

    What the source asks the agent to do

    1. 01

      Proper Async Usage

      Review the “Proper Async Usage” section in the pinned source before continuing.

      Review and apply the “Proper Async Usage” source section.
    2. 02

      When to Activate

      Writing new C code

      Writing new C codeReviewing C codeRefactoring existing .NET applications
    3. 03

      Core Principles

      Use records and init-only properties for data models. Mutability should be an explicit, justified choice.

      Use records and init-only properties for data models. Mutability should be an explicit, justified choice.Be clear about nullability, access modifiers, and intent.Use interfaces for service boundaries. Register via DI container.
    4. 04

      1. Prefer Immutability

      Use records and init-only properties for data models. Mutability should be an explicit, justified choice.

      Use records and init-only properties for data models. Mutability should be an explicit, justified choice.
    5. 05

      2. Explicit Over Implicit

      Be clear about nullability, access modifiers, and intent.

      Be clear about nullability, access modifiers, and intent.

    Permission review

    Static risk signals and limitations

    No configured static risk pattern was detected

    This is not proof of safety. Runtime behavior, indirect dependencies, and hidden external systems are outside the static scan.

    Evidence record

    Why each signal appears

    EvidenceSourceComputedTestedEditorial
    SignalValueEvidence typeMeaning
    Quality score75/100ComputedDocumentation, specificity, maintenance, and trust rules
    Repository stars234,327SourceRepository attention, not individual Skill quality
    Compatibility0 platformsSourceDeclared in the catalog source record
    Usage guideautomated source guideEditorialGenerated or reviewed according to the visible evidence level

    Pinned source

    Provenance and original SKILL.md

    Repository
    affaan-m/ECC
    Skill path
    skills/dotnet-patterns/SKILL.md
    Commit
    4e973d3eaf92d97f8d2e2d8abb39d8bdc8711b38
    License
    MIT
    Collected
    2026-07-28
    Default branch
    main
    View the original SKILL.md

    .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.

    // Good: Immutable value object
    public sealed record Money(decimal Amount, string Currency);
    
    // Good: Immutable DTO with init setters
    public sealed class CreateOrderRequest
    {
        public required string CustomerId { get; init; }
        public required IReadOnlyList<OrderItem> Items { get; init; }
    }
    
    // Bad: Mutable model with public setters
    public 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.

    // Good: Explicit access modifiers and nullability
    public 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.

    // Good: Interface-based dependency
    public interface IOrderRepository
    {
        Task<Order?> FindByIdAsync(Guid id, CancellationToken cancellationToken);
        Task<IReadOnlyList<Order>> FindByCustomerAsync(string customerId, CancellationToken cancellationToken);
        Task AddAsync(Order order, CancellationToken cancellationToken);
    }
    
    // Registration
    builder.Services.AddScoped<IOrderRepository, SqlOrderRepository>();
    

    Async/Await Patterns

    Proper Async Usage

    // Good: Async all the way, with CancellationToken
    public 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 async
    public OrderSummary GetOrderSummary(Guid orderId)
    {
        var order = _repository.FindByIdAsync(orderId, CancellationToken.None).Result; // Deadlock risk
        return new OrderSummary(order);
    }
    

    Parallel Async Operations

    // Good: Concurrent independent operations
    public 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.

    public 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;
    }
    
    // Registration
    builder.Services.Configure<SmtpOptions>(
        builder.Configuration.GetSection(SmtpOptions.SectionName));
    
    // Usage via injection
    public class EmailService(IOptions<SmtpOptions> options)
    {
        private readonly SmtpOptions _smtp = options.Value;
    }
    

    Result Pattern

    Return explicit success/failure instead of throwing for expected failures.

    public 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);
    }
    
    // Usage
    public 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

    public 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

    // Custom middleware
    public 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

    // Organized with route groups
    var 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

    // Good: Early returns with clear validation
    public 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-PatternFix
    async void methodsReturn Task (except event handlers)
    .Result or .Wait()Use await
    catch (Exception) { }Handle or rethrow with context
    new Service() in constructorsUse constructor injection
    public fieldsUse properties with appropriate accessors
    dynamic in business logicUse generics or explicit types
    Mutable static stateUse DI scoping or ConcurrentDictionary
    string.Format in loopsUse StringBuilder or interpolated string handlers

    Alternatives

    Compare before choosing