MediatR cancel publish subsequent messages - c#

My Mediatr is using the SyncContinueOnException publish strategy, is there any way to run some validation before start the propagation?
Example:
_mediatr.Publish(new MyNotification());
public class MyValidationHandler :
INotificationHandler<MyValidationHandler>
{
Task INotificationHandler<MyValidationHandler>.Handle(MyValidationHandler notification, CancellationToken cancellationToken)
{
// STOP propagation if some condition is false
}
}
public class FirstHandlers :
INotificationHandler<MyNotification>
{
Task INotificationHandler<MyNotification>.Handle(MyNotification notification, CancellationToken cancellationToken)
{
Console.WriteLine("x");
return Task.CompletedTask;
}
}
public class SecondHandlers :
INotificationHandler<MyNotification>
{
Task INotificationHandler<MyNotification>.Handle(MyNotification notification, CancellationToken cancellationToken)
{
Console.WriteLine("x");
return Task.CompletedTask;
}
}

Update Sorry, misread this originally!
One way to wrap the MediatR Publish behavior is to decorate the IMediator instance itself, either manually or with a library like Scrutor. You can add a marker interface to identify notifications that should pass through your validation logic, with any other events just flowing through to MediatR.
public class MediatorDecorator : IMediator
{
private readonly IMediator _mediator;
public MediatorDecorator(IMediator mediator)
{
_mediator = mediator;
}
public Task Publish<TNotification>(TNotification notification, CancellationToken cancellationToken = default) where TNotification : INotification
{
if (notification is IValidatableNotification)
{
// Your validation behavior here
return Task.CompletedTask; // if the validation fails, respond as you'd like ...
}
return _mediator.Publish(notification, cancellationToken);
}
// ...
}
And in Startup, after the MediatR registration, using Scrutor's Decorate:
services.AddMediatR(typeof(Startup));
services.Decorate<IMediator, MediatorDecorator>();

Related

Cannot access APIs when add IHostedService service in ASP.NET Core

I use .NET 6 and when I add an IHostedService service in ASP.NET Core, I cannot access the APIs. If I remove the IHostedService, I can access the APIs.
Add IHostedService:
//builder.Services.AddHostedService<WSService>();
builder.WebHost.ConfigureServices(services => services.AddHostedService<WSService>());
public class WSService : IHostedService
{
public async Task StartAsync(CancellationToken cancellationToken)
{
while (true)
{
System.Console.WriteLine("back ground task");
await Task.Delay(5 * 1000);
}
}
public async Task StopAsync(CancellationToken cancellationToken)
{
await Task.CompletedTask;
}
}
If add IHostedService, cannot access http://localhost:5000/Query
namespace Api.Controllers
{
[ApiController]
[Route("[controller]")]
public class QueryController : ControllerBase
{
private readonly IQueryData _queryData;
public QueryController(IQueryData queryData)
{
_queryData = queryData;
}
[HttpGet]
public async Task<IActionResult> Query([FromQuery] QueryDataRequest request, CancellationToken cancellationToken)
{
var data = await _queryData.PagingQueryAsync(request, cancellationToken);
return new JsonResult(data);
}
}
}
Add IHostedService will not print these logs, it seems the WebHost is not running
So, how to resolve this problem, thanks!
You are causing an infinite loop on your StartAsync method, and as noted by the documentation here:
StartAsync should be limited to short running tasks because hosted services are run sequentially, and no further services are started until StartAsync runs to completion.

MediatR Abstract Base Command

I have a scenario, where I would like to Enable/Disable a PurchaseOrderItem
So I have written two separate Command and commandHandlers. Ideally I have to create base abstract class and need to move my logic to one place.
How I can achieve this?
You can do it like this:
Commands:
enum CommandType {
Disable,
Enable
}
class EnableCommand : IRequest {
}
class DisableCommand : IRequest {
}
class CommonCommand : IRequest {
public CommandType Type {get; set;}
}
Handlers:
class EnableCommandHandler : IRequestHandler<EnableCommand> {
private readonly IMediator _mediator;
public EnableCommandHandler(IMediator mediator) {
_mediator = mediator;
}
public async Task Handle(EnableCommand command, CancellationToken cancellationToken) {
await _mediator.Send(new CommonCommand {Type = CommandType.Enable});
}
}
class DisableCommandHandler : IRequestHandler<DisableCommand> {
private readonly IMediator _mediator;
public EnableCommandHandler(IMediator mediator) {
_mediator = mediator;
}
public async Task Handle(DisableCommand command, CancellationToken cancellationToken) {
await _mediator.Send(new CommonCommand {Type = CommandType.Disable});
}
}
class CommonCommandHandler : IRequestHandler<CommonCommand> {
private readonly IMediator _mediator;
public EnableCommandHandler(IMediator mediator) {
_mediator = mediator;
}
public async Task Handle(CommonCommand command, CancellationToken cancellationToken) {
... // some common logic
switch(command.Type) {
... // some specific logic
}
}
}
And if you don't want to create 3 handlers you can create one for all this commands:
class CommonCommandHandler : IRequestHandler<EnableCommand> , IRequestHandler<DisableCommand>, IRequestHandler<CommonCommand> {
public async Task Handle(EnableCommand command, CancellationToken cancellationToken) {
await Handle(new CommonCommand {Type = CommandType.Enable});
}
public async Task Handle(DisableCommand command, CancellationToken cancellationToken) {
await Handle(new CommonCommand {Type = CommandType.Disable});
}
public async Task Handle(CommonCommand command, CancellationToken cancellationToken) {
... // some common logic
switch(command.Type) {
... // some specific logic
}
}
}
For last options you actually don't need CommonCommand as IRequest. So idea is to re-use handlers.
You can solve this problem with one command/handler.
public enum PurchaseOrderStatus
{
Enabled,
Disabled
}
public class TogglePurchaseOrderItemStatusCommand : IRequest
{
public PurchaseOrderStatus Status { get; }
public TogglePurchaseOrderItemStatusCommand(PurchaseOrderStatus status)
{
Status = status;
}
}
public class TogglePurchaseOrderItemCommandHandler : IRequestHandler<TogglePurchaseOrderItemCommand>
{
public Task<Unit> Handle(TogglePurchaseOrderItemCommand request, CancellationToken cancellationToken)
{
//Execute Logic Here Based on request.Status
}
}

How to get notify when a user closes the browser in SignalR Blazor?

I want to send a notification to all the users connected in a group when a user lefts. How can I do that?
As you can see in my code I've overridden the OnDisconnectedAsync method, but it works only when I call HubConnection.DisposeAsync();. But if I close the browser or tab, it does nothing.
public override Task OnConnectedAsync()
{
Console.WriteLine("User Joined.");
return base.OnConnectedAsync();
}
public override Task OnDisconnectedAsync(Exception exception)
{
Console.WriteLine("User Left.");
return base.OnDisconnectedAsync(exception);
}
Is there any way to get notified when the browser is closed?
You can create a service class that derives from CircuitHandler:
CircuitHandlerService.cs
using Microsoft.AspNetCore.Components.Server.Circuits;
using System;
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;
public class CircuitHandlerService : CircuitHandler
{
public ConcurrentDictionary<string, Circuit> Circuits { get; set; }
public event EventHandler CircuitsChanged;
protected virtual void OnCircuitsChanged()
=> CircuitsChanged?.Invoke(this, EventArgs.Empty);
public CircuitHandlerService()
{
Circuits = new ConcurrentDictionary<string, Circuit>();
}
public override Task OnCircuitOpenedAsync(Circuit circuit, CancellationToken cancellationToken)
{
Circuits[circuit.Id] = circuit;
OnCircuitsChanged();
return base.OnCircuitOpenedAsync(circuit, cancellationToken);
}
public override Task OnCircuitClosedAsync(Circuit circuit, CancellationToken cancellationToken)
{
Circuit circuitRemoved;
Circuits.TryRemove(circuit.Id, out circuitRemoved);
OnCircuitsChanged();
return base.OnCircuitClosedAsync(circuit, cancellationToken);
}
public override Task OnConnectionDownAsync(Circuit circuit, CancellationToken cancellationToken)
{
return base.OnConnectionDownAsync(circuit, cancellationToken);
}
public override Task OnConnectionUpAsync(Circuit circuit, CancellationToken cancellationToken)
{
return base.OnConnectionUpAsync(circuit, cancellationToken);
}
}
Add the service to the DI container:
services.AddSingleton<CircuitHandler>(new CircuitHandlerService());
Test the service:
#page "/"
#using Microsoft.AspNetCore.Components.Server.Circuits
#inject CircuitHandler circuitHandler
#implements IDisposable
<h1>Hello, world!</h1>
Welcome to your new app.
<p>
Number of Circuits: #((circuitHandler as BlazorCircuitHandler.Services.CircuitHandlerService).Circuits.Count)
<ul>
#foreach (var circuit in (circuitHandler as BlazorCircuitHandler.Services.CircuitHandlerService).Circuits)
{
<li>#circuit.Key</li>
}
</ul>
</p>
#code {
protected override void OnInitialized()
{
// register event handler
(circuitHandler as CircuitHandlerService).CircuitsChanged += HandleCircuitsChanged;
}
public void Dispose()
{
// unregister the event handler when the component is destroyed
(circuitHandler as CircuitHandlerService).CircuitsChanged -= HandleCircuitsChanged;
}
public void HandleCircuitsChanged(object sender, EventArgs args)
{
// notify the UI that the state has changed
InvokeAsync(() => StateHasChanged());
}
}
Use CircuitHandler
Blazor Server allows code to define a circuit handler , which allows running code on changes to the state of a user's circuit. A circuit handler is implemented by deriving from CircuitHandler and registering the class in the app's service container.
public class TrackingCircuitHandler : CircuitHandler
{
private HashSet<Circuit> circuits = new HashSet<Circuit>();
public override Task OnConnectionUpAsync(Circuit circuit, CancellationToken cancellationToken)
{
circuits.Add(circuit);
return Task.CompletedTask;
}
public override Task OnConnectionDownAsync(Circuit circuit, CancellationToken cancellationToken)
{
circuits.Remove(circuit);
return Task.CompletedTask;
}
public int ConnectedCircuits => circuits.Count;
}
Circuit handlers are registered using DI
services.AddSingleton<CircuitHandler, TrackingCircuitHandler>();

Swagger Cancel button should trigger CancellationSource

[ApiController]
[Route("[controller]")]
public class JobController : ControllerBase
{
private readonly IEventBus _bus;
public JobController(IUnitOfWork unitOfWork, IEventBus bus)
{
_bus = bus;
}
...
[HttpPost]
public async Task<IActionResult> Post([FromBody]JobRequest request)
{
try
{
var command = new JobCommand{ Id = 1, Name = "abc"};
await _bus.SendCommand(command);
}
catch (OperationCanceledException)
{
_logger.LogInformation("Task was cancelled!");
}
return CreatedAtAction(nameof(GetById), new { id = 0 }, null);
}
}
public class JobCommandHandler : IRequestHandler<JobCommand, bool>
{
private readonly ILogger<JobCommandHandler> _logger;
public JobCommandHandler(ILogger<JobCommandHandler> logger)
{
_logger = logger;
}
public async Task<bool> Handle(JobCommand request, CancellationToken cancellationToken)
{
//
// I was able to reproduce manual cancellation by using this code below
var cts = new CancellationTokenSource();
cts.Cancel();
cancellationToken = cts.Token;
// how this can be populated sent from the place where I'm issuing command?
cancellationToken.ThrowIfCancellationRequested();
...
// long running task
}
}
My question is:
Do I need to send the CancellationTokenSource together with command? If so, how to trigger that from the swagger Cancel button and is it good practice to include CancellationTokenSource
to be the property of CommandBase class which every Command will extend?
If you want to cancel all IO and processing if the Http Request which initiated everything got canceled, then, yes. You have to make everything in its path cancellation aware and pass the token through all layers.
You can get a request cancellation token from the HttpContext in the controller from HttpContext.RequestAborted.
Now, in your example, I'm not sure what IEventBus is. If that is some kind of distributed messaging backend, simply passing a CancellationToken will not work, in that case you could send a cancel event to cancel the action I guess.
If it is in-memory eventing only, then it should probably work.

Unit testing Custom MessageHandler

I have a custom handler as below:
public class LoggingHandler : DelegatingHandler
{
public LoggingHandler()
{
}
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
var logger = new Logger(new something1(), param2, param3);
logger.LogInformation(
$"Incoming request: {request.Method} {request.RequestUri} );
.
.
.
.
return httpResponse;
}
}
I am familiar with Moq and I am able to moq the request and response message and assert successfully on that.
However as you can see I have a logger initialization done in the SendAsync method and log information regarding request, response and errors.
How can I test the logger in this workflow?.
Problem will be that because of the manual initialization of the logger it is difficult to mock it.
The logger should have been an injected dependency.
public class LoggingHandler : DelegatingHandler {
private readonly ILogger logger;
public LoggingHandler(ILogger logger) {
this.logger = logger;
}
//...
If injection is not an option then have a virtual factory method that can be overridden when testing.
public class LoggingHandler : DelegatingHandler {
public LoggingHandler() {
}
protected virtual ILogger CreateLogger() {
//...
}
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken) {
var logger = CreateLogger();
logger.LogInformation(
$"Incoming request: {request.Method} {request.RequestUri} );
//....
return httpResponse;
}
//...

Categories

Resources