diff --git a/src/SimpleInjector.CodeSamples/AutomaticParameterizedFactoryExtensions.cs b/src/SimpleInjector.CodeSamples/AutomaticParameterizedFactoryExtensions.cs index d59ec1595..124b6f08f 100644 --- a/src/SimpleInjector.CodeSamples/AutomaticParameterizedFactoryExtensions.cs +++ b/src/SimpleInjector.CodeSamples/AutomaticParameterizedFactoryExtensions.cs @@ -240,7 +240,7 @@ InstanceProducer IDependencyInjectionBehavior.GetInstanceProducer( if (local != null) { - if (consumer.Target.TargetType.IsValueType && this.container.IsVerifying()) + if (consumer.Target.TargetType.IsValueType && this.container.IsVerifying) { throw new InvalidOperationException( "You can't use Verify() if the factory product contains value types."); diff --git a/src/SimpleInjector.Integration.AspNetCore/SimpleInjectorAspNetIntegrationExtensions.cs b/src/SimpleInjector.Integration.AspNetCore/SimpleInjectorAspNetIntegrationExtensions.cs index 1dc610cd9..7a03d1d21 100644 --- a/src/SimpleInjector.Integration.AspNetCore/SimpleInjectorAspNetIntegrationExtensions.cs +++ b/src/SimpleInjector.Integration.AspNetCore/SimpleInjectorAspNetIntegrationExtensions.cs @@ -461,7 +461,7 @@ private static IHttpContextAccessor GetHttpContextAccessor(IServiceProvider appS "Type 'Microsoft.AspNetCore.Http.IHttpContextAccessor' is not available in the " + "IApplicationBuilder.ApplicationServices collection. Please make sure it is " + "registered by adding it to the ConfigureServices method as follows: " + Environment.NewLine + - "services.AddSingleton();"); + "services.AddHttpContextAccessor();"); } return accessor; diff --git a/src/SimpleInjector.Integration.Web/WebRequestLifestyle.cs b/src/SimpleInjector.Integration.Web/WebRequestLifestyle.cs index 6f409eaf2..894854c6d 100644 --- a/src/SimpleInjector.Integration.Web/WebRequestLifestyle.cs +++ b/src/SimpleInjector.Integration.Web/WebRequestLifestyle.cs @@ -1,7 +1,7 @@ #region Copyright Simple Injector Contributors /* The Simple Injector is an easy-to-use Inversion of Control library for .NET * - * Copyright (c) 2013-2016 Simple Injector Contributors + * Copyright (c) 2013-2019 Simple Injector Contributors * * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and * associated documentation files (the "Software"), to deal in the Software without restriction, including @@ -23,6 +23,7 @@ namespace SimpleInjector.Integration.Web { using System; + using System.Collections.Generic; using System.Web; /// @@ -88,17 +89,17 @@ internal static void CleanUpWebRequest(HttpContext context) { Requires.IsNotNull(context, nameof(context)); - var scope = (Scope)context.Items[ScopeKey]; + // NOTE: We explicitly don't remove the scopes from the items dictionary, because if anything + // is resolved from the container after this point during the request, that would cause the + // creation of a new Scope that will never be disposed. This would make it seem like the + // application is working, while instead we are failing silently. By not removing the scope, + // this will cause it to throw an ObjectDisposedException once it is accessed after + // this point; effectively making the application to fail fast. + var scopes = (List)context.Items[ScopeKey]; - if (scope != null) + if (!(scopes is null)) { - // NOTE: We explicitly don't remove the object from the items dictionary, because if anything - // is resolved from the container after this point during the request, that would cause the - // creation of a new Scope, that will never be disposed. This would make it seem like the - // application is working, while instead we are failing silently. By not removing the object, - // this will cause the Scope to throw an ObjectDisposedException once it is accessed after - // this point; effectively making the application to fail fast. - scope.Dispose(); + DisposeScopes(scopes); } } @@ -128,24 +129,69 @@ private static Scope GetOrCreateScope(Container container) { HttpContext context = HttpContext.Current; - if (context == null) + if (context is null) { return null; } - var scope = (Scope)context.Items[ScopeKey]; + // The assumption is that there will only be a very limited set of containers used per request + // (typically just one), which makes using a List much faster (and less memory intensive) + // than using a dictionary. + var scopes = (List)context.Items[ScopeKey]; - if (scope == null) + if (scopes is null) { - // If there are multiple container instances that run on the same request (which is a - // strange but valid scenario), all containers will get the same Scope instance for that - // request. This behavior is correct and even allows all instances that are registered for - // disposal to be disposed in reversed order of creation, independent of the container that - // created them. - context.Items[ScopeKey] = scope = new Scope(); + context.Items[ScopeKey] = scopes = new List(capacity: 2); + } + + var scope = FindScopeForContainer(scopes, container); + + if (scope is null) + { + scopes.Add(scope = new Scope(container)); } return scope; } + + private static Scope FindScopeForContainer(List scopes, Container container) + { + foreach (var scope in scopes) + { + if (scope.Container == container) + { + return scope; + } + } + + return null; + } + + private static void DisposeScopes(List scopes) + { + if (scopes.Count != 1) + { + DisposeScopesInReverseOrder(scopes); + } + else + { + // Optimization: don't create a master scope if there is only one scope (the common case). + scopes[0].Dispose(); + } + } + + private static void DisposeScopesInReverseOrder(List scopes) + { + // Here we use a 'master' scope that will hold the real scopes. This allows all scopes + // to be disposed, even if a scope's Dispose method throws an exception. Scopes will + // also be disposed in opposite order of creation. + using (var masterScope = new Scope()) + { + foreach (var scope in scopes) + { + masterScope.RegisterForDisposal(scope); + } + } + } } } \ No newline at end of file diff --git a/src/SimpleInjector/Advanced/AdvancedExtensions.cs b/src/SimpleInjector/Advanced/AdvancedExtensions.cs index 31a864e9d..469b17658 100644 --- a/src/SimpleInjector/Advanced/AdvancedExtensions.cs +++ b/src/SimpleInjector/Advanced/AdvancedExtensions.cs @@ -51,13 +51,14 @@ public static bool IsLocked(this Container container) /// The container. /// true if the specified container is verifying; otherwise, false. /// Thrown when is null. + [Obsolete("Please use Container." + nameof(Container.IsVerifying) + " instead. " + + "This method will be removed in a future release.", + error: false)] + [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] public static bool IsVerifying(this Container container) { Requires.IsNotNull(container, nameof(container)); - // Need to check, because IsVerifying will throw when its ThreadLocal is disposed. - container.ThrowWhenDisposed(); - return container.IsVerifying; } diff --git a/src/SimpleInjector/Container.Common.cs b/src/SimpleInjector/Container.Common.cs index 5e1bf06b9..c5c290174 100644 --- a/src/SimpleInjector/Container.Common.cs +++ b/src/SimpleInjector/Container.Common.cs @@ -133,7 +133,14 @@ private interface IInstanceInitializer /// false. public bool IsVerifying { - get { return this.isVerifying.Value; } + get + { + // Need to check, because IsVerifying will throw when its ThreadLocal is disposed. + this.ThrowWhenDisposed(); + + return this.isVerifying.Value; + } + private set { this.isVerifying.Value = value; } } diff --git a/src/SimpleInjector/Scope.cs b/src/SimpleInjector/Scope.cs index b736f9c53..930a9d3ba 100644 --- a/src/SimpleInjector/Scope.cs +++ b/src/SimpleInjector/Scope.cs @@ -393,7 +393,7 @@ private static TImplementation GetScopelessInstance( ScopedRegistration registration) where TImplementation : class { - if (registration.Container.IsVerifying()) + if (registration.Container.IsVerifying) { return registration.Container.VerificationScope.GetInstanceInternal(registration); }