随着.NET 4.7.2的更改,现在可以在Web窗体中进行构造函数注入。我已经使用简单注入器使用Web窗体,但是想要一些输入,如果有任何“陷阱”我可能会丢失。
首先,我自己注册了页面,这些页面取自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);
}
}
当从上面的链接使用属性注入方法时,这很有效。我把它包括在这里是为了完整。
当我第一次连线时,有一个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。
这还不够还是还有更多的工作要做?我错过了什么“陷阱”?我不太熟悉以太简单注入器或WebForms的更深层次的内部工作,所以我担心我可能会遗漏一些巨大的东西。
截至目前,没有必要也没有计划任何范围的容器。
IMO,Web Forms中的这一新功能并未得到特别深思。主要问题是Web Forms打破了IServiceProvider
合同。
IServiceProvider.GetService
方法定义如果不存在此类服务,则应返回null
。但是,一旦你真正返回null
,例如当你无法构造那种类型时,Web Forms会从堆栈的深处抛出一个NullReferenceException
。
另一方面,Web Forms是否符合IServiceProvider
抽象,插入Simple Injector本来就是一个单一的陈述,因为SimpleInjector.Container
实际上实现了IServiceProvider
:
// WARNING: This won’t work
HttpRuntime.WebObjectActivator = container;
最重要的是,当IServiceProvider
通过HttpRuntime.WebObjectActivator
设置时,Web Forms几乎可以调用它,即使对于它自己的内部对象也是如此,对我而言,这对我来说毫无意义。
因此,您不必提供与IServiceProvider
合同兼容的IServiceProvider
实现,而是必须提供特殊的ASP.NET Web Forms兼容的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);
此实现验证类型是否包含公共构造函数,如果是,则将调用委托给Simple Injector,它将构造类型。否则,它将使用Activator.CreateInstance
来构造类型。
请注意,使用此实现您不需要自定义IConstructorSelectionBehavior
,因此您可以完全删除InternalConstructorResolutionBehavior
。
我们将发布一个使用Unity容器的Adapter(Activator)nupkg(以及很快的博客),也将开源它。以下是实现Adapter(Activator)的一般指导。