在 .NET 4.7.2 的 WebForms 中连接简单注入器
Wiring up Simple Injector in WebForms in .NET 4.7.2
随着 .NET 4.7.2 中的更改,现在可以在 Web 窗体中进行构造函数注入。我已经获得了使用 Web 窗体的简单注入器,但想要一些关于是否有任何 "gotchas" 我可能会丢失的信息。
首先,我从 here.
中获取了页面本身的注册信息
public static void RegisterWebPages(this Container container)
{
var pageTypes =
from assembly in BuildManager.GetReferencedAssemblies().Cast<Assembly>()
where !assembly.IsDynamic
where !assembly.GlobalAssemblyCache
from type in assembly.GetExportedTypes()
where type.IsSubclassOf(typeof(Page))
where !type.IsAbstract && !type.IsGenericType
select type;
foreach (Type type in pageTypes)
{
var reg = Lifestyle.Transient.CreateRegistration(type, container);
reg.SuppressDiagnosticWarning(
DiagnosticType.DisposableTransientComponent,
"ASP.NET creates and disposes page classes for us.");
container.AddRegistration(type, reg);
}
}
这在使用上面 link 中的 属性 注入方法时有效。为了完整起见,我将其包含在这里。
当我第一次连接它时,有一个 OutputCacheModule
有一个内部构造函数的问题。使用 here 中的代码,我能够解决该问题以及可能由内部构造函数引起的任何其他问题。为了完整性,这是该实现的代码。
public class InternalConstructorResolutionBehavior : IConstructorResolutionBehavior
{
private IConstructorResolutionBehavior original;
public InternalConstructorResolutionBehavior(Container container)
{
this.original = container.Options.ConstructorResolutionBehavior;
}
public ConstructorInfo GetConstructor(Type implementationType)
{
if (!implementationType.GetConstructors().Any())
{
var internalCtors = implementationType.GetConstructors(
BindingFlags.Instance | BindingFlags.NonPublic)
.Where(c => !c.IsPrivate)
.ToArray();
if (internalCtors.Length == 1) return internalCtors.First();
}
return original.GetConstructor(implementationType);
}
}
现在已经讲完了背景故事,下面是问题的实质。这是我连接的自定义激活器。
public class SimpleInjectorWebFormsActivator : IServiceProvider
{
private readonly Container container;
public SimpleInjectorWebFormsActivator(Container container)
{
this.container = container;
this.container.Options.DefaultScopedLifestyle = new WebRequestLifestyle();
this.container.Options.ConstructorResolutionBehavior =
new InternalConstructorResolutionBehavior(this.container);
}
public object GetService(Type serviceType)
{
return container.GetInstance(serviceType);
}
}
问题是,GetService
方法够用吗?目前很少有关于如何使用 WebForms 的新扩展点的信息。有一个 Autofac 示例比我简单的一行传递到 Simple Injector 要复杂得多,但由于我不熟悉 Autofac,我不知道其中有多少是用于容器的。
现在解决方案有效。页面加载没有错误。容器将调用传递给 Verify。
这够了吗还是还有更多工作要做?有没有我遗漏的"gotchas"?我不太熟悉 ether Simple Injector 或 WebForms 的更深层内部工作原理,所以我担心我可能会遗漏一些重要的东西。
截至目前,没有必要也没有计划使用任何作用域容器。
我们将发布一个使用 Unity 容器的 Adapter(Activator) nupkg(以及一个博客),并且还将开源它。以下是实施 Adapter(Activator) 的一些一般指导。
- 如果 IoC 容器无法解析 serviceType,您可以考虑缓存该类型。下次可以直接通过反射创建实例来获得一些性能增益。
- 您可以保留注册前注册的Adapter(Activator),如果您的Adapter(Activator)无法解析serviceType,请尝试使用该Adapter(Activator)。
- 如果 IoC 容器实现了 IDisposable,您的 Adapter(Activator) 应该实现 IRegisteredObject,其中 Container.Dispose 可以被调用。
IMO,Web 窗体中的这个新功能没有经过特别深思熟虑。主要问题是 Web 窗体违反了 IServiceProvider
合同。
IServiceProvider.GetService
方法定义 null
如果不存在此类服务,则应 returned。但是一旦你实际上 return null
,例如当您无法构造该类型时,Web 窗体会从其堆栈深处抛出一个 NullReferenceException
。
另一方面,如果 Web 窗体符合 IServiceProvider
抽象,插入简单注入器将是一个单一语句的问题,因为 SimpleInjector.Container
实际上实现了 IServiceProvider
:
// WARNING: This won’t work
HttpRuntime.WebObjectActivator = container;
最重要的是,当通过 HttpRuntime.WebObjectActivator
设置 IServiceProvider
时,Web 窗体几乎会调用它的所有内容,甚至是它自己的内部对象,这对我来说意义不大.
因此,您必须提供一个特殊的 ASP.NET Web 表单兼容 IServiceProvider
实现(因此 违反 合同)。
请注意,大多数 DI 容器实际上实现了 IServiceProvider
,但您会看到大多数都失败了,因为违反了合同。
适配器实现如下所示:
class SimpleInjectorWebFormsServiceActivator : IServiceProvider
{
private const BindingFlags flag =
BindingFlags.Instance | BindingFlags.NonPublic |
BindingFlags.Public | BindingFlags.CreateInstance;
private readonly Container container;
public SimpleInjectorWebFormsServiceActivator(Container container) =>
this.container = container;
public object GetService(Type serviceType) =>
serviceType.GetConstructors().Length > 0
? this.container.GetInstance(serviceType)
: Activator.CreateInstance(serviceType, flag, null, null, null);
}
并且可以设置如下:
HttpRuntime.WebObjectActivator =
new SimpleInjectorWebFormsServiceActivator(container);
此实现验证类型是否包含 public 构造函数,如果是,它将调用委托给将构造类型的简单注入器。否则,它将使用 Activator.CreateInstance
来构造类型。
请注意,使用此实现您 不需要 自定义 IConstructorSelectionBehavior
,因此您可以完全删除 InternalConstructorResolutionBehavior
。
随着 .NET 4.7.2 中的更改,现在可以在 Web 窗体中进行构造函数注入。我已经获得了使用 Web 窗体的简单注入器,但想要一些关于是否有任何 "gotchas" 我可能会丢失的信息。
首先,我从 here.
中获取了页面本身的注册信息public static void RegisterWebPages(this Container container)
{
var pageTypes =
from assembly in BuildManager.GetReferencedAssemblies().Cast<Assembly>()
where !assembly.IsDynamic
where !assembly.GlobalAssemblyCache
from type in assembly.GetExportedTypes()
where type.IsSubclassOf(typeof(Page))
where !type.IsAbstract && !type.IsGenericType
select type;
foreach (Type type in pageTypes)
{
var reg = Lifestyle.Transient.CreateRegistration(type, container);
reg.SuppressDiagnosticWarning(
DiagnosticType.DisposableTransientComponent,
"ASP.NET creates and disposes page classes for us.");
container.AddRegistration(type, reg);
}
}
这在使用上面 link 中的 属性 注入方法时有效。为了完整起见,我将其包含在这里。
当我第一次连接它时,有一个 OutputCacheModule
有一个内部构造函数的问题。使用 here 中的代码,我能够解决该问题以及可能由内部构造函数引起的任何其他问题。为了完整性,这是该实现的代码。
public class InternalConstructorResolutionBehavior : IConstructorResolutionBehavior
{
private IConstructorResolutionBehavior original;
public InternalConstructorResolutionBehavior(Container container)
{
this.original = container.Options.ConstructorResolutionBehavior;
}
public ConstructorInfo GetConstructor(Type implementationType)
{
if (!implementationType.GetConstructors().Any())
{
var internalCtors = implementationType.GetConstructors(
BindingFlags.Instance | BindingFlags.NonPublic)
.Where(c => !c.IsPrivate)
.ToArray();
if (internalCtors.Length == 1) return internalCtors.First();
}
return original.GetConstructor(implementationType);
}
}
现在已经讲完了背景故事,下面是问题的实质。这是我连接的自定义激活器。
public class SimpleInjectorWebFormsActivator : IServiceProvider
{
private readonly Container container;
public SimpleInjectorWebFormsActivator(Container container)
{
this.container = container;
this.container.Options.DefaultScopedLifestyle = new WebRequestLifestyle();
this.container.Options.ConstructorResolutionBehavior =
new InternalConstructorResolutionBehavior(this.container);
}
public object GetService(Type serviceType)
{
return container.GetInstance(serviceType);
}
}
问题是,GetService
方法够用吗?目前很少有关于如何使用 WebForms 的新扩展点的信息。有一个 Autofac 示例比我简单的一行传递到 Simple Injector 要复杂得多,但由于我不熟悉 Autofac,我不知道其中有多少是用于容器的。
现在解决方案有效。页面加载没有错误。容器将调用传递给 Verify。
这够了吗还是还有更多工作要做?有没有我遗漏的"gotchas"?我不太熟悉 ether Simple Injector 或 WebForms 的更深层内部工作原理,所以我担心我可能会遗漏一些重要的东西。
截至目前,没有必要也没有计划使用任何作用域容器。
我们将发布一个使用 Unity 容器的 Adapter(Activator) nupkg(以及一个博客),并且还将开源它。以下是实施 Adapter(Activator) 的一些一般指导。
- 如果 IoC 容器无法解析 serviceType,您可以考虑缓存该类型。下次可以直接通过反射创建实例来获得一些性能增益。
- 您可以保留注册前注册的Adapter(Activator),如果您的Adapter(Activator)无法解析serviceType,请尝试使用该Adapter(Activator)。
- 如果 IoC 容器实现了 IDisposable,您的 Adapter(Activator) 应该实现 IRegisteredObject,其中 Container.Dispose 可以被调用。
IMO,Web 窗体中的这个新功能没有经过特别深思熟虑。主要问题是 Web 窗体违反了 IServiceProvider
合同。
IServiceProvider.GetService
方法定义 null
如果不存在此类服务,则应 returned。但是一旦你实际上 return null
,例如当您无法构造该类型时,Web 窗体会从其堆栈深处抛出一个 NullReferenceException
。
另一方面,如果 Web 窗体符合 IServiceProvider
抽象,插入简单注入器将是一个单一语句的问题,因为 SimpleInjector.Container
实际上实现了 IServiceProvider
:
// WARNING: This won’t work
HttpRuntime.WebObjectActivator = container;
最重要的是,当通过 HttpRuntime.WebObjectActivator
设置 IServiceProvider
时,Web 窗体几乎会调用它的所有内容,甚至是它自己的内部对象,这对我来说意义不大.
因此,您必须提供一个特殊的 ASP.NET Web 表单兼容 IServiceProvider
实现(因此 违反 合同)。
请注意,大多数 DI 容器实际上实现了 IServiceProvider
,但您会看到大多数都失败了,因为违反了合同。
适配器实现如下所示:
class SimpleInjectorWebFormsServiceActivator : IServiceProvider
{
private const BindingFlags flag =
BindingFlags.Instance | BindingFlags.NonPublic |
BindingFlags.Public | BindingFlags.CreateInstance;
private readonly Container container;
public SimpleInjectorWebFormsServiceActivator(Container container) =>
this.container = container;
public object GetService(Type serviceType) =>
serviceType.GetConstructors().Length > 0
? this.container.GetInstance(serviceType)
: Activator.CreateInstance(serviceType, flag, null, null, null);
}
并且可以设置如下:
HttpRuntime.WebObjectActivator =
new SimpleInjectorWebFormsServiceActivator(container);
此实现验证类型是否包含 public 构造函数,如果是,它将调用委托给将构造类型的简单注入器。否则,它将使用 Activator.CreateInstance
来构造类型。
请注意,使用此实现您 不需要 自定义 IConstructorSelectionBehavior
,因此您可以完全删除 InternalConstructorResolutionBehavior
。