c# - 在 .NET 4.7.2 的 WebForms 中连接简单注入(inject)器

标签 c# dependency-injection webforms simple-injector

随着 .NET 4.7.2 中的更改,现在可以在 Web 窗体中进行构造函数注入(inject)。我已经获得了使用 Web 窗体的简单注入(inject)器,但想知道是否有任何我可能遗漏的“陷阱”。

首先我有页面本身的注册,它取自 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);
    }
}

这在使用上面链接中的属性注入(inject)方法时效果很好。为了完整起见,我将其包含在此处。

当我第一次连接它时,一个具有内部构造函数的 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。

这就足够了吗?还有更多工作要做吗?有没有我遗漏的“陷阱”?我不太熟悉 ether Simple Injector 或 WebForms 的更深层内部工作原理,所以我担心我可能会遗漏一些重要的东西。

截至目前,没有必要也没有计划使用任何作用域容器。

最佳答案

IMO,Web 表单中的这项新功能并没有经过深思熟虑。主要问题是 Web 窗体破坏了 IServiceProvider 契约。

IServiceProvider.GetService 方法定义如果不存在此类服务,则应返回 null。但是一旦你实际返回了 null,例如当您无法构造该类型时,Web 窗体会从其堆栈深处抛出一个 NullReferenceException

另一方面,如果 Web 窗体符合 IServiceProvider 抽象,插入 Simple Injector 将是一个单一语句的问题,因为 SimpleInjector.Container 实际上实现了 IServiceProvider:

// WARNING: This won’t work
HttpRuntime.WebObjectActivator = container; 

最重要的是,当通过 HttpRuntime.WebObjectActivator 设置 IServiceProvider 时,Web Forms 几乎会为所有事情调用它,甚至是它自己的内部对象,对我来说,没有什么意义。

因此,您必须提供一个特殊的 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)构造函数,如果是,它将调用委托(delegate)给将构造该类型的简单注入(inject)器。否则,它将使用 Activator.CreateInstance 来构造类型。

请注意,使用此实现您不需要自定义IConstructorSelectionBehavior,因此您可以完全删除InternalConstructorResolutionBehavior

关于c# - 在 .NET 4.7.2 的 WebForms 中连接简单注入(inject)器,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50632627/

相关文章:

angular - 导入的服务在抽象类上未定义

android - 使用 Dagger 注入(inject) RecyclerView.Adapter

angularjs - 是什么导致 AngularJS 指令中的依赖项注入(inject)失败?

ASP.NET 成员(member)提供商和 Azure

c# - ASP.NET WebForms WebSite - 控件在代码隐藏中而不是在前端更改它们的值

c# - 挂接到类库的 "OnLoad"

c# - 这有设计模式吗?

c# - 如何向 C# .NET Core 单元测试添加调试日志记录

c# - 无法使用 AutoCompleteBox WebForm 获取 DataValueField

c# - 是否可以将异步方法作为 c# 中事件处理程序的回调?