J'utilise le modèle CQS dans mon projet principal asp.net. Commençons par un exemple pour mieux expliquer ce que je veux réaliser. J'ai créé une commande:
public class EmptyCommand : INotification{}
Le gestionnaire de commandes:
public class EmptyCommandHandler : INotificationHandler<EmptyCommand>
{
public Task Handle(EmptyCommand notification, CancellationToken cancellationToken)
{
return Task.FromResult(string.Empty);
}
}
La requête:
public class EmptyQuery : IRequest<string>{}
Le gestionnaire de requêtes:
public class EmptyQueryHandler : IRequestHandler<EmptyQuery, string>
{
public Task<string> Handle(EmptyQuery notification, CancellationToken cancellationToken)
{
return Task.FromResult(string.Empty);
}
}
et ceci est un exemple simple de la façon d'exécuter la commande et la requête et d'appeler la méthode Handle à partir de EmptyCommandHandler et EmptyQueryHandler:
readonly IMediator _mediator;
public HomeController(IMediator mediator)
{
_mediator = mediator;
}
public async Task<IActionResult> Index()
{
await _mediator.Publish(new EmptyCommand());
var queryResult = await _mediator.Send(new EmptyQuery());
return View();
}
Veuillez garder à l'esprit que la requête peut renvoyer d'autres types pas nécessairement le string
. Je voudrais créer une sorte de classe de bridge par ex. MediatorBoostrapper
, ce qui me permet d'exécuter une logique métier (par exemple, commande/requête de journalisation via l'enregistreur) chaque fois que la méthode Publish est invoquée, puis invoquez public Task Handle(EmptyCommand notification,...
méthode du gestionnaire de commandes. La solution doit être générique, donc cette méthode sera invoquée chaque fois que j'exécuterai la méthode Publish
. Je veux également pouvoir faire la même chose pour la méthode Send
.
Je pensais à la création du public class MediatorBoostrapper : IMediator
mais je ne sais pas ce qui devrait être une bonne implémentation de la classe et si mon idée est bonne. Des idées? À votre santé
Modifier
Je veux avoir un exemple de la façon d'utiliser la Behaviors pour créer une manière générique d'exécuter une méthode externe à partir du gestionnaire générique chaque fois que j'exécute la méthode Send
pour les requêtes. Je veux avoir un exemple similaire pour la méthode Publish
, que j'utilise pour envoyer des commandes.
Je veux avoir un exemple de la façon d'utiliser répartition polymorphe pour la création du GenericCommandHandler et d'un GenericQueryHandler
J'ai créé un exemple de projet sur GitHub qui peut être trouvé ici Vous pouvez vous sentir libre d'essayer d'étendre ce projet avec votre solution.
Cette fois, je veux répondre à la question à partir de la fin.
2.
TL; DR Polymorphic Dispatch ne peut pas être utilisé pour le CQS
Après un certain temps à jouer avec la bibliothèque MediatR, à lire les commentaires sous ma question et à consulter mon ami, j'ai trouvé que le Dispatch polymorphe (PD) peut être utilisé pour créer un gestionnaire générique uniquement au cas où des commandes. La solution PD ne peut pas être implémentée pour les requêtes. Sur la base de la Documentation , les gestionnaires sont contraires et non covariant. Cela signifie que le PD ne fonctionne que dans le cas où le TResponse
est un type constant. Dans le cas des requêtes, ceci est faux et chaque gestionnaire de requête peut renvoyer un résultat différent.
J'ai également trouvé ce problème . Je pense qu'il est intéressant de savoir que vous ne pouvez utiliser la répartition polymorphe que si votre conteneur la prend en charge.
1. Comportements est la seule et unique solution pour CQS lors de l'utilisation de MediatR. Sur la base du commentaire sous ma question de #Steve et commentaire de jbogard J'ai trouvé le moyen d'utiliser les comportements et IRequestHandler pour le modèle de commande strict. Le commentaire complet:
Pour résumer les changements, il existe deux types principaux de demandes: celles qui renvoient une valeur et celles qui ne le font pas. Ceux qui n'implémentent pas maintenant
IRequest<T>
oùT : Unit
. Il s'agissait d'unifier les demandes et les gestionnaires en un seul type. Les types divergents ont brisé le pipeline pour de nombreux conteneurs, l'unification signifie que vous pouvez utiliser des pipelines pour tout type de demande.Cela m'a forcé à ajouter le type d'unité dans tous les cas, j'ai donc ajouté quelques classes d'assistance pour vous.
IRequestHandler<T>
- implémentez ceci et vous retournerezTask<Unit>
.AsyncRequestHandler<T>
- héritez de cela et vous retournerezTask
.RequestHandler<T>
- héritez de cela et vous ne retournerez rien(void)
.Pour les demandes qui renvoient des valeurs:
IRequestHandler<T, U>
- vous retournerezTask<U>
RequestHandler<T, U>
- vous retournerezU
Je me suis débarrassé de l'AsyncRequestHandler parce qu'il ne faisait vraiment rien après la consolidation, une classe de base redondante.
L'exemple
a) La gestion des commandes:
public class EmptyCommand : IRequest{...}
public class EmptyCommandHandler : RequestHandler<EmptyCommand>
{
protected override void Handle(EmptyCommand request){...}
}
b) La gestion des requêtes:
// can be any other type not necessarily `string`
public class EmptyQuery : IRequest<string>{...}
public class EmptyQueryHandler : IRequestHandler<EmptyQuery, string>
{
public Task<string> Handle(EmptyQuery notification, CancellationToken cancellationToken)
{
return Task.FromResult("Sample response");
}
}
c) L'exemple de classe LogginBehavior
:
public class LoggingBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
{
readonly ILogger<LoggingBehavior<TRequest, TResponse>> _logger;
public LoggingBehavior(ILogger<LoggingBehavior<TRequest, TResponse>> logger)
{
_logger = logger;
}
public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
{
var requestType = typeof(TRequest).Name;
var response = await next();
if (requestType.EndsWith("Command"))
{
_logger.LogInformation($"Command Request: {request}");
}
else if (requestType.EndsWith("Query"))
{
_logger.LogInformation($"Query Request: {request}");
_logger.LogInformation($"Query Response: {response}");
}
else
{
throw new Exception("The request is not the Command or Query type");
}
return response;
}
}
d) Pour enregistrer le LoggingBehavior
ajoutez la commande
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(LoggingBehavior<,>));
au corps de la méthode ConfigureServices
dans Startup.cs.
e) L'exemple de la façon d'exécuter un exemple de commande et de requête:
await _mediator.Send(new EmptyCommand());
var result = await _mediator.Send(new EmptyQuery());
MediatR prend en charge l'envoi de notifications aux gestionnaires génériques ( envoi polymorphe ). Par exemple:
public class GenericHandler<TNotification> : INotificationHandler<TNotification>
where TNotification : INotification
{
public Task Handle(TNotification notification, CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
}
Ce gestionnaire sera appelé pour chaque notification publiée via Publish()
. Il en va de même pour les requêtes (requêtes/commandes). Vous devriez également jeter un œil à comportements .
Si vous utilisez MediatR avec ASP.NET Core, je vous suggère d'utiliser la bibliothèque MediatR.Extensions.Microsoft.DependencyInjection qui prend en charge le câblage de tous les gestionnaires ensemble.