Как использовать 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; }
}
Ответы
Ответ 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>();