#c_sharp #aspnet #aspnet_mvc #ninject
Закрыт. Данный вопрос необходимо конкретизировать. Ответы на него в данный момент не принимаются. Хотите улучшить этот вопрос? Переформулируйте вопрос, чтобы он был сосредоточен только на одной проблеме, отредактировав его. Закрыт 3 года назад. Объясните на пальцах про IoC-контейнер Ninject — как используется и в целом зачем все это надо? Читаю литературу, никак не могу понять. Лучше на практике один раз увидеть.
Ответы
Ответ 1
На пальцах вряд ли получится, тема сложная. Возникла она приблизительно тогда же, когда объектно-ориентированные языки стали применять для разработки многозвенных приложений. Типичное многозвенное приложение состоит из трёх уровней: уровня представления, уровня предметной области и уровня доступа к данным. В классической схеме зависимость между уровнями распространяется сверху вниз: уровень представления зависит от уровня предметной области, а тот, в свою очередь — от уровня доступа к данным. +----------------------------+ | Уровень представления | +----------------------------+ \/ +----------------------------+ | Уровень предметной области | +----------------------------+ \/ +----------------------------+ | Уровень доступа к данным | +----------------------------+ Рассмотрим простой пример с веб-приложением. Пусть, например, у нас есть список заказов, которые мы хотим показывать пользователю: public OrderController : Controller { [HttpGet] public ActionResult Index() { var userId = GetCurrentUserId(); var repository = new OrderRepository(); var orders = repository.GetAllByUserId(userId); return View(orders); } } Для рендеринга используем простую разметку: @model IEnumerable@{ ViewBag.Title = "Список заказов"; } Это уровень представления, где конечный пользователь видит список заказов на сайте. Чтобы получить этот список, мы обращаемся к хранилищу заказов (это класс уровня предметной области) и получаем заказы (тоже предметная область). Список этих заказов мы отправляем в движок рендеринга HTML, который и превратит их в веб-страницу. А вот как выглядит код уровня доступа к данным, если обращается к данным через Entity Framework: public OrderRepository { public IReadOnlyCollection
@foreach (var order in Model) { Номер Дата Сумма } @order.Number @order.Date @order.Amount GetAllByUserId(int userId) { using (var dbContext = new MyDbContext()) { return dbContext.Orders .AsNoTracking() .Where(x => x.UserId == userId) .AsEnumerable() .Select(x => new Order(x.Id, x.Number, x.Date, x.Amount)) .ToArray(); } } } Мы загружаем из БД сырые данные, и создаём из них классы предметной области, обладающие также и поведением. Классы уровня доступа к данным, это, например, MyDbContext и OrderData. Получается вроде бы всё хорошо и расширяемо, но. Что нужно, чтобы перейти в таком приложении от веб интерфейса к оконному? В идеале — написать один новый уровень представления, оконный. Два оставшихся уровня останутся прежними. Здорово. А что нужно, чтобы перейти в таком приложении с SQL на или MongoDb или файловое хранилище? Переписать все уровни, поскольку нижний переписывать всё равно придётся, а вслед за ним придётся переписывать всё, что от него зависит. Нездорово. Что можно сделать, чтобы упростить перенос таких приложений на другие хранилища? Инвертировать зависимость, то есть сделать так, чтобы уровень доступа данных зависел от уровня предметной области. +----------------------------+ | Уровень представления | +----------------------------+ \/ +----------------------------+ | Уровень предметной области | +----------------------------+ /\ +----------------------------+ | Уровень доступа к данным | +----------------------------+ Если мы сделаем так, то получится, что предметная область станет центральной. От неё будут зависеть уровни представления (веб, консоль, десктоп) и уровня доступа к данным (SQL, MongoDb, XML-файлы). Мы сможем расширять приложение, добавляя модули сверху и снизу, поскольку они будут зависеть только от уровня предметной области. Возникает вопрос: но ведь тогда появляется зависимость от центрального уровня предметной области? Что, если мы захотим переписать её? Ответ неожиданный: именно предметная область определяет всё приложение. Если это Word то в предметной области описаны такие штуки, как документы, параграфы, форматирование и всё остальное. В отличие от предыдущих случаев задача подменить предметную область просто бессмысленна, у вас получается другое приложение. Значит, инвертирование зависимости вещь полезная. Но как её осуществить практически? Практически мы должны описать абстракцию (интерфейс) доступа к данным. Вместо конкретного класса OrderRepository у нас появляется интерфейс: public interface IOrderRepository { IReadOnlyCollection GetByUserId(int userId); } Это интерфейс предметной области. Он используется в контролере OrderController нашего MVC приложения (то есть он доступен с уровня представления). public OrderController : Controller { private readonly IOrderRepository _orderRepository; public OrderController(IOrderReposiotory orderRepository) { _orderRepository = orderRepository; } [HttpGet] public ActionResult GetOrders() { var userId = GetCurrentUserId(); var orders = _orderRepository.GetAllByUserId(userId); return View(model); } } Код отличается от предыдущего тем, что уже не может создать объект класса OrderRepository непосредственно, более того, из контроллера этот класс совсем недоступен. Мы имеем доступ только к абстракции (интерфейсу) и ожидаем получить реализацию через конструктор контроллера. Кто-то снаружи должен создать объект OrderRepository и передать его экземпляр в наш конструктор. Пока отложим рассмотрение вопроса, кто этот «кто-то» и посмотрим, как репозиторий реализован на уровне доступа к данным. public OrderRepository : IOrderRepository { public IReadOnlyCollection GetAllByUserId(int userId) { using (var dbContext = new MyDbContext()) { return dbContext.Orders .AsNoTracking() .Where(x => x.UserId == userId) .AsEnumerable() .Select(x => new Order(x.Id, x.Number, x.Amount)) .ToArray(); } } } Здесь интерфейс одного уровня реализуется на другом уровне. Физически зависимость означает, что из проекта, где реализован OrderRepository должен стоять reference на проект, где описан IOrderRepository и в данном случае мы видим, что зависимость инвертирована: уровень доступа к данным зависит от уровня представления. Теперь, если мы захотим изменить представление, нам не надо менять OrderRepository, достаточно вместо веб-интерфейса реализовать другой, например, консольный интерфейс. Если мы захотим изменить реализацию с EF на NHibernate, нам достаточно будет переписать только репозитории, не трогая весь остальной проект, например, контроллеры. Остаётся вопрос: а кто же увязывает друг с другом интерфейсы и реализации? Тот самый IoC-контейнер, в частности, NInject. В проекте, где создается этот контейнер, сходятся все зависимости, поэтому он называется корнем композиции. Что нужно сделать? Нужно подменить стандартный IDependencyResolver из ASP.NET MVC своей реализацией на базе NInject и зарегистрировать в контейнере свои зависимости. О реализации написано, например, здесь. Регистрация выполняется в приватном методе AddBindings: public class NinjectDependecyResolver : IDependencyResolver { private readonly IKernel kernel; public NinjectDependecyResolver() { kernel = new StandardKernel(); AddBindings(); } public object GetService(Type serviceType) { return kernel.TryGet(serviceType); } public IEnumerable
Комментариев нет:
Отправить комментарий