Подключение простого инжектора в WebForms в .NET 4.7.2 - PullRequest
0 голосов
/ 31 мая 2018

С изменениями в .NET 4.7.2 внедрение конструкторов теперь возможно в веб-формах.Я получил Simple Injector, работающий с веб-формами, но хотел бы получить некоторую информацию о том, есть ли какие-то "ошибки", которые я мог бы пропустить

Во-первых, у меня есть регистрация самих страниц, которая взята с здесь .

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, имеющим внутренний конструктор.Используя код из здесь Я смог исправить эту проблему и любые другие, которые могли возникнуть из внутренних конструкторов.Вот код для этой реализации для полноты.

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, я не знаю, сколько из этого для контейнера.

RightТеперь решение работает.Страницы загружаются без ошибок.Контейнер передает вызов Verify.

Достаточно ли этого или есть еще работа?Есть ли какие-то "ошибки", которые мне не хватает?Я не очень хорошо знаком с более глубокими внутренними принципами работы простого инжектора эфира или веб-форм, поэтому я боюсь, что мне может не хватать чего-то огромного.

На данный момент нет никакой необходимости в планах и не существует каких-либо планов.контейнеры с областью действия.

Ответы [ 2 ]

0 голосов
/ 01 июня 2018

IMO, эта новая функция в веб-формах не очень хорошо продумана.Основная проблема заключается в том, что веб-формы нарушают контракт IServiceProvider.

Метод IServiceProvider.GetService определяет, что null должен быть возвращен, если такой службы не существует.Но как только вы на самом деле возвращаете null, например, когда вы не можете создать этот тип, веб-формы бросают NullReferenceException из глубины своего стека.

С другой стороны, веб-формы соответствовали быабстракция IServiceProvider, включающая Simple Injector, была бы вопросом одного утверждения, поскольку SimpleInjector.Container фактически реализует IServiceProvider:

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

Кроме того, когда IServiceProviderчерез HttpRuntime.WebObjectActivator веб-формы будут вызывать его практически для всего, даже для своих внутренних объектов, что, на мой взгляд, не имеет большого смысла.

Поэтому вместо предоставления 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 для создания типа.

Обратите внимание, что при использовании этой реализации вам не нужно custom IConstructorSelectionBehavior, поэтому вы можете полностью удалить InternalConstructorResolutionBehavior.

0 голосов
/ 01 июня 2018

Мы опубликуем Adapter (Activator) nupkg (и вскоре блог), который использует контейнер Unity, а также откроет его с открытым исходным кодом.Вот некоторые общие рекомендации по реализации Адаптера (Активатора).

  1. Если контейнер IoC не может разрешить тип serviceType, вы можете рассмотреть возможность кэширования этого типа.В следующий раз вы можете создать экземпляр с помощью прямого отражения, чтобы получить некоторое улучшение производительности.
  2. Вы можете сохранить адаптер (активатор), зарегистрированный до вашей регистрации, и попробовать этот адаптер (активатор), если ваш не может разрешить тип serviceType.
  3. Если в контейнере IoC реализован IDisposable, ваш адаптер (активатор) должен реализовать IRegisteredObject, где может быть вызван Container.Dispose.
...