-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathServiceProviderExtensions.cs
More file actions
134 lines (110 loc) · 4.87 KB
/
Copy pathServiceProviderExtensions.cs
File metadata and controls
134 lines (110 loc) · 4.87 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
namespace ResolvePattern;
public static class ServiceProviderExtensions
{
/// <summary>
/// Registers <see cref="AmbientResolver"/> as both <see cref="IResolver"/> and <see cref="ILazyResolver"/>.
/// </summary>
public static IServiceCollection AddResolvePattern(this IServiceCollection services)
{
services.TryAddSingleton<IResolver>(AmbientResolver.Instance);
services.TryAddSingleton<ILazyResolver>(AmbientResolver.Instance);
return services;
}
/// <summary>
/// Registers an <see cref="ILazyResolverProvider"/> implementation and assigns its
/// <see cref="ILazyResolverProvider.Resolver"/> from the container at construction.
///
/// This is property injection, done by hand, one visible line per service.
/// Microsoft.Extensions.DependencyInjection has no property injection of its own — containers
/// like Autofac provide it (<c>PropertiesAutowired</c>), but relying on one would trade a
/// one-line helper for a container dependency.
/// </summary>
public static IServiceCollection AddResolved<TService, TImplementation>(this IServiceCollection services, ServiceLifetime lifetime = ServiceLifetime.Scoped)
where TService : class
where TImplementation : class, ILazyResolverProvider, TService
{
services.AddResolvePattern();
var factory = ActivatorUtilities.CreateFactory(typeof(TImplementation), Type.EmptyTypes);
services.Add(new ServiceDescriptor(typeof(TImplementation), provider =>
{
var service = (TImplementation)factory(provider, null);
service.Resolver = provider.GetRequiredService<ILazyResolver>();
return service;
}, lifetime));
services.AddTransient<TService>(provider => provider.GetRequiredService<TImplementation>());
return services;
}
/// <summary>
/// Opens an ambient resolver scope and hands back the <see cref="IResolver"/> bound to it.
///
/// You need one at every entry point — background workers, hosted jobs, migrations, console
/// apps, tests, and web requests alike. An ASP.NET Core request scope is an
/// <see cref="IServiceScope"/>, which is not the same thing as an ambient resolver scope, so a
/// web app still opens one per request from middleware. See the README.
///
/// using (serviceProvider.UseScope(out var resolver))
/// {
/// var svc = resolver.Resolve<IMyService>();
/// // ...
/// }
/// </summary>
public static IDisposable UseScope(this IServiceProvider provider, out IResolver resolver)
{
var scope = ResolverScope.Begin(provider);
resolver = scope;
return scope;
}
/// <summary>
/// Opens an ambient resolver scope, for callers that do not need the resolver itself — middleware, mostly, where the services downstream have their own.
/// </summary>
public static IDisposable UseScope(this IServiceProvider provider)
{
return provider.UseScope(out _);
}
/// <summary>
/// Opens an ambient resolver scope only if there is not one already, and otherwise hands back the resolver for the scope that is already open.
/// </summary>
public static IDisposable EnsureUseScope(this IServiceProvider provider, out IResolver resolver)
{
var current = ResolverScope.Current;
if (current != null)
{
resolver = current;
return NoOpDisposable.Instance;
}
return provider.UseScope(out resolver);
}
/// <summary>
/// Opens an ambient resolver scope only if there is not one already, for callers that do not need the resolver itself.
/// </summary>
public static IDisposable EnsureUseScope(this IServiceProvider provider)
{
return provider.EnsureUseScope(out _);
}
/// <summary>
/// Runs <paramref name="work"/> detached from the caller's scope, in a scope of its own that ends
/// when the work does — for fire-and-forget work that must not share, or outlive, the caller's scope.
/// See <see cref="ResolverScope.RunDetached"/>.
/// </summary>
public static Task RunInOwnScope(this IServiceProvider provider, Func<IResolver, Task> work)
{
// Captured now: the caller's scope may have ended by the time the work starts.
var scopeFactory = provider.GetRequiredService<IServiceScopeFactory>();
return ResolverScope.RunDetached(async () =>
{
using (var scope = ResolverScope.Begin(scopeFactory))
{
await work(scope);
}
});
}
private sealed class NoOpDisposable : IDisposable
{
public static NoOpDisposable Instance { get; } = new();
public void Dispose()
{
}
}
}