Skip to content

Commit f374a30

Browse files
author
Vadim Belov
committed
Add XML docs, cleanup, and refactor MediatR extensions
- Add comprehensive XML documentation to public APIs, pipeline behaviors, notification publishers, and service registration classes - Replace MediatR.* usings with EasyExtensions.Mediator.* equivalents and clean up namespaces - Refactor code for style consistency: remove unnecessary static lambdas, add braces, and clarify LINQ usage - Improve nullability handling and property initialization - Enhance exception handling and validation in service registration and generic handler logic - Clarify and document service registration process and pipeline behaviors - Remove redundant code and ensure all public APIs are well-documented - Overall, improve maintainability, clarity, and extensibility of the codebase
1 parent 1e978f5 commit f374a30

14 files changed

Lines changed: 285 additions & 73 deletions

Sources/EasyExtensions.Mediator/INotificationPublisher.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System.Collections.Generic;
22
using System.Threading.Tasks;
33
using System.Threading;
4+
using EasyExtensions.Mediator.Contracts;
45

56
namespace EasyExtensions.Mediator
67
{

Sources/EasyExtensions.Mediator/Internal/HandlersOrderer.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,12 @@ public static IList<object> Prioritize<TRequest>(IList<object> handlers, TReques
1515
}
1616

1717
var requestObjectDetails = new ObjectDetails(request);
18-
var handlerObjectsDetails = handlers.Select(static s => new ObjectDetails(s)).ToList();
18+
var handlerObjectsDetails = handlers.Select(s => new ObjectDetails(s)).ToList();
1919

2020
var uniqueHandlers = RemoveOverridden(handlerObjectsDetails).ToArray();
2121
Array.Sort(uniqueHandlers, requestObjectDetails);
2222

23-
return uniqueHandlers.Select(static s => s.Value).ToList();
23+
return uniqueHandlers.Select(s => s.Value).ToList();
2424
}
2525

2626
private static IEnumerable<ObjectDetails> RemoveOverridden(IList<ObjectDetails> handlersData)
@@ -45,7 +45,7 @@ private static IEnumerable<ObjectDetails> RemoveOverridden(IList<ObjectDetails>
4545
}
4646
}
4747

48-
return handlersData.Where(static w => !w.IsOverridden);
48+
return handlersData.Where(w => !w.IsOverridden);
4949
}
5050
}
5151
}

Sources/EasyExtensions.Mediator/MicrosoftExtensionsDI/MediatrServiceConfiguration.cs

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,25 @@
1-
using System;
1+
using EasyExtensions.Mediator;
2+
using EasyExtensions.Mediator.Entities;
3+
using EasyExtensions.Mediator.NotificationPublishers;
4+
using EasyExtensions.Mediator.Pipeline;
5+
using EasyExtensions.Mediator.Registration;
6+
using System;
27
using System.Collections.Generic;
38
using System.Linq;
49
using System.Reflection;
5-
using MediatR;
6-
using MediatR.Entities;
7-
using MediatR.NotificationPublishers;
8-
using MediatR.Pipeline;
9-
using MediatR.Registration;
1010

11+
#pragma warning disable IDE0130 // Namespace does not match folder structure
1112
namespace Microsoft.Extensions.DependencyInjection
13+
#pragma warning restore IDE0130 // Namespace does not match folder structure
1214
{
15+
/// <summary>
16+
/// Provides configuration options for registering MediatR services and behaviors with a dependency injection
17+
/// container.
18+
/// </summary>
19+
/// <remarks>Use this class to customize how MediatR handlers, behaviors, processors, and related services
20+
/// are discovered and registered during application startup. Configuration options include assembly scanning,
21+
/// service lifetimes, notification publishing strategies, and constraints for generic handler registration. Most
22+
/// methods return the current instance to allow fluent configuration.</remarks>
1323
public class MediatRServiceConfiguration
1424
{
1525
/// <summary>
@@ -38,32 +48,32 @@ public class MediatRServiceConfiguration
3848
public ServiceLifetime Lifetime { get; set; } = ServiceLifetime.Transient;
3949

4050
/// <summary>
41-
/// Request exception action processor strategy. Default value is <see cref="DependencyInjection.RequestExceptionActionProcessorStrategy.ApplyForUnhandledExceptions"/>
51+
/// Request exception action processor strategy. Default value is <see cref="RequestExceptionActionProcessorStrategy.ApplyForUnhandledExceptions"/>
4252
/// </summary>
4353
public RequestExceptionActionProcessorStrategy RequestExceptionActionProcessorStrategy { get; set; }
4454
= RequestExceptionActionProcessorStrategy.ApplyForUnhandledExceptions;
4555

46-
internal List<Assembly> AssembliesToRegister { get; } = new();
56+
internal List<Assembly> AssembliesToRegister { get; } = new List<Assembly>();
4757

4858
/// <summary>
4959
/// List of behaviors to register in specific order
5060
/// </summary>
51-
public List<ServiceDescriptor> BehaviorsToRegister { get; } = new();
61+
public List<ServiceDescriptor> BehaviorsToRegister { get; } = new List<ServiceDescriptor>();
5262

5363
/// <summary>
5464
/// List of stream behaviors to register in specific order
5565
/// </summary>
56-
public List<ServiceDescriptor> StreamBehaviorsToRegister { get; } = new();
66+
public List<ServiceDescriptor> StreamBehaviorsToRegister { get; } = new List<ServiceDescriptor>();
5767

5868
/// <summary>
5969
/// List of request pre processors to register in specific order
6070
/// </summary>
61-
public List<ServiceDescriptor> RequestPreProcessorsToRegister { get; } = new();
71+
public List<ServiceDescriptor> RequestPreProcessorsToRegister { get; } = new List<ServiceDescriptor>();
6272

6373
/// <summary>
6474
/// List of request post processors to register in specific order
6575
/// </summary>
66-
public List<ServiceDescriptor> RequestPostProcessorsToRegister { get; } = new();
76+
public List<ServiceDescriptor> RequestPostProcessorsToRegister { get; } = new List<ServiceDescriptor>();
6777

6878
/// <summary>
6979
/// Automatically register processors during assembly scanning
@@ -508,7 +518,5 @@ public MediatRServiceConfiguration AddOpenRequestPostProcessor(Type openBehavior
508518

509519
return this;
510520
}
511-
512-
513521
}
514522
}
Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,24 @@
1-
namespace Microsoft.Extensions.DependencyInjection
1+
#pragma warning disable IDE0130 // Namespace does not match folder structure
2+
namespace Microsoft.Extensions.DependencyInjection
3+
#pragma warning restore IDE0130 // Namespace does not match folder structure
24
{
5+
/// <summary>
6+
/// Specifies the strategy used to determine when to process actions in response to request exceptions.
7+
/// </summary>
8+
/// <remarks>Use this enumeration to control whether exception actions are applied only to unhandled
9+
/// exceptions or to all exceptions encountered during request processing. This can be useful for customizing error
10+
/// handling behavior in request pipelines.</remarks>
311
public enum RequestExceptionActionProcessorStrategy
412
{
13+
/// <summary>
14+
/// Gets or sets a value indicating whether the policy should be applied to unhandled exceptions.
15+
/// </summary>
516
ApplyForUnhandledExceptions,
17+
18+
/// <summary>
19+
/// Gets or sets a value indicating whether the operation should be applied to all exceptions, regardless of
20+
/// type.
21+
/// </summary>
622
ApplyForAllExceptions
723
}
824
}

Sources/EasyExtensions.Mediator/MicrosoftExtensionsDI/ServiceCollectionExtensions.cs

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1+
using EasyExtensions.Mediator;
2+
using EasyExtensions.Mediator.Pipeline;
3+
using EasyExtensions.Mediator.Registration;
14
using System;
25
using System.Linq;
3-
using MediatR;
4-
using MediatR.Pipeline;
5-
using MediatR.Registration;
66

7+
#pragma warning disable IDE0130 // Namespace does not match folder structure
78
namespace Microsoft.Extensions.DependencyInjection
9+
#pragma warning restore IDE0130 // Namespace does not match folder structure
810
{
911
/// <summary>
1012
/// Extensions to scan for MediatR handlers and registers them.
@@ -46,13 +48,9 @@ public static IServiceCollection AddMediatR(this IServiceCollection services,
4648
{
4749
throw new ArgumentException("No assemblies found to scan. Supply at least one assembly to scan for handlers.");
4850
}
49-
5051
ServiceRegistrar.SetGenericRequestHandlerRegistrationLimitations(configuration);
51-
5252
ServiceRegistrar.AddMediatRClassesWithTimeout(services, configuration);
53-
5453
ServiceRegistrar.AddRequiredServices(services, configuration);
55-
5654
return services;
5755
}
5856
}

Sources/EasyExtensions.Mediator/NotificationPublishers/ForeachAwaitPublisher.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,16 @@ namespace EasyExtensions.Mediator.NotificationPublishers
1515
/// </summary>
1616
public class ForeachAwaitPublisher : INotificationPublisher
1717
{
18+
/// <summary>
19+
/// Publishes a notification to all specified notification handler executors asynchronously.
20+
/// </summary>
21+
/// <remarks>Each handler in the collection is invoked in sequence. The operation completes when
22+
/// all handlers have finished processing the notification. If the cancellation token is triggered, the
23+
/// operation may be canceled before all handlers are invoked.</remarks>
24+
/// <param name="handlerExecutors">A collection of notification handler executors that will process the notification. Cannot be null.</param>
25+
/// <param name="notification">The notification to be published to each handler. Cannot be null.</param>
26+
/// <param name="cancellationToken">A cancellation token that can be used to cancel the publish operation.</param>
27+
/// <returns>A task that represents the asynchronous publish operation.</returns>
1828
public async Task Publish(IEnumerable<NotificationHandlerExecutor> handlerExecutors, INotification notification, CancellationToken cancellationToken)
1929
{
2030
foreach (var handler in handlerExecutors)

Sources/EasyExtensions.Mediator/NotificationPublishers/TaskWhenAllPublisher.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,14 @@ namespace EasyExtensions.Mediator.NotificationPublishers
1818
/// </summary>
1919
public class TaskWhenAllPublisher : INotificationPublisher
2020
{
21+
/// <summary>
22+
/// Invokes all notification handler executors for the specified notification and waits for their completion.
23+
/// </summary>
24+
/// <param name="handlerExecutors">A collection of handler executors to be invoked for the notification. Cannot be null.</param>
25+
/// <param name="notification">The notification to be published to each handler. Cannot be null.</param>
26+
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
27+
/// <returns>A task that represents the asynchronous operation. The task completes when all handler executors have
28+
/// finished processing the notification.</returns>
2129
public Task Publish(IEnumerable<NotificationHandlerExecutor> handlerExecutors, INotification notification, CancellationToken cancellationToken)
2230
{
2331
var tasks = handlerExecutors

Sources/EasyExtensions.Mediator/Pipeline/RequestExceptionActionProcessorBehavior.cs

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using EasyExtensions.Mediator.Internal;
12
using Microsoft.Extensions.DependencyInjection;
23
using System;
34
using System.Collections.Generic;
@@ -20,8 +21,26 @@ public class RequestExceptionActionProcessorBehavior<TRequest, TResponse> : IPip
2021
{
2122
private readonly IServiceProvider _serviceProvider;
2223

24+
/// <summary>
25+
/// Initializes a new instance of the RequestExceptionActionProcessorBehavior class using the specified service
26+
/// provider.
27+
/// </summary>
28+
/// <param name="serviceProvider">The service provider used to resolve dependencies required by the behavior. Cannot be null.</param>
2329
public RequestExceptionActionProcessorBehavior(IServiceProvider serviceProvider) => _serviceProvider = serviceProvider;
2430

31+
/// <summary>
32+
/// Handles the specified request by invoking the next handler in the pipeline and applying any registered
33+
/// exception actions if an exception occurs.
34+
/// </summary>
35+
/// <remarks>If an exception is thrown during the execution of the next handler, any registered
36+
/// exception actions for the exception type are invoked before the exception is rethrown. Exception actions are
37+
/// executed in the order they are registered and may themselves throw exceptions.</remarks>
38+
/// <param name="request">The request message to process. Cannot be null.</param>
39+
/// <param name="next">A delegate representing the next handler in the pipeline to invoke. Cannot be null.</param>
40+
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
41+
/// <returns>A task that represents the asynchronous operation. The task result contains the response produced by the
42+
/// handler pipeline.</returns>
43+
/// <exception cref="InvalidOperationException">Thrown if an exception action method cannot be invoked or does not return a valid task.</exception>
2544
public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken cancellationToken)
2645
{
2746
try
@@ -34,9 +53,9 @@ public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TRe
3453

3554
var actionsForException = exceptionTypes
3655
.SelectMany(exceptionType => GetActionsForException(exceptionType, request))
37-
.GroupBy(static actionForException => actionForException.Action.GetType())
38-
.Select(static actionForException => actionForException.First())
39-
.Select(static actionForException => (MethodInfo: GetMethodInfoForAction(actionForException.ExceptionType), actionForException.Action))
56+
.GroupBy(actionForException => actionForException.Action.GetType())
57+
.Select(actionForException => actionForException.First())
58+
.Select(actionForException => (MethodInfo: GetMethodInfoForAction(actionForException.ExceptionType), actionForException.Action))
4059
.ToList();
4160

4261
foreach (var actionForException in actionsForException)

Sources/EasyExtensions.Mediator/Pipeline/RequestExceptionHandlerState.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ public class RequestExceptionHandlerState<TResponse>
1414
/// <summary>
1515
/// The response that is returned if <see cref="Handled"/> is <code>true</code>.
1616
/// </summary>
17-
public TResponse? Response { get; private set; }
17+
public TResponse Response { get; private set; } = default!;
1818

1919
/// <summary>
2020
/// Call to indicate whether the current exception should be considered handled and the specified response should be returned.

Sources/EasyExtensions.Mediator/Pipeline/RequestExceptionProcessorBehavior.cs

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using EasyExtensions.Mediator.Internal;
12
using Microsoft.Extensions.DependencyInjection;
23
using System;
34
using System.Collections.Generic;
@@ -20,8 +21,27 @@ public class RequestExceptionProcessorBehavior<TRequest, TResponse> : IPipelineB
2021
{
2122
private readonly IServiceProvider _serviceProvider;
2223

24+
/// <summary>
25+
/// Initializes a new instance of the RequestExceptionProcessorBehavior class using the specified service
26+
/// provider.
27+
/// </summary>
28+
/// <param name="serviceProvider">The service provider used to resolve dependencies required by the behavior. Cannot be null.</param>
2329
public RequestExceptionProcessorBehavior(IServiceProvider serviceProvider) => _serviceProvider = serviceProvider;
2430

31+
/// <summary>
32+
/// Handles the request by invoking the next handler in the pipeline and processes any exceptions using
33+
/// registered exception handlers.
34+
/// </summary>
35+
/// <remarks>If an exception occurs during request processing, registered exception handlers are
36+
/// invoked in order of specificity. If an exception is handled and a response is provided, that response is
37+
/// returned; otherwise, the original exception is rethrown.</remarks>
38+
/// <param name="request">The request message to be handled. Cannot be null.</param>
39+
/// <param name="next">A delegate representing the next handler in the pipeline. Cannot be null.</param>
40+
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
41+
/// <returns>A task that represents the asynchronous operation. The task result contains the response produced by the
42+
/// handler or an exception handler.</returns>
43+
/// <exception cref="InvalidOperationException">Thrown if an exception handler does not return a Task or if the exception is marked as handled but no
44+
/// response is provided.</exception>
2545
public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken cancellationToken)
2646
{
2747
try
@@ -36,9 +56,9 @@ public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TRe
3656

3757
var handlersForException = exceptionTypes
3858
.SelectMany(exceptionType => GetHandlersForException(exceptionType, request))
39-
.GroupBy(static handlerForException => handlerForException.Handler.GetType())
40-
.Select(static handlerForException => handlerForException.First())
41-
.Select(static handlerForException => (MethodInfo: GetMethodInfoForHandler(handlerForException.ExceptionType), handlerForException.Handler))
59+
.GroupBy(handlerForException => handlerForException.Handler.GetType())
60+
.Select(handlerForException => handlerForException.First())
61+
.Select(handlerForException => (MethodInfo: GetMethodInfoForHandler(handlerForException.ExceptionType), handlerForException.Handler))
4262
.ToList();
4363

4464
foreach (var handlerForException in handlersForException)
@@ -86,21 +106,16 @@ private static IEnumerable<Type> GetExceptionTypes(Type? exceptionType)
86106
{
87107
var exceptionHandlerInterfaceType = typeof(IRequestExceptionHandler<,,>).MakeGenericType(typeof(TRequest), typeof(TResponse), exceptionType);
88108
var enumerableExceptionHandlerInterfaceType = typeof(IEnumerable<>).MakeGenericType(exceptionHandlerInterfaceType);
89-
90109
var exceptionHandlers = (IEnumerable<object>)_serviceProvider.GetRequiredService(enumerableExceptionHandlerInterfaceType);
91-
92110
return HandlersOrderer.Prioritize(exceptionHandlers.ToList(), request)
93111
.Select(handler => (exceptionType, action: handler));
94112
}
95113

96114
private static MethodInfo GetMethodInfoForHandler(Type exceptionType)
97115
{
98116
var exceptionHandlerInterfaceType = typeof(IRequestExceptionHandler<,,>).MakeGenericType(typeof(TRequest), typeof(TResponse), exceptionType);
99-
100-
var handleMethodInfo = exceptionHandlerInterfaceType.GetMethod(nameof(IRequestExceptionHandler<TRequest, TResponse, Exception>.Handle))
101-
?? throw new InvalidOperationException($"Could not find method {nameof(IRequestExceptionHandler<TRequest, TResponse, Exception>.Handle)} on type {exceptionHandlerInterfaceType}");
102-
103-
return handleMethodInfo;
117+
return exceptionHandlerInterfaceType.GetMethod(nameof(IRequestExceptionHandler<TRequest, TResponse, Exception>.Handle))
118+
?? throw new InvalidOperationException($"Could not find method {nameof(IRequestExceptionHandler<TRequest, TResponse, Exception>.Handle)} on type {exceptionHandlerInterfaceType}");
104119
}
105120
}
106121
}

0 commit comments

Comments
 (0)