diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 28cd76b..a8f73bb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,6 +10,8 @@ on: jobs: build: strategy: + matrix: + os: [windows-latest] fail-fast: false runs-on: windows-latest steps: @@ -17,6 +19,10 @@ jobs: uses: actions/checkout@v2 with: fetch-depth: 0 + - name: Setup dotnet 6.0 + uses: actions/setup-dotnet@v1 + with: + dotnet-version: '6.0.x' - name: Build and Test run: ./Build.ps1 shell: pwsh diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f56b564..62859bd 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -7,6 +7,8 @@ on: jobs: build: strategy: + matrix: + os: [windows-latest] fail-fast: false runs-on: windows-latest steps: @@ -14,6 +16,10 @@ jobs: uses: actions/checkout@v2 with: fetch-depth: 0 + - name: Setup dotnet 6.0 + uses: actions/setup-dotnet@v1 + with: + dotnet-version: '6.0.x' - name: Build and Test run: ./Build.ps1 shell: pwsh diff --git a/Directory.Build.props b/Directory.Build.props index b453d01..e8054b8 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,7 +1,8 @@ Jimmy Bogard - latest + 10.0 + enable $(NoWarn);1701;1702;1591 true Apache-2.0 diff --git a/src/MediatR.Extensions.Microsoft.DependencyInjection/MediatR.Extensions.Microsoft.DependencyInjection.csproj b/src/MediatR.Extensions.Microsoft.DependencyInjection/MediatR.Extensions.Microsoft.DependencyInjection.csproj index f439cfd..9c836eb 100644 --- a/src/MediatR.Extensions.Microsoft.DependencyInjection/MediatR.Extensions.Microsoft.DependencyInjection.csproj +++ b/src/MediatR.Extensions.Microsoft.DependencyInjection/MediatR.Extensions.Microsoft.DependencyInjection.csproj @@ -3,8 +3,9 @@ MediatR extensions for ASP.NET Core Copyright Jimmy Bogard - netstandard2.0 + netstandard2.1 MediatR.Extensions.Microsoft.DependencyInjection + MediatR MediatR.Extensions.Microsoft.DependencyInjection mediator;request;response;queries;commands;notifications true @@ -25,10 +26,10 @@ - - - - + + + + diff --git a/src/MediatR.Extensions.Microsoft.DependencyInjection/MediatrServiceConfiguration.cs b/src/MediatR.Extensions.Microsoft.DependencyInjection/MediatrServiceConfiguration.cs index 11dbdc1..a3908ad 100644 --- a/src/MediatR.Extensions.Microsoft.DependencyInjection/MediatrServiceConfiguration.cs +++ b/src/MediatR.Extensions.Microsoft.DependencyInjection/MediatrServiceConfiguration.cs @@ -1,56 +1,49 @@ using Microsoft.Extensions.DependencyInjection; -namespace MediatR +namespace MediatR; + +using System; + +public class MediatRServiceConfiguration { - using System; + public Func TypeEvaluator { get; private set; } = t => true; + public Type MediatorImplementationType { get; private set; } + public ServiceLifetime Lifetime { get; private set; } + public RequestExceptionActionProcessorStrategy RequestExceptionActionProcessorStrategy { get; set; } + + public MediatRServiceConfiguration() + { + MediatorImplementationType = typeof(Mediator); + Lifetime = ServiceLifetime.Transient; + } + + public MediatRServiceConfiguration Using() where TMediator : IMediator + { + MediatorImplementationType = typeof(TMediator); + return this; + } + + public MediatRServiceConfiguration AsSingleton() + { + Lifetime = ServiceLifetime.Singleton; + return this; + } + + public MediatRServiceConfiguration AsScoped() + { + Lifetime = ServiceLifetime.Scoped; + return this; + } - public enum RequestExceptionActionProcessorStrategy + public MediatRServiceConfiguration AsTransient() { - ApplyForUnhandledExceptions, - ApplyForAllExceptions + Lifetime = ServiceLifetime.Transient; + return this; } - public class MediatRServiceConfiguration + public MediatRServiceConfiguration WithEvaluator(Func evaluator) { - public Func TypeEvaluator { get; private set; } = t => true; - public Type MediatorImplementationType { get; private set; } - public ServiceLifetime Lifetime { get; private set; } - public RequestExceptionActionProcessorStrategy RequestExceptionActionProcessorStrategy { get; set; } - - public MediatRServiceConfiguration() - { - MediatorImplementationType = typeof(Mediator); - Lifetime = ServiceLifetime.Transient; - } - - public MediatRServiceConfiguration Using() where TMediator : IMediator - { - MediatorImplementationType = typeof(TMediator); - return this; - } - - public MediatRServiceConfiguration AsSingleton() - { - Lifetime = ServiceLifetime.Singleton; - return this; - } - - public MediatRServiceConfiguration AsScoped() - { - Lifetime = ServiceLifetime.Scoped; - return this; - } - - public MediatRServiceConfiguration AsTransient() - { - Lifetime = ServiceLifetime.Transient; - return this; - } - - public MediatRServiceConfiguration WithEvaluator(Func evaluator) - { - TypeEvaluator = evaluator; - return this; - } + TypeEvaluator = evaluator; + return this; } } \ No newline at end of file diff --git a/src/MediatR.Extensions.Microsoft.DependencyInjection/Registration/ServiceRegistrar.cs b/src/MediatR.Extensions.Microsoft.DependencyInjection/Registration/ServiceRegistrar.cs index 132c662..39bae8c 100644 --- a/src/MediatR.Extensions.Microsoft.DependencyInjection/Registration/ServiceRegistrar.cs +++ b/src/MediatR.Extensions.Microsoft.DependencyInjection/Registration/ServiceRegistrar.cs @@ -6,258 +6,249 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; -namespace MediatR.Registration +namespace MediatR.Registration; + +public static class ServiceRegistrar { - public static class ServiceRegistrar + public static void AddMediatRClasses(IServiceCollection services, IEnumerable assembliesToScan, MediatRServiceConfiguration configuration) { - public static void AddMediatRClasses(IServiceCollection services, IEnumerable assembliesToScan, MediatRServiceConfiguration configuration) - { - assembliesToScan = assembliesToScan.Distinct().ToArray(); + assembliesToScan = assembliesToScan.Distinct().ToArray(); - ConnectImplementationsToTypesClosing(typeof(IRequestHandler<,>), services, assembliesToScan, false, configuration); - ConnectImplementationsToTypesClosing(typeof(INotificationHandler<>), services, assembliesToScan, true, configuration); - ConnectImplementationsToTypesClosing(typeof(IRequestPreProcessor<>), services, assembliesToScan, true, configuration); - ConnectImplementationsToTypesClosing(typeof(IRequestPostProcessor<,>), services, assembliesToScan, true, configuration); - ConnectImplementationsToTypesClosing(typeof(IRequestExceptionHandler<,,>), services, assembliesToScan, true, configuration); - ConnectImplementationsToTypesClosing(typeof(IRequestExceptionAction<,>), services, assembliesToScan, true, configuration); + ConnectImplementationsToTypesClosing(typeof(IRequestHandler<,>), services, assembliesToScan, false, configuration); + ConnectImplementationsToTypesClosing(typeof(INotificationHandler<>), services, assembliesToScan, true, configuration); + ConnectImplementationsToTypesClosing(typeof(IStreamRequestHandler<,>), services, assembliesToScan, false, configuration); + ConnectImplementationsToTypesClosing(typeof(IRequestPreProcessor<>), services, assembliesToScan, true, configuration); + ConnectImplementationsToTypesClosing(typeof(IRequestPostProcessor<,>), services, assembliesToScan, true, configuration); + ConnectImplementationsToTypesClosing(typeof(IRequestExceptionHandler<,,>), services, assembliesToScan, true, configuration); + ConnectImplementationsToTypesClosing(typeof(IRequestExceptionAction<,>), services, assembliesToScan, true, configuration); - var multiOpenInterfaces = new[] - { - typeof(INotificationHandler<>), - typeof(IRequestPreProcessor<>), - typeof(IRequestPostProcessor<,>), - typeof(IRequestExceptionHandler<,,>), - typeof(IRequestExceptionAction<,>) - }; - - foreach (var multiOpenInterface in multiOpenInterfaces) - { - var arity = multiOpenInterface.GetGenericArguments().Length; + var multiOpenInterfaces = new[] + { + typeof(INotificationHandler<>), + typeof(IRequestPreProcessor<>), + typeof(IRequestPostProcessor<,>), + typeof(IRequestExceptionHandler<,,>), + typeof(IRequestExceptionAction<,>) + }; + + foreach (var multiOpenInterface in multiOpenInterfaces) + { + var arity = multiOpenInterface.GetGenericArguments().Length; - var concretions = assembliesToScan - .SelectMany(a => a.DefinedTypes) - .Where(type => type.FindInterfacesThatClose(multiOpenInterface).Any()) - .Where(type => type.IsConcrete() && type.IsOpenGeneric()) - .Where(type => type.GetGenericArguments().Length == arity) - .Where(configuration.TypeEvaluator) - .ToList(); + var concretions = assembliesToScan + .SelectMany(a => a.DefinedTypes) + .Where(type => type.FindInterfacesThatClose(multiOpenInterface).Any()) + .Where(type => type.IsConcrete() && type.IsOpenGeneric()) + .Where(type => type.GetGenericArguments().Length == arity) + .Where(configuration.TypeEvaluator) + .ToList(); - foreach (var type in concretions) - { - services.AddTransient(multiOpenInterface, type); - } + foreach (var type in concretions) + { + services.AddTransient(multiOpenInterface, type); } } + } - /// - /// Helper method use to differentiate behavior between request handlers and notification handlers. - /// Request handlers should only be added once (so set addIfAlreadyExists to false) - /// Notification handlers should all be added (set addIfAlreadyExists to true) - /// - /// - /// - /// - /// - private static void ConnectImplementationsToTypesClosing(Type openRequestInterface, - IServiceCollection services, - IEnumerable assembliesToScan, - bool addIfAlreadyExists, - MediatRServiceConfiguration configuration) + private static void ConnectImplementationsToTypesClosing(Type openRequestInterface, + IServiceCollection services, + IEnumerable assembliesToScan, + bool addIfAlreadyExists, + MediatRServiceConfiguration configuration) + { + var concretions = new List(); + var interfaces = new List(); + foreach (var type in assembliesToScan.SelectMany(a => a.DefinedTypes).Where(t => !t.IsOpenGeneric()).Where(configuration.TypeEvaluator)) { - var concretions = new List(); - var interfaces = new List(); - foreach (var type in assembliesToScan.SelectMany(a => a.DefinedTypes).Where(t => !t.IsOpenGeneric()).Where(configuration.TypeEvaluator)) - { - var interfaceTypes = type.FindInterfacesThatClose(openRequestInterface).ToArray(); - if (!interfaceTypes.Any()) continue; + var interfaceTypes = type.FindInterfacesThatClose(openRequestInterface).ToArray(); + if (!interfaceTypes.Any()) continue; - if (type.IsConcrete()) - { - concretions.Add(type); - } - - foreach (var interfaceType in interfaceTypes) - { - interfaces.Fill(interfaceType); - } + if (type.IsConcrete()) + { + concretions.Add(type); } - foreach (var @interface in interfaces) + foreach (var interfaceType in interfaceTypes) { - var exactMatches = concretions.Where(x => x.CanBeCastTo(@interface)).ToList(); - if (addIfAlreadyExists) - { - foreach (var type in exactMatches) - { - services.AddTransient(@interface, type); - } - } - else - { - if (exactMatches.Count > 1) - { - exactMatches.RemoveAll(m => !IsMatchingWithInterface(m, @interface)); - } - - foreach (var type in exactMatches) - { - services.TryAddTransient(@interface, type); - } - } - - if (!@interface.IsOpenGeneric()) - { - AddConcretionsThatCouldBeClosed(@interface, concretions, services); - } + interfaces.Fill(interfaceType); } } - private static bool IsMatchingWithInterface(Type handlerType, Type handlerInterface) + foreach (var @interface in interfaces) { - if (handlerType == null || handlerInterface == null) + var exactMatches = concretions.Where(x => x.CanBeCastTo(@interface)).ToList(); + if (addIfAlreadyExists) { - return false; - } - - if (handlerType.IsInterface) - { - if (handlerType.GenericTypeArguments.SequenceEqual(handlerInterface.GenericTypeArguments)) + foreach (var type in exactMatches) { - return true; + services.AddTransient(@interface, type); } } else { - return IsMatchingWithInterface(handlerType.GetInterface(handlerInterface.Name), handlerInterface); - } - - return false; - } - - private static void AddConcretionsThatCouldBeClosed(Type @interface, List concretions, IServiceCollection services) - { - foreach (var type in concretions - .Where(x => x.IsOpenGeneric() && x.CouldCloseTo(@interface))) - { - try + if (exactMatches.Count > 1) { - services.TryAddTransient(@interface, type.MakeGenericType(@interface.GenericTypeArguments)); + exactMatches.RemoveAll(m => !IsMatchingWithInterface(m, @interface)); } - catch (Exception) + + foreach (var type in exactMatches) { + services.TryAddTransient(@interface, type); } } - } - private static bool CouldCloseTo(this Type openConcretion, Type closedInterface) - { - var openInterface = closedInterface.GetGenericTypeDefinition(); - var arguments = closedInterface.GenericTypeArguments; - - var concreteArguments = openConcretion.GenericTypeArguments; - return arguments.Length == concreteArguments.Length && openConcretion.CanBeCastTo(openInterface); + if (!@interface.IsOpenGeneric()) + { + AddConcretionsThatCouldBeClosed(@interface, concretions, services); + } } + } - private static bool CanBeCastTo(this Type pluggedType, Type pluginType) + private static bool IsMatchingWithInterface(Type handlerType, Type handlerInterface) + { + if (handlerType == null || handlerInterface == null) { - if (pluggedType == null) return false; - - if (pluggedType == pluginType) return true; - - return pluginType.GetTypeInfo().IsAssignableFrom(pluggedType.GetTypeInfo()); + return false; } - public static bool IsOpenGeneric(this Type type) + if (handlerType.IsInterface) { - return type.GetTypeInfo().IsGenericTypeDefinition || type.GetTypeInfo().ContainsGenericParameters; + if (handlerType.GenericTypeArguments.SequenceEqual(handlerInterface.GenericTypeArguments)) + { + return true; + } } - - public static IEnumerable FindInterfacesThatClose(this Type pluggedType, Type templateType) + else { - return FindInterfacesThatClosesCore(pluggedType, templateType).Distinct(); + return IsMatchingWithInterface(handlerType.GetInterface(handlerInterface.Name), handlerInterface); } - private static IEnumerable FindInterfacesThatClosesCore(Type pluggedType, Type templateType) - { - if (pluggedType == null) yield break; - - if (!pluggedType.IsConcrete()) yield break; + return false; + } - if (templateType.GetTypeInfo().IsInterface) + private static void AddConcretionsThatCouldBeClosed(Type @interface, List concretions, IServiceCollection services) + { + foreach (var type in concretions + .Where(x => x.IsOpenGeneric() && x.CouldCloseTo(@interface))) + { + try { - foreach ( - var interfaceType in - pluggedType.GetInterfaces() - .Where(type => type.GetTypeInfo().IsGenericType && (type.GetGenericTypeDefinition() == templateType))) - { - yield return interfaceType; - } + services.TryAddTransient(@interface, type.MakeGenericType(@interface.GenericTypeArguments)); } - else if (pluggedType.GetTypeInfo().BaseType.GetTypeInfo().IsGenericType && - (pluggedType.GetTypeInfo().BaseType.GetGenericTypeDefinition() == templateType)) + catch (Exception) { - yield return pluggedType.GetTypeInfo().BaseType; } + } + } + + private static bool CouldCloseTo(this Type openConcretion, Type closedInterface) + { + var openInterface = closedInterface.GetGenericTypeDefinition(); + var arguments = closedInterface.GenericTypeArguments; + + var concreteArguments = openConcretion.GenericTypeArguments; + return arguments.Length == concreteArguments.Length && openConcretion.CanBeCastTo(openInterface); + } + + private static bool CanBeCastTo(this Type pluggedType, Type pluginType) + { + if (pluggedType == null) return false; + + if (pluggedType == pluginType) return true; + + return pluginType.GetTypeInfo().IsAssignableFrom(pluggedType.GetTypeInfo()); + } + + private static bool IsOpenGeneric(this Type type) + { + return type.GetTypeInfo().IsGenericTypeDefinition || type.GetTypeInfo().ContainsGenericParameters; + } + + private static IEnumerable FindInterfacesThatClose(this Type pluggedType, Type templateType) + { + return FindInterfacesThatClosesCore(pluggedType, templateType).Distinct(); + } + + private static IEnumerable FindInterfacesThatClosesCore(Type pluggedType, Type templateType) + { + if (pluggedType == null) yield break; - if (pluggedType.GetTypeInfo().BaseType == typeof(object)) yield break; + if (!pluggedType.IsConcrete()) yield break; - foreach (var interfaceType in FindInterfacesThatClosesCore(pluggedType.GetTypeInfo().BaseType, templateType)) + if (templateType.GetTypeInfo().IsInterface) + { + foreach ( + var interfaceType in + pluggedType.GetInterfaces() + .Where(type => type.GetTypeInfo().IsGenericType && (type.GetGenericTypeDefinition() == templateType))) { yield return interfaceType; } } - - private static bool IsConcrete(this Type type) + else if (pluggedType.GetTypeInfo().BaseType.GetTypeInfo().IsGenericType && + (pluggedType.GetTypeInfo().BaseType.GetGenericTypeDefinition() == templateType)) { - return !type.GetTypeInfo().IsAbstract && !type.GetTypeInfo().IsInterface; + yield return pluggedType.GetTypeInfo().BaseType; } - private static void Fill(this IList list, T value) + if (pluggedType.GetTypeInfo().BaseType == typeof(object)) yield break; + + foreach (var interfaceType in FindInterfacesThatClosesCore(pluggedType.GetTypeInfo().BaseType, templateType)) { - if (list.Contains(value)) return; - list.Add(value); + yield return interfaceType; } + } + + private static bool IsConcrete(this Type type) + { + return !type.GetTypeInfo().IsAbstract && !type.GetTypeInfo().IsInterface; + } + + private static void Fill(this IList list, T value) + { + if (list.Contains(value)) return; + list.Add(value); + } - public static void AddRequiredServices(IServiceCollection services, MediatRServiceConfiguration serviceConfiguration) + public static void AddRequiredServices(IServiceCollection services, MediatRServiceConfiguration serviceConfiguration) + { + // Use TryAdd, so any existing ServiceFactory/IMediator registration doesn't get overriden + services.TryAddTransient(p => p.GetRequiredService); + services.TryAdd(new ServiceDescriptor(typeof(IMediator), serviceConfiguration.MediatorImplementationType, serviceConfiguration.Lifetime)); + services.TryAdd(new ServiceDescriptor(typeof(ISender), sp => sp.GetRequiredService(), serviceConfiguration.Lifetime)); + services.TryAdd(new ServiceDescriptor(typeof(IPublisher), sp => sp.GetRequiredService(), serviceConfiguration.Lifetime)); + + // Use TryAddTransientExact (see below), we dó want to register our Pre/Post processor behavior, even if (a more concrete) + // registration for IPipelineBehavior<,> already exists. But only once. + services.TryAddTransientExact(typeof(IPipelineBehavior<,>), typeof(RequestPreProcessorBehavior<,>)); + services.TryAddTransientExact(typeof(IPipelineBehavior<,>), typeof(RequestPostProcessorBehavior<,>)); + + if (serviceConfiguration.RequestExceptionActionProcessorStrategy == RequestExceptionActionProcessorStrategy.ApplyForUnhandledExceptions) { - // Use TryAdd, so any existing ServiceFactory/IMediator registration doesn't get overriden - services.TryAddTransient(p => p.GetService); - services.TryAdd(new ServiceDescriptor(typeof(IMediator), serviceConfiguration.MediatorImplementationType, serviceConfiguration.Lifetime)); - services.TryAdd(new ServiceDescriptor(typeof(ISender), sp => sp.GetService(), serviceConfiguration.Lifetime)); - services.TryAdd(new ServiceDescriptor(typeof(IPublisher), sp => sp.GetService(), serviceConfiguration.Lifetime)); - - // Use TryAddTransientExact (see below), we dó want to register our Pre/Post processor behavior, even if (a more concrete) - // registration for IPipelineBehavior<,> already exists. But only once. - services.TryAddTransientExact(typeof(IPipelineBehavior<,>), typeof(RequestPreProcessorBehavior<,>)); - services.TryAddTransientExact(typeof(IPipelineBehavior<,>), typeof(RequestPostProcessorBehavior<,>)); - - if (serviceConfiguration.RequestExceptionActionProcessorStrategy == RequestExceptionActionProcessorStrategy.ApplyForUnhandledExceptions) - { - services.TryAddTransientExact(typeof(IPipelineBehavior<,>), typeof(RequestExceptionActionProcessorBehavior<,>)); - services.TryAddTransientExact(typeof(IPipelineBehavior<,>), typeof(RequestExceptionProcessorBehavior<,>)); - } - else - { - services.TryAddTransientExact(typeof(IPipelineBehavior<,>), typeof(RequestExceptionProcessorBehavior<,>)); - services.TryAddTransientExact(typeof(IPipelineBehavior<,>), typeof(RequestExceptionActionProcessorBehavior<,>)); - } + services.TryAddTransientExact(typeof(IPipelineBehavior<,>), typeof(RequestExceptionActionProcessorBehavior<,>)); + services.TryAddTransientExact(typeof(IPipelineBehavior<,>), typeof(RequestExceptionProcessorBehavior<,>)); } - - /// - /// Adds a new transient registration to the service collection only when no existing registration of the same service type and implementation type exists. - /// In contrast to TryAddTransient, which only checks the service type. - /// - /// The service collection - /// Service type - /// Implementation type - private static void TryAddTransientExact(this IServiceCollection services, Type serviceType, Type implementationType) + else { - if (services.Any(reg => reg.ServiceType == serviceType && reg.ImplementationType == implementationType)) - { - return; - } + services.TryAddTransientExact(typeof(IPipelineBehavior<,>), typeof(RequestExceptionProcessorBehavior<,>)); + services.TryAddTransientExact(typeof(IPipelineBehavior<,>), typeof(RequestExceptionActionProcessorBehavior<,>)); + } + } - services.AddTransient(serviceType, implementationType); + /// + /// Adds a new transient registration to the service collection only when no existing registration of the same service type and implementation type exists. + /// In contrast to TryAddTransient, which only checks the service type. + /// + /// The service collection + /// Service type + /// Implementation type + private static void TryAddTransientExact(this IServiceCollection services, Type serviceType, Type implementationType) + { + if (services.Any(reg => reg.ServiceType == serviceType && reg.ImplementationType == implementationType)) + { + return; } + + services.AddTransient(serviceType, implementationType); } } \ No newline at end of file diff --git a/src/MediatR.Extensions.Microsoft.DependencyInjection/RequestExceptionActionProcessorStrategy.cs b/src/MediatR.Extensions.Microsoft.DependencyInjection/RequestExceptionActionProcessorStrategy.cs new file mode 100644 index 0000000..fa2c823 --- /dev/null +++ b/src/MediatR.Extensions.Microsoft.DependencyInjection/RequestExceptionActionProcessorStrategy.cs @@ -0,0 +1,7 @@ +namespace MediatR; + +public enum RequestExceptionActionProcessorStrategy +{ + ApplyForUnhandledExceptions, + ApplyForAllExceptions +} \ No newline at end of file diff --git a/src/MediatR.Extensions.Microsoft.DependencyInjection/ServiceCollectionExtensions.cs b/src/MediatR.Extensions.Microsoft.DependencyInjection/ServiceCollectionExtensions.cs index f57708d..69045d4 100644 --- a/src/MediatR.Extensions.Microsoft.DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/MediatR.Extensions.Microsoft.DependencyInjection/ServiceCollectionExtensions.cs @@ -1,94 +1,93 @@ -namespace MediatR -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Reflection; - using Pipeline; - using Registration; - using Microsoft.Extensions.DependencyInjection; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using MediatR.Pipeline; +using MediatR.Registration; +using Microsoft.Extensions.DependencyInjection; + +namespace MediatR; +/// +/// Extensions to scan for MediatR handlers and registers them. +/// - Scans for any handler interface implementations and registers them as +/// - Scans for any and implementations and registers them as transient instances +/// Registers and as transient instances +/// After calling AddMediatR you can use the container to resolve an instance. +/// This does not scan for any instances including and . +/// To register behaviors, use the with the open generic or closed generic types. +/// +public static class ServiceCollectionExtensions +{ /// - /// Extensions to scan for MediatR handlers and registers them. - /// - Scans for any handler interface implementations and registers them as - /// - Scans for any and implementations and registers them as transient instances - /// Registers and as transient instances - /// After calling AddMediatR you can use the container to resolve an instance. - /// This does not scan for any instances including and . - /// To register behaviors, use the with the open generic or closed generic types. + /// Registers handlers and mediator types from the specified assemblies /// - public static class ServiceCollectionExtensions - { - /// - /// Registers handlers and mediator types from the specified assemblies - /// - /// Service collection - /// Assemblies to scan - /// Service collection - public static IServiceCollection AddMediatR(this IServiceCollection services, params Assembly[] assemblies) - => services.AddMediatR(assemblies, configuration: null); + /// Service collection + /// Assemblies to scan + /// Service collection + public static IServiceCollection AddMediatR(this IServiceCollection services, params Assembly[] assemblies) + => services.AddMediatR(assemblies, configuration: null); - /// - /// Registers handlers and mediator types from the specified assemblies - /// - /// Service collection - /// Assemblies to scan - /// The action used to configure the options - /// Service collection - public static IServiceCollection AddMediatR(this IServiceCollection services, Action configuration, params Assembly[] assemblies) - => services.AddMediatR(assemblies, configuration); + /// + /// Registers handlers and mediator types from the specified assemblies + /// + /// Service collection + /// Assemblies to scan + /// The action used to configure the options + /// Service collection + public static IServiceCollection AddMediatR(this IServiceCollection services, Action? configuration, params Assembly[] assemblies) + => services.AddMediatR(assemblies, configuration); - /// - /// Registers handlers and mediator types from the specified assemblies - /// - /// Service collection - /// Assemblies to scan - /// The action used to configure the options - /// Service collection - public static IServiceCollection AddMediatR(this IServiceCollection services, IEnumerable assemblies, Action configuration) + /// + /// Registers handlers and mediator types from the specified assemblies + /// + /// Service collection + /// Assemblies to scan + /// The action used to configure the options + /// Service collection + public static IServiceCollection AddMediatR(this IServiceCollection services, IEnumerable assemblies, Action? configuration) + { + if (!assemblies.Any()) { - if (!assemblies.Any()) - { - throw new ArgumentException("No assemblies found to scan. Supply at least one assembly to scan for handlers."); - } - var serviceConfig = new MediatRServiceConfiguration(); + throw new ArgumentException("No assemblies found to scan. Supply at least one assembly to scan for handlers."); + } + var serviceConfig = new MediatRServiceConfiguration(); - configuration?.Invoke(serviceConfig); + configuration?.Invoke(serviceConfig); - ServiceRegistrar.AddRequiredServices(services, serviceConfig); + ServiceRegistrar.AddRequiredServices(services, serviceConfig); - ServiceRegistrar.AddMediatRClasses(services, assemblies, serviceConfig); + ServiceRegistrar.AddMediatRClasses(services, assemblies, serviceConfig); - return services; - } + return services; + } - /// - /// Registers handlers and mediator types from the assemblies that contain the specified types - /// - /// - /// - /// Service collection - public static IServiceCollection AddMediatR(this IServiceCollection services, params Type[] handlerAssemblyMarkerTypes) - => services.AddMediatR(handlerAssemblyMarkerTypes, configuration: null); + /// + /// Registers handlers and mediator types from the assemblies that contain the specified types + /// + /// + /// + /// Service collection + public static IServiceCollection AddMediatR(this IServiceCollection services, params Type[] handlerAssemblyMarkerTypes) + => services.AddMediatR(handlerAssemblyMarkerTypes, configuration: null); - /// - /// Registers handlers and mediator types from the assemblies that contain the specified types - /// - /// - /// - /// The action used to configure the options - /// Service collection - public static IServiceCollection AddMediatR(this IServiceCollection services, Action configuration, params Type[] handlerAssemblyMarkerTypes) - => services.AddMediatR(handlerAssemblyMarkerTypes, configuration); + /// + /// Registers handlers and mediator types from the assemblies that contain the specified types + /// + /// + /// + /// The action used to configure the options + /// Service collection + public static IServiceCollection AddMediatR(this IServiceCollection services, Action? configuration, params Type[] handlerAssemblyMarkerTypes) + => services.AddMediatR(handlerAssemblyMarkerTypes, configuration); - /// - /// Registers handlers and mediator types from the assemblies that contain the specified types - /// - /// - /// - /// The action used to configure the options - /// Service collection - public static IServiceCollection AddMediatR(this IServiceCollection services, IEnumerable handlerAssemblyMarkerTypes, Action configuration) - => services.AddMediatR(handlerAssemblyMarkerTypes.Select(t => t.GetTypeInfo().Assembly), configuration); - } -} + /// + /// Registers handlers and mediator types from the assemblies that contain the specified types + /// + /// + /// + /// The action used to configure the options + /// Service collection + public static IServiceCollection AddMediatR(this IServiceCollection services, IEnumerable handlerAssemblyMarkerTypes, Action? configuration) + => services.AddMediatR(handlerAssemblyMarkerTypes.Select(t => t.GetTypeInfo().Assembly), configuration); +} \ No newline at end of file diff --git a/src/TestApp/ConstrainedPingedHandler.cs b/src/TestApp/ConstrainedPingedHandler.cs new file mode 100644 index 0000000..9ecf394 --- /dev/null +++ b/src/TestApp/ConstrainedPingedHandler.cs @@ -0,0 +1,22 @@ +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using MediatR; + +namespace TestApp; + +public class ConstrainedPingedHandler : INotificationHandler + where TNotification : Pinged +{ + private readonly TextWriter _writer; + + public ConstrainedPingedHandler(TextWriter writer) + { + _writer = writer; + } + + public Task Handle(TNotification notification, CancellationToken cancellationToken) + { + return _writer.WriteLineAsync("Got pinged constrained async."); + } +} diff --git a/src/TestApp/ConstrainedRequestPostProcessor.cs b/src/TestApp/ConstrainedRequestPostProcessor.cs index 317f1c3..88d19ee 100644 --- a/src/TestApp/ConstrainedRequestPostProcessor.cs +++ b/src/TestApp/ConstrainedRequestPostProcessor.cs @@ -1,23 +1,24 @@ using System.IO; +using System.Threading; using System.Threading.Tasks; +using MediatR; using MediatR.Pipeline; -namespace TestApp +namespace TestApp; + +public class ConstrainedRequestPostProcessor + : IRequestPostProcessor + where TRequest : Ping, IRequest { - //public class ConstrainedRequestPostProcessor - // : IRequestPostProcessor - // where TRequest : Ping - //{ - // private readonly TextWriter _writer; + private readonly TextWriter _writer; - // public ConstrainedRequestPostProcessor(TextWriter writer) - // { - // _writer = writer; - // } + public ConstrainedRequestPostProcessor(TextWriter writer) + { + _writer = writer; + } - // public Task Process(TRequest request, TResponse response) - // { - // return _writer.WriteLineAsync("- All Done with Ping"); - // } - //} + public Task Process(TRequest request, TResponse response, CancellationToken token) + { + return _writer.WriteLineAsync("- All Done with Ping"); + } } \ No newline at end of file diff --git a/src/TestApp/GenericHandler.cs b/src/TestApp/GenericHandler.cs index e21b0d1..450a87c 100644 --- a/src/TestApp/GenericHandler.cs +++ b/src/TestApp/GenericHandler.cs @@ -2,22 +2,21 @@ using System.Threading.Tasks; using MediatR; -namespace TestApp +namespace TestApp; + +using System.IO; + +public class GenericHandler : INotificationHandler { - using System.IO; + private readonly TextWriter _writer; - public class GenericHandler : INotificationHandler + public GenericHandler(TextWriter writer) { - private readonly TextWriter _writer; - - public GenericHandler(TextWriter writer) - { - _writer = writer; - } + _writer = writer; + } - public Task Handle(INotification notification, CancellationToken cancellationToken) - { - return _writer.WriteLineAsync("Got notified."); - } + public Task Handle(INotification notification, CancellationToken cancellationToken) + { + return _writer.WriteLineAsync("Got notified."); } } \ No newline at end of file diff --git a/src/TestApp/GenericPipelineBehavior.cs b/src/TestApp/GenericPipelineBehavior.cs index 25940d7..654aa24 100644 --- a/src/TestApp/GenericPipelineBehavior.cs +++ b/src/TestApp/GenericPipelineBehavior.cs @@ -3,23 +3,23 @@ using System.Threading.Tasks; using MediatR; -namespace TestApp +namespace TestApp; + +public class GenericPipelineBehavior : IPipelineBehavior + where TRequest: IRequest { - public class GenericPipelineBehavior : IPipelineBehavior - { - private readonly TextWriter _writer; + private readonly TextWriter _writer; - public GenericPipelineBehavior(TextWriter writer) - { - _writer = writer; - } + public GenericPipelineBehavior(TextWriter writer) + { + _writer = writer; + } - public async Task Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate next) - { - await _writer.WriteLineAsync("-- Handling Request"); - var response = await next(); - await _writer.WriteLineAsync("-- Finished Request"); - return response; - } + public async Task Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate next) + { + await _writer.WriteLineAsync("-- Handling Request"); + var response = await next(); + await _writer.WriteLineAsync("-- Finished Request"); + return response; } -} +} \ No newline at end of file diff --git a/src/TestApp/GenericRequestPostProcessor.cs b/src/TestApp/GenericRequestPostProcessor.cs index ce7f9ee..d1271a4 100644 --- a/src/TestApp/GenericRequestPostProcessor.cs +++ b/src/TestApp/GenericRequestPostProcessor.cs @@ -1,22 +1,24 @@ using System.IO; using System.Threading; using System.Threading.Tasks; +using MediatR; using MediatR.Pipeline; -namespace TestApp +namespace TestApp; + +public class GenericRequestPostProcessor : IRequestPostProcessor + where TRequest : IRequest + { - public class GenericRequestPostProcessor : IRequestPostProcessor - { - private readonly TextWriter _writer; + private readonly TextWriter _writer; - public GenericRequestPostProcessor(TextWriter writer) - { - _writer = writer; - } + public GenericRequestPostProcessor(TextWriter writer) + { + _writer = writer; + } - public Task Process(TRequest request, TResponse response, CancellationToken cancellationToken) - { - return _writer.WriteLineAsync("- All Done"); - } + public Task Process(TRequest request, TResponse response, CancellationToken cancellationToken) + { + return _writer.WriteLineAsync("- All Done"); } } \ No newline at end of file diff --git a/src/TestApp/GenericRequestPreProcessor.cs b/src/TestApp/GenericRequestPreProcessor.cs index 18f4db5..36ca8ca 100644 --- a/src/TestApp/GenericRequestPreProcessor.cs +++ b/src/TestApp/GenericRequestPreProcessor.cs @@ -3,20 +3,19 @@ using System.Threading.Tasks; using MediatR.Pipeline; -namespace TestApp +namespace TestApp; + +public class GenericRequestPreProcessor : IRequestPreProcessor where TRequest : notnull { - public class GenericRequestPreProcessor : IRequestPreProcessor - { - private readonly TextWriter _writer; + private readonly TextWriter _writer; - public GenericRequestPreProcessor(TextWriter writer) - { - _writer = writer; - } + public GenericRequestPreProcessor(TextWriter writer) + { + _writer = writer; + } - public Task Process(TRequest request, CancellationToken cancellationToken) - { - return _writer.WriteLineAsync("- Starting Up"); - } + public Task Process(TRequest request, CancellationToken cancellationToken) + { + return _writer.WriteLineAsync("- Starting Up"); } } \ No newline at end of file diff --git a/src/TestApp/Jing.cs b/src/TestApp/Jing.cs index 9c5f5ae..5bc4f4e 100644 --- a/src/TestApp/Jing.cs +++ b/src/TestApp/Jing.cs @@ -5,10 +5,9 @@ using System.Threading.Tasks; using MediatR; -namespace TestApp +namespace TestApp; + +public class Jing : IRequest { - public class Jing : IRequest - { - public string Message { get; set; } - } -} + public string? Message { get; set; } +} \ No newline at end of file diff --git a/src/TestApp/JingHandler.cs b/src/TestApp/JingHandler.cs index e24d6a4..7c172f2 100644 --- a/src/TestApp/JingHandler.cs +++ b/src/TestApp/JingHandler.cs @@ -3,20 +3,19 @@ using System.Threading.Tasks; using MediatR; -namespace TestApp +namespace TestApp; + +public class JingHandler : AsyncRequestHandler { - public class JingHandler : AsyncRequestHandler - { - private readonly TextWriter _writer; + private readonly TextWriter _writer; - public JingHandler(TextWriter writer) - { - _writer = writer; - } + public JingHandler(TextWriter writer) + { + _writer = writer; + } - protected override Task Handle(Jing request, CancellationToken cancellationToken) - { - return _writer.WriteLineAsync($"--- Handled Jing: {request.Message}, no Jong"); - } + protected override Task Handle(Jing request, CancellationToken cancellationToken) + { + return _writer.WriteLineAsync($"--- Handled Jing: {request.Message}, no Jong"); } -} +} \ No newline at end of file diff --git a/src/TestApp/Ping.cs b/src/TestApp/Ping.cs index 3b9a69c..99a3c18 100644 --- a/src/TestApp/Ping.cs +++ b/src/TestApp/Ping.cs @@ -1,10 +1,9 @@ using MediatR; -namespace TestApp +namespace TestApp; + +public class Ping : IRequest { - public class Ping : IRequest - { - public string Message { get; set; } - public bool Throw { get; set; } - } + public string? Message { get; set; } + public bool Throw { get; set; } } \ No newline at end of file diff --git a/src/TestApp/PingHandler.cs b/src/TestApp/PingHandler.cs index 529c2f4..359f1b3 100644 --- a/src/TestApp/PingHandler.cs +++ b/src/TestApp/PingHandler.cs @@ -3,29 +3,28 @@ using System.Threading; using MediatR; -namespace TestApp +namespace TestApp; + +using System.Threading.Tasks; + +public class PingHandler : IRequestHandler { - using System.Threading.Tasks; + private readonly TextWriter _writer; - public class PingHandler : IRequestHandler + public PingHandler(TextWriter writer) { - private readonly TextWriter _writer; + _writer = writer; + } - public PingHandler(TextWriter writer) - { - _writer = writer; - } + public async Task Handle(Ping request, CancellationToken cancellationToken) + { + await _writer.WriteLineAsync($"--- Handled Ping: {request.Message}"); - public async Task Handle(Ping request, CancellationToken cancellationToken) + if (request.Throw) { - await _writer.WriteLineAsync($"--- Handled Ping: {request.Message}"); - - if (request.Throw) - { - throw new ApplicationException("Requested to throw"); - } - - return new Pong { Message = request.Message + " Pong" }; + throw new ApplicationException("Requested to throw"); } + + return new Pong { Message = request.Message + " Pong" }; } } \ No newline at end of file diff --git a/src/TestApp/PingPongExceptionHandlers.cs b/src/TestApp/PingPongExceptionHandlers.cs index e9b98a9..4ded800 100644 --- a/src/TestApp/PingPongExceptionHandlers.cs +++ b/src/TestApp/PingPongExceptionHandlers.cs @@ -4,37 +4,34 @@ using System.Threading.Tasks; using MediatR.Pipeline; -namespace TestApp +namespace TestApp; + +public class PingPongExceptionHandlerForType : IRequestExceptionHandler { - public class PingPongExceptionHandlerForType : IRequestExceptionHandler + public Task Handle(Ping request, ApplicationException exception, RequestExceptionHandlerState state, CancellationToken cancellationToken) { - public Task Handle(Ping request, ApplicationException exception, RequestExceptionHandlerState state, CancellationToken cancellationToken) - { - state.SetHandled(new Pong { Message = exception.Message + " Handled by Type" }); + state.SetHandled(new Pong { Message = exception.Message + " Handled by Type" }); - return Task.CompletedTask; - } + return Task.CompletedTask; } +} - public class PingPongExceptionActionForType1 : IRequestExceptionAction - { - private readonly TextWriter _output; - - public PingPongExceptionActionForType1(TextWriter output) => _output = output; - - public Task Execute(Ping request, ApplicationException exception, CancellationToken cancellationToken) - => _output.WriteLineAsync("Logging exception 1"); - } +public class PingPongExceptionActionForType1 : IRequestExceptionAction +{ + private readonly TextWriter _output; - public class PingPongExceptionActionForType2 : IRequestExceptionAction - { - private readonly TextWriter _output; + public PingPongExceptionActionForType1(TextWriter output) => _output = output; - public PingPongExceptionActionForType2(TextWriter output) => _output = output; + public Task Execute(Ping request, ApplicationException exception, CancellationToken cancellationToken) + => _output.WriteLineAsync("Logging exception 1"); +} - public Task Execute(Ping request, ApplicationException exception, CancellationToken cancellationToken) - => _output.WriteLineAsync("Logging exception 2"); - } +public class PingPongExceptionActionForType2 : IRequestExceptionAction +{ + private readonly TextWriter _output; + public PingPongExceptionActionForType2(TextWriter output) => _output = output; + public Task Execute(Ping request, ApplicationException exception, CancellationToken cancellationToken) + => _output.WriteLineAsync("Logging exception 2"); } \ No newline at end of file diff --git a/src/TestApp/Pinged.cs b/src/TestApp/Pinged.cs index 663fe1d..6073628 100644 --- a/src/TestApp/Pinged.cs +++ b/src/TestApp/Pinged.cs @@ -1,9 +1,8 @@ using MediatR; -namespace TestApp +namespace TestApp; + +public class Pinged : INotification { - public class Pinged : INotification - { - } } \ No newline at end of file diff --git a/src/TestApp/PingedHandler.cs b/src/TestApp/PingedHandler.cs index b653f00..f0d849e 100644 --- a/src/TestApp/PingedHandler.cs +++ b/src/TestApp/PingedHandler.cs @@ -1,69 +1,68 @@ using System.Threading; using MediatR; -namespace TestApp -{ - using System.IO; - using System.Threading.Tasks; +namespace TestApp; - public class PingedHandler : INotificationHandler - { - private readonly TextWriter _writer; +using System.IO; +using System.Threading.Tasks; - public PingedHandler(TextWriter writer) - { - _writer = writer; - } +public class PingedHandler : INotificationHandler +{ + private readonly TextWriter _writer; - public Task Handle(Pinged notification, CancellationToken cancellationToken) - { - return _writer.WriteLineAsync("Got pinged async."); - } + public PingedHandler(TextWriter writer) + { + _writer = writer; } - public class PongedHandler : INotificationHandler + public Task Handle(Pinged notification, CancellationToken cancellationToken) { - private readonly TextWriter _writer; + return _writer.WriteLineAsync("Got pinged async."); + } +} - public PongedHandler(TextWriter writer) - { - _writer = writer; - } +public class PongedHandler : INotificationHandler +{ + private readonly TextWriter _writer; - public Task Handle(Ponged notification, CancellationToken cancellationToken) - { - return _writer.WriteLineAsync("Got ponged async."); - } + public PongedHandler(TextWriter writer) + { + _writer = writer; } - //public class ConstrainedPingedHandler : INotificationHandler - // where TNotification : Pinged - //{ - // private readonly TextWriter _writer; + public Task Handle(Ponged notification, CancellationToken cancellationToken) + { + return _writer.WriteLineAsync("Got ponged async."); + } +} - // public ConstrainedPingedHandler(TextWriter writer) - // { - // _writer = writer; - // } +//public class ConstrainedPingedHandler : INotificationHandler +// where TNotification : Pinged +//{ +// private readonly TextWriter _writer; - // public Task Handle(TNotification notification, CancellationToken cancellationToken) - // { - // return _writer.WriteLineAsync("Got pinged constrained async."); - // } - //} +// public ConstrainedPingedHandler(TextWriter writer) +// { +// _writer = writer; +// } - public class PingedAlsoHandler : INotificationHandler - { - private readonly TextWriter _writer; +// public Task Handle(TNotification notification, CancellationToken cancellationToken) +// { +// return _writer.WriteLineAsync("Got pinged constrained async."); +// } +//} - public PingedAlsoHandler(TextWriter writer) - { - _writer = writer; - } +public class PingedAlsoHandler : INotificationHandler +{ + private readonly TextWriter _writer; - public Task Handle(Pinged notification, CancellationToken cancellationToken) - { - return _writer.WriteLineAsync("Got pinged also async."); - } + public PingedAlsoHandler(TextWriter writer) + { + _writer = writer; + } + + public Task Handle(Pinged notification, CancellationToken cancellationToken) + { + return _writer.WriteLineAsync("Got pinged also async."); } } \ No newline at end of file diff --git a/src/TestApp/Pong.cs b/src/TestApp/Pong.cs index ec24766..11e6122 100644 --- a/src/TestApp/Pong.cs +++ b/src/TestApp/Pong.cs @@ -1,7 +1,6 @@ -namespace TestApp +namespace TestApp; + +public class Pong { - public class Pong - { - public string Message { get; set; } - } + public string? Message { get; set; } } \ No newline at end of file diff --git a/src/TestApp/Ponged.cs b/src/TestApp/Ponged.cs index d51725f..0acb5db 100644 --- a/src/TestApp/Ponged.cs +++ b/src/TestApp/Ponged.cs @@ -1,9 +1,8 @@ using MediatR; -namespace TestApp +namespace TestApp; + +public class Ponged : INotification { - public class Ponged : INotification - { - } } \ No newline at end of file diff --git a/src/TestApp/Program.cs b/src/TestApp/Program.cs index d97420a..026d704 100644 --- a/src/TestApp/Program.cs +++ b/src/TestApp/Program.cs @@ -6,45 +6,42 @@ using System.Threading.Tasks; using MediatR.Pipeline; -namespace TestApp -{ +namespace TestApp; - public class Program +public class Program +{ + public static Task Main(string[] args) { - public static Task Main(string[] args) - { - var writer = new WrappingWriter(Console.Out); - var mediator = BuildMediator(writer); - return Runner.Run(mediator, writer, "ASP.NET Core DI"); - } + var writer = new WrappingWriter(Console.Out); + var mediator = BuildMediator(writer); + return Runner.Run(mediator, writer, "ASP.NET Core DI"); + } - private static IMediator BuildMediator(WrappingWriter writer) - { - var services = new ServiceCollection(); + private static IMediator BuildMediator(WrappingWriter writer) + { + var services = new ServiceCollection(); - services.AddScoped(p => p.GetService); + services.AddScoped(p => p.GetRequiredService); - services.AddSingleton(writer); + services.AddSingleton(writer); - //Pipeline + //Pipeline - //This causes a type load exception. https://github.com/jbogard/MediatR.Extensions.Microsoft.DependencyInjection/issues/12 - //services.AddScoped(typeof(IRequestPostProcessor<,>), typeof(ConstrainedRequestPostProcessor<,>)); - //services.AddScoped(typeof(INotificationHandler<>), typeof(ConstrainedPingedHandler<>)); + //This causes a type load exception. https://github.com/jbogard/MediatR.Extensions.Microsoft.DependencyInjection/issues/12 + services.AddScoped(typeof(IRequestPostProcessor<,>), typeof(ConstrainedRequestPostProcessor<,>)); + services.AddScoped(typeof(INotificationHandler<>), typeof(ConstrainedPingedHandler<>)); - services.AddMediatR(typeof(Ping)); + services.AddMediatR(typeof(Ping)); - services.AddScoped(typeof(IPipelineBehavior<,>), typeof(GenericPipelineBehavior<,>)); + services.AddScoped(typeof(IPipelineBehavior<,>), typeof(GenericPipelineBehavior<,>)); - foreach (var service in services) - { - Console.WriteLine(service.ServiceType + " - " + service.ImplementationType); - } + foreach (var service in services) + { + Console.WriteLine(service.ServiceType + " - " + service.ImplementationType); + } - var provider = services.BuildServiceProvider(); + var provider = services.BuildServiceProvider(); - return provider.GetRequiredService(); - } + return provider.GetRequiredService(); } - -} +} \ No newline at end of file diff --git a/src/TestApp/Runner.cs b/src/TestApp/Runner.cs index e0f4f55..e37a2b1 100644 --- a/src/TestApp/Runner.cs +++ b/src/TestApp/Runner.cs @@ -3,130 +3,128 @@ using System.Text; using MediatR; -namespace TestApp -{ - using System.IO; - using System.Threading.Tasks; +namespace TestApp; - public static class Runner - { - public static async Task Run(IMediator mediator, WrappingWriter writer, string projectName) - { - await writer.WriteLineAsync("==============="); - await writer.WriteLineAsync(projectName); - await writer.WriteLineAsync("==============="); - - await writer.WriteLineAsync("Sending Ping..."); - var pong = await mediator.Send(new Ping { Message = "Ping" }); - await writer.WriteLineAsync("Received: " + pong.Message); - - await writer.WriteLineAsync("Publishing Pinged..."); - await mediator.Publish(new Pinged()); - - await writer.WriteLineAsync("Publishing Ponged..."); - var failedPong = false; - try - { - await mediator.Publish(new Ponged()); - } - catch (Exception e) - { - failedPong = true; - await writer.WriteLineAsync(e.ToString()); - } - - bool failedJing = false; - await writer.WriteLineAsync("Sending Jing..."); - try - { - await mediator.Send(new Jing { Message = "Jing" }); - } - catch (Exception e) - { - failedJing = true; - await writer.WriteLineAsync(e.ToString()); - } - - await writer.WriteLineAsync("---------------"); - var contents = writer.Contents; - var order = new[] { - contents.IndexOf("- Starting Up", StringComparison.OrdinalIgnoreCase), - contents.IndexOf("-- Handling Request", StringComparison.OrdinalIgnoreCase), - contents.IndexOf("--- Handled Ping", StringComparison.OrdinalIgnoreCase), - contents.IndexOf("-- Finished Request", StringComparison.OrdinalIgnoreCase), - contents.IndexOf("- All Done", StringComparison.OrdinalIgnoreCase), - contents.IndexOf("- All Done with Ping", StringComparison.OrdinalIgnoreCase), - }; - - var results = new RunResults - { - RequestHandlers = contents.Contains("--- Handled Ping:"), - VoidRequestsHandlers = contents.Contains("--- Handled Jing:"), - PipelineBehaviors = contents.Contains("-- Handling Request"), - RequestPreProcessors = contents.Contains("- Starting Up"), - RequestPostProcessors = contents.Contains("- All Done"), - ConstrainedGenericBehaviors = contents.Contains("- All Done with Ping") && !failedJing, - OrderedPipelineBehaviors = order.SequenceEqual(order.OrderBy(i => i)), - NotificationHandler = contents.Contains("Got pinged async"), - MultipleNotificationHandlers = contents.Contains("Got pinged async") && contents.Contains("Got pinged also async"), - ConstrainedGenericNotificationHandler = contents.Contains("Got pinged constrained async") && !failedPong, - CovariantNotificationHandler = contents.Contains("Got notified") - }; - - await writer.WriteLineAsync($"Request Handler...................{(results.RequestHandlers ? "Y" : "N")}"); - await writer.WriteLineAsync($"Void Request Handler..............{(results.VoidRequestsHandlers ? "Y" : "N")}"); - await writer.WriteLineAsync($"Pipeline Behavior.................{(results.PipelineBehaviors ? "Y" : "N")}"); - await writer.WriteLineAsync($"Pre-Processor.....................{(results.RequestPreProcessors ? "Y" : "N")}"); - await writer.WriteLineAsync($"Post-Processor....................{(results.RequestPostProcessors ? "Y" : "N")}"); - await writer.WriteLineAsync($"Constrained Post-Processor........{(results.ConstrainedGenericBehaviors ? "Y" : "N")}"); - await writer.WriteLineAsync($"Ordered Behaviors.................{(results.OrderedPipelineBehaviors ? "Y" : "N")}"); - await writer.WriteLineAsync($"Notification Handler..............{(results.NotificationHandler ? "Y" : "N")}"); - await writer.WriteLineAsync($"Notification Handlers.............{(results.MultipleNotificationHandlers ? "Y" : "N")}"); - await writer.WriteLineAsync($"Constrained Notification Handler..{(results.ConstrainedGenericNotificationHandler ? "Y" : "N")}"); - await writer.WriteLineAsync($"Covariant Notification Handler....{(results.CovariantNotificationHandler ? "Y" : "N")}"); - } - } +using System.IO; +using System.Threading.Tasks; - public class RunResults +public static class Runner +{ + public static async Task Run(IMediator mediator, WrappingWriter writer, string projectName) { - public bool RequestHandlers { get; set; } - public bool VoidRequestsHandlers { get; set; } - public bool PipelineBehaviors { get; set; } - public bool RequestPreProcessors { get; set; } - public bool RequestPostProcessors { get; set; } - public bool OrderedPipelineBehaviors { get; set; } - public bool ConstrainedGenericBehaviors { get; set; } - public bool NotificationHandler { get; set; } - public bool MultipleNotificationHandlers { get; set; } - public bool CovariantNotificationHandler { get; set; } - public bool ConstrainedGenericNotificationHandler { get; set; } - } + await writer.WriteLineAsync("==============="); + await writer.WriteLineAsync(projectName); + await writer.WriteLineAsync("==============="); - public class WrappingWriter : TextWriter - { - private readonly TextWriter _innerWriter; - private readonly StringBuilder _stringWriter = new StringBuilder(); + await writer.WriteLineAsync("Sending Ping..."); + var pong = await mediator.Send(new Ping { Message = "Ping" }); + await writer.WriteLineAsync("Received: " + pong.Message); + + await writer.WriteLineAsync("Publishing Pinged..."); + await mediator.Publish(new Pinged()); - public WrappingWriter(TextWriter innerWriter) + await writer.WriteLineAsync("Publishing Ponged..."); + var failedPong = false; + try { - _innerWriter = innerWriter; + await mediator.Publish(new Ponged()); } - - public override void Write(char value) + catch (Exception e) { - _stringWriter.Append(value); - _innerWriter.Write(value); + failedPong = true; + await writer.WriteLineAsync(e.ToString()); } - public override Task WriteLineAsync(string value) + bool failedJing = false; + await writer.WriteLineAsync("Sending Jing..."); + try { - _stringWriter.AppendLine(value); - return _innerWriter.WriteLineAsync(value); + await mediator.Send(new Jing { Message = "Jing" }); + } + catch (Exception e) + { + failedJing = true; + await writer.WriteLineAsync(e.ToString()); } - public override Encoding Encoding => _innerWriter.Encoding; + await writer.WriteLineAsync("---------------"); + var contents = writer.Contents; + var order = new[] { + contents.IndexOf("- Starting Up", StringComparison.OrdinalIgnoreCase), + contents.IndexOf("-- Handling Request", StringComparison.OrdinalIgnoreCase), + contents.IndexOf("--- Handled Ping", StringComparison.OrdinalIgnoreCase), + contents.IndexOf("-- Finished Request", StringComparison.OrdinalIgnoreCase), + contents.IndexOf("- All Done", StringComparison.OrdinalIgnoreCase), + contents.IndexOf("- All Done with Ping", StringComparison.OrdinalIgnoreCase), + }; + + var results = new RunResults + { + RequestHandlers = contents.Contains("--- Handled Ping:"), + VoidRequestsHandlers = contents.Contains("--- Handled Jing:"), + PipelineBehaviors = contents.Contains("-- Handling Request"), + RequestPreProcessors = contents.Contains("- Starting Up"), + RequestPostProcessors = contents.Contains("- All Done"), + ConstrainedGenericBehaviors = contents.Contains("- All Done with Ping") && !failedJing, + OrderedPipelineBehaviors = order.SequenceEqual(order.OrderBy(i => i)), + NotificationHandler = contents.Contains("Got pinged async"), + MultipleNotificationHandlers = contents.Contains("Got pinged async") && contents.Contains("Got pinged also async"), + ConstrainedGenericNotificationHandler = contents.Contains("Got pinged constrained async") && !failedPong, + CovariantNotificationHandler = contents.Contains("Got notified") + }; + + await writer.WriteLineAsync($"Request Handler...................{(results.RequestHandlers ? "Y" : "N")}"); + await writer.WriteLineAsync($"Void Request Handler..............{(results.VoidRequestsHandlers ? "Y" : "N")}"); + await writer.WriteLineAsync($"Pipeline Behavior.................{(results.PipelineBehaviors ? "Y" : "N")}"); + await writer.WriteLineAsync($"Pre-Processor.....................{(results.RequestPreProcessors ? "Y" : "N")}"); + await writer.WriteLineAsync($"Post-Processor....................{(results.RequestPostProcessors ? "Y" : "N")}"); + await writer.WriteLineAsync($"Constrained Post-Processor........{(results.ConstrainedGenericBehaviors ? "Y" : "N")}"); + await writer.WriteLineAsync($"Ordered Behaviors.................{(results.OrderedPipelineBehaviors ? "Y" : "N")}"); + await writer.WriteLineAsync($"Notification Handler..............{(results.NotificationHandler ? "Y" : "N")}"); + await writer.WriteLineAsync($"Notification Handlers.............{(results.MultipleNotificationHandlers ? "Y" : "N")}"); + await writer.WriteLineAsync($"Constrained Notification Handler..{(results.ConstrainedGenericNotificationHandler ? "Y" : "N")}"); + await writer.WriteLineAsync($"Covariant Notification Handler....{(results.CovariantNotificationHandler ? "Y" : "N")}"); + } +} + +public class RunResults +{ + public bool RequestHandlers { get; init; } + public bool VoidRequestsHandlers { get; init; } + public bool PipelineBehaviors { get; init; } + public bool RequestPreProcessors { get; init; } + public bool RequestPostProcessors { get; init; } + public bool OrderedPipelineBehaviors { get; init; } + public bool ConstrainedGenericBehaviors { get; init; } + public bool NotificationHandler { get; init; } + public bool MultipleNotificationHandlers { get; init; } + public bool CovariantNotificationHandler { get; init; } + public bool ConstrainedGenericNotificationHandler { get; init; } +} + +public class WrappingWriter : TextWriter +{ + private readonly TextWriter _innerWriter; + private readonly StringBuilder _stringWriter = new(); + + public WrappingWriter(TextWriter innerWriter) + { + _innerWriter = innerWriter; + } + + public override void Write(char value) + { + _stringWriter.Append(value); + _innerWriter.Write(value); + } - public string Contents => _stringWriter.ToString(); + public override Task WriteLineAsync(string? value) + { + _stringWriter.AppendLine(value); + return _innerWriter.WriteLineAsync(value); } + public override Encoding Encoding => _innerWriter.Encoding; + + public string Contents => _stringWriter.ToString(); } \ No newline at end of file diff --git a/src/TestApp/TestApp.csproj b/src/TestApp/TestApp.csproj index 00243b7..2dd5576 100644 --- a/src/TestApp/TestApp.csproj +++ b/src/TestApp/TestApp.csproj @@ -1,7 +1,7 @@  - netcoreapp3.1 + net6.0 Exe @@ -10,11 +10,7 @@ - - - - - + diff --git a/test/MediatR.Extensions.Microsoft.DependencyInjection.Tests/AssemblyResolutionTests.cs b/test/MediatR.Extensions.Microsoft.DependencyInjection.Tests/AssemblyResolutionTests.cs index b5a352d..520c921 100644 --- a/test/MediatR.Extensions.Microsoft.DependencyInjection.Tests/AssemblyResolutionTests.cs +++ b/test/MediatR.Extensions.Microsoft.DependencyInjection.Tests/AssemblyResolutionTests.cs @@ -1,57 +1,62 @@ using Microsoft.Extensions.DependencyInjection; -namespace MediatR.Extensions.Microsoft.DependencyInjection.Tests +namespace MediatR.Extensions.Microsoft.DependencyInjection.Tests; + +using System; +using System.Linq; +using System.Reflection; +using Shouldly; +using Xunit; + +public class AssemblyResolutionTests { - using System; - using System.Linq; - using System.Reflection; - using Shouldly; - using Xunit; + private readonly IServiceProvider _provider; + + public AssemblyResolutionTests() + { + IServiceCollection services = new ServiceCollection(); + services.AddSingleton(new Logger()); + services.AddMediatR(typeof(Ping).GetTypeInfo().Assembly); + _provider = services.BuildServiceProvider(); + } + + [Fact] + public void ShouldResolveMediator() + { + _provider.GetService().ShouldNotBeNull(); + } + + [Fact] + public void ShouldResolveRequestHandler() + { + _provider.GetService>().ShouldNotBeNull(); + } - public class AssemblyResolutionTests + [Fact] + public void ShouldResolveInternalHandler() { - private readonly IServiceProvider _provider; - - public AssemblyResolutionTests() - { - IServiceCollection services = new ServiceCollection(); - services.AddSingleton(new Logger()); - services.AddMediatR(typeof(Ping).GetTypeInfo().Assembly); - _provider = services.BuildServiceProvider(); - } - - [Fact] - public void ShouldResolveMediator() - { - _provider.GetService().ShouldNotBeNull(); - } - - [Fact] - public void ShouldResolveRequestHandler() - { - _provider.GetService>().ShouldNotBeNull(); - } - - [Fact] - public void ShouldResolveInternalHandler() - { - _provider.GetService>().ShouldNotBeNull(); - } - - [Fact] - public void ShouldResolveNotificationHandlers() - { - _provider.GetServices>().Count().ShouldBe(3); - } - - [Fact] - public void ShouldRequireAtLeastOneAssembly() - { - var services = new ServiceCollection(); - - Action registration = () => services.AddMediatR(new Type[0]); - - registration.ShouldThrow(); - } + _provider.GetService>().ShouldNotBeNull(); + } + + [Fact] + public void ShouldResolveNotificationHandlers() + { + _provider.GetServices>().Count().ShouldBe(3); + } + + [Fact] + public void ShouldResolveStreamHandlers() + { + _provider.GetService>().ShouldNotBeNull(); + } + + [Fact] + public void ShouldRequireAtLeastOneAssembly() + { + var services = new ServiceCollection(); + + Action registration = () => services.AddMediatR(new Type[0]); + + registration.ShouldThrow(); } } \ No newline at end of file diff --git a/test/MediatR.Extensions.Microsoft.DependencyInjection.Tests/CustomMediatorTests.cs b/test/MediatR.Extensions.Microsoft.DependencyInjection.Tests/CustomMediatorTests.cs index e1196d8..7859afd 100644 --- a/test/MediatR.Extensions.Microsoft.DependencyInjection.Tests/CustomMediatorTests.cs +++ b/test/MediatR.Extensions.Microsoft.DependencyInjection.Tests/CustomMediatorTests.cs @@ -1,56 +1,55 @@ using Microsoft.Extensions.DependencyInjection; -namespace MediatR.Extensions.Microsoft.DependencyInjection.Tests +namespace MediatR.Extensions.Microsoft.DependencyInjection.Tests; + +using System; +using System.Linq; +using Shouldly; +using Xunit; + +public class CustomMediatorTests { - using System; - using System.Linq; - using Shouldly; - using Xunit; + private readonly IServiceProvider _provider; + + public CustomMediatorTests() + { + IServiceCollection services = new ServiceCollection(); + services.AddSingleton(new Logger()); + services.AddMediatR(cfg => cfg.Using(), typeof(CustomMediatorTests)); + _provider = services.BuildServiceProvider(); + } + + [Fact] + public void ShouldResolveMediator() + { + _provider.GetService().ShouldNotBeNull(); + _provider.GetRequiredService().GetType().ShouldBe(typeof(MyCustomMediator)); + } + + [Fact] + public void ShouldResolveRequestHandler() + { + _provider.GetService>().ShouldNotBeNull(); + } + + [Fact] + public void ShouldResolveNotificationHandlers() + { + _provider.GetServices>().Count().ShouldBe(3); + } - public class CustomMediatorTests + [Fact] + public void Can_Call_AddMediatr_multiple_times() { - private readonly IServiceProvider _provider; - - public CustomMediatorTests() - { - IServiceCollection services = new ServiceCollection(); - services.AddSingleton(new Logger()); - services.AddMediatR(cfg => cfg.Using(), typeof(CustomMediatorTests)); - _provider = services.BuildServiceProvider(); - } - - [Fact] - public void ShouldResolveMediator() - { - _provider.GetService().ShouldNotBeNull(); - _provider.GetService().GetType().ShouldBe(typeof(MyCustomMediator)); - } - - [Fact] - public void ShouldResolveRequestHandler() - { - _provider.GetService>().ShouldNotBeNull(); - } - - [Fact] - public void ShouldResolveNotificationHandlers() - { - _provider.GetServices>().Count().ShouldBe(3); - } - - [Fact] - public void Can_Call_AddMediatr_multiple_times() - { - IServiceCollection services = new ServiceCollection(); - services.AddSingleton(new Logger()); - services.AddMediatR(cfg => cfg.Using(), typeof(CustomMediatorTests)); + IServiceCollection services = new ServiceCollection(); + services.AddSingleton(new Logger()); + services.AddMediatR(cfg => cfg.Using(), typeof(CustomMediatorTests)); - // Call AddMediatr again, this should NOT override our custom mediatr (With MS DI, last registration wins) - services.AddMediatR(typeof(CustomMediatorTests)); + // Call AddMediatr again, this should NOT override our custom mediatr (With MS DI, last registration wins) + services.AddMediatR(typeof(CustomMediatorTests)); - var provider = services.BuildServiceProvider(); - var mediator = provider.GetRequiredService(); - mediator.GetType().ShouldBe(typeof(MyCustomMediator)); - } + var provider = services.BuildServiceProvider(); + var mediator = provider.GetRequiredService(); + mediator.GetType().ShouldBe(typeof(MyCustomMediator)); } } \ No newline at end of file diff --git a/test/MediatR.Extensions.Microsoft.DependencyInjection.Tests/DerivingRequestsTests.cs b/test/MediatR.Extensions.Microsoft.DependencyInjection.Tests/DerivingRequestsTests.cs index 37404ea..6ff2f26 100644 --- a/test/MediatR.Extensions.Microsoft.DependencyInjection.Tests/DerivingRequestsTests.cs +++ b/test/MediatR.Extensions.Microsoft.DependencyInjection.Tests/DerivingRequestsTests.cs @@ -4,34 +4,33 @@ using Shouldly; using Xunit; -namespace MediatR.Extensions.Microsoft.DependencyInjection.Tests +namespace MediatR.Extensions.Microsoft.DependencyInjection.Tests; + +public class DerivingRequestsTests { - public class DerivingRequestsTests - { - private readonly IServiceProvider _provider; - private readonly IMediator _mediator; + private readonly IServiceProvider _provider; + private readonly IMediator _mediator; - public DerivingRequestsTests() - { - IServiceCollection services = new ServiceCollection(); - services.AddSingleton(new Logger()); - services.AddMediatR(typeof(Ping)); - _provider = services.BuildServiceProvider(); - _mediator = _provider.GetRequiredService(); - } + public DerivingRequestsTests() + { + IServiceCollection services = new ServiceCollection(); + services.AddSingleton(new Logger()); + services.AddMediatR(typeof(Ping)); + _provider = services.BuildServiceProvider(); + _mediator = _provider.GetRequiredService(); + } - [Fact] - public async Task ShouldReturnPingPong() - { - Pong pong = await _mediator.Send(new Ping() { Message = "Ping" }); - pong.Message.ShouldBe("Ping Pong"); - } + [Fact] + public async Task ShouldReturnPingPong() + { + Pong pong = await _mediator.Send(new Ping() { Message = "Ping" }); + pong.Message.ShouldBe("Ping Pong"); + } - [Fact] - public async Task ShouldReturnDerivedPingPong() - { - Pong pong = await _mediator.Send(new DerivedPing() { Message = "Ping" }); - pong.Message.ShouldBe("DerivedPing Pong"); - } + [Fact] + public async Task ShouldReturnDerivedPingPong() + { + Pong pong = await _mediator.Send(new DerivedPing() { Message = "Ping" }); + pong.Message.ShouldBe("DerivedPing Pong"); } -} +} \ No newline at end of file diff --git a/test/MediatR.Extensions.Microsoft.DependencyInjection.Tests/DuplicateAssemblyResolutionTests.cs b/test/MediatR.Extensions.Microsoft.DependencyInjection.Tests/DuplicateAssemblyResolutionTests.cs index 0da3fe5..684a709 100644 --- a/test/MediatR.Extensions.Microsoft.DependencyInjection.Tests/DuplicateAssemblyResolutionTests.cs +++ b/test/MediatR.Extensions.Microsoft.DependencyInjection.Tests/DuplicateAssemblyResolutionTests.cs @@ -1,28 +1,27 @@ using Microsoft.Extensions.DependencyInjection; -namespace MediatR.Extensions.Microsoft.DependencyInjection.Tests +namespace MediatR.Extensions.Microsoft.DependencyInjection.Tests; + +using System; +using System.Linq; +using Shouldly; +using Xunit; + +public class DuplicateAssemblyResolutionTests { - using System; - using System.Linq; - using Shouldly; - using Xunit; + private readonly IServiceProvider _provider; - public class DuplicateAssemblyResolutionTests + public DuplicateAssemblyResolutionTests() { - private readonly IServiceProvider _provider; - - public DuplicateAssemblyResolutionTests() - { - IServiceCollection services = new ServiceCollection(); - services.AddSingleton(new Logger()); - services.AddMediatR(typeof(Ping), typeof(Ping)); - _provider = services.BuildServiceProvider(); - } + IServiceCollection services = new ServiceCollection(); + services.AddSingleton(new Logger()); + services.AddMediatR(typeof(Ping), typeof(Ping)); + _provider = services.BuildServiceProvider(); + } - [Fact] - public void ShouldResolveNotificationHandlersOnlyOnce() - { - _provider.GetServices>().Count().ShouldBe(3); - } + [Fact] + public void ShouldResolveNotificationHandlersOnlyOnce() + { + _provider.GetServices>().Count().ShouldBe(3); } } \ No newline at end of file diff --git a/test/MediatR.Extensions.Microsoft.DependencyInjection.Tests/Handlers.cs b/test/MediatR.Extensions.Microsoft.DependencyInjection.Tests/Handlers.cs index 45f7dcb..4e24184 100644 --- a/test/MediatR.Extensions.Microsoft.DependencyInjection.Tests/Handlers.cs +++ b/test/MediatR.Extensions.Microsoft.DependencyInjection.Tests/Handlers.cs @@ -1,4 +1,5 @@ using System; +using System.Runtime.CompilerServices; namespace MediatR.Extensions.Microsoft.DependencyInjection.Tests { @@ -8,8 +9,8 @@ namespace MediatR.Extensions.Microsoft.DependencyInjection.Tests public class Ping : IRequest { - public string Message { get; set; } - public Action ThrowAction { get; set; } + public string? Message { get; init; } + public Action? ThrowAction { get; init; } } public class DerivedPing : Ping @@ -18,22 +19,22 @@ public class DerivedPing : Ping public class Pong { - public string Message { get; set; } + public string? Message { get; init; } } public class Zing : IRequest { - public string Message { get; set; } + public string? Message { get; init; } } public class Zong { - public string Message { get; set; } + public string? Message { get; init; } } public class Ding : IRequest { - public string Message { get; set; } + public string? Message { get; init; } } public class Pinged : INotification @@ -43,6 +44,11 @@ public class Pinged : INotification class InternalPing : IRequest { } + public class StreamPing : IStreamRequest + { + public string? Message { get; init; } + } + public class GenericHandler : INotificationHandler { public Task Handle(INotification notification, CancellationToken cancellationToken) @@ -125,6 +131,22 @@ public Task Handle(Zing message, CancellationToken cancellationToken) } } + public class PingStreamHandler : IStreamRequestHandler + { + private readonly Logger _output; + + public PingStreamHandler(Logger output) + { + _output = output; + } + public async IAsyncEnumerable Handle(StreamPing request, [EnumeratorCancellation] CancellationToken cancellationToken) + { + _output.Messages.Add("Handler"); + yield return await Task.Run(() => new Pong { Message = request.Message + " Pang" }, cancellationToken); + } + } + + public class DuplicateTest : IRequest { } public class DuplicateHandler1 : IRequestHandler { @@ -149,12 +171,23 @@ class InternalPingHandler : IRequestHandler class MyCustomMediator : IMediator { - public Task Send(object request, CancellationToken cancellationToken = new CancellationToken()) + public Task Send(object request, CancellationToken cancellationToken = new()) { throw new System.NotImplementedException(); } - public Task Publish(object notification, CancellationToken cancellationToken = new CancellationToken()) + public IAsyncEnumerable CreateStream(IStreamRequest request, + CancellationToken cancellationToken = new()) + { + throw new NotImplementedException(); + } + + public IAsyncEnumerable CreateStream(object request, CancellationToken cancellationToken = new()) + { + throw new NotImplementedException(); + } + + public Task Publish(object notification, CancellationToken cancellationToken = new()) { throw new System.NotImplementedException(); } @@ -178,13 +211,13 @@ namespace MediatR.Extensions.Microsoft.DependencyInjection.Tests.Included public class Foo : IRequest { - public string Message { get; set; } - public Action ThrowAction { get; set; } + public string? Message { get; init; } + public Action? ThrowAction { get; init; } } public class Bar { - public string Message { get; set; } + public string? Message { get; init; } } public class FooHandler : IRequestHandler diff --git a/test/MediatR.Extensions.Microsoft.DependencyInjection.Tests/MediatR.Extensions.Microsoft.DependencyInjection.Tests.csproj b/test/MediatR.Extensions.Microsoft.DependencyInjection.Tests/MediatR.Extensions.Microsoft.DependencyInjection.Tests.csproj index 35d1e55..47e1601 100644 --- a/test/MediatR.Extensions.Microsoft.DependencyInjection.Tests/MediatR.Extensions.Microsoft.DependencyInjection.Tests.csproj +++ b/test/MediatR.Extensions.Microsoft.DependencyInjection.Tests/MediatR.Extensions.Microsoft.DependencyInjection.Tests.csproj @@ -1,7 +1,7 @@  - net461;netcoreapp3.1 + net6.0 true MediatR.Extensions.Microsoft.DependencyInjection.Tests MediatR.Extensions.Microsoft.DependencyInjection.Tests @@ -16,14 +16,10 @@ - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - + + + + diff --git a/test/MediatR.Extensions.Microsoft.DependencyInjection.Tests/PipeLineMultiCallToConstructorTest.cs b/test/MediatR.Extensions.Microsoft.DependencyInjection.Tests/PipeLineMultiCallToConstructorTest.cs index da7b019..2edfd24 100644 --- a/test/MediatR.Extensions.Microsoft.DependencyInjection.Tests/PipeLineMultiCallToConstructorTest.cs +++ b/test/MediatR.Extensions.Microsoft.DependencyInjection.Tests/PipeLineMultiCallToConstructorTest.cs @@ -1,105 +1,105 @@ using System.Threading; using Microsoft.Extensions.DependencyInjection; -namespace MediatR.Extensions.Microsoft.DependencyInjection.Tests -{ - using System.Reflection; - using System.Threading.Tasks; - using Shouldly; - using Xunit; - - public class PipelineMultiCallToConstructorTests - { - public class ConstructorTestBehavior : IPipelineBehavior - { - private readonly Logger _output; +namespace MediatR.Extensions.Microsoft.DependencyInjection.Tests; - public ConstructorTestBehavior(Logger output) => _output = output; +using System.Reflection; +using System.Threading.Tasks; +using Shouldly; +using Xunit; - public async Task Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate next) - { - _output.Messages.Add("ConstructorTestBehavior before"); - var response = await next(); - _output.Messages.Add("ConstructorTestBehavior after"); +public class PipelineMultiCallToConstructorTests +{ + public class ConstructorTestBehavior : IPipelineBehavior + where TRequest : IRequest + { + private readonly Logger _output; - return response; - } - } + public ConstructorTestBehavior(Logger output) => _output = output; - public class ConstructorTestRequest : IRequest + public async Task Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate next) { - public string Message { get; set; } - } + _output.Messages.Add("ConstructorTestBehavior before"); + var response = await next(); + _output.Messages.Add("ConstructorTestBehavior after"); - public class ConstructorTestResponse - { - public string Message { get; set; } + return response; } + } - public class ConstructorTestHandler : IRequestHandler - { + public class ConstructorTestRequest : IRequest + { + public string? Message { get; set; } + } - private static volatile object _lockObject = new object(); - private readonly Logger _logger; - private static int _constructorCallCount; + public class ConstructorTestResponse + { + public string? Message { get; set; } + } - public static int ConstructorCallCount => _constructorCallCount; + public class ConstructorTestHandler : IRequestHandler + { - public static void ResetCallCount() - { - lock (_lockObject) - { - _constructorCallCount = 0; - } - } + private static volatile object _lockObject = new(); + private readonly Logger _logger; + private static int _constructorCallCount; - public ConstructorTestHandler(Logger logger) + public static int ConstructorCallCount => _constructorCallCount; + + public static void ResetCallCount() + { + lock (_lockObject) { - _logger = logger; - lock (_lockObject) - { - _constructorCallCount++; - } + _constructorCallCount = 0; } + } - public Task Handle(ConstructorTestRequest request, CancellationToken cancellationToken) + public ConstructorTestHandler(Logger logger) + { + _logger = logger; + lock (_lockObject) { - _logger.Messages.Add("Handler"); - return Task.FromResult(new ConstructorTestResponse { Message = request.Message + " ConstructorPong" }); + _constructorCallCount++; } } - [Fact] - public async Task Should_not_call_constructor_multiple_times_when_using_a_pipeline() + public Task Handle(ConstructorTestRequest request, CancellationToken cancellationToken) { - ConstructorTestHandler.ResetCallCount(); - ConstructorTestHandler.ConstructorCallCount.ShouldBe(0); + _logger.Messages.Add("Handler"); + return Task.FromResult(new ConstructorTestResponse { Message = request.Message + " ConstructorPong" }); + } + } - var output = new Logger(); - IServiceCollection services = new ServiceCollection(); + [Fact] + public async Task Should_not_call_constructor_multiple_times_when_using_a_pipeline() + { + ConstructorTestHandler.ResetCallCount(); + ConstructorTestHandler.ConstructorCallCount.ShouldBe(0); - services.AddSingleton(output); - services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ConstructorTestBehavior<,>)); - services.AddMediatR(typeof(Ping).GetTypeInfo().Assembly); - var provider = services.BuildServiceProvider(); + var output = new Logger(); + IServiceCollection services = new ServiceCollection(); - var mediator = provider.GetService(); + services.AddSingleton(output); + services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ConstructorTestBehavior<,>)); + services.AddMediatR(typeof(Ping).GetTypeInfo().Assembly); + var provider = services.BuildServiceProvider(); - var response = await mediator.Send(new ConstructorTestRequest { Message = "ConstructorPing" }); + var mediator = provider.GetRequiredService(); - response.Message.ShouldBe("ConstructorPing ConstructorPong"); + var response = await mediator.Send(new ConstructorTestRequest { Message = "ConstructorPing" }); - output.Messages.ShouldBe(new[] - { - "ConstructorTestBehavior before", - "First pre processor", - "Next pre processor", - "Handler", - "First post processor", - "Next post processor", - "ConstructorTestBehavior after" - }); - ConstructorTestHandler.ConstructorCallCount.ShouldBe(1); - } + response.Message.ShouldBe("ConstructorPing ConstructorPong"); + + output.Messages.ShouldBe(new[] + { + "ConstructorTestBehavior before", + "First pre processor", + "Next pre processor", + "Handler", + "First post processor", + "Next post processor", + "ConstructorTestBehavior after" + }); + ConstructorTestHandler.ConstructorCallCount.ShouldBe(1); } } \ No newline at end of file diff --git a/test/MediatR.Extensions.Microsoft.DependencyInjection.Tests/PipelineTests.cs b/test/MediatR.Extensions.Microsoft.DependencyInjection.Tests/PipelineTests.cs index 9e00ea0..2fef3da 100644 --- a/test/MediatR.Extensions.Microsoft.DependencyInjection.Tests/PipelineTests.cs +++ b/test/MediatR.Extensions.Microsoft.DependencyInjection.Tests/PipelineTests.cs @@ -1,511 +1,526 @@ using Microsoft.Extensions.DependencyInjection; -namespace MediatR.Extensions.Microsoft.DependencyInjection.Tests +namespace MediatR.Extensions.Microsoft.DependencyInjection.Tests; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; +using Pipeline; +using Shouldly; +using Xunit; + +public class PipelineTests { - using System; - using System.Collections.Generic; - using System.Linq; - using System.Reflection; - using System.Threading; - using System.Threading.Tasks; - using Pipeline; - using Shouldly; - using Xunit; - - public class PipelineTests + public class OuterBehavior : IPipelineBehavior { - public class OuterBehavior : IPipelineBehavior - { - private readonly Logger _output; - - public OuterBehavior(Logger output) - { - _output = output; - } + private readonly Logger _output; - public async Task Handle(Ping request, CancellationToken cancellationToken, RequestHandlerDelegate next) - { - _output.Messages.Add("Outer before"); - var response = await next(); - _output.Messages.Add("Outer after"); - - return response; - } + public OuterBehavior(Logger output) + { + _output = output; } - public class InnerBehavior : IPipelineBehavior + public async Task Handle(Ping request, CancellationToken cancellationToken, RequestHandlerDelegate next) { - private readonly Logger _output; + _output.Messages.Add("Outer before"); + var response = await next(); + _output.Messages.Add("Outer after"); - public InnerBehavior(Logger output) - { - _output = output; - } + return response; + } + } - public async Task Handle(Ping request, CancellationToken cancellationToken, RequestHandlerDelegate next) - { - _output.Messages.Add("Inner before"); - var response = await next(); - _output.Messages.Add("Inner after"); + public class InnerBehavior : IPipelineBehavior + { + private readonly Logger _output; - return response; - } + public InnerBehavior(Logger output) + { + _output = output; } - public class InnerBehavior : IPipelineBehavior + public async Task Handle(Ping request, CancellationToken cancellationToken, RequestHandlerDelegate next) { - private readonly Logger _output; + _output.Messages.Add("Inner before"); + var response = await next(); + _output.Messages.Add("Inner after"); - public InnerBehavior(Logger output) - { - _output = output; - } + return response; + } + } - public async Task Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate next) - { - _output.Messages.Add("Inner generic before"); - var response = await next(); - _output.Messages.Add("Inner generic after"); + public class InnerBehavior : IPipelineBehavior + where TRequest : IRequest + { + private readonly Logger _output; - return response; - } + public InnerBehavior(Logger output) + { + _output = output; } - public class OuterBehavior : IPipelineBehavior + public async Task Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate next) { - private readonly Logger _output; + _output.Messages.Add("Inner generic before"); + var response = await next(); + _output.Messages.Add("Inner generic after"); - public OuterBehavior(Logger output) - { - _output = output; - } + return response; + } + } - public async Task Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate next) - { - _output.Messages.Add("Outer generic before"); - var response = await next(); - _output.Messages.Add("Outer generic after"); + public class OuterBehavior : IPipelineBehavior + where TRequest : IRequest + { + private readonly Logger _output; - return response; - } + public OuterBehavior(Logger output) + { + _output = output; } - public class ConstrainedBehavior : IPipelineBehavior - where TRequest : Ping - where TResponse : Pong + public async Task Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate next) { - private readonly Logger _output; + _output.Messages.Add("Outer generic before"); + var response = await next(); + _output.Messages.Add("Outer generic after"); - public ConstrainedBehavior(Logger output) - { - _output = output; - } + return response; + } + } - public async Task Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate next) - { - _output.Messages.Add("Constrained before"); - var response = await next(); - _output.Messages.Add("Constrained after"); + public class ConstrainedBehavior : IPipelineBehavior + where TRequest : Ping, IRequest + where TResponse : Pong + { + private readonly Logger _output; - return response; - } + public ConstrainedBehavior(Logger output) + { + _output = output; } - public class FirstPreProcessor : IRequestPreProcessor + public async Task Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate next) { - private readonly Logger _output; + _output.Messages.Add("Constrained before"); + var response = await next(); + _output.Messages.Add("Constrained after"); - public FirstPreProcessor(Logger output) - { - _output = output; - } - public Task Process(TRequest request, CancellationToken cancellationToken) - { - _output.Messages.Add("First pre processor"); - return Task.FromResult(0); - } + return response; } + } - public class FirstConcretePreProcessor : IRequestPreProcessor - { - private readonly Logger _output; + public class FirstPreProcessor : IRequestPreProcessor where TRequest : notnull + { + private readonly Logger _output; - public FirstConcretePreProcessor(Logger output) - { - _output = output; - } - public Task Process(Ping request, CancellationToken cancellationToken) - { - _output.Messages.Add("First concrete pre processor"); - return Task.FromResult(0); - } + public FirstPreProcessor(Logger output) + { + _output = output; } - - public class NextPreProcessor : IRequestPreProcessor + public Task Process(TRequest request, CancellationToken cancellationToken) { - private readonly Logger _output; - - public NextPreProcessor(Logger output) - { - _output = output; - } - public Task Process(TRequest request, CancellationToken cancellationToken) - { - _output.Messages.Add("Next pre processor"); - return Task.FromResult(0); - } + _output.Messages.Add("First pre processor"); + return Task.FromResult(0); } + } - public class NextConcretePreProcessor : IRequestPreProcessor - { - private readonly Logger _output; + public class FirstConcretePreProcessor : IRequestPreProcessor + { + private readonly Logger _output; - public NextConcretePreProcessor(Logger output) - { - _output = output; - } - public Task Process(Ping request, CancellationToken cancellationToken) - { - _output.Messages.Add("Next concrete pre processor"); - return Task.FromResult(0); - } + public FirstConcretePreProcessor(Logger output) + { + _output = output; } - - public class FirstPostProcessor : IRequestPostProcessor + public Task Process(Ping request, CancellationToken cancellationToken) { - private readonly Logger _output; - - public FirstPostProcessor(Logger output) - { - _output = output; - } - public Task Process(TRequest request, TResponse response, CancellationToken cancellationToken) - { - _output.Messages.Add("First post processor"); - return Task.FromResult(0); - } + _output.Messages.Add("First concrete pre processor"); + return Task.FromResult(0); } + } - public class FirstConcretePostProcessor : IRequestPostProcessor - { - private readonly Logger _output; + public class NextPreProcessor : IRequestPreProcessor where TRequest : notnull + { + private readonly Logger _output; - public FirstConcretePostProcessor(Logger output) - { - _output = output; - } - public Task Process(Ping request, Pong response, CancellationToken cancellationToken) - { - _output.Messages.Add("First concrete post processor"); - return Task.FromResult(0); - } + public NextPreProcessor(Logger output) + { + _output = output; } - - public class NextPostProcessor : IRequestPostProcessor + public Task Process(TRequest request, CancellationToken cancellationToken) { - private readonly Logger _output; - - public NextPostProcessor(Logger output) - { - _output = output; - } - public Task Process(TRequest request, TResponse response, CancellationToken cancellationToken) - { - _output.Messages.Add("Next post processor"); - return Task.FromResult(0); - } + _output.Messages.Add("Next pre processor"); + return Task.FromResult(0); } + } - public class NextConcretePostProcessor : IRequestPostProcessor - { - private readonly Logger _output; + public class NextConcretePreProcessor : IRequestPreProcessor + { + private readonly Logger _output; - public NextConcretePostProcessor(Logger output) - { - _output = output; - } - public Task Process(Ping request, Pong response, CancellationToken cancellationToken) - { - _output.Messages.Add("Next concrete post processor"); - return Task.FromResult(0); - } + public NextConcretePreProcessor(Logger output) + { + _output = output; } - - public class PingPongGenericExceptionAction : IRequestExceptionAction + public Task Process(Ping request, CancellationToken cancellationToken) { - private readonly Logger _output; - - public PingPongGenericExceptionAction(Logger output) => _output = output; + _output.Messages.Add("Next concrete pre processor"); + return Task.FromResult(0); + } + } - public Task Execute(Ping request, Exception exception, CancellationToken cancellationToken) - { - _output.Messages.Add("Logging generic exception"); + public class FirstPostProcessor : IRequestPostProcessor + where TRequest : IRequest + { + private readonly Logger _output; - return Task.CompletedTask; - } + public FirstPostProcessor(Logger output) + { + _output = output; } - - public class PingPongApplicationExceptionAction : IRequestExceptionAction + public Task Process(TRequest request, TResponse response, CancellationToken cancellationToken) { - private readonly Logger _output; + _output.Messages.Add("First post processor"); + return Task.FromResult(0); + } + } - public PingPongApplicationExceptionAction(Logger output) => _output = output; + public class FirstConcretePostProcessor : IRequestPostProcessor + { + private readonly Logger _output; - public Task Execute(Ping request, ApplicationException exception, CancellationToken cancellationToken) - { - _output.Messages.Add("Logging ApplicationException exception"); + public FirstConcretePostProcessor(Logger output) + { + _output = output; + } + public Task Process(Ping request, Pong response, CancellationToken cancellationToken) + { + _output.Messages.Add("First concrete post processor"); + return Task.FromResult(0); + } + } - return Task.CompletedTask; - } + public class NextPostProcessor : IRequestPostProcessor + where TRequest : IRequest + { + private readonly Logger _output; + + public NextPostProcessor(Logger output) + { + _output = output; + } + public Task Process(TRequest request, TResponse response, CancellationToken cancellationToken) + { + _output.Messages.Add("Next post processor"); + return Task.FromResult(0); } + } + + public class NextConcretePostProcessor : IRequestPostProcessor + { + private readonly Logger _output; - public class PingPongExceptionActionForType1 : IRequestExceptionAction + public NextConcretePostProcessor(Logger output) { - private readonly Logger _output; + _output = output; + } + public Task Process(Ping request, Pong response, CancellationToken cancellationToken) + { + _output.Messages.Add("Next concrete post processor"); + return Task.FromResult(0); + } + } - public PingPongExceptionActionForType1(Logger output) => _output = output; + public class PingPongGenericExceptionAction : IRequestExceptionAction + { + private readonly Logger _output; + + public PingPongGenericExceptionAction(Logger output) => _output = output; - public Task Execute(Ping request, SystemException exception, CancellationToken cancellationToken) - { - _output.Messages.Add("Logging exception 1"); + public Task Execute(Ping request, Exception exception, CancellationToken cancellationToken) + { + _output.Messages.Add("Logging generic exception"); - return Task.CompletedTask; - } + return Task.CompletedTask; } + } - public class PingPongExceptionActionForType2 : IRequestExceptionAction - { - private readonly Logger _output; + public class PingPongApplicationExceptionAction : IRequestExceptionAction + { + private readonly Logger _output; - public PingPongExceptionActionForType2(Logger output) => _output = output; + public PingPongApplicationExceptionAction(Logger output) => _output = output; - public Task Execute(Ping request, SystemException exception, CancellationToken cancellationToken) - { - _output.Messages.Add("Logging exception 2"); + public Task Execute(Ping request, ApplicationException exception, CancellationToken cancellationToken) + { + _output.Messages.Add("Logging ApplicationException exception"); - return Task.CompletedTask; - } + return Task.CompletedTask; } + } - public class PingPongExceptionHandlerForType : IRequestExceptionHandler + public class PingPongExceptionActionForType1 : IRequestExceptionAction + { + private readonly Logger _output; + + public PingPongExceptionActionForType1(Logger output) => _output = output; + + public Task Execute(Ping request, SystemException exception, CancellationToken cancellationToken) { - public Task Handle(Ping request, ApplicationException exception, RequestExceptionHandlerState state, CancellationToken cancellationToken) - { - state.SetHandled(new Pong { Message = exception.Message + " Handled by Specific Type" }); + _output.Messages.Add("Logging exception 1"); - return Task.CompletedTask; - } + return Task.CompletedTask; } + } - public class PingPongGenericExceptionHandler : IRequestExceptionHandler - { - private readonly Logger _output; + public class PingPongExceptionActionForType2 : IRequestExceptionAction + { + private readonly Logger _output; - public PingPongGenericExceptionHandler(Logger output) => _output = output; + public PingPongExceptionActionForType2(Logger output) => _output = output; - public Task Handle(Ping request, Exception exception, RequestExceptionHandlerState state, CancellationToken cancellationToken) - { - _output.Messages.Add(exception.Message + " Logged by Generic Type"); + public Task Execute(Ping request, SystemException exception, CancellationToken cancellationToken) + { + _output.Messages.Add("Logging exception 2"); - return Task.CompletedTask; - } + return Task.CompletedTask; } + } - [Fact] - public async Task Should_wrap_with_behavior() + public class PingPongExceptionHandlerForType : IRequestExceptionHandler + { + public Task Handle(Ping request, ApplicationException exception, RequestExceptionHandlerState state, CancellationToken cancellationToken) { - var output = new Logger(); - IServiceCollection services = new ServiceCollection(); - services.AddSingleton(output); - services.AddTransient, OuterBehavior>(); - services.AddTransient, InnerBehavior>(); - services.AddMediatR(typeof(Ping).GetTypeInfo().Assembly); - var provider = services.BuildServiceProvider(); + state.SetHandled(new Pong { Message = exception.Message + " Handled by Specific Type" }); - var mediator = provider.GetService(); + return Task.CompletedTask; + } + } - var response = await mediator.Send(new Ping { Message = "Ping" }); + public class PingPongGenericExceptionHandler : IRequestExceptionHandler + { + private readonly Logger _output; - response.Message.ShouldBe("Ping Pong"); + public PingPongGenericExceptionHandler(Logger output) => _output = output; - output.Messages.ShouldBe(new[] - { - "Outer before", - "Inner before", - "First concrete pre processor", - "Next concrete pre processor", - "First pre processor", - "Next pre processor", - "Handler", - "First concrete post processor", - "Next concrete post processor", - "First post processor", - "Next post processor", - "Inner after", - "Outer after" - }); - } + public Task Handle(Ping request, Exception exception, RequestExceptionHandlerState state, CancellationToken cancellationToken) + { + _output.Messages.Add(exception.Message + " Logged by Generic Type"); + return Task.CompletedTask; + } + } - [Fact] - public async Task Should_wrap_generics_with_behavior() - { - var output = new Logger(); - IServiceCollection services = new ServiceCollection(); - services.AddSingleton(output); - services.AddTransient(typeof(IPipelineBehavior<,>), typeof(OuterBehavior<,>)); - services.AddTransient(typeof(IPipelineBehavior<,>), typeof(InnerBehavior<,>)); - services.AddMediatR(typeof(Ping).GetTypeInfo().Assembly); - var provider = services.BuildServiceProvider(); + [Fact] + public async Task Should_wrap_with_behavior() + { + var output = new Logger(); + IServiceCollection services = new ServiceCollection(); + services.AddSingleton(output); + services.AddTransient, OuterBehavior>(); + services.AddTransient, InnerBehavior>(); + services.AddMediatR(typeof(Ping).GetTypeInfo().Assembly); + var provider = services.BuildServiceProvider(); - var mediator = provider.GetService(); - - var response = await mediator.Send(new Ping { Message = "Ping" }); + var mediator = provider.GetRequiredService(); - response.Message.ShouldBe("Ping Pong"); + var response = await mediator.Send(new Ping { Message = "Ping" }); - output.Messages.ShouldBe(new[] - { - "Outer generic before", - "Inner generic before", - "First concrete pre processor", - "Next concrete pre processor", - "First pre processor", - "Next pre processor", - "Handler", - "First concrete post processor", - "Next concrete post processor", - "First post processor", - "Next post processor", - "Inner generic after", - "Outer generic after", - }); - } + response.Message.ShouldBe("Ping Pong"); - [Fact] - public async Task Should_pick_up_pre_and_post_processors() + output.Messages.ShouldBe(new[] { - var output = new Logger(); - IServiceCollection services = new ServiceCollection(); - services.AddSingleton(output); - services.AddMediatR(typeof(Ping).GetTypeInfo().Assembly); - var provider = services.BuildServiceProvider(); - - var mediator = provider.GetService(); + "Outer before", + "Inner before", + "First concrete pre processor", + "Next concrete pre processor", + "First pre processor", + "Next pre processor", + "Handler", + "First concrete post processor", + "Next concrete post processor", + "First post processor", + "Next post processor", + "Inner after", + "Outer after" + }); + } - var response = await mediator.Send(new Ping { Message = "Ping" }); - response.Message.ShouldBe("Ping Pong"); + [Fact] + public async Task Should_wrap_generics_with_behavior() + { + var output = new Logger(); + IServiceCollection services = new ServiceCollection(); + services.AddSingleton(output); + services.AddTransient(typeof(IPipelineBehavior<,>), typeof(OuterBehavior<,>)); + services.AddTransient(typeof(IPipelineBehavior<,>), typeof(InnerBehavior<,>)); + services.AddMediatR(typeof(Ping).GetTypeInfo().Assembly); + var provider = services.BuildServiceProvider(); + + var mediator = provider.GetRequiredService(); + + var response = await mediator.Send(new Ping { Message = "Ping" }); - output.Messages.ShouldBe(new[] - { - "First concrete pre processor", - "Next concrete pre processor", - "First pre processor", - "Next pre processor", - "Handler", - "First concrete post processor", - "Next concrete post processor", - "First post processor", - "Next post processor", - }); - } + response.Message.ShouldBe("Ping Pong"); - [Fact] - public async Task Should_pick_up_specific_exception_behaviors() + output.Messages.ShouldBe(new[] { - var output = new Logger(); - IServiceCollection services = new ServiceCollection(); - services.AddSingleton(output); - services.AddMediatR(typeof(Ping).GetTypeInfo().Assembly); - var provider = services.BuildServiceProvider(); + "Outer generic before", + "Inner generic before", + "First concrete pre processor", + "Next concrete pre processor", + "First pre processor", + "Next pre processor", + "Handler", + "First concrete post processor", + "Next concrete post processor", + "First post processor", + "Next post processor", + "Inner generic after", + "Outer generic after", + }); + } - var mediator = provider.GetService(); + [Fact] + public async Task Should_pick_up_pre_and_post_processors() + { + var output = new Logger(); + IServiceCollection services = new ServiceCollection(); + services.AddSingleton(output); + services.AddMediatR(typeof(Ping).GetTypeInfo().Assembly); + var provider = services.BuildServiceProvider(); - var response = await mediator.Send(new Ping {Message = "Ping", ThrowAction = msg => throw new ApplicationException(msg.Message + " Thrown")}); + var mediator = provider.GetRequiredService(); - response.Message.ShouldBe("Ping Thrown Handled by Specific Type"); - output.Messages.ShouldNotContain("Logging ApplicationException exception"); - } + var response = await mediator.Send(new Ping { Message = "Ping" }); + + response.Message.ShouldBe("Ping Pong"); - [Fact] - public void Should_pick_up_base_exception_behaviors() + output.Messages.ShouldBe(new[] { - var output = new Logger(); - IServiceCollection services = new ServiceCollection(); - services.AddSingleton(output); - services.AddMediatR(typeof(Ping).GetTypeInfo().Assembly); - var provider = services.BuildServiceProvider(); + "First concrete pre processor", + "Next concrete pre processor", + "First pre processor", + "Next pre processor", + "Handler", + "First concrete post processor", + "Next concrete post processor", + "First post processor", + "Next post processor", + }); + } - var mediator = provider.GetService(); + [Fact] + public async Task Should_pick_up_specific_exception_behaviors() + { + var output = new Logger(); + IServiceCollection services = new ServiceCollection(); + services.AddSingleton(output); + services.AddMediatR(typeof(Ping).GetTypeInfo().Assembly); + var provider = services.BuildServiceProvider(); - Should.Throw(async () => await mediator.Send(new Ping {Message = "Ping", ThrowAction = msg => throw new Exception(msg.Message + " Thrown")})); + var mediator = provider.GetRequiredService(); - output.Messages.ShouldContain("Ping Thrown Logged by Generic Type"); - output.Messages.ShouldContain("Logging generic exception"); - } + var response = await mediator.Send(new Ping {Message = "Ping", ThrowAction = msg => throw new ApplicationException(msg.Message + " Thrown")}); - [Fact] - public void Should_pick_up_exception_actions() - { - var output = new Logger(); - IServiceCollection services = new ServiceCollection(); - services.AddSingleton(output); - services.AddMediatR(typeof(Ping).GetTypeInfo().Assembly); - var provider = services.BuildServiceProvider(); + response.Message.ShouldBe("Ping Thrown Handled by Specific Type"); + output.Messages.ShouldNotContain("Logging ApplicationException exception"); + } - var mediator = provider.GetService(); + [Fact] + public void Should_pick_up_base_exception_behaviors() + { + var output = new Logger(); + IServiceCollection services = new ServiceCollection(); + services.AddSingleton(output); + services.AddMediatR(typeof(Ping).GetTypeInfo().Assembly); + var provider = services.BuildServiceProvider(); - Should.Throw(async () => await mediator.Send(new Ping {Message = "Ping", ThrowAction = msg => throw new SystemException(msg.Message + " Thrown")})); + var mediator = provider.GetRequiredService(); - output.Messages.ShouldContain("Logging exception 1"); - output.Messages.ShouldContain("Logging exception 2"); - } + Should.Throw(async () => await mediator.Send(new Ping {Message = "Ping", ThrowAction = msg => throw new Exception(msg.Message + " Thrown")})); - [Fact(Skip = "MS DI does not support constrained generics yet, see https://github.com/aspnet/DependencyInjection/issues/471")] - public async Task Should_handle_constrained_generics() - { - var output = new Logger(); - IServiceCollection services = new ServiceCollection(); - services.AddSingleton(output); - services.AddTransient(typeof(IPipelineBehavior<,>), typeof(OuterBehavior<,>)); - services.AddTransient(typeof(IPipelineBehavior<,>), typeof(InnerBehavior<,>)); - services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ConstrainedBehavior<,>)); - services.AddMediatR(typeof(Ping).GetTypeInfo().Assembly); - var provider = services.BuildServiceProvider(); + output.Messages.ShouldContain("Ping Thrown Logged by Generic Type"); + output.Messages.ShouldContain("Logging generic exception"); + } - var mediator = provider.GetService(); + [Fact] + public void Should_pick_up_exception_actions() + { + var output = new Logger(); + IServiceCollection services = new ServiceCollection(); + services.AddSingleton(output); + services.AddMediatR(typeof(Ping).GetTypeInfo().Assembly); + var provider = services.BuildServiceProvider(); - var response = await mediator.Send(new Ping { Message = "Ping" }); + var mediator = provider.GetRequiredService(); - response.Message.ShouldBe("Ping Pong"); + Should.Throw(async () => await mediator.Send(new Ping {Message = "Ping", ThrowAction = msg => throw new SystemException(msg.Message + " Thrown")})); - output.Messages.ShouldBe(new[] - { - "Outer generic before", - "Inner generic before", - "Constrained before", - "Handler", - "Constrained after", - "Inner generic after", - "Outer generic after", - }); + output.Messages.ShouldContain("Logging exception 1"); + output.Messages.ShouldContain("Logging exception 2"); + } - output.Messages.Clear(); + [Fact] + public async Task Should_handle_constrained_generics() + { + var output = new Logger(); + IServiceCollection services = new ServiceCollection(); + services.AddSingleton(output); + services.AddTransient(typeof(IPipelineBehavior<,>), typeof(OuterBehavior<,>)); + services.AddTransient(typeof(IPipelineBehavior<,>), typeof(InnerBehavior<,>)); + services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ConstrainedBehavior<,>)); + services.AddMediatR(typeof(Ping).GetTypeInfo().Assembly); + var provider = services.BuildServiceProvider(); - var zingResponse = await mediator.Send(new Zing { Message = "Zing" }); + var mediator = provider.GetRequiredService(); - zingResponse.Message.ShouldBe("Zing Zong"); + var response = await mediator.Send(new Ping { Message = "Ping" }); - output.Messages.ShouldBe(new[] - { - "Outer generic before", - "Inner generic before", - "Handler", - "Inner generic after", - "Outer generic after", - }); - } + response.Message.ShouldBe("Ping Pong"); + output.Messages.ShouldBe(new[] + { + "Outer generic before", + "Inner generic before", + "Constrained before", + "First concrete pre processor", + "Next concrete pre processor", + "First pre processor", + "Next pre processor", + "Handler", + "First concrete post processor", + "Next concrete post processor", + "First post processor", + "Next post processor", + "Constrained after", + "Inner generic after", + "Outer generic after" + }); + + output.Messages.Clear(); + + var zingResponse = await mediator.Send(new Zing { Message = "Zing" }); + + zingResponse.Message.ShouldBe("Zing Zong"); + + output.Messages.ShouldBe(new[] + { + "Outer generic before", + "Inner generic before", + "First pre processor", + "Next pre processor", + "Handler", + "First post processor", + "Next post processor", + "Inner generic after", + "Outer generic after" + }); } + } \ No newline at end of file diff --git a/test/MediatR.Extensions.Microsoft.DependencyInjection.Tests/StreamPipelineTests.cs b/test/MediatR.Extensions.Microsoft.DependencyInjection.Tests/StreamPipelineTests.cs new file mode 100644 index 0000000..ad60424 --- /dev/null +++ b/test/MediatR.Extensions.Microsoft.DependencyInjection.Tests/StreamPipelineTests.cs @@ -0,0 +1,87 @@ +using System.Runtime.CompilerServices; +using Microsoft.Extensions.DependencyInjection; + +namespace MediatR.Extensions.Microsoft.DependencyInjection.Tests; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; +using Pipeline; +using Shouldly; +using Xunit; + +public class StreamPipelineTests +{ + public class OuterBehavior : IStreamPipelineBehavior + { + private readonly Logger _output; + + public OuterBehavior(Logger output) + { + _output = output; + } + + public async IAsyncEnumerable Handle(StreamPing request, [EnumeratorCancellation] CancellationToken cancellationToken, StreamHandlerDelegate next) + { + _output.Messages.Add("Outer before"); + await foreach (var response in next().WithCancellation(cancellationToken)) + { + yield return response; + } + _output.Messages.Add("Outer after"); + } + } + + public class InnerBehavior : IStreamPipelineBehavior + { + private readonly Logger _output; + + public InnerBehavior(Logger output) + { + _output = output; + } + + public async IAsyncEnumerable Handle(StreamPing request, [EnumeratorCancellation] CancellationToken cancellationToken, StreamHandlerDelegate next) + { + _output.Messages.Add("Inner before"); + await foreach (var response in next().WithCancellation(cancellationToken)) + { + yield return response; + } + _output.Messages.Add("Inner after"); + } + } + + [Fact] + public async Task Should_wrap_with_behavior() + { + var output = new Logger(); + IServiceCollection services = new ServiceCollection(); + services.AddSingleton(output); + services.AddTransient, OuterBehavior>(); + services.AddTransient, InnerBehavior>(); + services.AddMediatR(typeof(Ping).GetTypeInfo().Assembly); + var provider = services.BuildServiceProvider(); + + var mediator = provider.GetRequiredService(); + + var stream = mediator.CreateStream(new StreamPing { Message = "Ping" }); + + await foreach (var response in stream) + { + response.Message.ShouldBe("Ping Pang"); + } + + output.Messages.ShouldBe(new[] + { + "Outer before", + "Inner before", + "Handler", + "Inner after", + "Outer after" + }); + } +} \ No newline at end of file diff --git a/test/MediatR.Extensions.Microsoft.DependencyInjection.Tests/TypeEvaluatorTests.cs b/test/MediatR.Extensions.Microsoft.DependencyInjection.Tests/TypeEvaluatorTests.cs index cfc4f03..ff90107 100644 --- a/test/MediatR.Extensions.Microsoft.DependencyInjection.Tests/TypeEvaluatorTests.cs +++ b/test/MediatR.Extensions.Microsoft.DependencyInjection.Tests/TypeEvaluatorTests.cs @@ -1,39 +1,38 @@ using Microsoft.Extensions.DependencyInjection; -namespace MediatR.Extensions.Microsoft.DependencyInjection.Tests +namespace MediatR.Extensions.Microsoft.DependencyInjection.Tests; + +using MediatR.Extensions.Microsoft.DependencyInjection.Tests.Included; +using Shouldly; +using System; +using System.Reflection; +using Xunit; + +public class TypeEvaluatorTests { - using MediatR.Extensions.Microsoft.DependencyInjection.Tests.Included; - using Shouldly; - using System; - using System.Reflection; - using Xunit; + private readonly IServiceProvider _provider; - public class TypeEvaluatorTests + public TypeEvaluatorTests() { - private readonly IServiceProvider _provider; - - public TypeEvaluatorTests() + IServiceCollection services = new ServiceCollection(); + services.AddSingleton(new Logger()); + services.AddMediatR(new[] { typeof(Ping).GetTypeInfo().Assembly }, cfg => { - IServiceCollection services = new ServiceCollection(); - services.AddSingleton(new Logger()); - services.AddMediatR(new[] { typeof(Ping).GetTypeInfo().Assembly }, cfg => - { - cfg.WithEvaluator(t => t.Namespace == "MediatR.Extensions.Microsoft.DependencyInjection.Tests.Included"); - }); - _provider = services.BuildServiceProvider(); - } + cfg.WithEvaluator(t => t.Namespace == "MediatR.Extensions.Microsoft.DependencyInjection.Tests.Included"); + }); + _provider = services.BuildServiceProvider(); + } - [Fact] - public void ShouldResolveMediator() - { - _provider.GetService().ShouldNotBeNull(); - } + [Fact] + public void ShouldResolveMediator() + { + _provider.GetService().ShouldNotBeNull(); + } - [Fact] - public void ShouldOnlyResolveIncludedRequestHandlers() - { - _provider.GetService>().ShouldNotBeNull(); - _provider.GetService>().ShouldBeNull(); - } + [Fact] + public void ShouldOnlyResolveIncludedRequestHandlers() + { + _provider.GetService>().ShouldNotBeNull(); + _provider.GetService>().ShouldBeNull(); } -} +} \ No newline at end of file diff --git a/test/MediatR.Extensions.Microsoft.DependencyInjection.Tests/TypeResolutionTests.cs b/test/MediatR.Extensions.Microsoft.DependencyInjection.Tests/TypeResolutionTests.cs index 7443fd9..7fe9080 100644 --- a/test/MediatR.Extensions.Microsoft.DependencyInjection.Tests/TypeResolutionTests.cs +++ b/test/MediatR.Extensions.Microsoft.DependencyInjection.Tests/TypeResolutionTests.cs @@ -1,73 +1,72 @@ using Microsoft.Extensions.DependencyInjection; -namespace MediatR.Extensions.Microsoft.DependencyInjection.Tests +namespace MediatR.Extensions.Microsoft.DependencyInjection.Tests; + +using System; +using System.Linq; +using System.Reflection; +using Shouldly; +using Xunit; + +public class TypeResolutionTests { - using System; - using System.Linq; - using System.Reflection; - using Shouldly; - using Xunit; + private readonly IServiceProvider _provider; - public class TypeResolutionTests + public TypeResolutionTests() { - private readonly IServiceProvider _provider; - - public TypeResolutionTests() - { - IServiceCollection services = new ServiceCollection(); - services.AddSingleton(new Logger()); - services.AddMediatR(typeof(Ping)); - _provider = services.BuildServiceProvider(); - } + IServiceCollection services = new ServiceCollection(); + services.AddSingleton(new Logger()); + services.AddMediatR(typeof(Ping)); + _provider = services.BuildServiceProvider(); + } - [Fact] - public void ShouldResolveMediator() - { - _provider.GetService().ShouldNotBeNull(); - } + [Fact] + public void ShouldResolveMediator() + { + _provider.GetService().ShouldNotBeNull(); + } - [Fact] - public void ShouldResolveSender() - { - _provider.GetService().ShouldNotBeNull(); - } + [Fact] + public void ShouldResolveSender() + { + _provider.GetService().ShouldNotBeNull(); + } - [Fact] - public void ShouldResolvePublisher() - { - _provider.GetService().ShouldNotBeNull(); - } + [Fact] + public void ShouldResolvePublisher() + { + _provider.GetService().ShouldNotBeNull(); + } - [Fact] - public void ShouldResolveRequestHandler() - { - _provider.GetService>().ShouldNotBeNull(); - } + [Fact] + public void ShouldResolveRequestHandler() + { + _provider.GetService>().ShouldNotBeNull(); + } - [Fact] - public void ShouldResolveVoidRequestHandler() - { - _provider.GetService>().ShouldNotBeNull(); - } + [Fact] + public void ShouldResolveVoidRequestHandler() + { + _provider.GetService>().ShouldNotBeNull(); + } - [Fact] - public void ShouldResolveNotificationHandlers() - { - _provider.GetServices>().Count().ShouldBe(3); - } + [Fact] + public void ShouldResolveNotificationHandlers() + { + _provider.GetServices>().Count().ShouldBe(3); + } - [Fact] - public void ShouldResolveFirstDuplicateHandler() - { - _provider.GetService>().ShouldNotBeNull(); - _provider.GetService>() - .ShouldBeAssignableTo(); - } + [Fact] + public void ShouldResolveFirstDuplicateHandler() + { + _provider.GetService>().ShouldNotBeNull(); + _provider.GetService>() + .ShouldBeAssignableTo(); + } - [Fact] - public void ShouldResolveIgnoreSecondDuplicateHandler() - { - _provider.GetServices>().Count().ShouldBe(1); - } + [Fact] + public void ShouldResolveIgnoreSecondDuplicateHandler() + { + _provider.GetServices>().Count().ShouldBe(1); } } \ No newline at end of file