Skip to content
This repository has been archived by the owner on Feb 15, 2023. It is now read-only.

Commit

Permalink
Merge pull request #78 from jbogard/update-to-8-0
Browse files Browse the repository at this point in the history
Update to 8.0
  • Loading branch information
jbogard committed Dec 30, 2019
2 parents ab26360 + 5c4bf2f commit 1519a10
Show file tree
Hide file tree
Showing 10 changed files with 224 additions and 10 deletions.
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
<PropertyGroup>
<Authors>Jimmy Bogard</Authors>
<LangVersion>latest</LangVersion>
<VersionPrefix>7.0.0</VersionPrefix>
<VersionPrefix>8.0.0</VersionPrefix>
</PropertyGroup>
</Project>
22 changes: 20 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,30 @@ or with an assembly:
services.AddMediatR(typeof(Startup).GetTypeInfo().Assembly);
```

Supports generic variance of handlers.
This registers:

- `IMediator` as transient
- `IRequestHandler<>` concrete implementations as transient
- `INotificationHandler<>` concrete implementations as transient
- `IRequestPreProcessor<>` concrete implementations as transient
- `IRequestHandler<>` concrete implementations as transient
- `IRequestPostProcessor<,>` concrete implementations as transient
- `IRequestExceptionHandler<,,>` concrete implementations as transient

This also registers open generic implementations for:

- `INotificationHandler<>`
- `IRequestPreProcessor<>`
- `IRequestHandler<>`
- `IRequestPostProcessor<,>`
- `IRequestExceptionHandler<,,>`

Keep in mind that the built-in container does not support constrained open generics. If you want this behavior, you will need to add any one of the conforming containers.

To customize registration, such as lifecycle or the registration type:

```c#
services.AddMediatR(cfg => cfg.Using<MyCustomMediator>().AsSingleton(), typeof(Startup));
```

To register behaviors, pre- or post-processors, register them individually before or after calling `AddMediatR`.
To register behaviors, register them individually before or after calling `AddMediatR`.
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="2.0.0" />
<PackageReference Include="MediatR" Version="7.0.0" />
<PackageReference Include="MediatR" Version="8.0.0" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,23 @@ public static void AddMediatRClasses(IServiceCollection services, IEnumerable<As
ConnectImplementationsToTypesClosing(typeof(INotificationHandler<>), services, assembliesToScan, true);
ConnectImplementationsToTypesClosing(typeof(IRequestPreProcessor<>), services, assembliesToScan, true);
ConnectImplementationsToTypesClosing(typeof(IRequestPostProcessor<,>), services, assembliesToScan, true);
ConnectImplementationsToTypesClosing(typeof(IRequestExceptionHandler<,,>), services, assembliesToScan, true);
ConnectImplementationsToTypesClosing(typeof(IRequestExceptionAction<,>), services, assembliesToScan, true);

var multiOpenInterfaces = new[]
{
typeof(INotificationHandler<>),
typeof(IRequestPreProcessor<>),
typeof(IRequestPostProcessor<,>)
typeof(IRequestPostProcessor<,>),
typeof(IRequestExceptionHandler<,,>),
typeof(IRequestExceptionAction<,>)
};

foreach (var multiOpenInterface in multiOpenInterfaces)
{
var concretions = assembliesToScan
.SelectMany(a => a.DefinedTypes)
.Where(type => Enumerable.Any<Type>(type.FindInterfacesThatClose(multiOpenInterface)))
.Where(type => type.FindInterfacesThatClose(multiOpenInterface).Any())
.Where(type => type.IsConcrete() && type.IsOpenGeneric())
.ToList();

Expand Down Expand Up @@ -59,7 +63,7 @@ private static void ConnectImplementationsToTypesClosing(Type openRequestInterfa
var interfaces = new List<Type>();
foreach (var type in assembliesToScan.SelectMany(a => a.DefinedTypes).Where(t => !t.IsOpenGeneric()))
{
var interfaceTypes = Enumerable.ToArray<Type>(type.FindInterfacesThatClose(openRequestInterface));
var interfaceTypes = type.FindInterfacesThatClose(openRequestInterface).ToArray();
if (!interfaceTypes.Any()) continue;

if (type.IsConcrete())
Expand Down Expand Up @@ -165,7 +169,7 @@ public static bool IsOpenGeneric(this Type type)

public static IEnumerable<Type> FindInterfacesThatClose(this Type pluggedType, Type templateType)
{
return Enumerable.Distinct<Type>(FindInterfacesThatClosesCore(pluggedType, templateType));
return FindInterfacesThatClosesCore(pluggedType, templateType).Distinct();
}

private static IEnumerable<Type> FindInterfacesThatClosesCore(Type pluggedType, Type templateType)
Expand Down Expand Up @@ -214,6 +218,8 @@ public static void AddRequiredServices(IServiceCollection services, MediatRServi
services.AddTransient<ServiceFactory>(p => p.GetService);
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(RequestPreProcessorBehavior<,>));
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(RequestPostProcessorBehavior<,>));
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(RequestExceptionActionProcessorBehavior<,>));
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(RequestExceptionProcessorBehavior<,>));
services.Add(new ServiceDescriptor(typeof(IMediator), serviceConfiguration.MediatorImplementationType, serviceConfiguration.Lifetime));
}
}
Expand Down
1 change: 1 addition & 0 deletions src/TestApp/Ping.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@ namespace TestApp
public class Ping : IRequest<Pong>
{
public string Message { get; set; }
public bool Throw { get; set; }
}
}
7 changes: 7 additions & 0 deletions src/TestApp/PingHandler.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System;
using System.IO;
using System.Threading;
using MediatR;
Expand All @@ -18,6 +19,12 @@ public PingHandler(TextWriter writer)
public async Task<Pong> Handle(Ping request, CancellationToken cancellationToken)
{
await _writer.WriteLineAsync($"--- Handled Ping: {request.Message}");

if (request.Throw)
{
throw new ApplicationException("Requested to throw");
}

return new Pong { Message = request.Message + " Pong" };
}
}
Expand Down
40 changes: 40 additions & 0 deletions src/TestApp/PingPongExceptionHandlers.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using MediatR.Pipeline;

namespace TestApp
{
public class PingPongExceptionHandlerForType : IRequestExceptionHandler<Ping, Pong, ApplicationException>
{
public Task Handle(Ping request, ApplicationException exception, RequestExceptionHandlerState<Pong> state, CancellationToken cancellationToken)
{
state.SetHandled(new Pong { Message = exception.Message + " Handled by Type" });

return Task.CompletedTask;
}
}

public class PingPongExceptionActionForType1 : IRequestExceptionAction<Ping, ApplicationException>
{
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 PingPongExceptionActionForType2 : IRequestExceptionAction<Ping, ApplicationException>
{
private readonly TextWriter _output;

public PingPongExceptionActionForType2(TextWriter output) => _output = output;

public Task Execute(Ping request, ApplicationException exception, CancellationToken cancellationToken)
=> _output.WriteLineAsync("Logging exception 2");
}


}
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
namespace MediatR.Extensions.Microsoft.DependencyInjection.Tests
using System;

namespace MediatR.Extensions.Microsoft.DependencyInjection.Tests
{
using System.Collections.Generic;
using System.Threading;
Expand All @@ -7,6 +9,7 @@
public class Ping : IRequest<Pong>
{
public string Message { get; set; }
public Action<Ping> ThrowAction { get; set; }
}

public class DerivedPing : Ping
Expand Down Expand Up @@ -85,6 +88,9 @@ public PingHandler(Logger logger)
public Task<Pong> Handle(Ping message, CancellationToken cancellationToken)
{
_logger.Messages.Add("Handler");

message.ThrowAction?.Invoke(message);

return Task.FromResult(new Pong { Message = message.Message + " Pong" });
}
}
Expand Down Expand Up @@ -143,6 +149,11 @@ class InternalPingHandler : IRequestHandler<InternalPing>

class MyCustomMediator : IMediator
{
public Task<object> Send(object request, CancellationToken cancellationToken = new CancellationToken())
{
throw new System.NotImplementedException();
}

public Task Publish(object notification, CancellationToken cancellationToken = new CancellationToken())
{
throw new System.NotImplementedException();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.0.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,86 @@ public Task Process(Ping request, Pong response, CancellationToken cancellationT
}
}

public class PingPongGenericExceptionAction : IRequestExceptionAction<Ping>
{
private readonly Logger _output;

public PingPongGenericExceptionAction(Logger output) => _output = output;

public Task Execute(Ping request, Exception exception, CancellationToken cancellationToken)
{
_output.Messages.Add("Logging generic exception");

return Task.CompletedTask;
}
}

public class PingPongApplicationExceptionAction : IRequestExceptionAction<Ping, ApplicationException>
{
private readonly Logger _output;

public PingPongApplicationExceptionAction(Logger output) => _output = output;

public Task Execute(Ping request, ApplicationException exception, CancellationToken cancellationToken)
{
_output.Messages.Add("Logging ApplicationException exception");

return Task.CompletedTask;
}
}

public class PingPongExceptionActionForType1 : IRequestExceptionAction<Ping, SystemException>
{
private readonly Logger _output;

public PingPongExceptionActionForType1(Logger output) => _output = output;

public Task Execute(Ping request, SystemException exception, CancellationToken cancellationToken)
{
_output.Messages.Add("Logging exception 1");

return Task.CompletedTask;
}
}

public class PingPongExceptionActionForType2 : IRequestExceptionAction<Ping, SystemException>
{
private readonly Logger _output;

public PingPongExceptionActionForType2(Logger output) => _output = output;

public Task Execute(Ping request, SystemException exception, CancellationToken cancellationToken)
{
_output.Messages.Add("Logging exception 2");

return Task.CompletedTask;
}
}

public class PingPongExceptionHandlerForType : IRequestExceptionHandler<Ping, Pong, ApplicationException>
{
public Task Handle(Ping request, ApplicationException exception, RequestExceptionHandlerState<Pong> state, CancellationToken cancellationToken)
{
state.SetHandled(new Pong { Message = exception.Message + " Handled by Specific Type" });

return Task.CompletedTask;
}
}

public class PingPongGenericExceptionHandler : IRequestExceptionHandler<Ping, Pong>
{
private readonly Logger _output;

public PingPongGenericExceptionHandler(Logger output) => _output = output;

public Task Handle(Ping request, Exception exception, RequestExceptionHandlerState<Pong> state, CancellationToken cancellationToken)
{
_output.Messages.Add(exception.Message + " Logged by Generic Type");

return Task.CompletedTask;
}
}

[Fact]
public async Task Should_wrap_with_behavior()
{
Expand Down Expand Up @@ -331,6 +411,57 @@ public async Task Should_pick_up_pre_and_post_processors()
});
}

[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();

var mediator = provider.GetService<IMediator>();

var response = await mediator.Send(new Ping {Message = "Ping", ThrowAction = msg => throw new ApplicationException(msg.Message + " Thrown")});

response.Message.ShouldBe("Ping Thrown Handled by Specific Type");
output.Messages.ShouldNotContain("Logging ApplicationException exception");
}

[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();

var mediator = provider.GetService<IMediator>();

Should.Throw<Exception>(async () => await mediator.Send(new Ping {Message = "Ping", ThrowAction = msg => throw new Exception(msg.Message + " Thrown")}));

output.Messages.ShouldContain("Ping Thrown Logged by Generic Type");
output.Messages.ShouldContain("Logging generic exception");
}

[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 mediator = provider.GetService<IMediator>();

Should.Throw<SystemException>(async () => await mediator.Send(new Ping {Message = "Ping", ThrowAction = msg => throw new SystemException(msg.Message + " Thrown")}));

output.Messages.ShouldContain("Logging exception 1");
output.Messages.ShouldContain("Logging exception 2");
}

[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()
{
Expand Down

0 comments on commit 1519a10

Please sign in to comment.