在.NET 4.7.2中的WebForms中连接简单注入器

Bra*_*lon 6 c# dependency-injection webforms simple-injector

随着.NET 4.7.2的更改,现在可以在Web窗体中进行构造函数注入.我已经使用简单注入器使用Web窗体,但是想要一些输入,如果有任何"陷阱"我可能会丢失.

首先,我有自己的网页是从拍摄的注册这里.

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

当从上面的链接使用属性注入方法时,这很有效.我把它包括在这里是为了完整.

当我第一次连线时,有OutputCacheModule一个内部构造函数的问题.使用此处的代码,我能够解决该问题以及可能源自内部构造函数的任何其他问题.以下是该实现的完整性代码.

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

现在有了背景故事,这就是问题的关键所在.这是我连接的自定义激活器.

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

问题是,GetService方法足够吗?关于如何使用WebForms的新扩展点,现在几乎没有什么.有一个Autofac示例比我简单的一行传递给Simple Injector复杂得多,但由于我不熟悉Autofac,我不知道容器有多少.

现在解决方案有效.页面加载没有错误.容器将调用传递给Verify.

这还不够还是还有更多的工作要做?我错过了什么"陷阱"?我不太熟悉以太简单注入器或WebForms的更深层次的内部工作,所以我担心我可能会遗漏一些巨大的东西.

截至目前,没有必要也没有计划任何范围的容器.

Ste*_*ven 11

IMO,Web Forms中的这个新功能并没有特别好好地考虑过.主要问题是Web Forms违反IServiceProvider合同.

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

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

// WARNING: This won’t work
HttpRuntime.WebObjectActivator = container; 
Run Code Online (Sandbox Code Playgroud)

最重要的是,当一个人IServiceProvider通过时HttpRuntime.WebObjectActivator,Web Forms几乎会为所有内容调用它,即使对于它自己的内部对象也是如此,对我而言,这对我来说毫无意义.

因此,您不必提供与合同兼容IServiceProvider实现,而是必须提供特殊的ASP.NET Web Forms兼容实现(因此违反了合同).IServiceProviderIServiceProvider

请注意,大多数DI容器实际上都是实现的IServiceProvider,但是由于合同违约,您会看到大多数DI容器都失败了.

最好的办法是让您的自定义Activator.CreateInstance实现解析应用程序类型,并恢复IConstructorSelectionBehavior其他所有内容.实现看起来像这样:

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);
}
Run Code Online (Sandbox Code Playgroud)

并且可以设置如下:

HttpRuntime.WebObjectActivator =
    new SimpleInjectorWebFormsServiceActivator(container);
Run Code Online (Sandbox Code Playgroud)

此实现通过检查定义了哪个程序集(或表单和用户控件的基本类型)来验证类型是否为应用程序类型.如果它是一个应用程序类型,它将从Simple Injector解析并使用InternalConstructorResolutionBehavior其他方式.

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