DKNet is an enterprise-grade .NET library collection focused on advanced EF Core extensions, dynamic predicate building, and the Specification pattern. It provides production-ready tools for building robust, type-safe, and testable data access layers, including dynamic LINQ support, LinqKit integration. Designed for modern cloud-native applications, DKNet enforces strict code quality, async best practices, and full documentation for all public APIs. Enterprise-grade .NET library suite for modern application development, featuring advanced EF Core extensions (dynamic predicates, specifications, LinqKit), robust Domain-Driven Design (DDD) patterns, and domain event support. DKNet empowers scalable, maintainable, and testable solutions with type-safe validation, async/await, XML documentation, and high code quality standards. Ideal for cloud-native, microservices, and enterprise architectures.
$ dotnet add package DKNet.SlimBus.ExtensionsEnhanced SlimMessageBus integration with Entity Framework Core, providing fluent interfaces for CQRS patterns, automatic transaction management, and result-based error handling. This package bridges SlimMessageBus with EF Core for seamless domain-driven applications.
Install via NuGet Package Manager:
dotnet add package DKNet.SlimBus.Extensions
Or via Package Manager Console:
Install-Package DKNet.SlimBus.Extensions
using Microsoft.Extensions.DependencyInjection;
using DKNet.SlimBus.Extensions;
// Configure SlimBus with EF Core integration
services.AddSlimBusForEfCore(builder =>
{
builder
.WithProviderMemory() // or other providers
.AutoDeclareFrom(typeof(CreateProductHandler).Assembly)
.AddJsonSerializer();
});
// Add your DbContext
services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(connectionString));
using DKNet.SlimBus.Extensions;
using FluentResults;
// Command definition
public record CreateProduct : Fluents.Requests.IWitResponse<ProductResult>
{
public required string Name { get; init; }
public required decimal Price { get; init; }
public required string CategoryId { get; init; }
}
public record ProductResult
{
public required Guid Id { get; init; }
public required string Name { get; init; }
public required decimal Price { get; init; }
}
// Command handler
public class CreateProductHandler : Fluents.Requests.IHandler<CreateProduct, ProductResult>
{
private readonly AppDbContext _context;
private readonly IMapper _mapper;
public CreateProductHandler(AppDbContext context, IMapper mapper)
{
_context = context;
_mapper = mapper;
}
public async Task<IResult<ProductResult>> Handle(CreateProduct request, CancellationToken cancellationToken)
{
// Validation
if (await _context.Products.AnyAsync(p => p.Name == request.Name, cancellationToken))
return Result.Fail<ProductResult>($"Product with name '{request.Name}' already exists");
// Create entity
var product = new Product(request.Name, request.Price, request.CategoryId);
_context.Products.Add(product);
// EF Core SaveChanges is called automatically after successful processing
return Result.Ok(_mapper.Map<ProductResult>(product));
}
}
using DKNet.SlimBus.Extensions;
// Query definition
public record GetProduct : Fluents.Queries.IWitResponse<ProductResult>
{
public required Guid Id { get; init; }
}
// Query handler (no auto-save for queries)
public class GetProductHandler : Fluents.Queries.IHandler<GetProduct, ProductResult>
{
private readonly AppDbContext _context;
private readonly IMapper _mapper;
public GetProductHandler(AppDbContext context, IMapper mapper)
{
_context = context;
_mapper = mapper;
}
public async Task<ProductResult?> Handle(GetProduct request, CancellationToken cancellationToken)
{
var product = await _context.Products
.FirstOrDefaultAsync(p => p.Id == request.Id, cancellationToken);
return product == null ? null : _mapper.Map<ProductResult>(product);
}
}
using X.PagedList;
using DKNet.SlimBus.Extensions;
// Paged query definition
public record GetProductsPage : Fluents.Queries.IWitPageResponse<ProductResult>
{
public int PageIndex { get; init; } = 0;
public int PageSize { get; init; } = 20;
public string? CategoryId { get; init; }
}
// Paged query handler
public class GetProductsPageHandler : Fluents.Queries.IPageHandler<GetProductsPage, ProductResult>
{
private readonly AppDbContext _context;
private readonly IMapper _mapper;
public GetProductsPageHandler(AppDbContext context, IMapper mapper)
{
_context = context;
_mapper = mapper;
}
public async Task<IPagedList<ProductResult>> Handle(GetProductsPage request, CancellationToken cancellationToken)
{
var query = _context.Products.AsQueryable();
if (!string.IsNullOrEmpty(request.CategoryId))
query = query.Where(p => p.CategoryId == request.CategoryId);
return await query
.OrderBy(p => p.Name)
.Select(p => _mapper.Map<ProductResult>(p))
.ToPagedListAsync(request.PageIndex, request.PageSize, cancellationToken);
}
}
// Handlers are automatically discovered and registered
services.AddSlimBusForEfCore(builder =>
{
builder
.WithProviderMemory()
// Auto-discover handlers from assemblies
.AutoDeclareFrom(typeof(CreateProductHandler).Assembly, typeof(GetProductHandler).Assembly)
.AddJsonSerializer();
});
using SlimMessageBus.Host.Interceptor;
public class ValidationInterceptor<TRequest, TResponse> : IRequestHandlerInterceptor<TRequest, TResponse>
{
private readonly IValidator<TRequest> _validator;
public ValidationInterceptor(IValidator<TRequest> validator)
{
_validator = validator;
}
public async Task<TResponse> OnHandle(TRequest request, Func<Task<TResponse>> next, IConsumerContext context)
{
// Validate request
var validationResult = await _validator.ValidateAsync(request, context.CancellationToken);
if (!validationResult.IsValid)
{
if (typeof(TResponse).IsAssignableFrom(typeof(IResultBase)))
{
var errors = validationResult.Errors.Select(e => e.ErrorMessage);
return (TResponse)(object)Result.Fail(errors);
}
throw new ValidationException(validationResult.Errors);
}
return await next();
}
}
// Register the interceptor
services.AddScoped(typeof(IRequestHandlerInterceptor<,>), typeof(ValidationInterceptor<,>));
Fluents.Requests.INoResponse - Commands that don't return dataFluents.Requests.IWitResponse<TResponse> - Commands that return dataFluents.Requests.IHandler<TRequest> - Handler for no-response commandsFluents.Requests.IHandler<TRequest, TResponse> - Handler for commands with responseFluents.Queries.IWitResponse<TResponse> - Single-item queriesFluents.Queries.IWitPageResponse<TResponse> - Paged queriesFluents.Queries.IHandler<TQuery, TResponse> - Single-item query handlerFluents.Queries.IPageHandler<TQuery, TResponse> - Paged query handlerFluents.EventsConsumers.IHandler<TEvent> - Domain event handlerAddSlimBusForEfCore() - Register SlimBus with EF Core auto-saveEfAutoSavePostProcessor<,> - Automatic SaveChanges after successful commandsusing DKNet.SlimBus.Extensions;
// Domain event
public record ProductCreatedEvent(Guid ProductId, string Name, decimal Price);
// Event handler
public class ProductCreatedHandler : Fluents.EventsConsumers.IHandler<ProductCreatedEvent>
{
private readonly ILogger<ProductCreatedHandler> _logger;
private readonly IEmailService _emailService;
public ProductCreatedHandler(ILogger<ProductCreatedHandler> logger, IEmailService emailService)
{
_logger = logger;
_emailService = emailService;
}
public async Task Handle(ProductCreatedEvent notification, CancellationToken cancellationToken)
{
_logger.LogInformation("Product created: {ProductId} - {Name}", notification.ProductId, notification.Name);
// Send notification email
await _emailService.SendProductCreatedNotificationAsync(notification.ProductId, cancellationToken);
}
}
public record ProcessOrder : Fluents.Requests.IWitResponse<OrderResult>
{
public required Guid CustomerId { get; init; }
public required List<OrderItemRequest> Items { get; init; }
}
public class ProcessOrderHandler : Fluents.Requests.IHandler<ProcessOrder, OrderResult>
{
private readonly AppDbContext _context;
private readonly IInventoryService _inventoryService;
private readonly IPaymentService _paymentService;
public ProcessOrderHandler(AppDbContext context, IInventoryService inventoryService, IPaymentService paymentService)
{
_context = context;
_inventoryService = inventoryService;
_paymentService = paymentService;
}
public async Task<IResult<OrderResult>> Handle(ProcessOrder request, CancellationToken cancellationToken)
{
// Validate inventory
var inventoryCheck = await _inventoryService.CheckAvailabilityAsync(request.Items, cancellationToken);
if (!inventoryCheck.IsSuccess)
return Result.Fail<OrderResult>(inventoryCheck.Errors);
// Create order
var order = new Order(request.CustomerId);
foreach (var item in request.Items)
{
order.AddItem(item.ProductId, item.Quantity, item.Price);
}
_context.Orders.Add(order);
// Process payment
var paymentResult = await _paymentService.ProcessPaymentAsync(order.TotalAmount, cancellationToken);
if (!paymentResult.IsSuccess)
return Result.Fail<OrderResult>(paymentResult.Errors);
order.MarkAsPaid(paymentResult.Value.TransactionId);
// EF Core will auto-save all changes if no errors occur
return Result.Ok(new OrderResult
{
OrderId = order.Id,
TotalAmount = order.TotalAmount,
Status = order.Status
});
}
}
The package uses FluentResults for comprehensive error handling:
public async Task<IResult<ProductResult>> Handle(CreateProduct request, CancellationToken cancellationToken)
{
// Business rule validation
if (request.Price <= 0)
return Result.Fail<ProductResult>("Price must be greater than zero");
// Multiple validation errors
var errors = new List<string>();
if (string.IsNullOrEmpty(request.Name))
errors.Add("Name is required");
if (request.Price <= 0)
errors.Add("Price must be positive");
if (errors.Any())
return Result.Fail<ProductResult>(errors);
// Success case
var product = new Product(request.Name, request.Price);
_context.Products.Add(product);
return Result.Ok(_mapper.Map<ProductResult>(product));
}
See the main CONTRIBUTING.md for guidelines on how to contribute to this project.
This project is licensed under the MIT License.
Part of the DKNet Framework - A comprehensive .NET framework for building modern, scalable applications.