Есть ли хороший / правильный способ решения проблемы цикла внедрения зависимостей в учебнике ASP.NET MVC ContactsManager? - PullRequest
22 голосов
/ 21 сентября 2009

Если вы не знаете, о чем я говорю, либо пройдите учебное пособие и попробуйте добавить инъекцию зависимости самостоятельно, либо попытайте удачу, объяснив проблему.

Примечание: Эта проблема не входит в рамки исходного руководства по ASP.NET. В учебнике только предполагается, что используемые шаблоны удобны для внедрения зависимостей.

Проблема в основном в том, что между контроллером, ModelStateWrapper и ContactManagerService существует цикл зависимости.

  1. Конструктор ContactController использует IContactManagerService.
  2. Конструктор ContactManagerService принимает IContactManagerRepository (не важно) и IValidationDictionary (который реализует ModelStateWrapper) .
  3. Конструктор ModelStateWrapper принимает ModelStateDictionary (это свойство называется "ModelState" на контроллере) .

Таким образом, цикл зависимости выглядит следующим образом: Controller> Service> ModelStateWrapper> Controller

Если вы попытаетесь добавить внедрение зависимости к этому, произойдет сбой. Итак, мой вопрос: что мне с этим делать? Другие опубликовали этот вопрос, но ответов немного, они разные, и все кажутся "взломанными".

Мое текущее решение состоит в том, чтобы удалить IModelStateWrapper из конструктора IService и добавить вместо него метод Initialize, например, так:

public class ContactController : Controller
{
    private readonly IContactService _contactService;

    public ContactController(IContactService contactService)
    {
        _contactService = contactService;
        contactService.Initialize(new ModelStateWrapper(ModelState));
    }

    //Class implementation...
}

public class ContactService : IContactService
{
    private IValidationDictionary _validationDictionary;
    private readonly IContactRepository _contactRepository;

    public ContactService(IContactRepository contactRepository)
    {
        _contactRepository = contactRepository;
    }

    private void Initialize(IValidationDictionary validationDictionary)
    {
        if(validationDictionary == null)
            throw new ArgumentNullException("validationDictionary");

        _validationDictionary = validationDictionary;
    }

    //Class implementation...
}

public class ModelStateWrapper : IValidationDictionary
{
    private readonly ModelStateDictionary _modelState;

    public ModelStateWrapper(ModelStateDictionary modelState)
    {
        _modelState = modelState;
    }

    //Class implementation...
}

С помощью этой конструкции я могу настроить свой контейнер Unity следующим образом:

public static void ConfigureUnityContainer()
{
    IUnityContainer container = new UnityContainer();

    // Registrations
    container.RegisterTypeInHttpRequestLifetime<IContactRepository, EntityContactRepository>();
    container.RegisterTypeInHttpRequestLifetime<IContactService, ContactService>();

    ControllerBuilder.Current.SetControllerFactory(new UnityControllerFactory(container));
}

К сожалению, это означает, что метод "Initialize" в службе должен вызываться вручную конструктором контроллера. Есть ли способ лучше? Может быть, где я как-то включаю IValidationDictionary в мою конфигурацию единства? Должен ли я переключиться на другой контейнер DI? Я что-то упустил?

Ответы [ 3 ]

11 голосов
/ 21 сентября 2009

Как правило, циклические зависимости указывают на недостаток дизайна - я думаю, что могу с уверенностью сказать это, поскольку вы не являетесь первоначальным автором кода:)

Я бы не стал считать метод Initialize хорошим решением. Если вы не имеете дело со сценарием надстройки (а у вас нет), метод инъекции не является правильным решением. Вы уже почти поняли это, поскольку считаете неудовлетворительным то, что вам нужно вручную вызывать его, потому что ваш DI-контейнер не может.

Если я не полностью ошибаюсь, ContactController не требуется экземпляр IValidationDictionary до вызова его методов Action?

Если это так, самым простым решением, вероятно, было бы определить интерфейс IValidationDictionaryFactory и заставить конструктор ContactController взять экземпляр этого интерфейса.

Этот интерфейс может быть определен так:

public interface IValidationDictionaryFactory
{
    IValidationDictionary Create(Controller controller);
}

Любой метод Action на контроллере, которому требуется экземпляр IValidationDictionary, может затем вызвать метод Create для получения экземпляра.

Реализация по умолчанию будет выглядеть примерно так:

public class DefaultValidationDictionaryFactory : IValidationDictionaryFactory
{
    public IValidationDictionary Create(Controller controller)
    {
        return controller.ModelState;
    }
}
2 голосов
/ 01 ноября 2009

Как насчет небольшого изменения / улучшения дизайна примерно так: http://forums.asp.net/t/1486130.aspx

1 голос
/ 21 сентября 2009

У каждого контроллера есть виртуальный метод Инициализируйте , чтобы делать подобные вещи.

Я думаю, что нет лучшего способа, потому что IValidationDictionary - это уровень абстракции между вашим текущим запросом / контроллером / состоянием модели и IContactService. Внедрение состояния модели контроллеров в сервис, а затем внедрение сервиса в контроллер просто невозможно с помощью инжектора конструктора. Надо быть первым.

Может быть, есть способ использования свойства? Но я думаю, что это тоже будет сложно.

...