SliceR 1.1.0
See the version list below for details.
dotnet add package SliceR --version 1.1.0
NuGet\Install-Package SliceR -Version 1.1.0
<PackageReference Include="SliceR" Version="1.1.0" />
<PackageVersion Include="SliceR" Version="1.1.0" />
<PackageReference Include="SliceR" />
paket add SliceR --version 1.1.0
#r "nuget: SliceR, 1.1.0"
#:package SliceR@1.1.0
#addin nuget:?package=SliceR&version=1.1.0
#tool nuget:?package=SliceR&version=1.1.0
SliceR
SliceR is a lightweight library that integrates MediatR with FluentValidation and ASP.NET Core Authorization to enable clean, vertical "slicing" of application features. This approach helps maintain separation of concerns while ensuring proper validation and authorization across your application.
Features
- MediatR Integration: Seamlessly works with MediatR for command and query handling
- Automated Validation: Integrates FluentValidation to validate commands and queries before they're handled
- Policy-Based Authorization: Enforces authorization policies on requests
- Resource-Based Authorization: Supports authorization against specific resources
- ASP.NET Core Integration: Provides middleware and exception filters for web applications
- Minimal Configuration: Set up with a single extension method
Installation
Add a reference to the SliceR project in your solution:
<ProjectReference Include="..\path\to\SliceR\src\SliceR\SliceR.csproj" />
Getting Started
1. Register Services
In your Program.cs or Startup.cs:
using SliceR;
// ...
services.AddSliceR(typeof(YourStartupClass).Assembly)
.WithResourceResolvers(); // Enable automatic resource resolution
This will register all MediatR handlers, validators, and authorization components. The WithResourceResolvers() call enables automatic resource resolution for resource-based authorization.
2. Create Authorized Requests
For requests requiring authentication only:
public record GetUserDataQuery : IAuthenticatedRequest<UserDataResponse>
{
public string? UserId { get; init; }
}
Alternatively, you can use IAuthorizedRequest with a null policy:
public record GetUserDataQuery : IAuthorizedRequest<UserDataResponse>
{
public string? UserId { get; init; }
// No specific policy, just authentication
public string? PolicyName => null;
}
For requests requiring specific authorization policies:
public record DeleteUserCommand : IAuthorizedRequest<bool>
{
public string UserId { get; init; }
// Require the "users.delete" policy
public string PolicyName => "users.delete";
}
3. Resource-Based Authorization
For operations on specific resources:
public record UpdateDocumentCommand : IAuthorizedResourceRequest<Document, Unit>
{
public Guid DocumentId { get; init; }
public string NewContent { get; init; }
// The resource being accessed - can be set manually or resolved automatically
public Document? Resource { get; set; }
// The policy to check
public string PolicyName => "documents.update";
}
Automatic Resource Resolution
You can implement resource resolvers to automatically load resources before authorization in two ways:
Option 1: Dedicated Resolver Classes
public class DocumentResourceResolver : IResourceResolver<UpdateDocumentCommand, Document>
{
private readonly IDocumentRepository _repository;
public DocumentResourceResolver(IDocumentRepository repository) => _repository = repository;
public async Task<Document?> ResolveAsync(UpdateDocumentCommand request, CancellationToken cancellationToken)
{
return await _repository.GetByIdAsync(request.DocumentId);
}
}
// Register the resolver
services.AddTransient<IResourceResolver<UpdateDocumentCommand, Document>, DocumentResourceResolver>();
Option 2: Convention-Based Registration (Recommended for simple cases)
services.AddSliceR(typeof(YourStartupClass).Assembly)
.WithResourceResolver<UpdateDocumentCommand, Document>(async (request, serviceProvider, cancellationToken) =>
{
var repository = serviceProvider.GetRequiredService<IDocumentRepository>();
return await repository.GetByIdAsync(request.DocumentId);
})
.WithResourceResolver<DeleteUserCommand, User>(async (request, serviceProvider, cancellationToken) =>
{
var userService = serviceProvider.GetRequiredService<IUserService>();
return await userService.GetUserByIdAsync(request.UserId);
});
With automatic resource resolution, your controllers become much simpler:
[HttpPut("/documents/{id}")]
public async Task<IActionResult> UpdateDocument(Guid id, UpdateDocumentRequest request)
{
var command = new UpdateDocumentCommand
{
DocumentId = id,
NewContent = request.Content
// Resource will be resolved automatically!
};
return Ok(await _mediator.Send(command));
}
4. Adding Validation
Create validators using FluentValidation:
public class CreateUserCommandValidator : AbstractValidator<CreateUserCommand>
{
public CreateUserCommandValidator()
{
RuleFor(x => x.Username).NotEmpty().MinimumLength(3).MaximumLength(50);
RuleFor(x => x.Email).NotEmpty().EmailAddress();
RuleFor(x => x.Password).NotEmpty().MinimumLength(8);
}
}
How It Works
SliceR adds pipeline behaviors to MediatR:
- Validation Behavior: Automatically validates requests using registered FluentValidation validators
- Authorization Behavior: Ensures users are authorized to perform the requested operation
When a request fails validation or authorization, appropriate exceptions are thrown and handled by the registered exception filters.
Working with MVC Controllers
Automatic Exception Handling
When you call AddSliceR(), it automatically registers two exception filters that convert validation and authorization exceptions into standard ProblemDetails responses:
- ValidationExceptionFilter: Catches
ValidationExceptionfrom FluentValidation and returns a 400 Bad Request withValidationProblemDetails - AuthorizationExceptionFilter: Catches
AuthorizationFailedExceptionand returns either 401 Unauthorized or 403 Forbidden withProblemDetails
These filters are automatically added to your MVC pipeline, so you don't need any additional configuration. Simply send your MediatR commands/queries from your controllers:
[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
private readonly IMediator _mediator;
public UsersController(IMediator mediator) => _mediator = mediator;
[HttpPost]
public async Task<IActionResult> CreateUser(CreateUserCommand command)
{
// Validation and authorization happen automatically
// If validation fails, returns 400 with ValidationProblemDetails
// If authorization fails, returns 401/403 with ProblemDetails
var result = await _mediator.Send(command);
return Ok(result);
}
}
What Gets Converted to ProblemDetails
The exception filters handle the following scenarios:
ValidationExceptionFilter:
- Converts
ValidationExceptionto HTTP 400 Bad Request - Returns
ValidationProblemDetailswith field-level errors - Groups errors by property name for easy client-side processing
AuthorizationExceptionFilter:
- Converts
AuthorizationFailedExceptionto:- HTTP 401 Unauthorized when authentication is missing
- HTTP 403 Forbidden when authorization policy fails
- Returns
ProblemDetailswith error details in extensions
Example Response Bodies
Validation failure response:
{
"type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
"title": "Bad Request",
"status": 400,
"detail": "Validation failed",
"errors": {
"Email": ["Email is required", "Email must be a valid email address"],
"Password": ["Password must be at least 8 characters"]
}
}
Authorization failure response:
{
"title": "Authorization Failed",
"status": 403,
"detail": "Failed requirement: users.delete",
"errors": ["User does not have permission to delete users"]
}
Important Notes for Controller Usage
- No Try-Catch Needed: The exception filters handle exceptions automatically
- Consistent API Responses: All validation and authorization failures return standard ProblemDetails
- Works with API Controllers: The filters are registered globally and work with all controllers
Advanced Configuration
Custom Authorization Provider
You can implement your own authorization provider:
public class CustomAuthorizationProvider : IAuthorizationProvider
{
// Implement authorization logic
public Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, string policyName)
{
// Custom authorization logic
}
public Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, string policyName, object resource)
{
// Custom resource-based authorization logic
}
}
// Register your custom provider
services.AddScoped<IAuthorizationProvider, CustomAuthorizationProvider>();
Service Lifetime
You can customize the service lifetime when registering SliceR:
services.AddSliceR(
assembly: typeof(YourStartupClass).Assembly,
lifetime: ServiceLifetime.Singleton,
includeInternalTypes: true
);
Benefits of the Vertical Slice Architecture
Using SliceR helps implement a vertical slice architecture where:
- Each feature is isolated with its own request, handler, validator, and authorization rules
- Cross-cutting concerns like validation and authorization are handled consistently
- Code organization follows feature boundaries rather than technical layers
- Features can be understood, tested, and maintained in isolation
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | 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. |
-
net9.0
- FluentValidation (>= 12.0.0)
- MediatR (>= 13.0.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
## SliceR 1.1.0 - Smart Resource Resolution Pipeline
### 🚀 New Features
**IAuthenticatedRequest Interface**
- Added convenience interface for authentication-only requests (no specific authorization policy required)
- Eliminates the need to set PolicyName = null for simple authentication scenarios
**Smart Resource Resolution Pipeline**
- IResourceResolver<TRequest, TResource> interface for automatic resource loading
- Automatic resource resolution in AuthorizationBehavior before authorization checks
- WithResourceResolver() extension method for convention-based resolver registration
- DelegateResourceResolver for inline resolver logic with full dependency injection support
### 💡 Usage Examples
**IAuthenticatedRequest**
```csharp
public record GetUserDataQuery : IAuthenticatedRequest<UserDataResponse>
{
public string? UserId { get; init; }
}
```
**Convention-Based Resource Resolution**
```csharp
services.AddSliceR()
.WithResourceResolver<UpdateDocumentCommand, Document>(async (request, serviceProvider, ct) => {
var repository = serviceProvider.GetRequiredService<IDocumentRepository>();
return await repository.GetByIdAsync(request.DocumentId);
});
```
**Simplified Controllers (New)**
```csharp
[HttpPut("/documents/{id}")]
public async Task<IActionResult> UpdateDocument(Guid id, UpdateDocumentRequest request)
{
var command = new UpdateDocumentCommand { DocumentId = id, NewContent = request.Content };
return Ok(await _mediator.Send(command)); // Resource resolved automatically!
}
```