Подтвердить что ты не робот

Как использовать инъекцию зависимостей с помощью веб-форм ASP.NET

Я пытаюсь разработать способ использования инъекции зависимостей с элементами управления ASP.NET Web Forms.

У меня есть множество элементов управления, которые напрямую создают репозитории, и используют их для доступа и привязки к данным и т.д.

Я ищу шаблон, в котором я могу передать репозитории внешним элементам управления (IoC), поэтому мои элементы управления не знают о том, как создаются репозитории и откуда они пришли и т.д.

Я бы предпочел не иметь зависимости от контейнера IoC от своих элементов управления, поэтому просто хочу иметь возможность создавать элементы управления с помощью конструктора или свойства.

(И чтобы просто усложнить ситуацию, эти элементы управления строятся и размещаются на странице с помощью CMS во время выполнения!)

Любые мысли?

4b9b3361

Ответ 1

ОБНОВЛЕНИЕ 2019: С появлением Web Forms 4.7.2 улучшена поддержка DI. Это лишает законной силы ниже. См.: Подключение простого инжектора в WebForms в .NET 4.7.2

Вы можете использовать автоматическое внедрение конструктора, заменив PageHandlerFactory по умолчанию на пользовательский. Таким образом, вы можете использовать перегруженный конструктор для загрузки зависимостей. Ваша страница может выглядеть так:

public partial class HomePage : System.Web.UI.Page
{
    private readonly IDependency dependency;

    public HomePage(IDependency dependency)
    {
        this.dependency = dependency;
    }

    // Do note this protected ctor. You need it for this to work.
    protected HomePage () { }
}

Настроить этот пользовательский PageHandlerFactory можно в PageHandlerFactory web.config следующим образом:

<?xml version="1.0"?>
<configuration>
  <system.web>
    <httpHandlers>
      <add verb="*" path="*.aspx"
        type="YourApp.CustomPageHandlerFactory, YourApp"/>
    </httpHandlers>
  </system.web>
</configuration>

Ваша CustomPageHandlerFactory может выглядеть так:

public class CustomPageHandlerFactory : PageHandlerFactory
{
    private static object GetInstance(Type type)
    {
        // TODO: Get instance using your favorite DI library.
        // for instance using the Common Service Locator:
        return Microsoft.Practices.ServiceLocation
            .ServiceLocator.Current.GetInstance(type);
    }

    public override IHttpHandler GetHandler(HttpContext cxt, 
        string type, string vPath, string path)
    {
        var page = base.GetHandler(cxt, type, vPath, path);

        if (page != null)
        {
            // Magic happens here ;-)
            InjectDependencies(page);
        }

        return page;
    }

    private static void InjectDependencies(object page)
    {
        Type pageType = page.GetType().BaseType;

        var ctor = GetInjectableCtor(pageType);

        if (ctor != null)
        {
            object[] arguments = (
                from parameter in ctor.GetParameters()
                select GetInstance(parameter.ParameterType)
                .ToArray();

            ctor.Invoke(page, arguments);
        }
    }

    private static ConstructorInfo GetInjectableCtor(
        Type type)
    {
        var overloadedPublicConstructors = (
            from constructor in type.GetConstructors()
            where constructor.GetParameters().Length > 0
            select constructor).ToArray();

        if (overloadedPublicConstructors.Length == 0)
        {
            return null;
        }

        if (overloadedPublicConstructors.Length == 1)
        {
            return overloadedPublicConstructors[0];
        }

        throw new Exception(string.Format(
            "The type {0} has multiple public " +
            "ctors and can't be initialized.", type));
    }
}

Недостатком является то, что это работает только тогда, когда ваша сторона работает в режиме полного доверия. Вы можете прочитать больше об этом здесь. Но учтите, что разработка приложений ASP.NET с частичным доверием кажется безнадежным делом.

Ответ 2

Autofac поддерживает довольно ненавязчивую инъекцию зависимостей в ASP.NET WebForms. Я понимаю, что это просто перехватывает жизненный цикл страницы ASP.NET с помощью http-модуля и делает инъекцию свойств. Единственный улов в том, что для элементов управления я не думаю, что это происходит до после события Init.

Ответ 3

Лучший способ - иметь базовый класс для таких элементов управления, как:

public class PartialView : UserControl
{
    protected override void OnInit(System.EventArgs e)
    {
        ObjectFactory.BuildUp(this);
        base.OnInit(e);
    }
}

Это будет вводить любой элемент управления, который наследуется от этого базового класса (использует structmap). Объединив это с конфигурацией, основанной на свойствах, вы сможете иметь такие элементы управления, как:

public partial class AdminHeader : PartialView
{
   IMyRepository Repository{get;set;}
}

Обновление 1: Если вы не можете наследовать элементы управления, возможно, у CMS есть крючок сразу после создания элементов управления, там вы можете вызвать BuildUp. Кроме того, если CMS позволяет вам перехватывать что-то, чтобы извлечь экземпляр, вы можете использовать инъекцию на основе конструктора, но я предпочитаю BuildUp для этого конкретного сценария, поскольку asp.net не имеет для этого крюка.

Ответ 4

Начиная с .NET 4.7.2 (что нового), теперь разработчикам легко использовать Dependency Injection в приложениях WebForms. С помощью UnityAdapter вы можете добавить его в существующее приложение WebForms за 4 простых шага. Смотрите этот блог.

Ответ 5

Вы также можете создать несколько экземпляров singleton в событии global.asax Application_Start и сделать их доступными как общедоступные статические свойства readonly.

Ответ 6

Это решение, которое я недавно использовал, чтобы не подключаться к конвейеру (я нахожу, что смущает всех, кто смотрит на мой код в будущем, но да, я также вижу его преимущества):

public static class TemplateControlExtensions
{
    static readonly PerRequestObjectManager perRequestObjectManager = new PerRequestObjectManager();

    private static WIIIPDataContext GetDataContext(this TemplateControl templateControl)
    {
        var dataContext = (WIIIPDataContext) perRequestObjectManager.GetValue("DataContext");

        if (dataContext == null) 
        {
           dataContext = new WIIIPDataContext();
           perRequestObjectManager.SetValue("DataContext", dataContext);   
        }

        return dataContext;
    }

    public static IMailer GetMailer(this TemplateControl templateControl)
    {
        return (IMailer)IoC.Container.Resolve(typeof(IMailer));
    }

    public static T Query<T>(this TemplateControl templateControl, Query<T> query)
    {
        query.DataContext = GetDataContext(templateControl);
        return query.GetQuery();
    }

    public static void ExecuteCommand(this TemplateControl templateControl, Command command)
    {
        command.DataContext = GetDataContext(templateControl);
        command.Execute();
    }

    private class PerRequestObjectManager
    {
        public object GetValue(string key)
        {
            if (HttpContext.Current != null && HttpContext.Current.Items.Contains(key))
                return HttpContext.Current.Items[key];
            else
                return null;
        }

        public void SetValue(string key, object newValue)
        {
            if (HttpContext.Current != null)
                HttpContext.Current.Items[key] = newValue;
        }
    }
}

Это показывает, как вы можете легко создать свой собственный менеджер времени жизни, а также подключиться к контейнеру IoC, если захотите. О, и я также использую структуру запросов/команд, которая является не связанной, но больше о том, что можно найти здесь:

Ограничьте свои абстракции: Рефакторинг для сокращения абстракций