问题描述
随着 .NET 4.7.2 中的变化,现在可以在 Web 窗体中注入构造函数.我已经让 Simple Injector 与 Web Forms 一起工作,但是想要一些关于是否有任何我可能遗漏的陷阱"的输入.
With the changes in .NET 4.7.2, constructor injection is now possible in Web Forms. I have gotten Simple Injector working with Web Forms, but would like some input as to if there any "gotchas" I might be missing.
首先,我从此处注册了页面本身.
First I have the registration of the Pages themselves which is taken from 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);
}
}
这在使用上面链接中的属性注入方法时很有效.为了完整起见,我将其包括在此处.
This has worked when using the property injection method from the link above just fine. I am including it here for completeness.
当我第一次连接它时,一个具有内部构造函数的 OutputCacheModule
存在问题.使用 此处 中的代码,我能够解决该问题以及可能存在的任何其他问题由内部构造函数产生.为了完整起见,这是该实现的代码.
When I wired it up the first time, there was an issue with one OutputCacheModule
having an internal constructor. Using the code from here I was able to fix that issue and any others that might have arisen from internal constructors. Here is the code for that implementation for completeness.
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);
}
}
现在抛开背景故事,这是问题的核心.这是我连接的自定义激活器.
Now with the backstory out of the way, here is the meat of the question. This is the custom activator I have wired up.
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,我不知道其中有多少用于容器.
The question is, is the GetService
method enough? There is very little out there right now about how to use the new extension point for WebForms. There is an Autofac example that is significantly more complex than my simple one line pass through to Simple Injector, but as I am not familiar with Autofac I don't know how much of that is for the container.
现在该解决方案有效.页面加载没有错误.容器将调用传递给验证.
Right now the solution works. Pages load without error. The container passes the call to Verify.
这就够了还是还有更多的工作要做?有没有我遗漏的陷阱"?我对 ether Simple Injector 或 WebForms 的更深层次的内部工作不太熟悉,所以我担心我可能会遗漏一些重要的东西.
Is this enough or is there more work to be done? Are there any "gotchas" that I am missing? I am not very familiar with the deeper inner workings of ether Simple Injector or WebForms, so I'm worried I might be missing something huge.
截至目前,没有必要也没有计划有任何作用域容器.
As of right now there is no need nor plans for there to be any scoped containers.
推荐答案
IMO,Web 窗体中的这个新功能并没有经过深思熟虑.主要问题是 Web Forms 违反了 IServiceProvider
契约.
IMO, this new feature in Web Forms is not particularly well thought through. The main problem is that Web Forms breaks the IServiceProvider
contract.
IServiceProvider.GetService
方法定义了如果不存在这样的服务,则应该返回 null
.但是一旦你真正返回 null
,例如当您无法构造该类型时,Web 表单会从其堆栈的深处抛出 NullReferenceException
.
The IServiceProvider.GetService
method defines that null
should be returned if no such service exists. But once you actually return null
, e.g. when you can’t construct that type, Web Forms throws a NullReferenceException
from deep down its stack.
另一方面,Web 表单是否符合 IServiceProvider
抽象,插入 Simple Injector 将是一个单一语句的问题,因为 SimpleInjector.Container
实际实现了IServiceProvider
:
Would Web Forms, on the other hand, have conformed to the IServiceProvider
abstraction, plugging in Simple Injector would have been a matter of a single statement, since SimpleInjector.Container
actually implements IServiceProvider
:
// WARNING: This won’t work
HttpRuntime.WebObjectActivator = container;
最重要的是,当通过 HttpRuntime.WebObjectActivator
设置了 IServiceProvider
时,Web 窗体几乎会为所有内容调用它,甚至是它自己的内部对象,其中,对我来说,毫无意义.
On top of this, when an IServiceProvider
is set through HttpRuntime.WebObjectActivator
, Web Forms will call it for almost everything, even for its own internal objects, which, to me, makes little sense.
因此,您不必提供与 IServiceProvider
合同兼容的 IServiceProvider
实现,而是必须提供一个特殊的 ASP.NET与 Web 表单兼容的 IServiceProvider
实现(因此破坏合同).
Therefore, instead of supplying an IServiceProvider
implementation that is compatible to the IServiceProvider
contract, you will have to provide a special ASP.NET Web Forms-compatible IServiceProvider
implementation (which therefore breaks the contract).
请注意,大多数 DI 容器实际上都实现了 IServiceProvider
,但是您会看到其中大部分都失败了,因为违反了合同.
Note that most DI Containers actually implement IServiceProvider
, but you would see most of them fail, because of this contract breach.
适配器实现如下所示:
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
来构造类型.
This implementation verifies whether the type contains public constructors and if so, it delegates the call to Simple Injector, which will construct the type. Otherwise, it will use Activator.CreateInstance
to construct the type.
请注意,使用此实现您不需要自定义IConstructorSelectionBehavior
,因此您可以完全删除您的InternalConstructorResolutionBehavior
.
Do note that using this implementation you don’t need custom IConstructorSelectionBehavior
, so you can remove your InternalConstructorResolutionBehavior
altogether.
这篇关于在 .NET 4.7.2 中连接 WebForms 中的简单注入器的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持编程学习网!