MediatR
MediatR copied to clipboard
How to Execute Post Handler in Every Query or Command
Goal: A handler for the class "LoggingBehavior" should be run as a posthandler in every time when you have executed any query or command handler.
In order words, always run the class LoggingBehavior (using the log information) after you have used query or command handler.
Today: Before running the query or command handler you execute the class LoggingBehavior.
Problem: Is it possbily to achieve the goal based on the current source code? if yes, how do you do it?
Info: *The source code is located at this website (https://github.com/valdisiljuconoks/PizzaArchitecture/tree/mediatr) *Using VS 2017 *I'm new in MediateR
`using System;
using FluentValidation.AspNetCore;
using Mediating.Sample.Infrastructure;
using Mediating.Sample.Infrastructure.DependencyRegistries;
using MediatR;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using StructureMap;
namespace Mediating.Sample
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
private IConfiguration Configuration { get; }
public IServiceProvider ConfigureServices(IServiceCollection services)
{
services.AddMvc()
.AddControllersAsServices()
.AddMvcOptions(config => { config.Filters.Add(typeof(ExceptionToJsonFilter)); })
.AddRazorOptions(opt =>
{
opt.ViewLocationFormats.Add("~/Features/{1}/{0}.cshtml");
opt.ViewLocationFormats.Add("~/Features/Shared/{0}.cshtml");
})
.AddFluentValidation(_ => _.RegisterValidatorsFromAssemblyContaining<Startup>());
services.AddMediatR();
return UseStructureMapContainer(services);
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if(env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseBrowserLink();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
app.UseMvc(routes =>
{
routes.MapRoute("default",
"{controller=UserManagement}/{action=List}/{id?}");
});
}
private IServiceProvider UseStructureMapContainer(IServiceCollection services)
{
var container = new Container();
container.Populate(services);
container.Configure(config =>
{
config.Scan(scanner =>
{
scanner.TheCallingAssembly();
scanner.WithDefaultConventions();
});
config.AddRegistry<MediatorRegistrations>();
config.AddRegistry<HttpRegistries>();
});
return container.GetInstance<IServiceProvider>();
}
}
}
`
using FluentValidation;
using Mediating.Sample.Features.UserManagement;
using Mediating.Sample.Infrastructure.Mediator.PipelineBehaviors;
using MediatR;
using MediatR.Pipeline;
using StackExchange.Redis;
using StructureMap;
namespace Mediating.Sample.Infrastructure.DependencyRegistries
{
public class MediatorRegistrations : Registry
{
public MediatorRegistrations()
{
Scan(_ =>
{
_.TheCallingAssembly();
_.WithDefaultConventions();
// fluent validation
_.AddAllTypesOf(typeof(IValidator<>));
});
// order of the registration matters here
For(typeof(IPipelineBehavior<,>)).Add(typeof(RequestPreProcessorBehavior<,>));
For(typeof(IPipelineBehavior<,>)).Add(typeof(LoggingBehavior<,>));
For(typeof(IPipelineBehavior<,>)).Add(typeof(ValidationBehavior<,>));
For(typeof(IPipelineBehavior<,>)).Add(typeof(RequestPostProcessorBehavior<,>));
For<IUserStorage>().Use<InMemoryUserStorage>().Singleton();
}
}
}
using System.Threading.Tasks;
using Mediating.Sample.Infrastructure.ControllerHelpers;
using MediatR;
using Microsoft.AspNetCore.Mvc;
namespace Mediating.Sample.Features.UserManagement
{
public class UserManagementController : Controller
{
private readonly IMediator _mediator;
public UserManagementController(IMediator mediator)
{
_mediator = mediator;
}
public async Task<ActionResult> List(GetAllUsersList.Query query)
{
var result =await _mediator.Send(query);
var model = new GetAllUsersList.ViewModel(result);
return View(model);
}
public ActionResult Create()
{
var model = new CreateUser.Command();
return View(model);
}
[HttpPost]
public async Task<ActionResult> Create(CreateUser.Command command)
{
await _mediator.Send(command);
return this.RedirectToActionJson("List");
}
}
}
using System.Threading;
using System.Threading.Tasks;
using MediatR;
using Microsoft.Extensions.Logging;
namespace Mediating.Sample.Infrastructure.Mediator.PipelineBehaviors
{
public class LoggingBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
{
private readonly ILogger _logger;
public LoggingBehavior(ILoggerFactory factory)
{
_logger = factory.CreateLogger("LoggingBehavior");
}
public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
{
_logger.LogInformation($"Executing `{request.GetType().FullName}` request...");
var response = await next();
_logger.LogInformation($"Finished `{request.GetType().FullName}`.");
return response;
}
}
}
-----------------------------------------------
using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Mediating.Sample.Features.UserManagement.Domain; using MediatR;
namespace Mediating.Sample.Features.UserManagement { public class GetAllUsersList { public class Query : IRequest<ICollection<User>> { }
public class Handler : IRequestHandler<Query, ICollection<User>>
{
private readonly IUserStorage _storage;
public Handler(IUserStorage storage)
{
_storage = storage;
}
public Task<ICollection<User>> Handle(Query request, CancellationToken cancellationToken)
{
return Task.FromResult(_storage.Users);
}
}
public class ViewModel
{
public ViewModel(ICollection<User> users)
{
Users = users ?? throw new ArgumentNullException(nameof(users));
}
public ICollection<User> Users { get; }
}
}
}
Can you not await the next() and assign that to a variable? I’m pretty sure the execute in order so will eventually return and then The result should be populated leaving you to log the response?
I must admit I’ve not tried it though so if I find some time later I’ll setup a workspace and I’ll give it a try.
I solve it with an interface marking and add it as constraint to the Logging Pipeline
public class PipelineLoggingBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> where TRequest : IRequest<TResponse>, ITraceableRequest{}
It allow me to add even different types of logging for Commands and Queries