Flowsy.Mediation 10.0.0

There is a newer version of this package available.
See the version list below for details.
dotnet add package Flowsy.Mediation --version 10.0.0                
NuGet\Install-Package Flowsy.Mediation -Version 10.0.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="Flowsy.Mediation" Version="10.0.0" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add Flowsy.Mediation --version 10.0.0                
#r "nuget: Flowsy.Mediation, 10.0.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.
// Install Flowsy.Mediation as a Cake Addin
#addin nuget:?package=Flowsy.Mediation&version=10.0.0

// Install Flowsy.Mediation as a Cake Tool
#tool nuget:?package=Flowsy.Mediation&version=10.0.0                

Flowsy Mediation

Concepts

The mediation pattern allows us to separate the core of our application from the infrastructure that supports it. A good way to achieve this separation is to think of our application as a model of the business processes from the real world, in which users ask for something to be done and expect some kind of result from the requested operation.

This way, a common application flow would be:

  1. User requests some action to be executed.
  2. The application validates the request and returns some error information to the user in case of invalid input.
  3. The application executes the requested operation.
  4. The application returns to the user the result of the operation or some error information if something went wrong during the process.

To go even further, we can split those requests in two types:

  • Query: A request for reading data with no alteration of the application's current state.
  • Command: A request for creating, updating or removing data, thus altering the application's current state.

Dependencies

This package relies on other packages to set the foundation for infrastructure-decoupled applications:

Usage

1. Define an Operation Context

We can provide our requests with relevant information about the context in which they are being executed. For instance, we can create a class to store information associated with the current user or application executing our requests:

public sealed class OperationContext
{
    public OperationContext(string serviceAccountId, string userId)
    {
        ServiceAccountId = serviceAccountId;
        UserId = userId;
    }
    
    // Identifies the application or service executing the request
    public string ServiceAccountId { get; } 
    
    // Identifies the user executing the request
    public string UserId { get; }
}

2. Define Some Queries

// Queries/CustomersByRegion/CustomersByRegionQuery.cs
// using ...
using Flowsy.Mediation;
// using ...

public class CustomersByRegionQuery : AbstractRequest<OperationContext, IEnumerable<CustomerDto>>
{
    public string CountryId { get; set; } = string.Empty;
    public string? StateId { get; set; }
}

3. Define Some Query Validators

// Queries/CustomersByRegion/CustomersByRegionQueryValidator.cs
// using ...
using Flowsy.Mediation;
using FluentValidation;
// using ...

public class CustomersByRegionQueryValidator : AbstractValidator<CustomersByRegionQuery>
{
    public CustomersByRegionQueryValidator()
    {
        RuleFor(query => query.CountryId)
            .NotEmpty()
            .WithMessage("Country identifier is required.");
    }
}

4. Define Some Query Handlers

// Queries/CustomersByRegion/CustomersByRegionQueryHandler.cs
// using ...
using Flowsy.Mediation;
// using ...

public class CustomersByRegionQueryHandler : AbstractRequestHandler<OperationContext, CustomerByIdQuery, IEnumerable<CustomerDto>>
{
    private readonly ICustomerRepository _customerRepository;
    
    public CustomersByRegionQueryHandler(ICustomerRepository customerRepository)
    {
        _customerRepository = customerRepository;
    }

    protected override async Task<IEnumerable<CustomerDto>> HandleAsync(CustomersByRegionQuery request, CancellationToken cancellationToken)
    {
        var serviceAccountId = request.Context.ServiceAccountId;
        var userId = request.Context.UserId;
        // Do something with serviceAccountId and userId
        
        var customers = await _customerRepository.GetManyAsync<Customer>(request.CountryId, request.StateId, cancellationToken);
        
        return customers.Select(c => {
            var customerDto = new CustomerDto();
            // Copy properties from c to customerDto
            return customerDto;
            }); 
    }
}

5. Define Some Commands

// Commands/CreateCustomer/CreateCustomerCommand.cs
// using ...
using Flowsy.Mediation;
// using ...

public class CreateCustomerCommand : AbstractRequest<OperationContext, CreateCustomerCommandResult>
{
    public string FirstName { get; set; } = string.Empty;
    public string LastName { get; set; } = string.Empty;
    public string Email { get; set; } = string.Empty;
}

6. Define Some Command Validators

// Commands/CreateCustomer/CreateCustomerCommandValidator.cs
// using ...
using Flowsy.Mediation;
using FluentValidation;
// using ...

public class CreateCustomerCommandValidator : AbstractValidator<CreateCustomerCommand>
{
    public CreateCustomerCommandValidator()
    {
        RuleFor(command => command.FirstName)
            .NotEmpty()
            .WithMessage("First name is required.");
            
        RuleFor(command => command.Email)
            .EmailAddress()
            .WithMessage("Invalid email address")
            .DependentRules(() => 
            {
                RuleFor(command => command.Email)
                    .NotEmpty()
                    .WithMessage("Email is required.")
                    .MaximumLength(320)
                    .WithMessage("Up to 320 characters.");
            });
    }
}

7. Define Some Command Results

// Commands/CreateCustomer/CreateCustomerCommandResult.cs
public class CreateCustomerCommandResult
{
    public int CustomerId { get; set; }
}

8. Define Some Command Handlers

// Commands/CreateCustomer/CreateCustomerCommandHandler.cs
// using ...
using Flowsy.Mediation;
// using ...

public class CreateCustomerCommandHandler : AbstractRequestHandler<OperationContext, CreateCustomerCommand, CreateCustomerCommandResult>
{
    private readonly ICustomerRepository _customerRepository;
    
    public CreateCustomerCommandHandler(ICustomerRepository customerRepository)
    {
        _customerRepository = customerRepository;
    }

    protected override async Task<CreateCustomerCommandResult> HandleAsync(CreateCustomerCommand request, CancellationToken cancellationToken)
    {
        var serviceAccountId = request.Context.ServiceAccountId;
        var userId = request.Context.UserId;
        // Do something with serviceAccountId and userId
        
        var customer = new Customer();
        
        // Copy properties from request to customer
    
        await _customerRepository.CreateAsync(customer, cancellationToken);
        
        return new CreateCustomerCommandResult
        {
            CustomerId = customer.CustomerId
        }; 
    }
}

9. Resolve the Request Context

In order to provide our requests with their corresponding context, we must implement the IRequestContextProvider interface.

// HttpRequestContextProvider.cs
// using ...
using Flowsy.Mediation;
// using ...

public sealed class HttpRequestContextProvider : IRequestContextProvider<OperationContext>
{
    private readonly IHttpContextAccessor _httpContextAccessor;
    
    public HttpRequestContextProvider(IHttpContextAccessor httpContextAccessor)
    {
        _httpContextAccessor = httpContextAccessor;
    }
    
    public OperationContext Provide()
    {
        var serviceAccountId = "";
        var userId = "";
        var user = _httpContextAccessor.HttpContext?.User;
        // Read information from user to resolve the service account ID and user ID
        
        return new OperationContext(serviceAccountId, userId);
    }
    
    public Task<OperationContext> ProvideAsync(CancellationToken cancellationToken)
        => Task.Run(() => Provide(), cancellationToken);
}

10. Register Queries and Commands

Add a reference to the assemblies containing the application logic and place this code in the Program.cs file.

// Program.cs
// using ...
using Flowsy.Mediation;
// using ...

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHttpContextAccessor();

builder.Services.AddScoped<IRequestContextProvider<OperationContext>, HttpRequestContextProvider>();

builder.Services
    .AddMediation(
        typeof(CustomersByRegionQuery).Assembly, // Register queries and commands from this assembly
        typeof(CreateCustomerCommand).Assembly // Register queries and commands from this assembly
        // Register queries and commands from others assemblies
    )
    // Registers RequestContextResolutionBehavior to add context information to every request using
    // an instance of IRequestContextProvider<TContext> if any was registered in the dependency injection system
    .UseRequestContext()
    // Registers RequestValidationBehavior to validate every request
    .UseRequestValidation()
    // Registers RequestLoggingBehavior to log information for every request and its result
    .UseRequestLogging();

// Add other services

var app = builder.Build();

// Use services

app.Run();
Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  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. 
.NET Core netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.1 is compatible. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on Flowsy.Mediation:

Package Downloads
Flowsy.Web.Api

Foundation components for Web APIs.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
11.0.4 179 1/17/2024
11.0.3 106 1/17/2024
11.0.2 104 1/17/2024
11.0.1 108 1/16/2024
11.0.0 111 1/16/2024
10.0.0 116 1/16/2024
9.0.2 159 12/14/2023
9.0.1 104 12/14/2023
9.0.0 105 12/14/2023
8.0.1 124 12/6/2023
8.0.0 131 12/5/2023
7.3.2 229 10/5/2023
7.3.1 152 10/5/2023
7.3.0 140 10/5/2023
7.2.1 168 10/4/2023
7.2.0 144 10/4/2023
7.1.0 143 10/4/2023
7.0.0 264 8/29/2023
6.1.7 368 6/2/2023
6.1.6 187 5/24/2023
6.1.5 136 5/24/2023
6.1.4 331 3/25/2023
6.1.3 386 3/10/2023
6.1.2 282 3/10/2023
6.1.1 304 3/10/2023
6.1.0 229 3/10/2023
6.0.3 273 3/9/2023
6.0.2 295 3/9/2023
6.0.1 354 3/9/2023
6.0.0 299 3/9/2023
5.1.1 340 2/27/2023
5.1.0 321 2/24/2023
5.0.0 316 2/23/2023
4.1.0 310 2/22/2023
4.0.0 376 2/21/2023
3.4.0 305 2/21/2023
3.3.0 224 2/21/2023
3.2.0 425 2/21/2023
3.1.0 246 2/21/2023
3.0.1 1,737 11/6/2022
3.0.0 642 11/5/2022
2.0.0 746 11/3/2022
1.1.2 341 11/2/2022
1.1.1 541 11/2/2022
1.1.0 344 11/2/2022
1.0.0 355 11/2/2022