-
Notifications
You must be signed in to change notification settings - Fork 152
/
Copy pathSimpleInjectorServiceCollectionExtensions.cs
775 lines (675 loc) · 38.6 KB
/
SimpleInjectorServiceCollectionExtensions.cs
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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
// Copyright (c) Simple Injector Contributors. All rights reserved.
// Licensed under the MIT License. See LICENSE file in the project root for license information.
namespace SimpleInjector
{
using System;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Logging;
using SimpleInjector.Diagnostics;
using SimpleInjector.Integration.ServiceCollection;
using SimpleInjector.Lifestyles;
/// <summary>
/// Extensions to configure Simple Injector on top of <see cref="IServiceCollection"/>.
/// </summary>
public static class SimpleInjectorServiceCollectionExtensions
{
private static readonly object AddOptionsKey = new object();
private static readonly object AddLoggingKey = new object();
private static readonly object AddLocalizationKey = new object();
/// <summary>
/// Sets up the basic configuration that allows Simple Injector to be used in frameworks that require
/// the use of <see cref="IServiceCollection"/> for registration of framework components.
/// In case of the absense of a
/// <see cref="ContainerOptions.DefaultScopedLifestyle">DefaultScopedLifestyle</see>, this method
/// will configure <see cref="AsyncScopedLifestyle"/> as the default scoped lifestyle.
/// In case a <paramref name="setupAction"/> is supplied, that delegate will be called that allow
/// further configuring the container.
/// </summary>
/// <param name="services">The framework's <see cref="IServiceCollection"/> instance.</param>
/// <param name="container">The application's <see cref="Container"/> instance.</param>
/// <param name="setupAction">An optional setup action.</param>
/// <returns>The supplied <paramref name="services"/>.</returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="services"/> or
/// <paramref name="container"/> are null references.</exception>
public static IServiceCollection AddSimpleInjector(
this IServiceCollection services,
Container container,
Action<SimpleInjectorAddOptions>? setupAction = null)
{
if (services is null)
{
throw new ArgumentNullException(nameof(services));
}
if (container is null)
{
throw new ArgumentNullException(nameof(container));
}
var options = new SimpleInjectorAddOptions(
services,
container,
new DefaultServiceProviderAccessor(container));
// This stores the options, which includes the IServiceCollection. IServiceCollection is required
// when calling UseSimpleInjector to enable auto cross wiring.
AddSimpleInjectorOptions(container, options);
// Set lifestyle before calling setupAction. Code in the delegate might depend on that.
TrySetDefaultScopedLifestyle(container);
HookAspNetCoreHostHostedServiceServiceProviderInitialization(options);
setupAction?.Invoke(options);
RegisterServiceScope(options);
if (options.AutoCrossWireFrameworkComponents)
{
AddAutoCrossWiring(options);
}
return services;
}
/// <summary>
/// Finalizes the configuration of Simple Injector on top of <see cref="IServiceCollection"/>. Will
/// ensure framework components can be injected into Simple Injector-resolved components, unless
/// <see cref="SimpleInjectorAddOptions.AutoCrossWireFrameworkComponents"/> is set to <c>false</c>.
/// </summary>
/// <param name="provider">The application's <see cref="IServiceProvider"/>.</param>
/// <param name="container">The application's <see cref="Container"/> instance.</param>
/// <returns>The supplied <paramref name="provider"/>.</returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="provider"/> or
/// <paramref name="container"/> are null references.</exception>
public static IServiceProvider UseSimpleInjector(this IServiceProvider provider, Container container)
{
if (provider is null)
{
throw new ArgumentNullException(nameof(provider));
}
if (container is null)
{
throw new ArgumentNullException(nameof(container));
}
SimpleInjectorAddOptions addOptions = GetOptions(container);
addOptions.SetServiceProviderIfNull(provider);
return provider;
}
/// <summary>
/// Finalizes the configuration of Simple Injector on top of <see cref="IServiceCollection"/>. Will
/// ensure framework components can be injected into Simple Injector-resolved components, unless
/// <see cref="SimpleInjectorUseOptions.AutoCrossWireFrameworkComponents"/> is set to <c>false</c>
/// using the <paramref name="setupAction"/>.
/// </summary>
/// <param name="provider">The application's <see cref="IServiceProvider"/>.</param>
/// <param name="container">The application's <see cref="Container"/> instance.</param>
/// <param name="setupAction">An optional setup action.</param>
/// <returns>The supplied <paramref name="provider"/>.</returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="provider"/> or
/// <paramref name="container"/> are null references.</exception>
//// I wanted to add this obsolete message in 4.8, but it was confusing considering the obsolete
//// messages for everything on top of SimpleInjectorUseOptions. When those obsolete messages are
//// resolved by the user, there is no harm in calling this method any longer. So it will get
//// obsoleted in a later release.
////[Obsolete(
//// "You are supplying a setup action, but due breaking changes in ASP.NET Core 3, the Simple " +
//// "Injector container can get locked at an earlier stage, making it impossible to further setup " +
//// "the container at this stage. Please call the UseSimpleInjector(IServiceProvider, Container) " +
//// "overload instead. Take a look at the compiler warnings on the individual methods you are " +
//// "calling inside your setupAction delegate to understand how to migrate them. " +
//// " For more information, see: https://simpleinjector.org/aspnetcore. " +
//// "Will be treated as an error from version 4.10. Will be removed in version 5.0.",
//// error: false)]
public static IServiceProvider UseSimpleInjector(
this IServiceProvider provider,
Container container,
Action<SimpleInjectorUseOptions>? setupAction)
{
if (provider is null)
{
throw new ArgumentNullException(nameof(provider));
}
if (container is null)
{
throw new ArgumentNullException(nameof(container));
}
SimpleInjectorAddOptions addOptions = GetOptions(container);
addOptions.SetServiceProviderIfNull(provider);
var useOptions = new SimpleInjectorUseOptions(addOptions, provider);
setupAction?.Invoke(useOptions);
return provider;
}
/// <summary>
/// Cross wires an ASP.NET Core or third-party service to the container, to allow the service to be
/// injected into components that are built by Simple Injector.
/// </summary>
/// <typeparam name="TService">The type of service object to cross-wire.</typeparam>
/// <param name="options">The options.</param>
/// <returns>The supplied <paramref name="options"/>.</returns>
/// <exception cref="ArgumentNullException">Thrown when the parameter is a null reference.
/// </exception>
public static SimpleInjectorAddOptions CrossWire<TService>(this SimpleInjectorAddOptions options)
where TService : class
{
return CrossWire(options, typeof(TService));
}
/// <summary>
/// Cross wires an ASP.NET Core or third-party service to the container, to allow the service to be
/// injected into components that are built by Simple Injector.
/// </summary>
/// <param name="options">The options.</param>
/// <param name="serviceType">The type of service object to ross-wire.</param>
/// <returns>The supplied <paramref name="options"/>.</returns>
/// <exception cref="ArgumentNullException">Thrown when one of the parameters is a null reference.
/// </exception>
public static SimpleInjectorAddOptions CrossWire(
this SimpleInjectorAddOptions options, Type serviceType)
{
if (options is null)
{
throw new ArgumentNullException(nameof(options));
}
if (serviceType is null)
{
throw new ArgumentNullException(nameof(serviceType));
}
// At this point there is no IServiceProvider (ApplicationServices) yet, which is why we need to
// postpone the registration of the cross-wired service. When the container gets locked, we will
// (hopefully) have the IServiceProvider available.
options.Container.Options.ContainerLocking += (s, e) =>
{
Registration registration = CreateCrossWireRegistration(
options,
options.ApplicationServices,
serviceType,
DetermineLifestyle(serviceType, options.Services));
options.Container.AddRegistration(serviceType, registration);
};
return options;
}
/// <summary>
/// Allows components that are built by Simple Injector to depend on the (non-generic)
/// <see cref="ILogger">Microsoft.Extensions.Logging.ILogger</see> abstraction. Components are
/// injected with an contextual implementation. Using this method, application components can simply
/// depend on <b>ILogger</b> instead of its generic counter part, <b>ILogger<T></b>, which
/// simplifies development.
/// </summary>
/// <param name="options">The options.</param>
/// <returns>The supplied <paramref name="options"/>.</returns>
/// <exception cref="ArgumentNullException">
/// Thrown when <paramref name="options"/> is a null reference.</exception>
/// <exception cref="InvalidOperationException">Thrown when no <see cref="ILoggerFactory"/> entry
/// can be found in the framework's list of services defined by <see cref="IServiceCollection"/>.
/// </exception>
public static SimpleInjectorAddOptions AddLogging(this SimpleInjectorAddOptions options)
{
if (options is null)
{
throw new ArgumentNullException(nameof(options));
}
EnsureMethodOnlyCalledOnce(options, nameof(AddLogging), AddLoggingKey);
// Both RootLogger and Logger<T> depend on ILoggerFactory
VerifyLoggerFactoryAvailable(options.Services);
// Cross-wire ILoggerFactory explicitly, because auto cross-wiring might be disabled by the user.
options.Container.RegisterSingleton(() => options.GetRequiredFrameworkService<ILoggerFactory>());
Type genericLoggerType = GetGenericLoggerType();
options.Container.RegisterConditional(
typeof(ILogger),
c => c.Consumer is null
? typeof(RootLogger)
: genericLoggerType.MakeGenericType(c.Consumer.ImplementationType),
Lifestyle.Singleton,
_ => true);
return options;
}
/// <summary>
/// Allows components that are built by Simple Injector to depend on the (non-generic)
/// <see cref="ILogger">Microsoft.Extensions.Logging.ILogger</see> abstraction. Components are
/// injected with an contextual implementation. Using this method, application components can simply
/// depend on <b>ILogger</b> instead of its generic counter part, <b>ILogger<T></b>, which
/// simplifies development.
/// </summary>
/// <param name="options">The options.</param>
/// <returns>The supplied <paramref name="options"/>.</returns>
/// <exception cref="ArgumentNullException">
/// Thrown when <paramref name="options"/> is a null reference.</exception>
/// <exception cref="InvalidOperationException">Thrown when no <see cref="ILoggerFactory"/> entry
/// can be found in the framework's list of services defined by <see cref="IServiceCollection"/>.
/// </exception>
[Obsolete(
"Please call services.AddSimpleInjector(options => { options.AddLogging(); } instead on " +
"the IServiceCollection instance. For more information, see: " +
"https://simpleinjector.org/servicecollection. " +
"Will be treated as an error from version 4.9. Will be removed in version 5.0.",
error: false)]
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
public static SimpleInjectorUseOptions UseLogging(this SimpleInjectorUseOptions options)
{
if (options is null)
{
throw new ArgumentNullException(nameof(options));
}
if (options.Container.ContainerScope.GetItem(AddLoggingKey) != null)
{
throw new InvalidOperationException(
$"You already initialized logging by calling the {nameof(AddLogging)} extension " +
$"method. {nameof(UseLogging)} and {nameof(AddLogging)} are mutually exclusive—" +
$"they can't be used together. Prefer using {nameof(AddLogging)} as " +
"this method is obsolete.");
}
// Both RootLogger and Logger<T> depend on ILoggerFactory
VerifyLoggerFactoryAvailable(options.Services);
// Register logger factory explicitly. This allows the Logger<T> conditional registration to work
// even when auto cross wiring is disabled.
options.Container.RegisterInstance(options.Builder.GetRequiredFrameworkService<ILoggerFactory>());
Type genericLoggerType = GetGenericLoggerType();
options.Container.RegisterConditional(
typeof(ILogger),
c => c.Consumer is null
? typeof(RootLogger)
: genericLoggerType.MakeGenericType(c.Consumer.ImplementationType),
Lifestyle.Singleton,
_ => true);
return options;
}
/// <summary>
/// Allows components that are built by Simple Injector to depend on the (non-generic)
/// <see cref="IStringLocalizer">Microsoft.Extensions.Localization.IStringLocalizer</see> abstraction.
/// Components are injected with an contextual implementation. Using this method, application
/// components can simply depend on <b>IStringLocalizer</b> instead of its generic counter part,
/// <b>IStringLocalizer<T></b>, which simplifies development.
/// </summary>
/// <param name="options">The options.</param>
/// <returns>The supplied <paramref name="options"/>.</returns>
/// <exception cref="ArgumentNullException">
/// Thrown when <paramref name="options"/> is a null reference.</exception>
/// <exception cref="InvalidOperationException">Thrown when no <see cref="IStringLocalizerFactory"/>
/// entry can be found in the framework's list of services defined by <see cref="IServiceCollection"/>.
/// </exception>
/// <exception cref="ActivationException">Thrown when an <see cref="IStringLocalizer"/> is directly
/// resolved from the container. Instead use <see cref="IStringLocalizer"/> within a constructor
/// dependency.</exception>
public static SimpleInjectorAddOptions AddLocalization(this SimpleInjectorAddOptions options)
{
if (options is null)
{
throw new ArgumentNullException(nameof(options));
}
EnsureMethodOnlyCalledOnce(options, nameof(AddLocalization), AddLocalizationKey);
VerifyStringLocalizerFactoryAvailable(options.Services);
// Cross-wire IStringLocalizerFactory explicitly, because auto cross-wiring might be disabled.
options.Container.RegisterSingleton(
() => options.GetRequiredFrameworkService<IStringLocalizerFactory>());
Type genericLocalizerType = GetGenericLocalizerType();
options.Container.RegisterConditional(
typeof(IStringLocalizer),
c => c.Consumer is null
? throw new ActivationException(
"IStringLocalizer is being resolved directly from the container, but this is not " +
"supported as string localizers need to be related to a consuming type. Instead, " +
"make IStringLocalizer a constructor dependency of the type it is used in.")
: genericLocalizerType.MakeGenericType(c.Consumer.ImplementationType),
Lifestyle.Singleton,
_ => true);
return options;
}
/// <summary>
/// Allows components that are built by Simple Injector to depend on the (non-generic)
/// <see cref="IStringLocalizer">Microsoft.Extensions.Localization.IStringLocalizer</see> abstraction.
/// Components are injected with an contextual implementation. Using this method, application
/// components can simply depend on <b>IStringLocalizer</b> instead of its generic counter part,
/// <b>IStringLocalizer<T></b>, which simplifies development.
/// </summary>
/// <param name="options">The options.</param>
/// <returns>The supplied <paramref name="options"/>.</returns>
/// <exception cref="ArgumentNullException">
/// Thrown when <paramref name="options"/> is a null reference.</exception>
/// <exception cref="InvalidOperationException">Thrown when no <see cref="IStringLocalizerFactory"/>
/// entry can be found in the framework's list of services defined by <see cref="IServiceCollection"/>.
/// </exception>
/// <exception cref="ActivationException">Thrown when an <see cref="IStringLocalizer"/> is directly
/// resolved from the container. Instead use <see cref="IStringLocalizer"/> within a constructor
/// dependency.</exception>
[Obsolete(
"Please call services.AddSimpleInjector(options => { options.AddLocalization(); } instead on " +
"the IServiceCollection instance. For more information, see: " +
"https://simpleinjector.org/servicecollection. " +
"Will be treated as an error from version 4.9. Will be removed in version 5.0.",
error: false)]
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
public static SimpleInjectorUseOptions UseLocalization(this SimpleInjectorUseOptions options)
{
if (options is null)
{
throw new ArgumentNullException(nameof(options));
}
if (options.Container.ContainerScope.GetItem(AddLocalizationKey) != null)
{
throw new InvalidOperationException(
$"You already initialized logging by calling the {nameof(AddLocalization)} extension " +
$"method. {nameof(UseLocalization)} and {nameof(AddLocalization)} are mutually " +
$"exclusive—they can't be used together. Prefer using {nameof(AddLocalization)} as " +
"this method is obsolete.");
}
VerifyStringLocalizerFactoryAvailable(options.Services);
// registration to work even when auto cross wiring is disabled.
options.Container.RegisterInstance(
options.Builder.GetRequiredFrameworkService<IStringLocalizerFactory>());
Type genericLocalizerType = GetGenericLocalizerType();
options.Container.RegisterConditional(
typeof(IStringLocalizer),
c => c.Consumer is null
? throw new ActivationException(
"IStringLocalizer is being resolved directly from the container, but this is not " +
"supported as string localizers need to be related to a consuming type. Instead, " +
"make IStringLocalizer a constructor dependency of the type it is used in.")
: genericLocalizerType.MakeGenericType(c.Consumer.ImplementationType),
Lifestyle.Singleton,
_ => true);
return options;
}
/// <summary>
/// Cross wires an ASP.NET Core or third-party service to the container, to allow the service to be
/// injected into components that are built by Simple Injector.
/// </summary>
/// <typeparam name="TService">The type of service object to cross-wire.</typeparam>
/// <param name="options">The options.</param>
/// <returns>The supplied <paramref name="options"/>.</returns>
/// <exception cref="ArgumentNullException">Thrown when the parameter is a null reference.
/// </exception>
[Obsolete(
"Please call services.AddSimpleInjector(options => { options.CrossWire<TService>(); } instead " +
"on the IServiceCollection instance (typically from inside your Startup.ConfigureServices " +
"method). For more information, see: https://simpleinjector.org/servicecollection. " +
"Will be treated as an error from version 4.9. Will be removed in version 5.0.",
error: false)]
public static SimpleInjectorUseOptions CrossWire<TService>(this SimpleInjectorUseOptions options)
where TService : class
{
return CrossWire(options, typeof(TService));
}
/// <summary>
/// Cross wires an ASP.NET Core or third-party service to the container, to allow the service to be
/// injected into components that are built by Simple Injector.
/// </summary>
/// <param name="options">The options.</param>
/// <param name="serviceType">The type of service object to ross-wire.</param>
/// <returns>The supplied <paramref name="options"/>.</returns>
/// <exception cref="ArgumentNullException">Thrown when one of the parameters is a null reference.
/// </exception>
[Obsolete(
"Please call services.AddSimpleInjector(options => { options.CrossWire(Type); } instead " +
"on the IServiceCollection instance (typically from inside your Startup.ConfigureServices " +
"method). For more information, see: https://simpleinjector.org/servicecollection. " +
"Will be treated as an error from version 4.9. Will be removed in version 5.0.",
error: false)]
public static SimpleInjectorUseOptions CrossWire(
this SimpleInjectorUseOptions options, Type serviceType)
{
if (options is null)
{
throw new ArgumentNullException(nameof(options));
}
if (serviceType is null)
{
throw new ArgumentNullException(nameof(serviceType));
}
Registration registration = CreateCrossWireRegistration(
options.Builder,
options.ApplicationServices,
serviceType,
DetermineLifestyle(serviceType, options.Services));
options.Container.AddRegistration(serviceType, registration);
return options;
}
private static void VerifyLoggerFactoryAvailable(IServiceCollection services)
{
var descriptor = FindServiceDescriptor(services, typeof(ILoggerFactory));
if (descriptor is null)
{
throw new InvalidOperationException(
$"A registration for the {typeof(ILoggerFactory).FullName} is missing from the ASP.NET " +
"Core configuration system. This is most likely caused by a missing call to services" +
".AddLogging() as part of the ConfigureServices(IServiceCollection) method of the " +
"Startup class. The .AddLogging() extension method is part of the LoggingService" +
"CollectionExtensions class of the Microsoft.Extensions.Logging assembly.");
}
else if (descriptor.Lifetime != ServiceLifetime.Singleton)
{
// By default, the LoggerFactory implementation is registered using auto-wiring (so not with
// ImplementationInstance) which means we have to support that as well.
throw new InvalidOperationException(
$"Although a registration for {typeof(ILoggerFactory).FullName} exists in the ASP.NET " +
$"Core configuration system, the registration is not added as Singleton. Instead the " +
$"registration exists as {descriptor.Lifetime}. This might be caused by a third-party " +
"library that replaced .NET Core's default ILoggerFactory. Make sure that you use one " +
"of the AddSingleton overloads to register ILoggerFactory. Simple Injector does not " +
"support ILoggerFactory to be registered with anything other than Singleton.");
}
}
private static void VerifyStringLocalizerFactoryAvailable(IServiceCollection services)
{
var descriptor = FindServiceDescriptor(services, typeof(IStringLocalizerFactory));
if (descriptor is null)
{
throw new InvalidOperationException(
$"A registration for the {typeof(IStringLocalizerFactory).FullName} is missing from " +
"the ASP.NET Core configuration system. This is most likely caused by a missing call " +
"to services.AddLocalization() as part of the ConfigureServices(IServiceCollection) " +
"method of the Startup class. The .AddLocalization() extension method is part of the " +
"LocalizationServiceCollectionExtensions class of the Microsoft.Extensions.Localization" +
" assembly.");
}
else if (descriptor.Lifetime != ServiceLifetime.Singleton)
{
// By default, the IStringLocalizerFactory implementation is registered using auto-wiring
// (so not with ImplementationInstance) which means we have to support that as well.
throw new InvalidOperationException(
$"Although a registration for {typeof(IStringLocalizerFactory).FullName} exists in the " +
"ASP.NET Core configuration system, the registration is not added as Singleton. " +
$"Instead the registration exists as {descriptor.Lifetime}. This might be caused by a " +
"third-party library that replaced .NET Core's default IStringLocalizerFactory. Make " +
"sure that you use one of the AddSingleton overloads to register " +
"IStringLocalizerFactory. Simple Injector does not support IStringLocalizerFactory to " +
"be registered with anything other than Singleton.");
}
}
private static void RegisterServiceScope(SimpleInjectorAddOptions options)
{
options.Container.Register(
() => options.ServiceScopeFactory.CreateScope(),
Lifestyle.Scoped);
}
private static SimpleInjectorAddOptions GetOptions(Container container)
{
var options =
(SimpleInjectorAddOptions?)container.ContainerScope.GetItem(AddOptionsKey);
if (options is null)
{
throw new InvalidOperationException(
"Please ensure the " +
$"{nameof(SimpleInjectorServiceCollectionExtensions.AddSimpleInjector)} extension " +
"method is called on the IServiceCollection instance before using this method.");
}
return options;
}
private static void AddAutoCrossWiring(SimpleInjectorAddOptions options)
{
// By using ContainerLocking, we ensure that this ResolveUnregisteredType registration is made
// after all possible ResolveUnregisteredType registrations the users did themselves.
options.Container.Options.ContainerLocking += (_, __) =>
{
// If there's no IServiceProvider, the property will throw, which is something we want to do
// at this point, not later on, when an unregistered type is resolved.
IServiceProvider provider = options.ApplicationServices;
options.Container.ResolveUnregisteredType += (_, e) =>
{
if (!e.Handled)
{
Type serviceType = e.UnregisteredServiceType;
ServiceDescriptor? descriptor = FindServiceDescriptor(options.Services, serviceType);
if (descriptor != null)
{
Registration registration =
CreateCrossWireRegistration(
options,
provider,
serviceType,
ToLifestyle(descriptor.Lifetime));
e.Register(registration);
}
}
};
};
}
private static Lifestyle DetermineLifestyle(Type serviceType, IServiceCollection services)
{
var descriptor = FindServiceDescriptor(services, serviceType);
// In case the service type is an IEnumerable, a registration can't be found, but collections are
// in Core always registered as Transient, so it's safe to fall back to the transient lifestyle.
return ToLifestyle(descriptor?.Lifetime ?? ServiceLifetime.Transient);
}
private static Registration CreateCrossWireRegistration(
SimpleInjectorAddOptions options,
IServiceProvider provider,
Type serviceType,
Lifestyle lifestyle)
{
var registration = lifestyle.CreateRegistration(
serviceType,
lifestyle == Lifestyle.Singleton
? BuildSingletonInstanceCreator(serviceType, provider)
: BuildScopedInstanceCreator(serviceType, options.ServiceProviderAccessor),
options.Container);
// This registration is managed and disposed by IServiceProvider and should, therefore, not be
// disposed (again) by Simple Injector.
registration.SuppressDisposal = true;
if (lifestyle == Lifestyle.Transient && typeof(IDisposable).IsAssignableFrom(serviceType))
{
registration.SuppressDiagnosticWarning(
DiagnosticType.DisposableTransientComponent,
justification: "This is a cross-wired service. It will be disposed by IServiceScope.");
}
return registration;
}
private static Func<object> BuildSingletonInstanceCreator(
Type serviceType, IServiceProvider rootProvider)
{
return () => rootProvider.GetRequiredService(serviceType);
}
private static Func<object> BuildScopedInstanceCreator(
Type serviceType, IServiceProviderAccessor accessor)
{
// The ServiceProviderAccessor allows access to a request-specific IServiceProvider. This
// allows Scoped and Transient instances to be resolved from a scope instead of the root
// container—resolving them from the root container will cause memory leaks. Specific
// framework integration (such as Simple Injector's ASP.NET Core integration) can override
// this accessor with one that allows retrieving the IServiceProvider from a web request.
return () =>
{
IServiceProvider current;
try
{
current = accessor.Current;
}
catch (ActivationException ex)
{
// The DefaultServiceProviderAccessor will throw an ActivationException in case the
// IServiceProvider (or in fact the underlying IServiceScope) is requested outside the
// context of an active scope. Here we enrich that exception message with information
// of the actual requested cross-wired service.
throw new ActivationException(
$"Error resolving the cross-wired {serviceType.ToFriendlyName()}. {ex.Message}", ex);
}
return current.GetRequiredService(serviceType);
};
}
private static ServiceDescriptor? FindServiceDescriptor(IServiceCollection services, Type serviceType)
{
// In case there are multiple descriptors for a given type, .NET Core will use the last
// descriptor when one instance is resolved. We will have to get this last one as well.
ServiceDescriptor? descriptor = services.LastOrDefault(d => d.ServiceType == serviceType);
if (descriptor == null && serviceType.GetTypeInfo().IsGenericType)
{
// In case the registration is made as open-generic type, the previous query will return
// null, and we need to go find the last open generic registration for the service type.
var serviceTypeDefinition = serviceType.GetTypeInfo().GetGenericTypeDefinition();
descriptor = services.LastOrDefault(d => d.ServiceType == serviceTypeDefinition);
}
return descriptor;
}
private static Lifestyle ToLifestyle(ServiceLifetime lifetime)
{
switch (lifetime)
{
case ServiceLifetime.Singleton: return Lifestyle.Singleton;
case ServiceLifetime.Scoped: return Lifestyle.Scoped;
default: return Lifestyle.Transient;
}
}
private static void AddSimpleInjectorOptions(Container container, SimpleInjectorAddOptions builder)
{
var current = container.ContainerScope.GetItem(AddOptionsKey);
if (current is null)
{
container.ContainerScope.SetItem(AddOptionsKey, builder);
}
else
{
throw new InvalidOperationException(
$"The {nameof(AddSimpleInjector)} extension method can only be called once.");
}
}
private static void TrySetDefaultScopedLifestyle(Container container)
{
if (container.Options.DefaultScopedLifestyle is null)
{
container.Options.DefaultScopedLifestyle = new AsyncScopedLifestyle();
}
}
private static void HookAspNetCoreHostHostedServiceServiceProviderInitialization(
SimpleInjectorAddOptions options)
{
// ASP.NET Core 3's new Host class resolves hosted services much earlier in the pipeline. This
// registration ensures that the IServiceProvider is assigned before such resolve takes place,
// to ensure that that hosted service can be injected with cross-wired dependencies.
options.Services.AddSingleton<IHostedService>(provider =>
{
options.SetServiceProviderIfNull(provider);
// We can't return null here, so we return an empty implementation.
return new NullSimpleInjectorHostedService();
});
}
private static void EnsureMethodOnlyCalledOnce(
SimpleInjectorAddOptions options, string methodName, object key)
{
if (options.Container.ContainerScope.GetItem(key) != null)
{
throw new InvalidOperationException(
$"The {methodName} extension method can only be called once on a Container instance.");
}
else
{
options.Container.ContainerScope.SetItem(key, new object());
}
}
// We prefer using the Microsoft.Logger<T> type directly, but this is only something that can be done
// when that type has exactly one public constructor and that constructor only has a single parameter
// of type ILoggerFactory. These requirements are met in .NET Core 2 and 3, but MS might choose to add
// an extra constructor any time, in which case this integration would fail. To make the library
// forward compatible, we check whether the type still has one single constructor. If not, we fall
// back to Simple Injector's internally defined Logger<T> derivative. That type is guaranteed to have
// a single constructor.
private static Type GetGenericLoggerType() =>
typeof(Microsoft.Extensions.Logging.Logger<>).GetConstructors().Length == 1
? typeof(Microsoft.Extensions.Logging.Logger<>)
: typeof(Integration.ServiceCollection.Logger<>);
// We do exactly the same thing for StringLocalizer<T>.
private static Type GetGenericLocalizerType() =>
typeof(Microsoft.Extensions.Localization.StringLocalizer<>).GetConstructors().Length == 1
? typeof(Microsoft.Extensions.Localization.StringLocalizer<>)
: typeof(Integration.ServiceCollection.StringLocalizer<>);
private sealed class NullSimpleInjectorHostedService : IHostedService
{
public Task StartAsync(CancellationToken cancellationToken) => Task.CompletedTask;
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
}
}
}