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

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.

4b9b3361

Ответ 1

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

Из двух ваших решений мне не нравится второй, особенно из-за ссылки на контейнер IoC в вашем коде, который является плохим IMO.

Ответ 2

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

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

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