Skip to content

Commit 11df498

Browse files
authored
Merge pull request #1 from snowberry-software/develop
feat: support Snowberry.DependencyInjection
2 parents 8c229d8 + e0b5052 commit 11df498

34 files changed

+4192
-213
lines changed

src/NuGetPackage.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<Platforms>AnyCPU;x64;x86</Platforms>
55

66
<Authors>Snowberry Software</Authors>
7-
<AssemblyVersion>1.0.0.0</AssemblyVersion>
7+
<AssemblyVersion>1.0.1.0</AssemblyVersion>
88
<VersionPrefix>$(AssemblyVersion)</VersionPrefix>
99
<VersionSuffix>alpha</VersionSuffix>
1010

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,19 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

3-
<PropertyGroup>
4-
<TargetFramework>net9.0</TargetFramework>
5-
<ImplicitUsings>enable</ImplicitUsings>
6-
<Nullable>enable</Nullable>
3+
<PropertyGroup>
4+
<TargetFrameworks>net9.0;netstandard2.0</TargetFrameworks>
5+
<ImplicitUsings>enable</ImplicitUsings>
6+
<Nullable>enable</Nullable>
7+
<LangVersion>latest</LangVersion>
78

8-
<!-- Package -->
9-
<Description>The abstractions required for the main package.</Description>
10-
</PropertyGroup>
9+
<!-- Package -->
10+
<Description>The abstractions required for the main package.</Description>
11+
</PropertyGroup>
1112

12-
<Import Project="../NuGetPackage.props" />
13+
<ItemGroup Condition="$(TargetFramework) == 'netstandard2.0'">
14+
<PackageReference Include="System.Threading.Tasks.Extensions" Version="4.6.3" />
15+
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="9.0.9" />
16+
</ItemGroup>
17+
18+
<Import Project="../NuGetPackage.props" />
1319
</Project>
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
namespace Snowberry.Mediator.DependencyInjection.Shared.Contracts;
2+
3+
/// <summary>
4+
/// Defines a context for registering services with specific lifetimes.
5+
/// </summary>
6+
public interface IServiceContext
7+
{
8+
/// <summary>
9+
/// Tries to get a singleton instance of the service type <typeparamref name="T"/>.
10+
/// </summary>
11+
/// <typeparam name="T">The service type.</typeparam>
12+
/// <param name="found">Indicates whether the singleton was found.</param>
13+
/// <returns>The singleton.</returns>
14+
T? TryToGetSingleton<T>(out bool found);
15+
16+
/// <summary>
17+
/// Checks if the service type <typeparamref name="T"/> is already registered.
18+
/// </summary>
19+
/// <typeparam name="T">The service type.</typeparam>
20+
/// <returns>The result.</returns>
21+
bool IsServiceRegistered<T>();
22+
23+
/// <summary>
24+
/// Registers a service with the specified lifetime.
25+
/// </summary>
26+
/// <param name="serviceType">The service type.</param>
27+
/// <param name="implementationType">The implementation type.</param>
28+
/// <param name="lifetime">The service lifetime.</param>
29+
void Register(Type serviceType, Type implementationType, RegistrationServiceLifetime lifetime);
30+
31+
/// <summary>
32+
/// Registers a singleton instance of a service.
33+
/// </summary>
34+
/// <param name="serviceType">The service type.</param>
35+
/// <param name="instance">The instance type.</param>
36+
void Register(Type serviceType, object instance);
37+
}
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
using Snowberry.Mediator.Abstractions;
2+
using Snowberry.Mediator.Abstractions.Handler;
3+
using Snowberry.Mediator.Abstractions.Pipeline;
4+
using Snowberry.Mediator.DependencyInjection.Shared.Contracts;
5+
using Snowberry.Mediator.Extensions;
6+
using Snowberry.Mediator.Models;
7+
using Snowberry.Mediator.Registries;
8+
using Snowberry.Mediator.Registries.Contracts;
9+
10+
namespace Snowberry.Mediator.DependencyInjection.Shared;
11+
12+
/// <summary>
13+
/// Helper type for adding Mediator services to a service context.
14+
/// </summary>
15+
public static class DependencyInjectionHelper
16+
{
17+
/// <summary>
18+
/// Adds Mediator services to the specified service context.
19+
/// </summary>
20+
/// <param name="serviceContext">The service context.</param>
21+
/// <param name="options">The options.</param>
22+
/// <param name="serviceLifetime">The service lifetime.</param>
23+
/// <param name="append">Whether to append to existing registrations or replace them.</param>
24+
public static void AddSnowberryMediator(
25+
IServiceContext serviceContext,
26+
MediatorOptions options,
27+
RegistrationServiceLifetime serviceLifetime,
28+
bool append)
29+
{
30+
if (!append || !serviceContext.IsServiceRegistered<IMediator>())
31+
serviceContext.Register(typeof(IMediator), typeof(Mediator), serviceLifetime);
32+
33+
var allHandlers = new List<RequestHandlerInfo>();
34+
var allStreamHandlers = new List<StreamRequestHandlerInfo>();
35+
var allPipelineBehaviorHandlers = new List<PipelineBehaviorHandlerInfo>();
36+
var allStreamPipelineBehaviorHandlers = new List<StreamPipelineBehaviorHandlerInfo>();
37+
var allNotificationHandlers = new List<NotificationHandlerInfo>();
38+
39+
if (options.Assemblies != null && options.Assemblies.Count > 0)
40+
{
41+
for (int i = 0; i < options.Assemblies.Count; i++)
42+
{
43+
var assembly = options.Assemblies[i];
44+
45+
var scanResult = MediatorAssemblyHelper.ScanAssembly(assembly);
46+
47+
if (scanResult.RequestHandlerTypes != null)
48+
for (int j = 0; j < scanResult.RequestHandlerTypes.Count; j++)
49+
allHandlers.Add(scanResult.RequestHandlerTypes[j]);
50+
51+
if (scanResult.StreamRequestHandlerTypes != null)
52+
for (int j = 0; j < scanResult.StreamRequestHandlerTypes.Count; j++)
53+
allStreamHandlers.Add(scanResult.StreamRequestHandlerTypes[j]);
54+
55+
if (options.RegisterPipelineBehaviors && options.ScanPipelineBehaviors && scanResult.PipelineBehaviorTypes != null)
56+
for (int j = 0; j < scanResult.PipelineBehaviorTypes.Count; j++)
57+
allPipelineBehaviorHandlers.Add(scanResult.PipelineBehaviorTypes[j]);
58+
59+
if (options.RegisterStreamPipelineBehaviors && options.ScanStreamPipelineBehaviors && scanResult.StreamPipelineBehaviorTypes != null)
60+
for (int j = 0; j < scanResult.StreamPipelineBehaviorTypes.Count; j++)
61+
allStreamPipelineBehaviorHandlers.Add(scanResult.StreamPipelineBehaviorTypes[j]);
62+
63+
if (options.RegisterNotificationHandlers && options.ScanNotificationHandlers && scanResult.NotificationHandlerTypes != null)
64+
for (int j = 0; j < scanResult.NotificationHandlerTypes.Count; j++)
65+
allNotificationHandlers.Add(scanResult.NotificationHandlerTypes[j]);
66+
}
67+
}
68+
69+
var pipelineBehaviorType = typeof(IPipelineBehavior<,>);
70+
var streamPipelineBehaviorType = typeof(IStreamPipelineBehavior<,>);
71+
var requestHandlerType = typeof(IRequestHandler<,>);
72+
var streamRequestHandlerType = typeof(IStreamRequestHandler<,>);
73+
74+
if (options.PipelineBehaviorTypes != null && options.RegisterPipelineBehaviors)
75+
MediatorAssemblyHelper.ParseHandlerInfo(pipelineBehaviorType, options.PipelineBehaviorTypes, allPipelineBehaviorHandlers);
76+
77+
if (options.RequestHandlerTypes != null && options.RegisterRequestHandlers)
78+
MediatorAssemblyHelper.ParseHandlerInfo(requestHandlerType, options.RequestHandlerTypes, allHandlers);
79+
80+
if (options.StreamRequestHandlerTypes != null && options.RegisterStreamRequestHandlers)
81+
MediatorAssemblyHelper.ParseHandlerInfo(streamRequestHandlerType, options.StreamRequestHandlerTypes, allStreamHandlers);
82+
83+
if (options.StreamPipelineBehaviorTypes != null && options.RegisterStreamPipelineBehaviors)
84+
MediatorAssemblyHelper.ParseHandlerInfo(streamPipelineBehaviorType, options.StreamPipelineBehaviorTypes, allStreamPipelineBehaviorHandlers);
85+
86+
if (options.NotificationHandlerTypes != null && options.RegisterNotificationHandlers)
87+
MediatorAssemblyHelper.ParseNotificationHandlers(options.NotificationHandlerTypes, allNotificationHandlers);
88+
89+
for (int i = 0; i < allHandlers.Count; i++)
90+
{
91+
var handlerInfo = allHandlers[i];
92+
serviceContext.Register(handlerInfo.CreateRequestHandlerInterfaceType(), handlerInfo.HandlerType, serviceLifetime);
93+
}
94+
95+
for (int i = 0; i < allStreamHandlers.Count; i++)
96+
{
97+
var handlerInfo = allStreamHandlers[i];
98+
serviceContext.Register(handlerInfo.CreateStreamRequestHandlerInterfaceType(), handlerInfo.HandlerType, serviceLifetime);
99+
}
100+
101+
if (options.RegisterPipelineBehaviors && allPipelineBehaviorHandlers.Count > 0)
102+
AddPipelineBehaviors<IGlobalPipelineRegistry, GlobalPipelineRegistry, PipelineBehaviorHandlerInfo>(
103+
serviceContext,
104+
serviceLifetime,
105+
allPipelineBehaviorHandlers,
106+
append);
107+
108+
if (options.RegisterStreamPipelineBehaviors && allStreamPipelineBehaviorHandlers.Count > 0)
109+
AddPipelineBehaviors<IGlobalStreamPipelineRegistry, GlobalStreamPipelineRegistry, StreamPipelineBehaviorHandlerInfo>(
110+
serviceContext,
111+
serviceLifetime,
112+
allStreamPipelineBehaviorHandlers,
113+
append);
114+
115+
if (options.RegisterNotificationHandlers && allNotificationHandlers.Count > 0)
116+
AddNotificationHandlers(serviceContext, serviceLifetime, allNotificationHandlers, append);
117+
}
118+
119+
private static void AddPipelineBehaviors<TGlobalPipelineInterface, TGlobalPipelineRegistry, THandlerInfo>(
120+
IServiceContext serviceContext,
121+
RegistrationServiceLifetime serviceLifetime,
122+
IList<THandlerInfo> pipelineBehaviorHandlers,
123+
bool append
124+
)
125+
where TGlobalPipelineRegistry : TGlobalPipelineInterface, new()
126+
where TGlobalPipelineInterface : IBaseGlobalPipelineRegistry<THandlerInfo>
127+
where THandlerInfo : PipelineBehaviorHandlerInfo
128+
{
129+
if (pipelineBehaviorHandlers.Count == 0)
130+
return;
131+
132+
TGlobalPipelineInterface? globalPipelineRegistry = default;
133+
if (!append || !serviceContext.IsServiceRegistered<TGlobalPipelineInterface>())
134+
{
135+
globalPipelineRegistry = new TGlobalPipelineRegistry();
136+
serviceContext.Register(serviceType: typeof(TGlobalPipelineInterface), instance: globalPipelineRegistry);
137+
}
138+
else
139+
{
140+
globalPipelineRegistry = serviceContext.TryToGetSingleton<TGlobalPipelineInterface>(out bool foundSingleton);
141+
142+
if (!foundSingleton)
143+
{
144+
globalPipelineRegistry = new TGlobalPipelineRegistry();
145+
serviceContext.Register(serviceType: typeof(TGlobalPipelineInterface), instance: globalPipelineRegistry);
146+
}
147+
}
148+
149+
for (int i = 0; i < pipelineBehaviorHandlers.Count; i++)
150+
{
151+
var handler = pipelineBehaviorHandlers[i];
152+
globalPipelineRegistry!.Register(handler);
153+
154+
serviceContext.Register(handler.HandlerType, handler.HandlerType, serviceLifetime);
155+
}
156+
}
157+
158+
private static void AddNotificationHandlers(
159+
IServiceContext serviceContext,
160+
RegistrationServiceLifetime serviceLifetime,
161+
IList<NotificationHandlerInfo> notificationHandlers,
162+
bool append
163+
)
164+
{
165+
if (notificationHandlers.Count == 0)
166+
return;
167+
168+
IGlobalNotificationHandlerRegistry<NotificationHandlerInfo>? globalNotificationHandlerRegistry = null;
169+
170+
if (!append || !serviceContext.IsServiceRegistered<IGlobalNotificationHandlerRegistry<NotificationHandlerInfo>>())
171+
{
172+
globalNotificationHandlerRegistry = new GlobalNotificationHandlerRegistry();
173+
serviceContext.Register(serviceType: typeof(IGlobalNotificationHandlerRegistry<NotificationHandlerInfo>), instance: globalNotificationHandlerRegistry);
174+
}
175+
else
176+
{
177+
globalNotificationHandlerRegistry = serviceContext.TryToGetSingleton<IGlobalNotificationHandlerRegistry<NotificationHandlerInfo>>(out bool foundSingleton);
178+
179+
if (!foundSingleton)
180+
{
181+
globalNotificationHandlerRegistry = new GlobalNotificationHandlerRegistry();
182+
serviceContext.Register(serviceType: typeof(IGlobalNotificationHandlerRegistry<NotificationHandlerInfo>), instance: globalNotificationHandlerRegistry);
183+
}
184+
}
185+
186+
for (int i = 0; i < notificationHandlers.Count; i++)
187+
{
188+
var handler = notificationHandlers[i];
189+
globalNotificationHandlerRegistry!.Register(handler);
190+
191+
serviceContext.Register(handler.HandlerType, handler.HandlerType, serviceLifetime);
192+
}
193+
}
194+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
namespace Snowberry.Mediator.DependencyInjection.Shared;
2+
3+
/// <summary>
4+
/// The lifetime of a service.
5+
/// </summary>
6+
public enum RegistrationServiceLifetime
7+
{
8+
/// <summary>
9+
/// A single instance is created and shared throughout the application's lifetime.
10+
/// </summary>
11+
Singleton,
12+
13+
/// <summary>
14+
/// A new instance is created each time the service is requested.
15+
/// </summary>
16+
Transient,
17+
18+
/// <summary>
19+
/// A new instance is created for each scope.
20+
/// </summary>
21+
Scoped
22+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net9.0</TargetFramework>
5+
<ImplicitUsings>enable</ImplicitUsings>
6+
<Nullable>enable</Nullable>
7+
8+
<!-- Package -->
9+
<Description>
10+
A small helper library for supporting multiple Dependency Injection frameworks.
11+
</Description>
12+
</PropertyGroup>
13+
14+
<ItemGroup>
15+
<ProjectReference Include="..\Snowberry.Mediator\Snowberry.Mediator.csproj" />
16+
</ItemGroup>
17+
18+
<Import Project="../NuGetPackage.props" />
19+
</Project>
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
using Snowberry.DependencyInjection;
2+
using Snowberry.DependencyInjection.Interfaces;
3+
using Snowberry.Mediator.DependencyInjection.Shared;
4+
5+
namespace Snowberry.Mediator.DependencyInjection;
6+
7+
/// <summary>
8+
/// Extension methods for the <see cref="IServiceRegistry"/>.
9+
/// </summary>
10+
public static class ServiceCollectionExtensions
11+
{
12+
/// <summary>
13+
/// Adds the Mediator services to the specified <see cref="IServiceRegistry" />.
14+
/// </summary>
15+
/// <remarks>
16+
/// A service provider must be registered in the service collection before calling this method.
17+
/// Alternatively, if the <see cref="IServiceRegistry"/> instance is also an <see cref="IServiceProvider"/>, it will be registered as a singleton.
18+
/// </remarks>
19+
/// <param name="serviceRegistry">The service collection.</param>
20+
/// <param name="configure">The configuration method.</param>
21+
/// <param name="serviceLifetime">The service lifetime of the mediator and handlers.</param>
22+
/// <returns>The service collection.</returns>
23+
public static IServiceRegistry AddSnowberryMediator(this IServiceRegistry serviceRegistry, Action<MediatorOptions> configure, ServiceLifetime serviceLifetime = ServiceLifetime.Scoped)
24+
{
25+
return AddSnowberryMediator(serviceRegistry, configure, serviceLifetime, append: false);
26+
}
27+
28+
/// <summary>
29+
/// Appends the Mediator services to the specified <see cref="IServiceRegistry" /> and preserves existing registrations.
30+
/// </summary>
31+
/// <remarks>
32+
/// A service provider must be registered in the service collection before calling this method.
33+
/// Alternatively, if the <see cref="IServiceRegistry"/> instance is also an <see cref="IServiceProvider"/>, it will be registered as a singleton.
34+
/// </remarks>
35+
/// <param name="serviceRegistry">The service collection.</param>
36+
/// <param name="configure">The configuration method.</param>
37+
/// <param name="serviceLifetime">The service lifetime of the mediator and handlers.</param>
38+
/// <returns>The service collection.</returns>
39+
public static IServiceRegistry AppendSnowberryMediator(this IServiceRegistry serviceRegistry, Action<MediatorOptions> configure, ServiceLifetime serviceLifetime = ServiceLifetime.Scoped)
40+
{
41+
return AddSnowberryMediator(serviceRegistry, configure, serviceLifetime, append: true);
42+
}
43+
44+
/// <summary>
45+
/// Adds the Mediator services to the specified <see cref="IServiceRegistry" />.
46+
/// </summary>
47+
/// <remarks>
48+
/// A service provider must be registered in the service collection before calling this method.
49+
/// Alternatively, if the <see cref="IServiceRegistry"/> instance is also an <see cref="IServiceProvider"/>, it will be registered as a singleton.
50+
/// </remarks>
51+
/// <param name="serviceRegistry">The service collection.</param>
52+
/// <param name="configure">The configuration method.</param>
53+
/// <param name="serviceLifetime">The service lifetime of the mediator and handlers.</param>
54+
/// <param name="append">Whether to append the registrations to existing ones.</param>
55+
/// <returns>The service collection.</returns>
56+
private static IServiceRegistry AddSnowberryMediator(this IServiceRegistry serviceRegistry, Action<MediatorOptions> configure, ServiceLifetime serviceLifetime, bool append)
57+
{
58+
var options = new MediatorOptions();
59+
configure(options);
60+
61+
var serviceContext = new SnowberryServiceContext(serviceRegistry);
62+
DependencyInjectionHelper.AddSnowberryMediator(serviceContext, options, serviceLifetime: serviceLifetime switch
63+
{
64+
ServiceLifetime.Scoped => RegistrationServiceLifetime.Scoped,
65+
ServiceLifetime.Singleton => RegistrationServiceLifetime.Singleton,
66+
ServiceLifetime.Transient => RegistrationServiceLifetime.Transient,
67+
_ => throw new NotSupportedException($"The service lifetime '{serviceLifetime}' is not supported."),
68+
}, append: append);
69+
70+
if (!serviceRegistry.IsServiceRegistered<IServiceProvider>(serviceKey: null))
71+
{
72+
if (serviceRegistry is IServiceProvider serviceProvider)
73+
serviceRegistry.RegisterSingleton(instance: serviceProvider);
74+
else
75+
throw new InvalidOperationException($"The {nameof(IServiceRegistry)} instance is not an {nameof(IServiceProvider)}. Please register an {nameof(IServiceProvider)} instance before calling {nameof(AddSnowberryMediator)}.");
76+
}
77+
78+
return serviceRegistry;
79+
}
80+
}

0 commit comments

Comments
 (0)