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

Я пытаюсь разработать способ использования внедрения зависимостей с элементами управления веб-форм ASP.NET.

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

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

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

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

Есть предположения?

Ответов (6)

Решение

ОБНОВЛЕНИЕ 2019 : с введением веб-форм 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 можно в файле 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 с частичным доверием кажется безнадежным делом .

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

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, если хотите. О, и я также использую структуру запроса / команды, которая не связана, но больше о причинах, лежащих в основе этого, можно найти здесь:

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

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

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

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

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

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

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

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

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