Marventa.Framework 4.6.0

There is a newer version of this package available.
See the version list below for details.
dotnet add package Marventa.Framework --version 4.6.0
                    
NuGet\Install-Package Marventa.Framework -Version 4.6.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Marventa.Framework" Version="4.6.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Marventa.Framework" Version="4.6.0" />
                    
Directory.Packages.props
<PackageReference Include="Marventa.Framework" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Marventa.Framework --version 4.6.0
                    
#r "nuget: Marventa.Framework, 4.6.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Marventa.Framework@4.6.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Marventa.Framework&version=4.6.0
                    
Install as a Cake Addin
#tool nuget:?package=Marventa.Framework&version=4.6.0
                    
Install as a Cake Tool

🚀 Marventa.Framework

Enterprise .NET Framework - Convention over Configuration

NuGet License: MIT


📖 Table of Contents

  1. Installation
  2. Basic Setup (2 Lines!)
  3. Core - Domain Driven Design (DDD)
  4. Infrastructure - Database & Repository
  5. Behaviors - CQRS with MediatR
  6. Features - Caching
  7. Features - Event Bus
  8. Features - Storage
  9. Features - Search
  10. Features - Logging
  11. Security - Authentication
  12. Security - Authorization
  13. Security - Rate Limiting
  14. Infrastructure - Multi-Tenancy
  15. Infrastructure - Health Checks
  16. Infrastructure - API Versioning
  17. Infrastructure - Swagger/OpenAPI
  18. Middleware - Exception Handling
  19. 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.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
  }
}

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 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;
}

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

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.0
  • Header - Header: X-API-Version: 1.0
  • MediaType - 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
  },

  "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 v4.5.3

  • RefreshToken Architecture Fixed:

    • Removed cache-based IRefreshTokenService (not suitable for multi-server environments)
    • Added GenerateRefreshToken() to IJwtService for secure token generation
    • RefreshToken storage is now project's responsibility (database-backed)
    • Framework only provides cryptographically secure token generation utility
  • Cache Reliability:

    • Optional MemoryCache SizeLimit (nullable properties)
    • HybridCache gracefully handles Redis connection failures
    • Automatic fallback to InMemory when Redis is unavailable
    • Application continues working without Redis
  • Security Services:

    • IJwtService with comprehensive token management
    • IPasswordService with Argon2id hashing and strength validation
    • Simplified JWT configuration (removed refresh token settings)
  • Memory Cache Configuration:

    • Configurable options (SizeLimit, CompactionPercentage, ExpirationScanFrequency)
    • ASP.NET Core 7+ OutputCache support
    • Full control over cache behavior via appsettings.json
  • Modular Architecture:

    • Separated concerns into focused extension files
    • ConfigurationExtensions public for NuGet consumers
    • Better maintainability and discoverability

🆕 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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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.2.0 is deprecated because it is no longer maintained.
5.1.0 277 10/5/2025 5.1.0 is deprecated because it is no longer maintained.
5.0.0 184 10/4/2025 5.0.0 is deprecated because it is no longer maintained.
4.6.0 196 10/3/2025 4.6.0 is deprecated because it is no longer maintained.
4.5.5 215 10/2/2025 4.5.5 is deprecated because it is no longer maintained.
4.5.4 210 10/2/2025 4.5.4 is deprecated because it is no longer maintained.
4.5.3 208 10/2/2025 4.5.3 is deprecated because it is no longer maintained.
4.5.2 209 10/2/2025 4.5.2 is deprecated because it is no longer maintained.
4.5.1 211 10/2/2025 4.5.1 is deprecated because it is no longer maintained.
4.5.0 212 10/2/2025 4.5.0 is deprecated because it is no longer maintained.
4.4.0 218 10/1/2025 4.4.0 is deprecated because it is no longer maintained.
4.3.0 217 10/1/2025 4.3.0 is deprecated because it is no longer maintained.
4.2.0 218 10/1/2025 4.2.0 is deprecated because it is no longer maintained.
4.1.0 210 10/1/2025 4.1.0 is deprecated because it is no longer maintained.
4.0.2 218 10/1/2025 4.0.2 is deprecated because it is no longer maintained.
4.0.1 210 10/1/2025 4.0.1 is deprecated because it is no longer maintained.
4.0.0 286 9/30/2025 4.0.0 is deprecated because it is no longer maintained.
3.5.2 219 9/30/2025 3.5.2 is deprecated because it is no longer maintained.
3.5.1 250 9/30/2025 3.5.1 is deprecated because it is no longer maintained.
3.4.1 254 9/30/2025 3.4.1 is deprecated because it is no longer maintained.
3.4.0 249 9/30/2025 3.4.0 is deprecated because it is no longer maintained.
3.3.2 261 9/30/2025 3.3.2 is deprecated because it is no longer maintained.
3.2.0 253 9/30/2025 3.2.0 is deprecated because it is no longer maintained.
3.1.0 252 9/29/2025 3.1.0 is deprecated because it is no longer maintained.
3.0.1 251 9/29/2025 3.0.1 is deprecated because it is no longer maintained.
3.0.1-preview-20250929165802 245 9/29/2025 3.0.1-preview-20250929165802 is deprecated because it is no longer maintained.
3.0.0 248 9/29/2025 3.0.0 is deprecated because it is no longer maintained.
3.0.0-preview-20250929164242 251 9/29/2025 3.0.0-preview-20250929164242 is deprecated because it is no longer maintained.
3.0.0-preview-20250929162455 248 9/29/2025 3.0.0-preview-20250929162455 is deprecated because it is no longer maintained.
2.12.0-preview-20250929161039 242 9/29/2025 2.12.0-preview-20250929161039 is deprecated because it is no longer maintained.
2.11.0 253 9/29/2025 2.11.0 is deprecated because it is no longer maintained.
2.10.0 253 9/29/2025 2.10.0 is deprecated because it is no longer maintained.
2.9.0 247 9/29/2025 2.9.0 is deprecated because it is no longer maintained.
2.8.0 249 9/29/2025 2.8.0 is deprecated because it is no longer maintained.
2.7.0 260 9/29/2025 2.7.0 is deprecated because it is no longer maintained.
2.6.0 254 9/28/2025 2.6.0 is deprecated because it is no longer maintained.
2.5.0 260 9/28/2025 2.5.0 is deprecated because it is no longer maintained.
2.4.0 252 9/28/2025 2.4.0 is deprecated because it is no longer maintained.
2.3.0 253 9/28/2025 2.3.0 is deprecated because it is no longer maintained.
2.2.0 255 9/28/2025 2.2.0 is deprecated because it is no longer maintained.
2.1.0 253 9/26/2025 2.1.0 is deprecated because it is no longer maintained.
2.0.9 257 9/26/2025 2.0.9 is deprecated because it is no longer maintained.
2.0.5 250 9/25/2025 2.0.5 is deprecated because it is no longer maintained.
2.0.4 256 9/25/2025 2.0.4 is deprecated because it is no longer maintained.
2.0.3 261 9/25/2025 2.0.3 is deprecated because it is no longer maintained.
2.0.1 257 9/25/2025 2.0.1 is deprecated because it is no longer maintained.
2.0.0 258 9/25/2025 2.0.0 is deprecated because it is no longer maintained.
1.1.2 334 9/24/2025 1.1.2 is deprecated because it is no longer maintained.
1.1.1 335 9/24/2025 1.1.1 is deprecated because it is no longer maintained.
1.1.0 253 9/24/2025 1.1.0 is deprecated because it is no longer maintained.
1.0.0 258 9/24/2025 1.0.0 is deprecated because it is no longer maintained.