Skip to content

Commit 9bf55ef

Browse files
committed
Controllers, ViewComponents, PageModels, and TagHelpers can now be resolved with ScopedLifestyle.Flowing. Fixes #33
1 parent beb1a9b commit 9bf55ef

File tree

7 files changed

+79
-17
lines changed

7 files changed

+79
-17
lines changed

src/SimpleInjector.Integration.AspNetCore.Mvc.Core/SimpleInjectorControllerActivator.cs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,7 @@ namespace SimpleInjector.Integration.AspNetCore.Mvc
1212
/// <summary>Controller activator for Simple Injector.</summary>
1313
public sealed class SimpleInjectorControllerActivator : IControllerActivator
1414
{
15-
private readonly ConcurrentDictionary<Type, InstanceProducer?> controllerProducers =
16-
new ConcurrentDictionary<Type, InstanceProducer?>();
15+
private readonly ConcurrentDictionary<Type, InstanceProducer?> controllerProducers = new();
1716

1817
private readonly Container container;
1918

@@ -56,7 +55,13 @@ public object Create(ControllerContext context)
5655
$"https://docs.microsoft.com/en-us/aspnet/core/mvc/advanced/app-parts.");
5756
}
5857

59-
return producer.GetInstance();
58+
var scope = context.HttpContext.GetScope();
59+
60+
// Scope will be null when the core integration's RequestScopingStartupFilter didn't run. This
61+
// can happen if this activator is used without the application being configured using the
62+
// SimpleInjectorAddOptions.AddAspNetCore() extension method. In that case we call .GetInstance()
63+
// and expect the scoping to run using the default ambient scoping mechanism (non flowing).
64+
return scope is null ? producer.GetInstance() : producer.GetInstance(scope);
6065
}
6166

6267
/// <summary>Releases the controller.</summary>

src/SimpleInjector.Integration.AspNetCore.Mvc.ViewFeatures/SimpleInjectorViewComponentActivator.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,13 @@ public object Create(ViewComponentContext context)
5656
viewComponentType.FullName));
5757
}
5858

59-
return producer.GetInstance();
59+
var scope = context.ViewContext.HttpContext.GetScope();
60+
61+
// Scope will be null when the core integration's RequestScopingStartupFilter didn't run. This
62+
// can happen if this activator is used without the application being configured using the
63+
// SimpleInjectorAddOptions.AddAspNetCore() extension method. In that case we call .GetInstance()
64+
// and expect the scoping to run using the default ambient scoping mechanism (non flowing).
65+
return scope is null ? producer.GetInstance() : producer.GetInstance(scope);
6066
}
6167

6268
/// <summary>Releases the view component.</summary>

src/SimpleInjector.Integration.AspNetCore.Mvc/SimpleInjectorPageModelActivatorProvider.cs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,17 @@ public Func<PageContext, object> CreateActivator(CompiledPageActionDescriptor de
6969
Environment.NewLine,
7070
pageModelType.FullName));
7171
}
72-
73-
return _ => producer.GetInstance();
72+
73+
return context =>
74+
{
75+
var scope = context.HttpContext.GetScope();
76+
77+
// Scope will be null when the core integration's RequestScopingStartupFilter didn't run. This
78+
// can happen if this activator is used without the application being configured using the
79+
// SimpleInjectorAddOptions.AddAspNetCore() extension method. In that case we call .GetInstance()
80+
// and expect the scoping to run using the default ambient scoping mechanism (non flowing).
81+
return scope is null ? producer.GetInstance() : producer.GetInstance(scope);
82+
};
7483
}
7584

7685
/// <summary>

src/SimpleInjector.Integration.AspNetCore.Mvc/SimpleInjectorTagHelperActivator.cs

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,7 @@ namespace SimpleInjector.Integration.AspNetCore.Mvc
1313
/// <summary>Tag Helper Activator for Simple Injector.</summary>
1414
public class SimpleInjectorTagHelperActivator : ITagHelperActivator
1515
{
16-
private readonly ConcurrentDictionary<Type, InstanceProducer> tagHelperProducers =
17-
new ConcurrentDictionary<Type, InstanceProducer>();
16+
private readonly ConcurrentDictionary<Type, InstanceProducer> tagHelperProducers = new();
1817

1918
private readonly Container container;
2019
private readonly Predicate<Type> tagHelperSelector;
@@ -41,13 +40,23 @@ public SimpleInjectorTagHelperActivator(
4140
/// <inheritdoc />
4241
public TTagHelper Create<TTagHelper>(ViewContext context) where TTagHelper : ITagHelper =>
4342
this.UseSimpleInjector(typeof(TTagHelper))
44-
? (TTagHelper)this.GetInstanceFromSimpleInjector(typeof(TTagHelper))
43+
? (TTagHelper)this.GetInstanceFromSimpleInjector(typeof(TTagHelper), context)
4544
: this.activator.Create<TTagHelper>(context);
4645

4746
private bool UseSimpleInjector(Type type) => this.tagHelperSelector.Invoke(type);
4847

49-
private object GetInstanceFromSimpleInjector(Type type) =>
50-
this.tagHelperProducers.GetOrAdd(type, this.GetTagHelperProducer).GetInstance();
48+
private object GetInstanceFromSimpleInjector(Type type, ViewContext context)
49+
{
50+
var scope = context.HttpContext.GetScope();
51+
52+
var producer = this.tagHelperProducers.GetOrAdd(type, this.GetTagHelperProducer);
53+
54+
// Scope will be null when the core integration's RequestScopingStartupFilter didn't run. This
55+
// can happen if this activator is used without the application being configured using the
56+
// SimpleInjectorAddOptions.AddAspNetCore() extension method. In that case we call .GetInstance()
57+
// and expect the scoping to run using the default ambient scoping mechanism (non flowing).
58+
return scope is null ? producer.GetInstance() : producer.GetInstance(scope);
59+
}
5160

5261
// Find the registration for the tag helper in the container
5362
// and fallback to creating one when no registration exists.

src/SimpleInjector.Integration.AspNetCore/OnePerNestedScopeServiceProviderAccessor.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@ internal OnePerNestedScopeServiceProviderAccessor(Container container, IServiceP
2424

2525
// Only the scope wrapping the request (the root scope) contains the HttpContext in its Items dictionary.
2626
// With nested scopes, this property returns null.
27-
private HttpContext? RootScopeHttpContext =>
28-
this.CurrentScope?.GetItem(RequestScopingStartupFilter.HttpContextKey) as HttpContext;
27+
private HttpContext? RootScopeHttpContext => this.CurrentScope?.GetHttpContext();
2928

29+
// TODO: How to handle this?
3030
private Scope? CurrentScope => Lifestyle.Scoped.GetCurrentScope(this.container);
3131
}
3232
}

src/SimpleInjector.Integration.AspNetCore/RequestScopingStartupFilter.cs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,6 @@ namespace SimpleInjector.Integration.AspNetCore
1111

1212
internal sealed class RequestScopingStartupFilter : IStartupFilter
1313
{
14-
internal static readonly object HttpContextKey = new object();
15-
1614
private readonly Container container;
1715

1816
public RequestScopingStartupFilter(Container container)
@@ -32,13 +30,17 @@ public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next)
3230

3331
private void ConfigureRequestScoping(IApplicationBuilder builder)
3432
{
33+
bool flowing = this.container.Options.DefaultScopedLifestyle == ScopedLifestyle.Flowing;
34+
3535
builder.Use(async (httpContext, next) =>
3636
{
37-
Scope scope = AsyncScopedLifestyle.BeginScope(this.container);
37+
Scope scope = flowing
38+
? new(this.container)
39+
: AsyncScopedLifestyle.BeginScope(this.container);
3840

3941
try
4042
{
41-
scope.SetItem(HttpContextKey, httpContext);
43+
httpContext.ConnectToScope(scope);
4244

4345
await next();
4446
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Copyright (c) Simple Injector Contributors. All rights reserved.
2+
// Licensed under the MIT License. See LICENSE file in the project root for license information.
3+
4+
namespace SimpleInjector.Integration.AspNetCore
5+
{
6+
using Microsoft.AspNetCore.Http;
7+
using SimpleInjector;
8+
9+
/// <summary>
10+
/// Extension methods on top of HttpContext
11+
/// </summary>
12+
public static class SimpleInjectorHttpContextExtensions
13+
{
14+
private static readonly object HttpContextKey = new();
15+
16+
/// <summary>
17+
/// Gets the <see cref="Scope"/> connected to the supplied <see cref="HttpContext"/>.
18+
/// </summary>
19+
/// <param name="httpContext">The <see cref="HttpContext"/> to retrieve the <see cref="Scope"/> from.</param>
20+
/// <returns>The scope or null.</returns>
21+
public static Scope? GetScope(this HttpContext httpContext) => (Scope)httpContext.Items[HttpContextKey];
22+
23+
internal static HttpContext? GetHttpContext(this Scope scope) => scope.GetItem(HttpContextKey) as HttpContext;
24+
25+
internal static void ConnectToScope(this HttpContext httpContext, Scope scope)
26+
{
27+
scope.SetItem(HttpContextKey, httpContext);
28+
httpContext.Items[HttpContextKey] = scope;
29+
}
30+
}
31+
}

0 commit comments

Comments
 (0)