В MVC с контроллером, имеющим несколько конструкторов, как я могу указать, какой конструктор вызывать? - PullRequest
1 голос
/ 03 августа 2011

Часто бывает так, что я хочу иметь несколько конструкторов в моих контроллерах, поскольку один из конструкторов оптимален для ручного внедрения зависимостей во время модульного тестирования, а другой - для использования контейнера IOC для внедрения зависимостей.

При использовании стандартного сервисного локатора или DependencyResolver в MVC 3, есть ли способ указать фабрике контроллеров, какой конструктор использовать при активации контроллера?

Я бы предпочел не использовать один из атрибутов, специфичных для инфраструктуры IOC, поскольку я хочу, чтобы код оставался независимым от контейнера IOC.

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

Мой код будет выглядеть так:

public class SomeController : Controller
{
    private ISomeService _SomeService;
    private IEntityConverter _EntityConverter;

    // this would be used during unit tests when mocking the entity converter 
    public SomeController(ISomeService someService, IEntityConverter entityConverter)
    {
        _SomeService = someService;
        _EntityConverter = entityConverter;
    }

    // this would be used when injecting via an IOC container, where it would be tedious
    // to always specify the implementations for the converters
    public SomeController(ISomeService someService)
        : this(someService, new EntityConverter())
    {
    }

    public ActionResult Index(SomeViewModel someViewModel)
    {
        if (!ModelState.IsValid)
            return View(someViewModel);
        else
        {
            ServiceInput serviceInput = _EntityConverter.ConvertToServiceInput(someViewModel);
            _SomeService.DoSomething(serviceInput);
            return View("Success");
        }
    }
}

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

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

Возможно, это не лучшее решение. Я открыт для лучших альтернатив.

Ответы [ 4 ]

3 голосов
/ 03 августа 2011

Не используйте несколько конструкторов . Ваши классы должны иметь одно определение того, какие зависимости ему нужны. Это определение лежит в конструкторе, и поэтому у него должен быть только 1 открытый конструктор.

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

Одна хитрость, которая мне помогает, - это позволить null ссылкам вставляться в конструктор, не проверяя аргументы конструктора. Это очень удобно для модульных тестов в тех случаях, когда в тестируемом классе не используется зависимость, в то время как в производственной среде вы все равно будете иметь гарантию, что null не будет введен, поскольку контейнеры IOC никогда не вставят null в конструктор (или, по крайней мере, ни один из контейнеров, которые я знаю).

1 голос
/ 03 августа 2011

Это все связано с вашим сервис-локатором, вам нужно настроить его там, где вы регистрируете свои типы.

Вот пример использования конструктора по умолчанию без параметров при использовании Unity.

Обратите внимание, что по умолчанию в сервисном локаторе будет использоваться самый специфический конструктор (тот, у которого больше всего параметров)

ServiceLocator.Current.RegisterType<ICache, HttpContextCache>(new InjectionConstructor()); //use default constructor

Так зачем мне это нужно?Это правда, что я мог бы создать защищенный виртуальный метод для разрешения HttpContextBase и использовать извлечение и переопределение в тесте, или, в противном случае, еще один интерфейс с методом, который создает HttpContextBase, который можно вставить в этот класс ....сказать единице, чтобы использовать конструктор по умолчанию.

    private readonly HttpContextBase _httpContextBase;

    public HttpContextCache()
    {
        _httpContextBase = new HttpContextWrapper(HttpContext.Current);
    }

    public HttpContextCache(HttpContextBase httpContextBase)
    {
        _httpContextBase = httpContextBase;
    }

Это был редкий случай, и почти все другие классы в проекте имеют только один конструктор

1 голос
/ 03 августа 2011

Похоже на запах кода, который вам нужен явный конструктор для тестирования.Что если тестирующий ctor работает, а производственный - нет?Похоже, дверь для ошибок. Это было бы идеально для тестов, чтобы использовать тот же путь, что и реальные клиенты, насколько это возможно.

Что отличается между ними?вы используете какой-либо DI на основе атрибутов (например, MEF), я бы не слишком беспокоился о разделении.Наличие дополнительного атрибута (и соответствующей ссылки) не должно быть таким большим хитом ... если только вы не предвидите изменения в технологии МОК в будущем.

1 голос
/ 03 августа 2011

Может быть, я неправильно понимаю ваш вопрос, но я не уверен, почему вы захотите протестировать разные конструкторы. Тесты должны проверить, что на самом деле будет использоваться.

Для ваших тестов вы должны смоделировать свои зависимости, чтобы им не требовался контейнер IoC.

Использование Moq :

Контроллер:

public SomeController(IService1 service1, IService2 service2)
{
    // Do some cool stuff
}

Метод испытания:

[Test]
public void Some_Action_In_SomeController_Should_Do_Something()
{
    var service1Mock = new Mock<IService1>();
    var service2Mock = new Mock<IService2>();
    var controller = new SomeController(service1Mock.Object, service2Mock.Object);

    controller.Action();

    // Assert stuff
}
...