Как предотвратить неправильное использование конструктора в классе С#

Я пытаюсь реализовать слабосвязанное приложение в приложении asp.net MVC5. У меня есть контроллер:

public class HeaderController : Controller
    {
        private IMenuService _menuService;

        public HeaderController(IMenuService menuService)
        {
            this._menuService = menuService;
        }

        //
        // GET: /Header/
        public ActionResult Index()
        {

            return View();
        }


        public ActionResult GetMenu()
        {

            MenuItem menu = this._menuService.GetMenu();
            return View("Menu", menu);

        }

    }

И сервис, используемый в этом контроллере:

public class MenuService : IMenuService
{
    private IMenuRespository _menuRepository;

    public MenuService(IMenuRespository menuRepository)
    {
        this._menuRepository = menuRepository;
    }

    public MenuItem GetMenu()
    {
        return this._menuRepository.GetMenu();
    }
}

И репозиторий, который используется в классе службы:

public class MenuRepository : IMenuRespository
    {
        public MenuItem GetMenu()
        {
            //return the menu items
        }
    }

Интерфейсы, используемые для службы и репозитория, таковы:

 public interface IMenuService
    {
        MenuItem GetMenu();
    }

public interface IMenuRespository
    {
        MenuItem GetMenu();
    }

Конструктор для HeaderController принимает в MenuService с помощью Injection конструктора, и у меня есть ninject в качестве контейнера DI, обрабатывающего это.

Все работает отлично - кроме того, в моем контроллере я все еще могу сделать это:

MenuItem menu = new MenuService(new MenuRepository());

... который разрушает архитектуру. Как я могу предотвратить использование "нового" таким образом?

Ответ 1

Один из способов сделать это - переместить ваши интерфейсы и реализации в отдельные проекты/сборки Visual Studio и только ссылаться на проект реализации в проекте, который на самом деле нуждается в нем - все остальное может ссылаться на проект интерфейса для вашего IMenuService - в этот момент код может потреблять интерфейс, но фактически не обновлять какие-либо реализации.

Затем вы можете ссылаться на проект реализации, где бы вы ни находились в ваших зависимостях.

Решение WebApp:

WebApp Proj (контроллеры и т.д.) → Service Interface Proj

Сервис Impl Project → Service Interface Proj

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

Ответ 2

Я предполагаю, что часть проблем с ручным созданием объекта может возникнуть при работе с большой командой, в результате чего некоторые члены используют метод инсталляции конструктора неправильно. Если это так, я нашел многого, просветив их в ракурсе, разрешенном большинством проблем. Иногда вы находите, что кто-то делает это неправильно, но не часто. Другой альтернативой может быть добавление атрибута [EditorBrowsable(EditorBrowsableState.Never)] в конструктор контроллера. Конструктор исчезнет из intellisense; ну, похоже, он исчезнет. Однако он все еще может быть использован.

Вы можете разбить реализации в другую DLL, а не напрямую ссылаться (неявно ссылаясь) на проект MVC, и, следовательно, поскольку нет прямой ссылки, вы не можете использовать эти типы напрямую. С интерфейсами в одном проекте, который ссылается каждый проект, а проект с реализацией косвенно ссылается, таким образом, будут включены только интерфейсы. Я бы рекомендовал включить прямую ссылку в проект unit test, если вы выполняете модульные тесты, чтобы повысить охват тестирования.

Ответ 3

Пара возможных вариантов (которые я никогда не пробовал, но мог бы иметь некоторые ноги):

  • возможно, вы можете написать правило FXCop, какие ошибки, если конструктор используется в коде.

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

Если контейнер DI использует его через отражение, все должно быть в порядке (хотя в случае FXCop вы, вероятно, не могли бы бросить, если бы это было в методе в пространстве имен NInject)

Ответ 4

В качестве общего принципа проектирования интерфейсы (контракты) должны быть в одной сборке, а реализация должна выполняться в другой сборке. Сборка Контрактов должна быть ссылкой в ​​проекте MVC, и реализованная сборка должна быть скопирована в папку "bin". Вместо использования Загрузка динамического модуля" для загрузки типов. Таким образом, вы избежите вышеупомянутой проблемы, и это более широкое решение. Поскольку вы можете заменить реализацию без создания пользовательских интерфейсов и контактных сборок.