SliceR 1.3.0

dotnet add package SliceR --version 1.3.0
                    
NuGet\Install-Package SliceR -Version 1.3.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="SliceR" Version="1.3.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="SliceR" Version="1.3.0" />
                    
Directory.Packages.props
<PackageReference Include="SliceR" />
                    
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 SliceR --version 1.3.0
                    
#r "nuget: SliceR, 1.3.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 SliceR@1.3.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=SliceR&version=1.3.0
                    
Install as a Cake Addin
#tool nuget:?package=SliceR&version=1.3.0
                    
Install as a Cake Tool

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
  • Attribute-Based Authorization: Declarative authorization using attributes
  • Convention-Based Resource Resolvers: Automatic resolver discovery with performance-optimized registry
  • ASP.NET Core Integration: Provides middleware and exception filters for web applications
  • Multi-Framework Support: Targets .NET 8.0, .NET 9.0, and .NET 10.0
  • Minimal Configuration: Set up with a single extension method

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

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 handlers, validators, and authorization components. The WithResourceResolvers() call enables automatic resource resolution for resource-based authorization.

2. Create Authorized Requests

Using Attributes

The simplest way to add authorization is using attributes:

// Authentication only
[Authenticated]
public record GetUserDataQuery(string? UserId) : IRequest<UserDataResponse>;

// Alternative: using [Authorized] without parameters
[Authorized]
public record GetProfileQuery(string? UserId) : IRequest<ProfileResponse>;

// With specific policy
[Authorized("users.delete")]
public record DeleteUserCommand(string UserId) : IRequest<bool>;

// With explicit resource resolver
[Authorized("documents.update")]
[ResolveResource(typeof(DocumentResourceResolver))]
public record UpdateDocumentCommand(Guid DocumentId, string NewContent) : IRequest<Unit>
{
    public Document? Resource { get; set; }
}

// With convention-based resolver
[Authorized("documents.delete")]
[ResolveResource]  // Automatically finds DeleteDocumentCommandResolver
public record DeleteDocumentCommand(Guid DocumentId) : IRequest<Unit>
{
    public Document? Resource { get; set; }
}
Using Interfaces

Alternatively, use interfaces for more control:

// Authentication only
public record GetUserDataQuery : IAuthenticatedRequest<UserDataResponse>
{
    public string? UserId { get; init; }
}

// With specific policy
public record DeleteUserCommand : IAuthorizedRequest<bool>
{
    public string UserId { get; init; }
    public string PolicyName => "users.delete";
}

3. Resource-Based Authorization

Resources can be resolved automatically before authorization:

Convention-Based Resolvers

// Command with convention-based resolver
[Authorized("documents.update")]
[ResolveResource]  // No type needed - finds UpdateDocumentCommandResolver automatically
public record UpdateDocumentCommand(Guid DocumentId) : IRequest<Unit>
{
    public Document? Resource { get; set; }
}

// Resolver follows naming convention: {RequestName}Resolver
public class UpdateDocumentCommandResolver : IResourceResolver<UpdateDocumentCommand, Document>
{
    private readonly IDocumentRepository _repository;

    public UpdateDocumentCommandResolver(IDocumentRepository repository) => _repository = repository;

    public async Task<Document?> ResolveAsync(UpdateDocumentCommand request, CancellationToken cancellationToken)
    {
        return await _repository.GetByIdAsync(request.DocumentId);
    }
}

Explicit Resolver (Override Convention)

[Authorized("documents.archive")]
[ResolveResource(typeof(CustomDocumentResolver))]  // Explicitly specify resolver
public record ArchiveDocumentCommand(Guid DocumentId) : IRequest<Unit>
{
    public Document? Resource { get; set; }
}

With Interfaces

public record UpdateDocumentCommand : IAuthorizedResourceRequest<Document, Unit>
{
    public Guid DocumentId { get; init; }
    public Document? Resource { get; set; }
    public string PolicyName => "documents.update";
}

Inline Resolver Registration

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:

  1. Validation Behavior: Automatically validates requests using registered FluentValidation validators
  2. 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:

  1. ValidationExceptionFilter: Catches ValidationException from FluentValidation and returns a 400 Bad Request with ValidationProblemDetails
  2. AuthorizationExceptionFilter: Catches AuthorizationFailedException and returns either 401 Unauthorized or 403 Forbidden with ProblemDetails

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 ValidationException to HTTP 400 Bad Request
  • Returns ValidationProblemDetails with field-level errors
  • Groups errors by property name for easy client-side processing

AuthorizationExceptionFilter:

  • Converts AuthorizationFailedException to:
    • HTTP 401 Unauthorized when authentication is missing
    • HTTP 403 Forbidden when authorization policy fails
  • Returns ProblemDetails with 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
);
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 is compatible.  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
1.3.0 188 11/24/2025
1.2.0 171 9/13/2025
1.1.0 176 9/10/2025
1.0.0 373 6/12/2025

## SliceR 1.3.0 - .NET 10 Support & Performance Improvements

### New Features

**Multi-Framework Support**
- Added .NET 10.0 target framework
- Targets .NET 8.0, .NET 9.0, and .NET 10.0
- All 101 tests passing on all frameworks

**Performance Improvements**
- Convention-based resource resolvers now use registry pattern for O(1) lookups
- Resolver discovery happens once at startup instead of per-request
- Reduced runtime reflection overhead in authorization pipeline

**Internal Improvements**
- Added IResolverRegistry and ResolverRegistry for efficient resolver management
- First resolver wins when multiple resolvers exist for same request type (test-friendly)

### Breaking Changes
None - fully backward compatible with v1.2.0

### Previous Features (v1.2.0)

**Attribute-Based Authorization**
- [Authenticated] attribute for authentication-only scenarios
- [Authorized] attribute for declarative authorization policies
- [ResolveResource] attribute for automatic resource resolution

**Convention-Based Resource Resolvers**
- Use [ResolveResource] without type parameter
- Automatically discovers resolvers by naming convention
- Explicit type parameter overrides convention