dknet-overview

star 0

DKNet Framework overview, architecture patterns, and getting started guide for building enterprise .NET applications

baoduy By baoduy schedule Updated 2/17/2026

name: dknet-overview description: DKNet Framework overview, architecture patterns, and getting started guide for building enterprise .NET applications license: MIT

DKNet Framework Overview Skill

This skill helps GitHub Copilot understand the DKNet Framework architecture, patterns, and conventions when generating code for applications using DKNet NuGet packages.

๐ŸŽฏ Framework Purpose

DKNet is a comprehensive .NET framework for building enterprise applications with:

  • Clean Architecture - Onion Architecture with Domain-Driven Design
  • EF Core Extensions - Specifications, dynamic predicates, repositories, audit logs
  • ASP.NET Core Features - Idempotency, background tasks, API extensions
  • Messaging - Event-driven architecture with SlimBus
  • Quality First - Zero warnings, nullable types, comprehensive testing

๐Ÿ—๏ธ Architecture Layers

Domain Layer (Core)

// Entities with business logic
public class Order : EntityBase
{
    public Guid CustomerId { get; private set; }
    public OrderStatus Status { get; private set; }
    
    public void PlaceOrder()
    {
        if (Status != OrderStatus.Draft)
            throw new DomainException("Only draft orders can be placed");
            
        Status = OrderStatus.Placed;
        AddDomainEvent(new OrderPlacedEvent(Id));
    }
}

Application Layer

// CQRS Command Handler
public class PlaceOrderHandler : ICommandHandler<PlaceOrderCommand, OrderResult>
{
    private readonly IOrderRepository _repository;
    
    public async Task<OrderResult> Handle(
        PlaceOrderCommand command,
        CancellationToken cancellationToken)
    {
        var spec = new OrderByIdSpec(command.OrderId);
        var order = await _repository.FirstOrDefaultAsync(spec, cancellationToken);
        
        order.PlaceOrder();
        await _repository.UpdateAsync(order, cancellationToken);
        
        return OrderResult.FromEntity(order);
    }
}

Infrastructure Layer

// Repository implementation
public class OrderRepository : RepositoryBase<Order>, IOrderRepository
{
    public OrderRepository(AppDbContext context) : base(context)
    {
    }
}

Presentation Layer

// Minimal API endpoint
app.MapPost("/orders/{id}/place", async (
    Guid id,
    IMediator mediator,
    CancellationToken cancellationToken) =>
{
    var command = new PlaceOrderCommand(id);
    var result = await mediator.Send(command, cancellationToken);
    return Results.Ok(result);
})
.WithIdempotency(); // DKNet idempotency support

๐Ÿ“ฆ Core Packages

DKNet.Fw.Extensions

Core utilities and extension methods

using DKNet.Fw.Extensions;

// String extensions
var isValid = email.IsValidEmail();

// Type extensions
if (typeof(MyEnum).TryConvertToEnum("Value", out MyEnum result))
{
    // Use result
}

// Collection extensions
var nonEmpty = collection.WhereNotNullOrEmpty();

DKNet.EfCore.Abstractions

Base entities and common abstractions

using DKNet.EfCore.Abstractions;

// Base entity with Id, CreatedAt, UpdatedAt
public class Product : EntityBase
{
    public string Name { get; set; } = string.Empty;
    public decimal Price { get; set; }
}

// Soft delete support
public class Order : EntityBase, ISoftDelete
{
    public bool IsDeleted { get; set; }
}

// Audit trail
public class Document : EntityBase, IAuditable
{
    public string CreatedBy { get; set; } = string.Empty;
    public string? UpdatedBy { get; set; }
}

DKNet.EfCore.Specifications

Specification pattern for reusable queries

using DKNet.EfCore.Specifications;

// Define specification
public class ActiveProductsSpec : Specification<Product>
{
    public ActiveProductsSpec()
    {
        WithFilter(p => !p.IsDeleted && p.IsActive);
        AddInclude(p => p.Category);
        AddOrderBy(p => p.Name);
    }
}

// Use in repository
var products = await repository.ToListAsync(
    new ActiveProductsSpec(), 
    cancellationToken);

DKNet.EfCore.Repos

Repository pattern implementation

using DKNet.EfCore.Repos;

public interface IProductRepository : IRepositoryBase<Product>
{
}

public class ProductRepository : RepositoryBase<Product>, IProductRepository
{
    public ProductRepository(AppDbContext context) : base(context)
    {
    }
}

// Usage
var product = await repository.GetByIdAsync(id, cancellationToken);
await repository.AddAsync(product, cancellationToken);
await repository.SaveChangesAsync(cancellationToken);

DKNet.AspCore.Idempotency

Idempotent API operations

using DKNet.AspCore.Idempotency;

// Add idempotency support
builder.Services.AddIdempotency(options =>
{
    options.IdempotencyKeyHeader = "X-Idempotency-Key";
    options.CacheTimeout = TimeSpan.FromMinutes(5);
});

// Use with minimal API
app.MapPost("/orders", CreateOrder)
    .WithIdempotency();

// Or with attribute
[HttpPost]
[Idempotent]
public async Task<IActionResult> CreateOrder([FromBody] CreateOrderRequest request)
{
    // Implementation
}

DKNet.SlimBus.Extensions

Lightweight message bus

using DKNet.SlimBus.Extensions;

// Define message
public record OrderCreatedEvent(Guid OrderId, decimal Total);

// Publish message
await messageBus.PublishAsync(
    new OrderCreatedEvent(order.Id, order.Total),
    cancellationToken);

// Subscribe to message
public class OrderCreatedHandler : IMessageHandler<OrderCreatedEvent>
{
    public async Task HandleAsync(
        OrderCreatedEvent message,
        CancellationToken cancellationToken)
    {
        // Handle the event
    }
}

๐ŸŽฏ Key Patterns to Follow

1. Always Use Async/Await for I/O

// โœ… Good
public async Task<Order> GetOrderAsync(Guid id, CancellationToken ct)
{
    return await repository.GetByIdAsync(id, ct);
}

// โŒ Bad
public Order GetOrder(Guid id)
{
    return repository.GetByIdAsync(id, default).Result; // Blocks thread
}

2. Use Specifications for Queries

// โœ… Good - Reusable, testable
public class PendingOrdersSpec : Specification<Order>
{
    public PendingOrdersSpec()
    {
        WithFilter(o => o.Status == OrderStatus.Pending);
    }
}
var orders = await repository.ToListAsync(new PendingOrdersSpec(), ct);

// โŒ Bad - Not reusable
var orders = await context.Orders
    .Where(o => o.Status == OrderStatus.Pending)
    .ToListAsync(ct);

3. Use Dynamic Predicates for Complex Filters

// โœ… Good - Dynamic, safe
var predicate = PredicateBuilder.New<Product>(p => !p.IsDeleted);

if (!string.IsNullOrEmpty(searchTerm))
{
    predicate = predicate.DynamicAnd(builder => builder
        .With("Name", FilterOperations.Contains, searchTerm));
}

var products = await context.Products
    .AsNoTracking()
    .AsExpandable() // Required for LinqKit
    .Where(predicate)
    .ToListAsync(ct);

// โŒ Bad - Building SQL strings (SQL injection risk)
var sql = "SELECT * FROM Products WHERE Name LIKE '%" + searchTerm + "%'";

4. Always Include CancellationToken

// โœ… Good
public async Task<IActionResult> GetOrders(CancellationToken cancellationToken)
{
    var orders = await repository.ToListAsync(ct: cancellationToken);
    return Ok(orders);
}

// โŒ Bad
public async Task<IActionResult> GetOrders()
{
    var orders = await repository.ToListAsync(ct: default);
    return Ok(orders);
}

5. Use Nullable Reference Types

// โœ… Good - Explicit nullability
public class Product : EntityBase
{
    public string Name { get; set; } = string.Empty; // Never null
    public string? Description { get; set; } // Can be null
}

// โŒ Bad - Unclear nullability
public class Product : EntityBase
{
    public string Name { get; set; } // Is this nullable?
    public string Description { get; set; } // Is this nullable?
}

6. Always Add XML Documentation

// โœ… Good
/// <summary>
///     Gets the order by identifier asynchronously.
/// </summary>
/// <param name="id">The order identifier.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>The order if found, otherwise null.</returns>
public async Task<Order?> GetOrderAsync(Guid id, CancellationToken cancellationToken)
{
    return await repository.GetByIdAsync(id, cancellationToken);
}

// โŒ Bad - No documentation
public async Task<Order?> GetOrderAsync(Guid id, CancellationToken cancellationToken)
{
    return await repository.GetByIdAsync(id, cancellationToken);
}

๐Ÿšซ Common Anti-Patterns to Avoid

1. Don't Mix Sync and Async

// โŒ Bad - Deadlock risk
var result = asyncMethod().Result;
var result2 = asyncMethod().GetAwaiter().GetResult();

// โœ… Good
var result = await asyncMethod();

2. Don't Use .AsNoTracking() for Updates

// โŒ Bad - Changes won't be tracked
var product = await context.Products.AsNoTracking().FirstAsync();
product.Price = 100;
await context.SaveChangesAsync(); // Nothing saved!

// โœ… Good - Track for updates
var product = await context.Products.FirstAsync();
product.Price = 100;
await context.SaveChangesAsync(); // Saved!

3. Don't Forget .AsExpandable() with LinqKit

// โŒ Bad - Runtime error
var predicate = PredicateBuilder.New<Product>(p => p.IsActive);
var products = await context.Products
    .Where(predicate) // Error!
    .ToListAsync();

// โœ… Good
var predicate = PredicateBuilder.New<Product>(p => p.IsActive);
var products = await context.Products
    .AsExpandable() // Required!
    .Where(predicate)
    .ToListAsync();

4. Don't Create N+1 Queries

// โŒ Bad - N+1 queries
var orders = await context.Orders.ToListAsync();
foreach (var order in orders)
{
    var customer = await context.Customers.FindAsync(order.CustomerId);
    // Use customer
}

// โœ… Good - Single query with include
var orders = await context.Orders
    .Include(o => o.Customer)
    .ToListAsync();

๐Ÿ“ Code Quality Standards

  • Zero Warnings: TreatWarningsAsErrors=true enforced
  • Nullable Types: Always enabled with <Nullable>enable</Nullable>
  • XML Docs: Required on all public APIs
  • Test Coverage: Target 85%+
  • Async/Await: For all I/O operations
  • CancellationToken: Always include in async methods

๐Ÿ”— Related Skills

  • efcore-specifications - Deep dive into Specification pattern
  • efcore-repos - Repository pattern implementation details
  • efcore-abstractions - Base entities and interfaces
  • aspcore-idempotency - Idempotent API operations
  • fw-extensions - Core utility methods
  • slimbus-messaging - Message bus patterns

๐Ÿ“š Additional Resources


When to Use This Skill: Reference this skill when starting a new project with DKNet Framework, understanding the overall architecture, or learning the core patterns and conventions.

Install via CLI
npx skills add https://github.com/baoduy/drunk-mcp-proxy --skill dknet-overview
Repository Details
star Stars 0
call_split Forks 0
navigation Branch main
article Path SKILL.md
More from Creator