IoC/DI в лице winforms и другого сгенерированного кода
При использовании зависимостей (DI) и инверсии объектов управления (IoC) обычно будет иметь конструктор, который принимает набор зависимостей, необходимых для правильного функционирования объекта.
Например, если у меня есть форма, которая требует, чтобы служба заполнила поле со списком, вы можете увидеть что-то вроде этого:
// my files
public interface IDataService {
IList<MyData> GetData();
}
public interface IComboDataService {
IList<MyComboData> GetComboData();
}
public partial class PopulatedForm : BaseForm {
private IDataService service;
public PopulatedForm(IDataService service) {
//...
InitializeComponent();
}
}
Это отлично работает на верхнем уровне, я просто использую свой контейнер IoC для разрешения зависимостей:
var form = ioc.Resolve<PopulatedForm>();
Но перед лицом сгенерированного кода это становится сложнее. В winforms создается второй файл, составляющий остаток частичного класса. Этот файл ссылается на другие компоненты, такие как пользовательские элементы управления, и использует конструкторы no-args для создания таких элементов управления:
// generated file: PopulatedForm.Designer.cs
public partial class PopulatedForm {
private void InitializeComponent() {
this.customComboBox = new UserCreatedComboBox();
// customComboBox has an IComboDataService dependency
}
}
Поскольку это сгенерированный код, я не могу передать зависимостей и нет простого способа, чтобы мой контейнер IoC автоматически вводил все зависимости.
Одним из решений является передача зависимостей каждого дочернего компонента до PopulatedForm
, даже если он может не нуждаться в них напрямую, например, с IComboDataService
, требуемым UserCreatedComboBox
. Затем я обязан удостовериться, что зависимости предоставлены с помощью различных свойств или методов установки. Тогда мой конструктор PopulatedForm
может выглядеть следующим образом:
public PopulatedForm(IDataService service, IComboDataService comboDataService) {
this.service = service;
InitializeComponent();
this.customComboBox.ComboDataService = comboDataService;
}
Другим возможным решением является создание конструктора no-args для выполнения необходимого разрешения:
public class UserCreatedComboBox {
private IComboDataService comboDataService;
public UserCreatedComboBox() {
if (!DesignMode && IoC.Instance != null) {
comboDataService = Ioc.Instance.Resolve<IComboDataService>();
}
}
}
Ни одно из решений не является особенно хорошим. Какие шаблоны и альтернативы доступны для более умелой обработки инъекции зависимостей перед лицом сгенерированного кода? Мне бы хотелось увидеть как общие решения, такие как шаблоны, так и те, которые относятся к С#, Winforms и Autofac.
Ответы
Ответ 1
Я считаю, что здесь нет решения серебряной пули. Я бы использовал инъекцию свойств в этом случае, чтобы оставить конструктор без параметров. Также мне лично не нравится вводить услуги в классы пользовательского интерфейса, я предпочитаю вводить там какие-то докладчики. Затем у вас есть свойство Presenter, которое будет установлено контейнером IoC, и в настройке этого свойства вы получите свой код инициализации.
Из двух ваших решений мне не нравится второй, особенно из-за ссылки на контейнер IoC в вашем коде, который является плохим IMO.
Ответ 2
Я бы сказал, что ваш пользовательский интерфейс, особенно подэлементы вашего пользовательского интерфейса, не должен нуждаться в предоставлении каких-либо услуг.
Трудно судить, насколько это возможно для вашего приложения, но MVC или MVP предназначены для того, чтобы избежать этой необходимости.
Я бы попытался перепроектировать так, чтобы контроллер отвечал за взаимодействие с сервисами, и этот контроллер дает элементам просмотра все, что им нужно, вместо того, чтобы позволить элементам представления запрашивать то, что им нужно.