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

Как использовать Castle Windsor с веб-формами ASP.Net?

Я пытаюсь подключить инъекцию зависимостей с помощью Windsor к стандартным веб-формам asp.net. Я думаю, что я достиг этого, используя HttpModule и CustomAttribute (код, показанный ниже), хотя решение кажется немного неуклюжим и задается вопросом, есть ли лучшее поддерживаемое решение из коробки с помощью Windsor?

Здесь показано несколько файлов, которые показаны здесь

    // index.aspx.cs
    public partial class IndexPage : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            Logger.Write("page loading");
        }

        [Inject]
        public ILogger Logger { get; set; }
    }

    // WindsorHttpModule.cs
    public class WindsorHttpModule : IHttpModule
    {
        private HttpApplication _application;
        private IoCProvider _iocProvider;

        public void Init(HttpApplication context)
        {
            _application = context;
            _iocProvider = context as IoCProvider;

            if(_iocProvider == null)
            {
                throw new InvalidOperationException("Application must implement IoCProvider");
            }

            _application.PreRequestHandlerExecute += InitiateWindsor;
        }

        private void InitiateWindsor(object sender, System.EventArgs e)
        {
            Page currentPage = _application.Context.CurrentHandler as Page;
            if(currentPage != null)
            {
                InjectPropertiesOn(currentPage);
                currentPage.InitComplete += delegate { InjectUserControls(currentPage); };
            }
        }

        private void InjectUserControls(Control parent)
        {
            if(parent.Controls != null)
            {
                foreach (Control control in parent.Controls)
                {
                    if(control is UserControl)
                    {
                        InjectPropertiesOn(control);
                    }
                    InjectUserControls(control);
                }
            }
        }

        private void InjectPropertiesOn(object currentPage)
        {
            PropertyInfo[] properties = currentPage.GetType().GetProperties();
            foreach(PropertyInfo property in properties)
            {
                object[] attributes = property.GetCustomAttributes(typeof (InjectAttribute), false);
                if(attributes != null && attributes.Length > 0)
                {
                    object valueToInject = _iocProvider.Container.Resolve(property.PropertyType);
                    property.SetValue(currentPage, valueToInject, null);
                }
            }
        }
    }

    // Global.asax.cs
    public class Global : System.Web.HttpApplication, IoCProvider
    {
        private IWindsorContainer _container;

        public override void Init()
        {
            base.Init();

            InitializeIoC();
        }

        private void InitializeIoC()
        {
            _container = new WindsorContainer();
            _container.AddComponent<ILogger, Logger>();
        }

        public IWindsorContainer Container
        {
            get { return _container; }
        }
    }

    public interface IoCProvider
    {
        IWindsorContainer Container { get; }
    }
4b9b3361

Ответ 1

Я думаю, что вы в основном на правильном пути. Если вы еще этого не сделали, я бы предложил взглянуть на Rhino Igloo, структуру WebForms MVC, Здесь хороший блог в этом, а источник здесь - Айенде (автор Rhino Igloo) решает проблему использования Windsor с webforms достаточно хорошо в этом проекте/библиотеке.

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

Последний из всех spring.net подходит к этому более ориентированно на конфигурацию, но, возможно, стоит взглянуть на их реализацию - здесь хороший ссылка на сообщение в блоге.

Ответ 2

Здесь изменена версия кода OP, которая (i) кэширует введенные свойства, чтобы избежать повторных вызовов отражения, (ii) освобождает все разрешенные компоненты, (iii) инкапсулирует доступ к контейнеру, чтобы не подвергать реализацию.

// global.asax.cs
public class Global : HttpApplication
{
    private static IWindsorContainer _container;

    protected void Application_Start(object sender, EventArgs e)
    {
        _container = new WindsorContainer();
        _container.Install(FromAssembly.This());
    }

    internal static object Resolve(Type type)
    {
        return _container.Resolve(type);
    }

    internal static void Release(object component)
    {
        _container.Release(component);
    }

    //...
}

// WindsorHttpModule.cs
public class WindsorHttpModule : IHttpModule
{
    // cache the properties to inject for each page
    private static readonly ConcurrentDictionary<Type, PropertyInfo[]> InjectedProperties = new ConcurrentDictionary<Type, PropertyInfo[]>();
    private HttpApplication _context;

    public void Init(HttpApplication context)
    {
        _context = context;
        _context.PreRequestHandlerExecute += InjectProperties;
        _context.EndRequest += ReleaseComponents;
    }

    private void InjectProperties(object sender, EventArgs e)
    {
        var currentPage = _context.Context.CurrentHandler as Page;
        if (currentPage != null)
        {
            InjectProperties(currentPage);
            currentPage.InitComplete += delegate { InjectUserControls(currentPage); };
        }
    }

    private void InjectUserControls(Control parent)
    {
        foreach (Control control in parent.Controls)
        {
            if (control is UserControl)
            {
                InjectProperties(control);
            }
            InjectUserControls(control);
        }
    }

    private void InjectProperties(Control control)
    {
        ResolvedComponents = new List<object>();
        var pageType = control.GetType();

        PropertyInfo[] properties;
        if (!InjectedProperties.TryGetValue(pageType, out properties))
        {
            properties = control.GetType().GetProperties()
                .Where(p => p.GetCustomAttributes(typeof(InjectAttribute), false).Length > 0)
                .ToArray();
            InjectedProperties.TryAdd(pageType, properties);
        }

        foreach (var property in properties)
        {
            var component = Global.Resolve(property.PropertyType);
            property.SetValue(control, component, null);
            ResolvedComponents.Add(component);
        }
    }

    private void ReleaseComponents(object sender, EventArgs e)
    {
        var resolvedComponents = ResolvedComponents;
        if (resolvedComponents != null)
        {
            foreach (var component in ResolvedComponents)
            {
                Global.Release(component);
            }
        }
    }

    private List<object> ResolvedComponents
    {
        get { return (List<object>)HttpContext.Current.Items["ResolvedComponents"]; }
        set { HttpContext.Current.Items["ResolvedComponents"] = value; }
    }

    public void Dispose()
    { }

}

Ответ 3

Недавно я начал работу в компании, где есть много устаревших приложений для веб-форм, поэтому это выглядит как настоящий интересный подход и может предложить дальнейший путь, если мы хотим добавить DI на существующие веб-страницы, спасибо.

Один момент, который я заметил, это то, что метод Injection использует container.Resolve для явного решения компонентов, поэтому я думаю, что нам, возможно, понадобится сделать container.Release на компонентах, когда выгружает страницы.

Если у нас есть переходные компоненты и мы не делаем этого, мы можем столкнуться с утечками памяти. Не знаете, как будут работать элементы с образцами образа Per Web Request (т.е. Windsor забирает их в конце веб-запроса, даже если мы их явно разрешаем), но здесь тоже может играть безопасно.

Следовательно, возможно, потребуется расширить модуль, чтобы отслеживать компоненты, которые он разрешает, и освобождать их, чтобы Windsor знал, когда их очищать.

Ответ 4

Одна из недостатков в принятых ответах заключалась в том, что модуль http должен быть зарегистрирован в файле web.config(в зависимости от приложения), прежде чем модуль действительно разрешит зависимости на страницах с кодом. Что вам нужно:

<system.webServer>
    <modules>
      <add name="ClassNameForHttpModuleHere" type="NamespaceForClass"/>
    </modules>
  </system.webServer>

Кроме того, принятые решения работали как прелесть.

Ссылка на веб-сайт Microsoft для добавления http-модулей: https://msdn.microsoft.com/en-us/library/ms227673.aspx

Ответ 5

Вместо того, чтобы делать это так, вы также можете использовать распознаватель типа непосредственно с чем-то вроде:

ILogger Logger = ResolveType.Of<ILogger>();