Marventa.Framework
5.1.0
See the version list below for details.
dotnet add package Marventa.Framework --version 5.1.0
NuGet\Install-Package Marventa.Framework -Version 5.1.0
<PackageReference Include="Marventa.Framework" Version="5.1.0" />
<PackageVersion Include="Marventa.Framework" Version="5.1.0" />
<PackageReference Include="Marventa.Framework" />
paket add Marventa.Framework --version 5.1.0
#r "nuget: Marventa.Framework, 5.1.0"
#:package Marventa.Framework@5.1.0
#addin nuget:?package=Marventa.Framework&version=5.1.0
#tool nuget:?package=Marventa.Framework&version=5.1.0
🚀 Marventa.Framework
Enterprise .NET Framework - Convention over Configuration
📖 Table of Contents
- Installation
- Basic Setup (2 Lines!)
- Core - Domain Driven Design (DDD)
- 3.1. Domain - Entity
- 3.2. Domain - Aggregate Root
- 3.3. Domain - Value Object
- 3.4. Domain - Domain Events
- 3.5. Domain - Auditable Entity
- 3.6. Application - Result Pattern
- Infrastructure - Database & Repository
- Behaviors - CQRS with MediatR
- 5.1. Create Command
- 5.2. Create Query
- 5.3. Validation Behavior
- 5.4. Logging Behavior
- 5.5. Performance Behavior
- Features - Caching
- 6.1. InMemory Cache
- 6.2. Redis Cache
- 6.3. Hybrid Cache
- Features - Event Bus
- 7.1. RabbitMQ Event Bus
- 7.2. Kafka Producer/Consumer
- 7.3. MassTransit Integration
- Features - Storage
- 8.1. Local File Storage
- 8.2. Azure Blob Storage
- 8.3. AWS S3 Storage
- Features - Search
- 9.1. Elasticsearch
- Features - Notification
- Features - Localization (i18n)
- Features - Template Engine
- Features - Reporting
- 13.1. PDF Report Generation
- 13.2. Excel Report Generation
- 13.3. CSV Report Generation
- Features - Payment Gateway
- Features - Job Scheduler
- Features - Audit Logging
- Features - Logging
- 17.1. Serilog
- 17.2. OpenTelemetry Tracing
- Security - Authentication
- 18.1. JWT Token Generator
- 18.2. Password Hasher
- 18.3. AES Encryption
- Security - Authorization
- Security - Rate Limiting
- Infrastructure - Multi-Tenancy
- Infrastructure - Health Checks
- Infrastructure - API Versioning
- Infrastructure - Swagger/OpenAPI
- Middleware - Exception Handling
- Enterprise Patterns
- 26.1. Transactional Outbox Pattern
- 26.2. Repository Specification Pattern
- 26.3. Idempotency Pattern
- 26.4. Circuit Breaker & Resilience
- Advanced Security
- 27.1. API Key Authentication
- 27.2. Request/Response Logging
- Configuration - appsettings.json
1. Installation
dotnet add package Marventa.Framework
2. Basic Setup
Marventa Framework offers 4 flexible integration levels - choose what fits your needs:
⚡ Level 1: Everything (Recommended for Monoliths)
Get started instantly with ALL features (Core + Extended):
var builder = WebApplication.CreateBuilder(args);
// ✨ ONE LINE - ALL features enabled (Core + Extended)
// Includes: CQRS, Repository, Event Sourcing, JWT, Caching, Event Bus, Outbox,
// Notifications, Localization, Templating, Reporting, Payment, Job Scheduler, Audit Logging
builder.Services.AddMarventaFramework(builder.Configuration);
var app = builder.Build();
// ✨ ONE LINE - All middleware configured automatically
app.UseMarventa(builder.Configuration);
app.Run();
🎯 Level 2: Core Only (Recommended for Microservices)
Use only architectural patterns and infrastructure - perfect for microservices:
var builder = WebApplication.CreateBuilder(args);
// ✨ CORE FEATURES ONLY - Perfect for microservices
// Includes: CQRS, Repository, Event Sourcing, JWT, Caching, Event Bus, Outbox,
// Idempotency, Circuit Breaker, API Versioning, Swagger, Health Checks
builder.Services.AddMarventaCore(builder.Configuration);
var app = builder.Build();
app.UseMarventa(builder.Configuration);
app.Run();
🔧 Level 3: Core + Selected Extended Features
Mix and match - add only what you need:
var builder = WebApplication.CreateBuilder(args);
// Start with Core
builder.Services.AddMarventaCore(builder.Configuration);
// Add extended features selectively
builder.Services.AddNotifications(builder.Configuration); // Email, SMS, Push
builder.Services.AddMarventaLocalization(builder.Configuration); // i18n
builder.Services.AddAuditLogging(); // Compliance tracking
var app = builder.Build();
app.UseMarventa(builder.Configuration);
app.Run();
⚙️ Level 4: Granular Control (Advanced)
Full control with fluent API:
var builder = WebApplication.CreateBuilder(args);
// Fluent API for precise control
builder.Services.AddMarventaFramework(builder.Configuration, options =>
{
// Core features
options.AddCqrs();
options.AddRepositoryPattern();
options.AddEventBus();
options.AddOutboxPattern();
options.AddJwtAuthentication();
options.AddCaching();
// Extended features
options.AddEmailService();
options.AddSmsService();
options.AddAuditLogging();
options.AddTemplateEngine();
});
var app = builder.Build();
app.UseMarventa(builder.Configuration);
app.Run();
📊 Quick Comparison
| Integration Level | Use Case | Features Included |
|---|---|---|
Level 1: AddMarventaFramework() |
Monolith, Full-stack apps | Core + Extended (Everything) |
Level 2: AddMarventaCore() |
Microservices | Core only (Patterns + Infrastructure) |
| Level 3: Core + Selective | Hybrid, Custom needs | Core + Selected extended features |
| Level 4: Fluent API | Advanced, Fine-grained control | Pick each feature individually |
🔥 Feature Groupings
Core Features (Production-ready patterns):
- ✅ CQRS & MediatR
- ✅ Repository & Specification Pattern
- ✅ Event Sourcing & Domain Events
- ✅ Event Bus (RabbitMQ/Kafka/MassTransit)
- ✅ Transactional Outbox Pattern
- ✅ Idempotency
- ✅ JWT + API Key Authentication
- ✅ Redis Caching
- ✅ Circuit Breaker & Retry (Polly)
- ✅ API Versioning & Swagger
- ✅ Health Checks
- ✅ Multi-Tenancy
- ✅ Rate Limiting
Extended Features (Optional enterprise services):
- 📧 Notifications (Email, SMS, Push)
- 🌍 Localization (i18n)
- 📄 Template Engine (Liquid/Simple)
- 📊 Reporting (PDF, Excel, CSV)
- 💳 Payment Gateway (Stripe)
- ⏰ Job Scheduler (Hangfire)
- 📝 Audit Logging
🚀 Real-World Examples
E-commerce Monolith:
builder.Services.AddMarventaFramework(builder.Configuration);
// Get everything: CQRS, Events, Payments, Emails, Reporting, etc.
Order Microservice:
builder.Services.AddMarventaCore(builder.Configuration);
// Just patterns: CQRS, Repository, Event Bus, Outbox
Notification Microservice:
builder.Services.AddMarventaCore(builder.Configuration);
builder.Services.AddNotifications(builder.Configuration);
builder.Services.AddTemplateEngine(builder.Configuration);
// Core + Email/SMS/Push + Templates
Payment Microservice:
builder.Services.AddMarventaCore(builder.Configuration);
builder.Services.AddPaymentGateway(builder.Configuration);
builder.Services.AddAuditLogging();
// Core + Stripe + Audit
That's it! The framework automatically:
- ✅ Registers controllers and JSON serialization
- ✅ Scans assemblies for MediatR handlers, FluentValidation validators, and Mapster mappings
- ✅ Configures middleware pipeline in correct order
- ✅ Maps controller endpoints and health checks
- ✅ Activates features based on
appsettings.json
Advanced: Specify Assemblies to Scan
// Automatically scans calling assembly (recommended)
builder.Services.AddMarventa(builder.Configuration);
// Or explicitly specify assemblies to scan
builder.Services.AddMarventa(builder.Configuration, typeof(Program).Assembly);
// Or scan multiple assemblies
builder.Services.AddMarventa(
builder.Configuration,
typeof(Program).Assembly,
typeof(SomeOtherClass).Assembly
);
3. Core - Domain Driven Design
3.1. Domain - Entity
Purpose: Represents domain objects with identity.
using Marventa.Framework.Core.Domain;
public class Product : Entity<Guid>
{
public string Name { get; private set; }
public decimal Price { get; private set; }
public int Stock { get; private set; }
private Product() { }
public static Product Create(string name, decimal price, int stock)
{
return new Product
{
Id = Guid.NewGuid(),
Name = name,
Price = price,
Stock = stock
};
}
public void UpdateStock(int quantity)
{
Stock += quantity;
}
}
3.2. Domain - Aggregate Root
Purpose: Root entity that manages business rules and dispatches domain events.
public class Order : AggregateRoot<Guid>
{
private readonly List<OrderItem> _items = new();
public string OrderNumber { get; private set; }
public OrderStatus Status { get; private set; }
public IReadOnlyCollection<OrderItem> Items => _items.AsReadOnly();
public static Order Create(string orderNumber)
{
var order = new Order
{
Id = Guid.NewGuid(),
OrderNumber = orderNumber,
Status = OrderStatus.Pending
};
order.AddDomainEvent(new OrderCreatedEvent(order.Id));
return order;
}
public void Confirm()
{
Status = OrderStatus.Confirmed;
AddDomainEvent(new OrderConfirmedEvent(Id));
}
}
3.3. Domain - Value Object
Purpose: Objects without identity, compared by their values.
public class Address : ValueObject
{
public string Street { get; private set; }
public string City { get; private set; }
public string ZipCode { get; private set; }
public Address(string street, string city, string zipCode)
{
Street = street;
City = city;
ZipCode = zipCode;
}
protected override IEnumerable<object> GetEqualityComponents()
{
yield return Street;
yield return City;
yield return ZipCode;
}
}
3.4. Domain - Domain Events
Purpose: Represents events that occur within the domain.
public record ProductCreatedEvent(Guid ProductId, string Name) : DomainEvent;
public record OrderCreatedEvent(Guid OrderId) : DomainEvent;
public record OrderConfirmedEvent(Guid OrderId) : DomainEvent;
3.5. Domain - Auditable Entity
Purpose: Automatically tracks creation and update information.
public class Customer : AuditableEntity<Guid>
{
public string Name { get; set; }
public string Email { get; set; }
// CreatedAt, UpdatedAt, CreatedBy, UpdatedBy tracked automatically!
}
3.6. Application - Result Pattern
Purpose: Type-safe way to return success/failure states.
public async Task<Result<Guid>> CreateProduct(string name, decimal price)
{
if (price <= 0)
return Result<Guid>.Failure("Price must be positive");
var product = Product.Create(name, price, 0);
await _repository.AddAsync(product);
return Result<Guid>.Success(product.Id);
}
4. Infrastructure - Database & Repository
4.1. Persistence - Create DbContext
Purpose: Database connection with Entity Framework Core.
using Marventa.Framework.Infrastructure.Persistence;
public class ApplicationDbContext : BaseDbContext
{
public ApplicationDbContext(
DbContextOptions<ApplicationDbContext> options,
IHttpContextAccessor httpContextAccessor)
: base(options, httpContextAccessor)
{
}
public DbSet<Product> Products => Set<Product>();
public DbSet<Order> Orders => Set<Order>();
}
Add to Program.cs:
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
builder.Services.AddScoped<IUnitOfWork>(sp =>
new UnitOfWork(sp.GetRequiredService<ApplicationDbContext>()));
appsettings.json:
{
"ConnectionStrings": {
"DefaultConnection": "Server=localhost;Database=MyDb;Trusted_Connection=true;"
}
}
4.2. Persistence - Repository Pattern
Purpose: Abstracts database operations.
// Interface
public interface IProductRepository : IRepository<Product, Guid>
{
Task<Product?> GetByNameAsync(string name);
Task<List<Product>> SearchAsync(string searchTerm);
}
// Implementation
public class ProductRepository : GenericRepository<Product, Guid>, IProductRepository
{
public ProductRepository(ApplicationDbContext context) : base(context)
{
}
public async Task<Product?> GetByNameAsync(string name)
{
return await _dbSet.FirstOrDefaultAsync(p => p.Name == name);
}
public async Task<List<Product>> SearchAsync(string searchTerm)
{
return await _dbSet.Where(p => p.Name.Contains(searchTerm)).ToListAsync();
}
}
Add to Program.cs:
builder.Services.AddScoped<IProductRepository, ProductRepository>();
4.3. Persistence - Unit of Work
Purpose: Manages transactions and dispatches domain events.
public class ProductService
{
private readonly IProductRepository _repository;
private readonly IUnitOfWork _unitOfWork;
public async Task<Result<Guid>> CreateProductAsync(string name, decimal price)
{
var product = Product.Create(name, price, 0);
await _repository.AddAsync(product);
// Transaction + Domain Events
await _unitOfWork.SaveChangesAsync();
return Result<Guid>.Success(product.Id);
}
}
4.4. Persistence - Data Seeding
Purpose: Seed initial data into database with helper infrastructure.
// Create a seeder
public class UserSeeder : DataSeederBase<ApplicationDbContext>
{
public UserSeeder(ApplicationDbContext context) : base(context)
{
}
public override int Order => 1; // Execution order
public override async Task SeedAsync(CancellationToken cancellationToken = default)
{
if (await AnyAsync<User>(cancellationToken))
return;
var users = new List<User>
{
User.Create("admin@example.com", "Admin User"),
User.Create("user@example.com", "Regular User")
};
await AddRangeAsync(users, cancellationToken);
}
}
Register Seeders:
builder.Services.AddScoped<IDataSeeder, UserSeeder>();
builder.Services.AddScoped<IDataSeeder, ProductSeeder>();
Run Seeders:
// In Program.cs after app.Build()
using (var scope = app.Services.CreateScope())
{
var seederRunner = scope.ServiceProvider.GetRequiredService<DataSeederRunner>();
await seederRunner.RunAsync();
}
5. Behaviors - CQRS with MediatR
Purpose: MediatR is auto-registered with validation/logging/performance behaviors active.
5.1. Create Command
// Command
public record CreateProductCommand(string Name, decimal Price) : IRequest<Result<Guid>>;
// Handler
public class CreateProductCommandHandler : IRequestHandler<CreateProductCommand, Result<Guid>>
{
private readonly IProductRepository _repository;
private readonly IUnitOfWork _unitOfWork;
public async Task<Result<Guid>> Handle(CreateProductCommand request, CancellationToken ct)
{
var product = Product.Create(request.Name, request.Price, 0);
await _repository.AddAsync(product);
await _unitOfWork.SaveChangesAsync(ct);
return Result<Guid>.Success(product.Id);
}
}
// Validator
public class CreateProductCommandValidator : AbstractValidator<CreateProductCommand>
{
public CreateProductCommandValidator()
{
RuleFor(x => x.Name).NotEmpty().MaximumLength(200);
RuleFor(x => x.Price).GreaterThan(0);
}
}
5.2. Create Query
// Query
public record GetProductByIdQuery(Guid Id) : IRequest<Result<ProductDto>>;
// Handler
public class GetProductByIdQueryHandler : IRequestHandler<GetProductByIdQuery, Result<ProductDto>>
{
private readonly IProductRepository _repository;
public async Task<Result<ProductDto>> Handle(GetProductByIdQuery request, CancellationToken ct)
{
var product = await _repository.GetByIdAsync(request.Id);
if (product == null)
return Result<ProductDto>.Failure("Product not found");
return Result<ProductDto>.Success(new ProductDto(product.Id, product.Name, product.Price));
}
}
public record ProductDto(Guid Id, string Name, decimal Price);
Usage in Controller:
[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
private readonly IMediator _mediator;
[HttpPost]
public async Task<IActionResult> Create([FromBody] CreateProductCommand command)
{
var result = await _mediator.Send(command);
return result.IsSuccess ? Ok(result.Value) : BadRequest(result.ErrorMessage);
}
[HttpGet("{id}")]
public async Task<IActionResult> GetById(Guid id)
{
var result = await _mediator.Send(new GetProductByIdQuery(id));
return result.IsSuccess ? Ok(result.Value) : NotFound(result.ErrorMessage);
}
}
5.3. Validation Behavior
Purpose: Automatically validates all requests with FluentValidation. Auto-active! Just write validator classes.
5.4. Logging Behavior
Purpose: Logs all requests/responses. Auto-active!
5.5. Performance Behavior
Purpose: Warns about requests taking longer than 500ms. Auto-active!
6. Features - Caching
Purpose: Framework supports three caching strategies: InMemory, Redis, and Hybrid (two-level cache).
6.1. InMemory Cache
Configuration (appsettings.json):
{
"MemoryCache": {
"SizeLimit": 1024,
"CompactionPercentage": 0.25,
"ExpirationScanFrequency": "00:01:00"
}
}
Usage:
using Marventa.Framework.Features.Caching.Abstractions;
public class ProductService
{
private readonly ICacheService _cache;
public async Task<Product?> GetProductAsync(Guid id)
{
var cacheKey = $"product:{id}";
// Try get from cache
var cached = await _cache.GetAsync<Product>(cacheKey);
if (cached != null) return cached;
// Get from database
var product = await _repository.GetByIdAsync(id);
// Set cache with expiration
await _cache.SetAsync(cacheKey, product, TimeSpan.FromHours(1));
return product;
}
public async Task RemoveProductCacheAsync(Guid id)
{
await _cache.RemoveAsync($"product:{id}");
}
}
6.2. Output Cache
Purpose: ASP.NET Core 7+ output caching for HTTP responses.
Configuration:
{
"OutputCache": {
"Enabled": true,
"DefaultExpirationSeconds": 60,
"VaryByQuery": true,
"VaryByHeader": false,
"VaryByHeaderNames": []
}
}
Add to Program.cs:
builder.Services.AddMarventaOutputCache(builder.Configuration);
Usage in Controllers:
[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
// Cache response for 60 seconds (from configuration)
[OutputCache]
[HttpGet]
public async Task<IActionResult> GetAll()
{
var products = await _mediator.Send(new GetAllProductsQuery());
return Ok(products);
}
// Custom cache duration
[OutputCache(Duration = 300)]
[HttpGet("{id}")]
public async Task<IActionResult> GetById(Guid id)
{
var product = await _mediator.Send(new GetProductByIdQuery(id));
return Ok(product);
}
}
6.3. Redis Cache
Configuration (appsettings.json):
{
"Caching": { "Type": "Redis" },
"Redis": {
"ConnectionString": "localhost:6379",
"InstanceName": "MyApp:"
}
}
Usage: Same interface as InMemory (ICacheService). Framework automatically switches based on configuration.
6.4. Hybrid Cache
Purpose: Two-level caching - reads from InMemory (L1) first, then Redis (L2). Best of both worlds!
Configuration:
{
"Caching": { "Type": "Hybrid" },
"MemoryCache": {
"SizeLimit": 1024,
"CompactionPercentage": 0.25
},
"Redis": {
"ConnectionString": "localhost:6379",
"InstanceName": "MyApp:"
}
}
How it works:
- Get: Checks InMemory first, then Redis if not found
- Set: Writes to both InMemory and Redis
- Remove: Removes from both caches
Usage: Same ICacheService interface - completely transparent!
6.5. Modular Caching Setup
Add specific cache type:
// Add InMemory cache only
builder.Services.AddInMemoryCaching(builder.Configuration);
// Add Redis cache only
builder.Services.AddRedisCaching(builder.Configuration);
// Add Hybrid cache
builder.Services.AddHybridCaching(builder.Configuration);
// Auto-detect from configuration (used by AddMarventa)
builder.Services.AddMarventaCaching(builder.Configuration);
7. Features - Event Bus
7.1. RabbitMQ Event Bus
Configuration:
{
"RabbitMQ": {
"Host": "localhost",
"Username": "guest",
"Password": "guest"
}
}
Publish:
public class OrderCreatedEvent : IntegrationEvent
{
public Guid OrderId { get; }
public string OrderNumber { get; }
public OrderCreatedEvent(Guid orderId, string orderNumber)
{
OrderId = orderId;
OrderNumber = orderNumber;
}
}
await _eventBus.PublishAsync(new OrderCreatedEvent(orderId, orderNumber));
Subscribe:
public class OrderCreatedEventHandler : IIntegrationEventHandler<OrderCreatedEvent>
{
public async Task HandleAsync(OrderCreatedEvent @event)
{
Console.WriteLine($"Order created: {@event.OrderNumber}");
}
}
// Add to Program.cs
builder.Services.AddScoped<IIntegrationEventHandler<OrderCreatedEvent>, OrderCreatedEventHandler>();
7.2. Kafka Producer/Consumer
Configuration:
{
"Kafka": {
"BootstrapServers": "localhost:9092",
"GroupId": "myapp-group"
}
}
Usage:
// Produce
await _kafkaProducer.ProduceAsync("my-topic", new { UserId = 123 });
// Consume
await _kafkaConsumer.ConsumeAsync("my-topic", async message =>
{
Console.WriteLine($"Received: {message}");
});
7.3. MassTransit Integration
Configuration:
{
"MassTransit": { "Enabled": "true" },
"RabbitMQ": {
"Host": "localhost",
"Username": "guest",
"Password": "guest"
}
}
Usage:
// Consumer
public class OrderCreatedConsumer : IConsumer<OrderCreated>
{
public async Task Consume(ConsumeContext<OrderCreated> context)
{
Console.WriteLine($"Order {context.Message.OrderId}");
}
}
// Publish
await _publishEndpoint.Publish(new OrderCreated { OrderId = 123 });
8. Features - Storage
8.1. Local File Storage
Configuration:
{
"LocalStorage": {
"BasePath": "D:/uploads",
"BaseUrl": "https://myapp.com/files"
}
}
Usage:
// Upload
await _storage.UploadAsync(fileStream, "documents/file.pdf");
// Download
var stream = await _storage.DownloadAsync("documents/file.pdf");
// Delete
await _storage.DeleteAsync("documents/file.pdf");
// Get URL
var url = await _storage.GetUrlAsync("documents/file.pdf");
8.2. Azure Blob Storage
Configuration:
{
"Azure": {
"Storage": {
"ConnectionString": "your-connection-string",
"ContainerName": "uploads"
}
}
}
Usage: Same as Local Storage.
8.3. AWS S3 Storage
Configuration:
{
"AWS": {
"AccessKey": "your-key",
"SecretKey": "your-secret",
"Region": "us-east-1",
"BucketName": "my-bucket"
}
}
Usage: Same as Local Storage.
9. Features - Search
9.1. Elasticsearch
Configuration:
{
"Elasticsearch": {
"Uri": "http://localhost:9200"
}
}
Usage:
// Index
await _elasticsearchService.IndexAsync("products", product);
// Search
var results = await _elasticsearchService.SearchAsync<Product>("products", "laptop");
10. Features - Notification
Purpose: Send notifications via Email, SMS, and Push Notifications with multiple provider support.
10.1. Email Services
Supported Providers:
- SMTP - Standard SMTP with MailKit
- SendGrid - Cloud-based email delivery
- AWS SES - Amazon Simple Email Service (placeholder)
Configuration (appsettings.json):
{
"Notification": {
"EmailProvider": "Smtp",
"EnableEmail": true
},
"Smtp": {
"Host": "smtp.gmail.com",
"Port": 587,
"EnableSsl": true,
"Username": "your-email@gmail.com",
"Password": "your-password",
"FromEmail": "noreply@yourapp.com",
"FromName": "Your App",
"TimeoutSeconds": 30
}
}
Setup:
// Program.cs - Automatic registration based on configuration
builder.Services.AddNotifications(builder.Configuration);
// Or register specific email provider
builder.Services.AddSmtpEmail(builder.Configuration);
builder.Services.AddSendGridEmail(builder.Configuration);
Usage:
using Marventa.Framework.Features.Notification.Abstractions;
using Marventa.Framework.Features.Notification.Models;
public class UserService
{
private readonly IEmailService _emailService;
// Simple email
public async Task SendWelcomeEmailAsync(string email, string name)
{
var result = await _emailService.SendSimpleAsync(
to: email,
subject: "Welcome to Our App!",
body: $"<h1>Hello {name}!</h1><p>Welcome aboard!</p>",
isHtml: true
);
if (result.IsSuccess)
{
Console.WriteLine($"Email sent: {result.MessageId}");
}
}
// Advanced email with attachments
public async Task SendInvoiceEmailAsync(string email, byte[] pdfContent)
{
var message = new EmailMessage
{
Subject = "Your Invoice",
HtmlBody = "<p>Please find your invoice attached.</p>",
Priority = EmailPriority.High
};
message
.AddTo(email)
.AddCc("accounting@company.com")
.AddAttachment("invoice.pdf", pdfContent, "application/pdf");
var result = await _emailService.SendAsync(message);
}
}
SendGrid Configuration:
{
"Notification": {
"EmailProvider": "SendGrid",
"EnableEmail": true
},
"SendGrid": {
"ApiKey": "SG.your-api-key",
"FromEmail": "noreply@yourapp.com",
"FromName": "Your App",
"SandboxMode": false
}
}
10.2. SMS Services
Supported Providers:
- Twilio - Popular SMS service with global coverage
- Vonage (Nexmo) - Cloud communications platform
Configuration (Twilio):
{
"Notification": {
"SmsProvider": "Twilio",
"EnableSms": true
},
"Twilio": {
"AccountSid": "your-account-sid",
"AuthToken": "your-auth-token",
"FromPhoneNumber": "+1234567890",
"MessagingServiceSid": "optional-messaging-service-sid"
}
}
Setup:
// Program.cs
builder.Services.AddNotifications(builder.Configuration);
// Or register specific SMS provider
builder.Services.AddTwilioSms(builder.Configuration);
builder.Services.AddVonageSms(builder.Configuration);
Usage:
using Marventa.Framework.Features.Notification.Abstractions;
using Marventa.Framework.Features.Notification.Models;
public class AuthService
{
private readonly ISmsService _smsService;
// Simple SMS
public async Task SendOtpAsync(string phoneNumber, string otp)
{
var result = await _smsService.SendSimpleAsync(
to: phoneNumber,
body: $"Your OTP code is: {otp}. Valid for 5 minutes."
);
if (result.IsSuccess)
{
Console.WriteLine($"SMS sent: {result.MessageId}");
}
}
// Advanced SMS with metadata
public async Task SendOrderNotificationAsync(string phoneNumber, string orderId)
{
var message = new SmsMessage
{
To = phoneNumber,
Body = $"Your order #{orderId} has been shipped!",
Type = SmsType.Transactional
};
message.AddMetadata("orderId", orderId);
var result = await _smsService.SendAsync(message);
}
}
Vonage Configuration:
{
"Notification": {
"SmsProvider": "Vonage",
"EnableSms": true
},
"Vonage": {
"ApiKey": "your-api-key",
"ApiSecret": "your-api-secret",
"FromPhoneNumber": "YourCompany"
}
}
10.3. Push Notification
Supported Providers:
- Firebase Cloud Messaging (FCM) - Google's push notification service
Configuration:
{
"Notification": {
"PushProvider": "Firebase",
"EnablePush": true
},
"Firebase": {
"ProjectId": "your-firebase-project-id",
"ServerKey": "your-server-key",
"ServiceAccountKeyPath": "path/to/service-account.json",
"DefaultChannelId": "default-channel"
}
}
Setup:
// Program.cs
builder.Services.AddNotifications(builder.Configuration);
// Or register specific push provider
builder.Services.AddFirebasePush(builder.Configuration);
Usage:
using Marventa.Framework.Features.Notification.Abstractions;
using Marventa.Framework.Features.Notification.Models;
public class NotificationService
{
private readonly IPushNotificationService _pushService;
// Simple push notification
public async Task SendSimplePushAsync(string deviceToken)
{
var result = await _pushService.SendSimpleAsync(
deviceToken: deviceToken,
title: "New Message",
body: "You have a new message!"
);
}
// Advanced push notification
public async Task SendAdvancedPushAsync(List<string> deviceTokens)
{
var message = new PushNotificationMessage
{
Title = "Order Update",
Body = "Your order has been delivered!",
Icon = "ic_notification",
ImageUrl = "https://example.com/notification-image.png",
Sound = "default",
Priority = PushPriority.High,
ChannelId = "orders",
ClickAction = "OPEN_ORDER_ACTIVITY"
};
foreach (var token in deviceTokens)
{
message.AddDeviceToken(token);
}
message
.AddData("orderId", "12345")
.AddData("status", "delivered");
var result = await _pushService.SendAsync(message);
}
// Send to topic/channel
public async Task SendToTopicAsync()
{
var result = await _pushService.SendToTopicAsync(
topic: "news",
title: "Breaking News",
body: "Important announcement!"
);
}
}
Platform-Specific Features:
- Android: Channel ID, Color, Icon, Tag
- iOS: Badge count, Sound
- Both: Custom data payload, Click actions
11. Features - Localization (i18n)
Multi-language JSON resource support with culture switching:
// appsettings.json
{
"Localization": {
"DefaultCulture": "en-US",
"SupportedCultures": ["en-US", "tr-TR", "de-DE"],
"ResourcesPath": "Resources/Localization"
}
}
// Resources/Localization/en-US.json
{
"Welcome": "Welcome",
"ProductNotFound": "Product not found"
}
// Resources/Localization/tr-TR.json
{
"Welcome": "Hoş Geldiniz",
"ProductNotFound": "Ürün bulunamadı"
}
// Usage
public class ProductService
{
private readonly ILocalizationService _localization;
public string GetWelcomeMessage()
{
return _localization.GetString("Welcome");
}
public void SwitchLanguage(string culture)
{
_localization.SetCurrentCulture(culture);
}
}
Culture Detection:
- Query string:
?culture=tr-TR - Cookie:
.AspNetCore.Culture - Accept-Language header
12. Features - Template Engine
Dynamic content rendering with Liquid or Simple templates:
// appsettings.json
{
"Templating": {
"EngineType": "Simple", // or "Liquid"
"TemplatesPath": "Templates"
}
}
// Templates/order-confirmation.liquid
Hello {{ CustomerName }},
Your order #{{ OrderId }} totaling {{ Total | currency }} has been confirmed.
// Usage
public class EmailService
{
private readonly ITemplateEngine _templateEngine;
public async Task<string> RenderOrderEmail(Order order)
{
return await _templateEngine.RenderTemplateAsync("order-confirmation", new
{
CustomerName = order.CustomerName,
OrderId = order.Id,
Total = order.TotalAmount
});
}
}
13. Features - Reporting
13.1. PDF Report Generation
public class ReportService
{
private readonly IReportGenerator _pdfGenerator;
public async Task<byte[]> GeneratePdfReport(List<Product> products)
{
return await _pdfGenerator.GenerateAsync(products, new ReportOptions
{
Title = "Product Report",
Orientation = PageOrientation.Landscape
});
}
}
13.2. Excel Report Generation
public async Task<byte[]> GenerateExcelReport(List<Order> orders)
{
return await _excelGenerator.GenerateAsync(orders, new ReportOptions
{
SheetName = "Orders",
AutoFitColumns = true
});
}
13.3. CSV Report Generation
public async Task<byte[]> GenerateCsvReport(List<Customer> customers)
{
return await _csvGenerator.GenerateAsync(customers);
}
14. Features - Payment Gateway
Stripe payment abstraction:
// appsettings.json
{
"Stripe": {
"SecretKey": "sk_test_...",
"PublishableKey": "pk_test_...",
"WebhookSecret": "whsec_..."
}
}
// Usage
public class CheckoutService
{
private readonly IPaymentGateway _paymentGateway;
public async Task<PaymentResponse> ProcessPayment(decimal amount, string currency)
{
var result = await _paymentGateway.CreatePaymentAsync(new PaymentRequest
{
Amount = amount,
Currency = currency,
OrderId = "ORD-12345",
CustomerEmail = "customer@example.com"
});
if (result.IsSuccess)
{
// Payment succeeded
var paymentId = result.PaymentId;
}
return result;
}
}
15. Features - Job Scheduler
Hangfire wrapper with cron support:
public class BackgroundJobService
{
private readonly IJobScheduler _scheduler;
// Enqueue immediate job
public void SendWelcomeEmail(string userId)
{
_scheduler.Enqueue(() => SendEmailAsync(userId));
}
// Schedule delayed job
public void SendReminderLater(string userId)
{
_scheduler.Schedule(() => SendReminderAsync(userId), TimeSpan.FromHours(24));
}
// Recurring job with cron
public void SetupDailyReport()
{
_scheduler.AddOrUpdateRecurringJob(
"daily-report",
() => GenerateDailyReportAsync(),
CronExpressions.Daily // Every day at midnight
);
}
}
// Built-in cron expressions
// - CronExpressions.EveryMinute
// - CronExpressions.Hourly
// - CronExpressions.Daily
// - CronExpressions.Weekly
// - CronExpressions.Monthly
16. Features - Audit Logging
Entity change tracking for compliance:
public class ProductService
{
private readonly IAuditLogger _auditLogger;
private readonly IRepository<Product> _productRepository;
public async Task UpdateProduct(Guid id, UpdateProductDto dto)
{
var product = await _productRepository.GetByIdAsync(id);
var oldProduct = product.Clone(); // Keep copy for audit
product.UpdatePrice(dto.NewPrice);
product.UpdateStock(dto.NewStock);
// Audit log - automatically captures:
// - Changed properties (Price, Stock)
// - Old values vs New values
// - User who made change (from JWT claims)
// - IP address, UserAgent
// - Timestamp
await _auditLogger.LogUpdateAsync(oldProduct, product);
await _productRepository.UpdateAsync(product);
}
public async Task<List<AuditLog>> GetProductHistory(Guid productId)
{
return await _auditLogger.GetAuditLogsAsync("Product", productId.ToString());
}
}
Audit Log Model:
public class AuditLog
{
public Guid Id { get; set; }
public string? UserId { get; set; }
public string? Username { get; set; }
public string Action { get; set; } // Create, Update, Delete
public string EntityType { get; set; }
public string? EntityId { get; set; }
public string? OldValues { get; set; } // JSON
public string? NewValues { get; set; } // JSON
public List<string> ChangedProperties { get; set; }
public string? IpAddress { get; set; }
public string? UserAgent { get; set; }
public DateTime Timestamp { get; set; }
}
17. Features - Logging
17.1. Serilog
Configuration:
{
"ApplicationName": "MyApp",
"Serilog": {
"MinimumLevel": "Information",
"WriteTo": [
{ "Name": "Console" },
{ "Name": "File", "Args": { "path": "logs/log-.txt", "rollingInterval": "Day" } }
]
}
}
Usage:
_logger.LogInformation("Product {ProductId} created", productId);
_logger.LogError(ex, "Failed to create product");
17.2. OpenTelemetry Tracing
Configuration:
{
"OpenTelemetry": {
"ServiceName": "MyApp",
"OtlpEndpoint": "http://localhost:4317"
}
}
Purpose: Automatically traces HTTP, Database, and External API calls.
18. Security - Authentication
18.1. JWT Authentication Service
Configuration:
{
"Jwt": {
"Secret": "your-super-secret-key-at-least-32-characters",
"Issuer": "MyApp",
"Audience": "MyApp",
"ExpirationMinutes": 60
}
}
Generate Access Token:
using Marventa.Framework.Security.Authentication.Abstractions;
public class AuthService
{
private readonly IJwtService _jwtService;
// Simple usage
public string Login(User user)
{
var token = _jwtService.GenerateAccessToken(
userId: user.Id.ToString(),
email: user.Email,
roles: new[] { "Admin" },
additionalClaims: new Dictionary<string, string>
{
["department"] = "IT",
["permission"] = "products.write"
}
);
return token;
}
}
Validate and Extract Claims:
// Validate token
var principal = _jwtService.ValidateAccessToken(token);
if (principal == null)
{
// Token invalid or expired
}
// Get user ID from token
var userId = _jwtService.GetUserIdFromToken(token);
// Get all claims
var claims = _jwtService.GetClaimsFromToken(token);
// Check if token expired
var isExpired = _jwtService.IsTokenExpired(token);
// Get remaining lifetime
var remainingTime = _jwtService.GetTokenRemainingLifetime(token);
12.2. Refresh Token Generation
Purpose: Generate cryptographically secure refresh token strings.
Important: The framework only provides token generation. You must implement your own:
- RefreshToken entity in your domain model
- Repository for database storage
- Validation, rotation, and revocation logic
Generate Refresh Token:
using Marventa.Framework.Security.Authentication.Abstractions;
public class AuthService
{
private readonly IJwtService _jwtService;
private readonly IRefreshTokenRepository _refreshTokenRepository;
private readonly IUnitOfWork _unitOfWork;
public async Task<LoginResponse> LoginAsync(string email, string password)
{
// Validate user credentials...
// Generate tokens
var accessToken = _jwtService.GenerateAccessToken(user.Id.ToString(), user.Email);
var refreshTokenString = _jwtService.GenerateRefreshToken();
// Create your own domain entity
var refreshToken = new Domain.Entities.RefreshToken
{
Token = refreshTokenString,
UserId = user.Id,
ExpiresAt = DateTime.UtcNow.AddDays(7),
CreatedByIp = HttpContext.Connection.RemoteIpAddress?.ToString()
};
// Save to your database
await _refreshTokenRepository.AddAsync(refreshToken);
await _unitOfWork.SaveChangesAsync();
return new LoginResponse
{
AccessToken = accessToken,
RefreshToken = refreshTokenString,
ExpiresAt = refreshToken.ExpiresAt
};
}
public async Task<LoginResponse> RefreshTokenAsync(string refreshToken)
{
// Validate from your database
var token = await _refreshTokenRepository.GetByTokenAsync(refreshToken);
if (token == null || token.IsExpired || token.IsRevoked)
{
throw new UnauthorizedException("Invalid refresh token");
}
// Optional: Implement token rotation
token.RevokedAt = DateTime.UtcNow;
await _refreshTokenRepository.UpdateAsync(token);
// Generate new tokens
var accessToken = _jwtService.GenerateAccessToken(token.UserId.ToString(), user.Email);
var newRefreshTokenString = _jwtService.GenerateRefreshToken();
var newRefreshToken = new Domain.Entities.RefreshToken
{
Token = newRefreshTokenString,
UserId = token.UserId,
ExpiresAt = DateTime.UtcNow.AddDays(7),
ReplacedByToken = token.Token
};
await _refreshTokenRepository.AddAsync(newRefreshToken);
await _unitOfWork.SaveChangesAsync();
return new LoginResponse
{
AccessToken = accessToken,
RefreshToken = newRefreshTokenString,
ExpiresAt = newRefreshToken.ExpiresAt
};
}
}
Your Domain Entity Example:
public class RefreshToken : Entity<Guid>
{
public string Token { get; set; }
public Guid UserId { get; set; }
public DateTime ExpiresAt { get; set; }
public DateTime? RevokedAt { get; set; }
public string? CreatedByIp { get; set; }
public string? ReplacedByToken { get; set; }
public bool IsExpired => DateTime.UtcNow >= ExpiresAt;
public bool IsRevoked => RevokedAt != null;
public bool IsActive => !IsExpired && !IsRevoked;
}
12.3. Password Service
Purpose: Secure password hashing with Argon2id (winner of Password Hashing Competition 2015) and strength validation.
Why Argon2id?
- ✅ 2-6x FASTER than BCrypt
- ✅ More secure against GPU/ASIC attacks (high memory requirement)
- ✅ OWASP recommended (2024)
- ✅ Automatic migration from BCrypt hashes
using Marventa.Framework.Security.Encryption.Abstractions;
public class UserService
{
private readonly IPasswordService _passwordService;
// Hash password with Argon2id
public async Task RegisterAsync(string email, string password)
{
// Validate password strength
var (isValid, errorMessage) = _passwordService.ValidatePasswordStrength(
password: password,
minLength: 8,
requireUppercase: true,
requireLowercase: true,
requireDigit: true,
requireSpecialChar: true
);
if (!isValid)
{
throw new BusinessException($"Weak password: {errorMessage}");
}
// Hash with Argon2id (OWASP settings: m=19456, t=2, p=1)
var hashedPassword = _passwordService.HashPassword(password);
// Save user with hashed password...
}
// Verify password (supports both Argon2id and legacy BCrypt)
public async Task<bool> LoginAsync(string email, string password)
{
var user = await _userRepository.GetByEmailAsync(email);
if (user == null)
return false;
var isValid = _passwordService.VerifyPassword(password, user.PasswordHash);
// Automatic migration from BCrypt to Argon2id
if (isValid && _passwordService.NeedsRehash(user.PasswordHash))
{
user.PasswordHash = _passwordService.HashPassword(password);
await _userRepository.UpdateAsync(user);
}
return isValid;
}
// Generate secure random password
public string GenerateTemporaryPassword()
{
return _passwordService.GenerateSecurePassword(
length: 16,
includeSpecialCharacters: true
);
}
}
Hash Format:
Argon2id: $argon2id$v=19$m=19456,t=2,p=1$<salt>$<hash>
BCrypt (legacy): $2a$12$<salt+hash>
Performance Comparison: | Algorithm | Hash Time | Security | |-----------|-----------|----------| | Argon2id (current) | 30-50ms | ✅ GPU/ASIC resistant | | BCrypt (legacy) | 100-300ms | ⚠️ Vulnerable to GPU attacks |
Migration is automatic! Existing BCrypt hashes are verified normally, then upgraded to Argon2id on next successful login.
12.4. AES Encryption
Purpose: Symmetric encryption for sensitive data.
using Marventa.Framework.Security.Encryption;
var encryption = new AesEncryption(
key: "your-32-character-secret-key!",
iv: "your-16-char-iv"
);
// Encrypt sensitive data
var encrypted = encryption.Encrypt("sensitive data");
// Decrypt
var decrypted = encryption.Decrypt(encrypted);
Use Cases:
- Encrypting database connection strings
- Storing sensitive configuration values
- Protecting PII (Personal Identifiable Information)
19. Security - Authorization
19.1. Permission Based Authorization
[Authorize]
[RequirePermission("products.write")]
public async Task<IActionResult> Create([FromBody] CreateProductCommand command)
{
var result = await _mediator.Send(command);
return Ok(result);
}
20. Security - Rate Limiting
Configuration:
{
"RateLimiting": {
"Strategy": "IpAddress",
"RequestLimit": 100,
"TimeWindowSeconds": 60
}
}
Purpose: Automatically limits to 100 requests per 60 seconds per IP.
Response Headers:
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1633024800
21. Infrastructure - Multi-Tenancy
Configuration:
{
"MultiTenancy": {
"Strategy": "Header",
"HeaderName": "X-Tenant-Id"
}
}
Usage:
var tenantId = _tenantContext.TenantId;
var tenantName = _tenantContext.TenantName;
var data = _repository.GetAll()
.Where(x => x.TenantId == tenantId)
.ToList();
Client Request:
curl -H "X-Tenant-Id: tenant-123" https://api.myapp.com/products
22. Infrastructure - Health Checks
Configuration:
{
"HealthChecks": {
"Enabled": "true"
}
}
Purpose: Creates /health endpoint, automatically monitors Database/Redis/RabbitMQ.
Check:
curl http://localhost:5000/health
23. Infrastructure - API Versioning
Purpose: Provides flexible API versioning strategies.
Configuration:
{
"ApiVersioning": {
"Enabled": true,
"DefaultVersion": "1.0",
"ReportApiVersions": true,
"AssumeDefaultVersionWhenUnspecified": true,
"VersioningType": "UrlSegment",
"HeaderName": "X-API-Version",
"QueryStringParameterName": "api-version"
}
}
Versioning Types:
UrlSegment-/api/v1/products(default)QueryString-/api/products?api-version=1.0Header- Header:X-API-Version: 1.0MediaType- Accept:application/json;v=1.0
Usage in Controllers:
[ApiVersion("1.0")]
[Route("api/v{version:apiVersion}/[controller]")]
[ApiController]
public class ProductsV1Controller : ControllerBase
{
[HttpGet]
public IActionResult GetProducts()
{
return Ok(new[] { "Product 1", "Product 2" });
}
}
[ApiVersion("2.0")]
[Route("api/v{version:apiVersion}/[controller]")]
[ApiController]
public class ProductsV2Controller : ControllerBase
{
[HttpGet]
public IActionResult GetProducts()
{
return Ok(new { products = new[] { "Product 1", "Product 2" }, version = "2.0" });
}
}
Response Headers:
api-supported-versions: 1.0, 2.0
api-deprecated-versions: (none)
24. Infrastructure - Swagger/OpenAPI
Purpose: Auto-configured OpenAPI documentation with JWT support and environment restrictions.
Configuration:
{
"Swagger": {
"Enabled": true,
"Title": "My API",
"Description": "My API Documentation",
"Version": "v1",
"RequireAuthorization": true,
"EnvironmentRestriction": ["Development", "Staging"],
"Contact": {
"Name": "API Support",
"Email": "support@example.com",
"Url": "https://example.com/support"
},
"License": {
"Name": "MIT",
"Url": "https://opensource.org/licenses/MIT"
}
}
}
Features:
- ✅ Automatic JWT Bearer integration
- ✅ Multi-version support (when API Versioning enabled)
- ✅ XML comments auto-included
- ✅ Environment-based restrictions
- ✅ Swagger UI auto-configured
Access:
# Development/Staging only (based on EnvironmentRestriction)
https://localhost:5001/swagger
Usage in Program.cs:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddMarventa(builder.Configuration);
var app = builder.Build();
// Pass IWebHostEnvironment for environment-based Swagger
app.UseMarventa(builder.Configuration, app.Environment);
app.Run();
Controller XML Comments:
/// <summary>
/// Creates a new product
/// </summary>
/// <param name="command">Product creation data</param>
/// <returns>The created product ID</returns>
/// <response code="200">Product created successfully</response>
/// <response code="400">Invalid request</response>
[HttpPost]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<IActionResult> Create([FromBody] CreateProductCommand command)
{
var result = await _mediator.Send(command);
return result.IsSuccess ? Ok(result.Value) : BadRequest(result.ErrorMessage);
}
25. Middleware - Exception Handling
Purpose: Catches all exceptions and returns standard format. Auto-active!
Custom Exceptions:
throw new NotFoundException("Product not found");
throw new BusinessException("Insufficient stock");
throw new UnauthorizedException("Invalid credentials");
26. Enterprise Patterns
26.1. Transactional Outbox Pattern
The Outbox Pattern ensures reliable event publishing by storing events in the database in the same transaction as business data.
Setup:
// Program.cs
builder.Services.AddDbContext<YourDbContext>();
builder.Services.AddScoped<DbContext>(sp => sp.GetRequiredService<YourDbContext>());
builder.Services.AddOutbox(); // Registers outbox services and background processor
Usage:
// Domain Entity
public class Product : Entity<Guid>, IHasDomainEvents
{
public void Create()
{
AddDomainEvent(new ProductCreatedEvent(Id, Name, Price));
}
}
// Event Handler
public class ProductCreatedEventHandler : INotificationHandler<ProductCreatedEvent>
{
public async Task Handle(ProductCreatedEvent notification, CancellationToken cancellationToken)
{
// Event will be published reliably via outbox
await _eventBus.PublishAsync(notification);
}
}
How it works:
- Domain events saved to
OutboxMessagestable in same transaction - Background
OutboxProcessorpolls every 30 seconds - Processes pending messages and publishes events
- Marks messages as processed
- Cleans up old messages (7-day retention)
26.2. Repository Specification Pattern
Build reusable, composable query specifications for complex queries.
Create Specification:
public class ActiveProductsSpecification : BaseSpecification<Product>
{
public ActiveProductsSpecification()
{
// Filtering
Criteria = p => p.IsActive && !p.IsDeleted;
// Eager Loading
AddInclude(p => p.Category);
AddInclude(p => p.Supplier);
// Ordering
AddOrderBy(p => p.Name);
// Pagination
ApplyPaging(0, 20);
}
}
public class ProductsByPriceRangeSpec : BaseSpecification<Product>
{
public ProductsByPriceRangeSpec(decimal minPrice, decimal maxPrice)
{
Criteria = p => p.Price >= minPrice && p.Price <= maxPrice;
AddOrderByDescending(p => p.Price);
}
}
Use Specification:
// In Repository or Command Handler
var spec = new ActiveProductsSpecification();
var products = await _repository.FindAsync(spec);
var priceSpec = new ProductsByPriceRangeSpec(50m, 200m);
var filteredProducts = await _repository.FindAsync(priceSpec);
Available Methods:
Criteria- WHERE clause filterAddInclude()/AddInclude(string)- Eager loadingAddOrderBy()/AddOrderByDescending()- SortingApplyPaging(skip, take)- Pagination
26.3. Idempotency Pattern
Prevent duplicate request processing with distributed caching.
Setup:
// Program.cs
builder.Services.AddDistributedMemoryCache(); // or AddStackExchangeRedisCache
builder.Services.AddIdempotency();
var app = builder.Build();
app.UseRouting();
app.UseIdempotency(); // Must be after UseRouting()
Usage:
// Client sends Idempotency-Key header
POST /api/orders
Headers:
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000
Body: { "productId": "123", "quantity": 2 }
How it works:
- Middleware detects
Idempotency-Keyheader - Checks cache for existing response
- If found, returns cached response (duplicate detected)
- If not found, processes request and caches response
- Default 24-hour expiration
- Only caches successful responses (2xx status codes)
26.4. Circuit Breaker & Resilience
Add resilience to HTTP clients with Polly integration.
Setup:
// Program.cs
builder.Services.AddResilientHttpClient("PaymentService", "https://api.payment.com")
.AddHttpMessageHandler(() => new AuthHeaderHandler());
// Or configure manually
builder.Services.AddHttpClient("OrderService")
.AddPolicyHandler(ResilienceExtensions.GetRetryPolicy(3))
.AddPolicyHandler(ResilienceExtensions.GetCircuitBreakerPolicy(5, TimeSpan.FromSeconds(30)));
Usage:
public class PaymentService
{
private readonly IHttpClientFactory _httpClientFactory;
public async Task<bool> ProcessPayment(PaymentRequest request)
{
var client = _httpClientFactory.CreateClient("PaymentService");
var response = await client.PostAsJsonAsync("/payments", request);
return response.IsSuccessStatusCode;
}
}
Policies:
- Retry Policy: 3 retries with exponential backoff (2s, 4s, 8s)
- Circuit Breaker: Opens after 5 consecutive failures, stays open for 30s
- Timeout: Configurable per request
27. Advanced Security
27.1. API Key Authentication
Header-based authentication with role support.
Setup:
// Program.cs
builder.Services.AddApiKeyAuthentication();
// appsettings.json
{
"Authentication": {
"ApiKeys": [
{
"Key": "your-api-key-12345",
"Owner": "MobileApp",
"Roles": "User,Customer"
},
{
"Key": "admin-key-67890",
"Owner": "BackofficeAdmin",
"Roles": "Admin,User"
}
]
}
}
Usage:
[Authorize(AuthenticationSchemes = "ApiKey")]
[ApiController]
public class ProductsController : ControllerBase
{
[HttpGet]
[Authorize(Roles = "User")] // Requires User role
public async Task<IActionResult> GetProducts() { }
[HttpDelete("{id}")]
[Authorize(Roles = "Admin")] // Requires Admin role
public async Task<IActionResult> DeleteProduct(Guid id) { }
}
// Client Request
GET /api/products
Headers:
X-API-Key: your-api-key-12345
Features:
- Header name:
X-API-Key - Claims-based authentication
- Role-based authorization
- API key masking in logs (shows first 4 chars)
27.2. Request/Response Logging
Comprehensive logging with automatic sensitive data masking.
Setup:
// Program.cs
builder.Services.Configure<LoggingOptions>(builder.Configuration.GetSection("LoggingOptions"));
var app = builder.Build();
app.UseMiddleware<RequestResponseLoggingMiddleware>(); // Must be early in pipeline
// appsettings.json
{
"LoggingOptions": {
"EnableRequestResponseLogging": true,
"MaxBodyLogSize": 4096,
"LogRequestHeaders": true,
"LogRequestBody": true,
"LogResponseHeaders": true,
"LogResponseBody": true,
"SensitiveHeaders": ["Authorization", "X-API-Key", "Cookie"],
"SensitiveBodyFields": ["password", "token", "secret", "apikey"]
}
}
Features:
- Automatic header masking (Authorization, X-API-Key, Cookie)
- Automatic body field masking (password, token, secret, etc.)
- Configurable max body size
- JSON field detection and masking
- Response time tracking
- Content-type aware (only logs text-based content)
Example Log Output:
[INFO] HTTP GET /api/users/login
Request Headers: {"User-Agent": "PostmanRuntime/7.32.0", "Authorization": "***MASKED***"}
Request Body: {"username":"john","password":"***MASKED***","apiKey":"***MASKED***"}
Response Status: 200 OK
Response Body: {"success":true,"token":"***MASKED***","user":{...}}
Response Time: 45ms
28. Configuration - appsettings.json
Complete configuration example:
{
"ApplicationName": "MyApp",
"ConnectionStrings": {
"DefaultConnection": "Server=localhost;Database=MyDb;Trusted_Connection=true;"
},
"Cors": {
"AllowedOrigins": ["http://localhost:3000", "https://myapp.com"]
},
"ApiVersioning": {
"Enabled": true,
"DefaultVersion": "1.0",
"ReportApiVersions": true,
"AssumeDefaultVersionWhenUnspecified": true,
"VersioningType": "UrlSegment"
},
"Swagger": {
"Enabled": true,
"Title": "My API",
"Description": "My API Documentation",
"Version": "v1",
"RequireAuthorization": true,
"EnvironmentRestriction": ["Development", "Staging"]
},
"Jwt": {
"Secret": "your-super-secret-key-at-least-32-characters-long",
"Issuer": "MyApp",
"Audience": "MyApp",
"ExpirationMinutes": 60
},
"Caching": {
"Type": "Hybrid"
},
"MemoryCache": {
"SizeLimit": 1024,
"CompactionPercentage": 0.25,
"ExpirationScanFrequency": "00:01:00"
},
"OutputCache": {
"Enabled": true,
"DefaultExpirationSeconds": 60,
"VaryByQuery": true,
"VaryByHeader": false,
"VaryByHeaderNames": []
},
"Redis": {
"ConnectionString": "localhost:6379",
"InstanceName": "MyApp:"
},
"MultiTenancy": {
"Strategy": "Header",
"HeaderName": "X-Tenant-Id"
},
"RateLimiting": {
"Strategy": "IpAddress",
"RequestLimit": 100,
"TimeWindowSeconds": 60
},
"RabbitMQ": {
"Host": "localhost",
"VirtualHost": "/",
"Username": "guest",
"Password": "guest"
},
"Kafka": {
"BootstrapServers": "localhost:9092",
"GroupId": "myapp-group"
},
"MassTransit": {
"Enabled": "true"
},
"LocalStorage": {
"BasePath": "D:/uploads",
"BaseUrl": "https://myapp.com/files"
},
"Azure": {
"Storage": {
"ConnectionString": "DefaultEndpointsProtocol=https;AccountName=...",
"ContainerName": "uploads"
}
},
"AWS": {
"AccessKey": "your-access-key",
"SecretKey": "your-secret-key",
"Region": "us-east-1",
"BucketName": "my-bucket"
},
"MongoDB": {
"ConnectionString": "mongodb://localhost:27017",
"DatabaseName": "MyDatabase"
},
"Elasticsearch": {
"Uri": "http://localhost:9200"
},
"HealthChecks": {
"Enabled": "true"
},
"Serilog": {
"MinimumLevel": {
"Default": "Information",
"Override": {
"Microsoft": "Warning",
"System": "Warning"
}
},
"WriteTo": [
{ "Name": "Console" },
{
"Name": "File",
"Args": {
"path": "logs/log-.txt",
"rollingInterval": "Day",
"retainedFileCountLimit": 7
}
}
]
},
"OpenTelemetry": {
"ServiceName": "MyApp",
"OtlpEndpoint": "http://localhost:4317"
}
}
🎉 That's It!
Your application now has:
✅ Core: Domain Driven Design (Entity, Aggregate, ValueObject, DomainEvent) ✅ Behaviors: CQRS (MediatR + FluentValidation + Mapster + Logging + Performance) ✅ Infrastructure: Repository Pattern, Unit of Work, Multi-Tenancy, Health Checks, Data Seeding ✅ API: Swagger/OpenAPI, API Versioning (URL/Query/Header), XML Documentation ✅ Features: Caching, Event Bus (RabbitMQ/Kafka/MassTransit), Storage, Search, Logging ✅ Security: JWT Auth, CORS, Permission Authorization, Rate Limiting, Password Hashing ✅ Middleware: Global Exception Handling with correct pipeline order
With just 2 lines of setup! 🚀
🆕 What's New in v4.6.0
- Password Hashing Upgrade - Argon2id:
- Migrated from BCrypt to Argon2id (Password Hashing Competition winner 2015)
- 5x FASTER performance (115ms vs 634ms average)
- More secure against GPU/ASIC attacks (high memory requirement)
- OWASP recommended settings (m=19456, t=2, p=1)
- Automatic backward compatibility with BCrypt hashes
- Seamless migration on user login
🆕 What's New in v5.0.0 - MAJOR SECURITY AND ARCHITECTURE UPDATE
🔒 Critical Security Fixes:
- AES-GCM Encryption with Random Nonce: Fixed encryption vulnerability by using AES-GCM with cryptographically random nonce for each operation (previously used static IV with CBC mode)
- Entity Equality Implementation: Corrected GetHashCode() for transient entities to prevent hash collisions
- JWT Secret Validation: Added minimum 32-character validation for JWT secrets
- Domain Events Transaction Safety: Events now dispatched AFTER SaveChanges for transaction consistency
🏗️ Repository & Database Enhancements:
- Pagination Support: Added
GetPagedAsync()with eager loading support to prevent memory issues - N+1 Query Prevention: Include/ThenInclude support in all query methods
- Soft Delete Global Filters: Automatic filtering of soft-deleted entities via
ISoftDeletable - ICurrentUserService: Claims-based current user service for audit trails
📦 New Enterprise Patterns:
- Transactional Outbox Pattern: Ensures reliable event publishing with background processor
- Repository Specification Pattern: Reusable, composable query specifications with filtering, ordering, and pagination
- Idempotency Pattern: Prevents duplicate request processing with distributed caching
- Circuit Breaker with Polly: Resilient HTTP client with retry and circuit breaker policies
🔐 Advanced Security Features:
- API Key Authentication: Header-based authentication with role support
- Request/Response Logging: Comprehensive logging with automatic sensitive data masking (passwords, tokens, API keys)
- Configuration Validation: Startup validation for all configuration options
🚀 Infrastructure Improvements:
- Redis Prefix Removal: Efficient
RemoveByPrefixAsync()implementation - Performance Middleware: Automatic response time tracking with slow request logging
- Code Quality: Eliminated all magic strings, improved error handling, enhanced XML documentation
🆕 What's New in v4.6.0
- Password Hashing Migration: Migrated from BCrypt to Argon2id for enhanced security
- More secure against GPU/ASIC attacks (high memory requirement)
- OWASP recommended settings (m=19456, t=2, p=1)
- Automatic backward compatibility with BCrypt hashes
- Seamless migration on user login
📄 License
MIT License - See LICENSE for details.
📧 Support
For questions, please open an issue on GitHub Issues.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net8.0
- AspNetCore.HealthChecks.MongoDb (>= 8.1.0)
- AspNetCore.HealthChecks.RabbitMQ (>= 8.0.2)
- AspNetCore.HealthChecks.Redis (>= 8.0.1)
- AspNetCore.HealthChecks.SqlServer (>= 8.0.2)
- AspNetCore.HealthChecks.UI.Client (>= 8.0.1)
- AWSSDK.S3 (>= 4.0.7.6)
- AWSSDK.SimpleEmail (>= 4.0.1.6)
- Azure.Storage.Blobs (>= 12.25.1)
- BCrypt.Net-Next (>= 4.0.3)
- ClosedXML (>= 0.105.0)
- Confluent.Kafka (>= 2.11.1)
- Elasticsearch.Net (>= 7.17.5)
- FluentValidation (>= 12.0.0)
- FluentValidation.AspNetCore (>= 11.3.1)
- FluentValidation.DependencyInjectionExtensions (>= 12.0.0)
- Fluid.Core (>= 2.25.0)
- Google.Apis.Auth (>= 1.71.0)
- Hangfire.AspNetCore (>= 1.8.21)
- Hangfire.Core (>= 1.8.21)
- Hangfire.SqlServer (>= 1.8.21)
- Konscious.Security.Cryptography.Argon2 (>= 1.3.1)
- MailKit (>= 4.9.0)
- Mapster (>= 7.4.0)
- Mapster.DependencyInjection (>= 1.0.1)
- MassTransit (>= 8.5.3)
- MassTransit.RabbitMQ (>= 8.5.3)
- MediatR (>= 13.0.0)
- Microsoft.AspNetCore.Authentication.JwtBearer (>= 8.0.11)
- Microsoft.AspNetCore.Mvc.Versioning (>= 5.1.0)
- Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer (>= 5.1.0)
- Microsoft.EntityFrameworkCore (>= 8.0.11)
- Microsoft.EntityFrameworkCore.Relational (>= 8.0.11)
- Microsoft.EntityFrameworkCore.SqlServer (>= 8.0.11)
- Microsoft.Extensions.Caching.Memory (>= 8.0.1)
- Microsoft.Extensions.Caching.StackExchangeRedis (>= 8.0.11)
- MongoDB.Driver (>= 3.5.0)
- NEST (>= 7.17.5)
- Newtonsoft.Json (>= 13.0.4)
- Npgsql.EntityFrameworkCore.PostgreSQL (>= 8.0.11)
- OpenTelemetry (>= 1.12.0)
- OpenTelemetry.Exporter.OpenTelemetryProtocol (>= 1.12.0)
- OpenTelemetry.Extensions.Hosting (>= 1.12.0)
- OpenTelemetry.Instrumentation.AspNetCore (>= 1.12.0)
- OpenTelemetry.Instrumentation.Http (>= 1.12.0)
- Polly (>= 8.6.4)
- Polly.Extensions.Http (>= 3.0.0)
- QuestPDF (>= 2025.7.2)
- RabbitMQ.Client (>= 7.1.2)
- SendGrid (>= 9.29.3)
- Serilog (>= 4.3.0)
- Serilog.AspNetCore (>= 8.0.3)
- Serilog.Enrichers.Environment (>= 3.0.1)
- Serilog.Enrichers.Thread (>= 4.0.0)
- Serilog.Sinks.Console (>= 6.0.0)
- Serilog.Sinks.Elasticsearch (>= 10.0.0)
- StackExchange.Redis (>= 2.9.25)
- Stripe.net (>= 49.0.0)
- Swashbuckle.AspNetCore (>= 6.9.0)
- System.IdentityModel.Tokens.Jwt (>= 8.14.0)
- System.Text.Json (>= 8.0.5)
- Twilio (>= 7.13.3)
- Vonage (>= 7.3.0)
-
net9.0
- AspNetCore.HealthChecks.MongoDb (>= 9.0.0)
- AspNetCore.HealthChecks.RabbitMQ (>= 9.0.0)
- AspNetCore.HealthChecks.Redis (>= 9.0.0)
- AspNetCore.HealthChecks.SqlServer (>= 9.0.0)
- AspNetCore.HealthChecks.UI.Client (>= 9.0.0)
- AWSSDK.S3 (>= 4.0.7.6)
- AWSSDK.SimpleEmail (>= 4.0.1.6)
- Azure.Storage.Blobs (>= 12.25.1)
- BCrypt.Net-Next (>= 4.0.3)
- ClosedXML (>= 0.105.0)
- Confluent.Kafka (>= 2.11.1)
- Elasticsearch.Net (>= 7.17.5)
- FluentValidation (>= 12.0.0)
- FluentValidation.AspNetCore (>= 11.3.1)
- FluentValidation.DependencyInjectionExtensions (>= 12.0.0)
- Fluid.Core (>= 2.25.0)
- Google.Apis.Auth (>= 1.71.0)
- Hangfire.AspNetCore (>= 1.8.21)
- Hangfire.Core (>= 1.8.21)
- Hangfire.SqlServer (>= 1.8.21)
- Konscious.Security.Cryptography.Argon2 (>= 1.3.1)
- MailKit (>= 4.9.0)
- Mapster (>= 7.4.0)
- Mapster.DependencyInjection (>= 1.0.1)
- MassTransit (>= 8.5.3)
- MassTransit.RabbitMQ (>= 8.5.3)
- MediatR (>= 13.0.0)
- Microsoft.AspNetCore.Authentication.JwtBearer (>= 9.0.9)
- Microsoft.AspNetCore.Mvc.Versioning (>= 5.1.0)
- Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer (>= 5.1.0)
- Microsoft.EntityFrameworkCore (>= 9.0.9)
- Microsoft.EntityFrameworkCore.Relational (>= 9.0.9)
- Microsoft.EntityFrameworkCore.SqlServer (>= 9.0.9)
- Microsoft.Extensions.Caching.Memory (>= 9.0.9)
- Microsoft.Extensions.Caching.StackExchangeRedis (>= 9.0.9)
- MongoDB.Driver (>= 3.5.0)
- NEST (>= 7.17.5)
- Newtonsoft.Json (>= 13.0.4)
- Npgsql.EntityFrameworkCore.PostgreSQL (>= 9.0.4)
- OpenTelemetry (>= 1.12.0)
- OpenTelemetry.Exporter.OpenTelemetryProtocol (>= 1.12.0)
- OpenTelemetry.Extensions.Hosting (>= 1.12.0)
- OpenTelemetry.Instrumentation.AspNetCore (>= 1.12.0)
- OpenTelemetry.Instrumentation.Http (>= 1.12.0)
- Polly (>= 8.6.4)
- Polly.Extensions.Http (>= 3.0.0)
- QuestPDF (>= 2025.7.2)
- RabbitMQ.Client (>= 7.1.2)
- SendGrid (>= 9.29.3)
- Serilog (>= 4.3.0)
- Serilog.AspNetCore (>= 9.0.0)
- Serilog.Enrichers.Environment (>= 3.0.1)
- Serilog.Enrichers.Thread (>= 4.0.0)
- Serilog.Sinks.Console (>= 6.0.0)
- Serilog.Sinks.Elasticsearch (>= 10.0.0)
- StackExchange.Redis (>= 2.9.25)
- Stripe.net (>= 49.0.0)
- Swashbuckle.AspNetCore (>= 9.0.5)
- System.IdentityModel.Tokens.Jwt (>= 8.14.0)
- System.Text.Json (>= 9.0.9)
- Twilio (>= 7.13.3)
- Vonage (>= 7.3.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated | |
|---|---|---|---|
| 5.2.0 | 229 | 10/13/2025 | |
| 5.1.0 | 277 | 10/5/2025 | |
| 5.0.0 | 184 | 10/4/2025 | |
| 4.6.0 | 196 | 10/3/2025 | |
| 4.5.5 | 215 | 10/2/2025 | |
| 4.5.4 | 210 | 10/2/2025 | |
| 4.5.3 | 208 | 10/2/2025 | |
| 4.5.2 | 209 | 10/2/2025 | |
| 4.5.1 | 211 | 10/2/2025 | |
| 4.5.0 | 212 | 10/2/2025 | |
| 4.4.0 | 218 | 10/1/2025 | |
| 4.3.0 | 217 | 10/1/2025 | |
| 4.2.0 | 218 | 10/1/2025 | |
| 4.1.0 | 210 | 10/1/2025 | |
| 4.0.2 | 218 | 10/1/2025 | |
| 4.0.1 | 210 | 10/1/2025 | |
| 4.0.0 | 286 | 9/30/2025 | |
| 3.5.2 | 219 | 9/30/2025 | |
| 3.5.1 | 250 | 9/30/2025 | |
| 3.4.1 | 254 | 9/30/2025 | |
| 3.4.0 | 249 | 9/30/2025 | |
| 3.3.2 | 261 | 9/30/2025 | |
| 3.2.0 | 253 | 9/30/2025 | |
| 3.1.0 | 252 | 9/29/2025 | |
| 3.0.1 | 251 | 9/29/2025 | |
| 3.0.1-preview-20250929165802 | 246 | 9/29/2025 | |
| 3.0.0 | 248 | 9/29/2025 | |
| 3.0.0-preview-20250929164242 | 251 | 9/29/2025 | |
| 3.0.0-preview-20250929162455 | 248 | 9/29/2025 | |
| 2.12.0-preview-20250929161039 | 242 | 9/29/2025 | |
| 2.11.0 | 253 | 9/29/2025 | |
| 2.10.0 | 253 | 9/29/2025 | |
| 2.9.0 | 247 | 9/29/2025 | |
| 2.8.0 | 249 | 9/29/2025 | |
| 2.7.0 | 260 | 9/29/2025 | |
| 2.6.0 | 254 | 9/28/2025 | |
| 2.5.0 | 260 | 9/28/2025 | |
| 2.4.0 | 252 | 9/28/2025 | |
| 2.3.0 | 253 | 9/28/2025 | |
| 2.2.0 | 255 | 9/28/2025 | |
| 2.1.0 | 253 | 9/26/2025 | |
| 2.0.9 | 257 | 9/26/2025 | |
| 2.0.5 | 250 | 9/25/2025 | |
| 2.0.4 | 256 | 9/25/2025 | |
| 2.0.3 | 261 | 9/25/2025 | |
| 2.0.1 | 257 | 9/25/2025 | |
| 2.0.0 | 258 | 9/25/2025 | |
| 1.1.2 | 334 | 9/24/2025 | |
| 1.1.1 | 335 | 9/24/2025 | |
| 1.1.0 | 253 | 9/24/2025 | |
| 1.0.0 | 258 | 9/24/2025 |