Как использовать инъекцию зависимостей с помощью веб-форм ASP.NET
Я пытаюсь разработать способ использования инъекции зависимостей с элементами управления ASP.NET Web Forms.
У меня есть множество элементов управления, которые напрямую создают репозитории, и используют их для доступа и привязки к данным и т.д.
Я ищу шаблон, в котором я могу передать репозитории внешним элементам управления (IoC), поэтому мои элементы управления не знают о том, как создаются репозитории и откуда они пришли и т.д.
Я бы предпочел не иметь зависимости от контейнера IoC от своих элементов управления, поэтому просто хочу иметь возможность создавать элементы управления с помощью конструктора или свойства.
(И чтобы просто усложнить ситуацию, эти элементы управления строятся и размещаются на странице с помощью CMS во время выполнения!)
Любые мысли?
Ответы
Ответ 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, если захотите. О, и я также использую структуру запросов/команд, которая является не связанной, но больше о том, что можно найти здесь:
Ограничьте свои абстракции: Рефакторинг для сокращения абстракций