Marventa.Framework
4.5.0
See the version list below for details.
dotnet add package Marventa.Framework --version 4.5.0
NuGet\Install-Package Marventa.Framework -Version 4.5.0
<PackageReference Include="Marventa.Framework" Version="4.5.0" />
<PackageVersion Include="Marventa.Framework" Version="4.5.0" />
<PackageReference Include="Marventa.Framework" />
paket add Marventa.Framework --version 4.5.0
#r "nuget: Marventa.Framework, 4.5.0"
#:package Marventa.Framework@4.5.0
#addin nuget:?package=Marventa.Framework&version=4.5.0
#tool nuget:?package=Marventa.Framework&version=4.5.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 - Logging
- 10.1. Serilog
- 10.2. OpenTelemetry Tracing
- Security - Authentication
- 11.1. JWT Token Generator
- 11.2. Password Hasher
- 11.3. AES Encryption
- Security - Authorization
- Security - Rate Limiting
- Infrastructure - Multi-Tenancy
- Infrastructure - Health Checks
- Infrastructure - API Versioning
- Infrastructure - Swagger/OpenAPI
- Middleware - Exception Handling
- Configuration - appsettings.json
1. Installation
dotnet add package Marventa.Framework
2. Basic Setup
Program.cs - Just 2 Lines!
var builder = WebApplication.CreateBuilder(args);
// ✨ ONE LINE - All services registered automatically
// Includes: Controllers, MediatR, FluentValidation, Mapster, CORS, and all configured features
builder.Services.AddMarventa(builder.Configuration);
var app = builder.Build();
// ✨ ONE LINE - All middleware configured automatically
// Includes: Exception handling, CORS, Authentication, Authorization, Rate Limiting, and Endpoints
app.UseMarventa(builder.Configuration);
app.Run();
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 - Logging
10.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");
10.2. OpenTelemetry Tracing
Configuration:
{
"OpenTelemetry": {
"ServiceName": "MyApp",
"OtlpEndpoint": "http://localhost:4317"
}
}
Purpose: Automatically traces HTTP, Database, and External API calls.
11. Security - Authentication
11.1. JWT Authentication Service
Configuration:
{
"Jwt": {
"Secret": "your-super-secret-key-at-least-32-characters",
"Issuer": "MyApp",
"Audience": "MyApp",
"ExpirationMinutes": 60,
"RefreshTokenExpirationDays": 7,
"EnableTokenRotation": true,
"MaxRefreshTokensPerUser": 5,
"ValidateIpAddress": false
}
}
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);
11.2. Refresh Token Service
Purpose: Securely manage refresh tokens with rotation and revocation support.
Generate and Use Refresh Token:
using Marventa.Framework.Security.Authentication.Abstractions;
public class AuthService
{
private readonly IJwtService _jwtService;
private readonly IRefreshTokenService _refreshTokenService;
public async Task<TokenResponse> LoginAsync(string email, string password)
{
// Validate user credentials...
var accessToken = _jwtService.GenerateAccessToken(user.Id.ToString(), user.Email);
var refreshToken = await _refreshTokenService.GenerateRefreshTokenAsync(
userId: user.Id.ToString(),
ipAddress: HttpContext.Connection.RemoteIpAddress?.ToString()
);
return new TokenResponse
{
AccessToken = accessToken,
RefreshToken = refreshToken.Token,
ExpiresAt = refreshToken.ExpiresAt
};
}
public async Task<TokenResponse> RefreshTokenAsync(string refreshToken, string ipAddress)
{
// Validate refresh token
var validToken = await _refreshTokenService.ValidateRefreshTokenAsync(refreshToken);
if (validToken == null || !validToken.IsActive)
{
throw new UnauthorizedException("Invalid or expired refresh token");
}
// Rotate refresh token (old token auto-revoked)
var newRefreshToken = await _refreshTokenService.RotateRefreshTokenAsync(
oldToken: refreshToken,
ipAddress: ipAddress
);
// Generate new access token
var accessToken = _jwtService.GenerateAccessToken(newRefreshToken.UserId, "user@example.com");
return new TokenResponse
{
AccessToken = accessToken,
RefreshToken = newRefreshToken.Token,
ExpiresAt = newRefreshToken.ExpiresAt
};
}
public async Task<bool> LogoutAsync(string refreshToken, string ipAddress)
{
return await _refreshTokenService.RevokeRefreshTokenAsync(
token: refreshToken,
ipAddress: ipAddress,
reason: "User logout"
);
}
public async Task<bool> LogoutAllDevicesAsync(string userId, string ipAddress)
{
var revokedCount = await _refreshTokenService.RevokeAllUserTokensAsync(
userId: userId,
ipAddress: ipAddress,
reason: "Logout from all devices"
);
return revokedCount > 0;
}
public async Task<IEnumerable<RefreshToken>> GetUserActiveSessionsAsync(string userId)
{
return await _refreshTokenService.GetUserActiveTokensAsync(userId);
}
}
Token Response Model:
public class TokenResponse
{
public string AccessToken { get; set; }
public string RefreshToken { get; set; }
public DateTime ExpiresAt { get; set; }
}
11.3. Password Service
Purpose: Secure password hashing with BCrypt and strength validation.
using Marventa.Framework.Security.Encryption.Abstractions;
public class UserService
{
private readonly IPasswordService _passwordService;
// Hash password
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}");
}
var hashedPassword = _passwordService.HashPassword(password);
// Save user with hashed password...
}
// Verify password
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);
// Check if password hash needs rehashing (BCrypt cost updated)
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
);
}
}
11.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)
12. Security - Authorization
12.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);
}
13. 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
14. 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
15. Infrastructure - Health Checks
Configuration:
{
"HealthChecks": {
"Enabled": "true"
}
}
Purpose: Creates /health endpoint, automatically monitors Database/Redis/RabbitMQ.
Check:
curl http://localhost:5000/health
16. 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)
17. 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);
}
18. 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");
19. 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,
"RefreshTokenExpirationDays": 7,
"EnableTokenRotation": true,
"MaxRefreshTokensPerUser": 5,
"ValidateIpAddress": false
},
"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.5.0
Security Services Refactored:
IJwtServicewith comprehensive token management (replacedIJwtTokenGenerator)IPasswordServicewith strength validation and secure password generation (replacedIPasswordHasher)IRefreshTokenServicewith token rotation and revocation support- Modern, industry-standard naming conventions
Memory Cache Configuration:
- Configurable
MemoryCacheoptions (SizeLimit, CompactionPercentage, ExpirationScanFrequency) - ASP.NET Core 7+
OutputCachesupport with flexible policies - Full control over cache behavior via appsettings.json
- Configurable
Modular Architecture:
- Separated concerns into focused extension files
- ConfigurationExtensions made public for NuGet consumers
- Better maintainability and discoverability
Enhanced Caching:
- Three strategies: InMemory, Redis, Hybrid (L1 + L2)
- Configurable memory cache options
- Output cache middleware for HTTP response caching
🆕 What's in v4.4.0
- Swagger/OpenAPI: Auto-configured with JWT integration and environment-based restrictions
- API Versioning: Flexible versioning (URL/Query/Header/MediaType) with Swagger integration
- Data Seeding: Infrastructure for seeding initial data with execution order control
- Environment Helpers: Utilities for environment-based feature configuration
📄 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)
- Azure.Storage.Blobs (>= 12.25.1)
- BCrypt.Net-Next (>= 4.0.3)
- Confluent.Kafka (>= 2.11.1)
- Elasticsearch.Net (>= 7.17.5)
- FluentValidation (>= 12.0.0)
- FluentValidation.AspNetCore (>= 11.3.1)
- FluentValidation.DependencyInjectionExtensions (>= 12.0.0)
- Hangfire.AspNetCore (>= 1.8.21)
- Hangfire.Core (>= 1.8.21)
- Hangfire.SqlServer (>= 1.8.21)
- 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.EntityFrameworkCore (>= 1.10.0-beta.1)
- OpenTelemetry.Instrumentation.Http (>= 1.12.0)
- OpenTelemetry.Instrumentation.StackExchangeRedis (>= 1.12.0-beta.1)
- Polly (>= 8.6.4)
- Polly.Extensions.Http (>= 3.0.0)
- RabbitMQ.Client (>= 7.1.2)
- 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)
- Swashbuckle.AspNetCore (>= 6.9.0)
- System.IdentityModel.Tokens.Jwt (>= 8.14.0)
- System.Text.Json (>= 8.0.5)
-
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)
- Azure.Storage.Blobs (>= 12.25.1)
- BCrypt.Net-Next (>= 4.0.3)
- Confluent.Kafka (>= 2.11.1)
- Elasticsearch.Net (>= 7.17.5)
- FluentValidation (>= 12.0.0)
- FluentValidation.AspNetCore (>= 11.3.1)
- FluentValidation.DependencyInjectionExtensions (>= 12.0.0)
- Hangfire.AspNetCore (>= 1.8.21)
- Hangfire.Core (>= 1.8.21)
- Hangfire.SqlServer (>= 1.8.21)
- 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.EntityFrameworkCore (>= 1.10.0-beta.1)
- OpenTelemetry.Instrumentation.Http (>= 1.12.0)
- OpenTelemetry.Instrumentation.StackExchangeRedis (>= 1.12.0-beta.1)
- Polly (>= 8.6.4)
- Polly.Extensions.Http (>= 3.0.0)
- RabbitMQ.Client (>= 7.1.2)
- 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)
- Swashbuckle.AspNetCore (>= 9.0.5)
- System.IdentityModel.Tokens.Jwt (>= 8.14.0)
- System.Text.Json (>= 9.0.9)
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 | 245 | 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 |