При попытке создать обьект через контейнер Ninject вылетает exception : Object reference
not set to an instance of an object. Подскажите, пожалуйста, в чем может быть проблема?
Метод с класса NinjectDependencyResolver
private void AddBindings()
{
var mapperConfiguration = new MapperConfiguration(cfg =>
{
cfg.AddProfile(new TagProfile());
});
var mapper = mapperConfiguration.CreateMapper();
_kernel.Bind().ToSelf().InRequestScope();
_kernel.Bind, Repository>();
_kernel.Bind().ToConstant(mapper);
var repository = _kernel.Get>();// вылетает exception
_kernel.Bind();
}
Класс Repository
public class Repository : IRepository where T : class
{
private readonly BlogDbContext _db;
public Repository(BlogDbContext db)
{
_db = db;
}
}
P.S. Не знаю важно ли это, но репозиторий с его интерфейсом находится в одной сборке,
а регистрация Ninject в другой.
Ответы
Ответ 1
Ошибка говорит о том, что в экземпляре объекта не задана ссылка на объект.
Object reference not set to an instance of an object.
В этих двух строчках
_kernel.Bind, Repository>();
_kernel.Bind();
Вы "говорите" IoC-контейнеру, что при запросе IRepository или Repository,
необходимо вернуть реализацию, но не указываете ее.
Правильным будет следующий вызов.
_kernel.Bind>().To>();
_kernel.Bind().To();
Вы сообщаете IoC-контейнеру, что при запросе IRepository создать экземпляр класса
Repository. Для второй строки аналогично.
И ответ на вопрос из комментария:
для контекста делаю InRequestScope(), нужно ли мне это делать и для
репозитория и для сервиса?
Для репозитория и Сервиса не нужно делать InRequestScope(), только для контекста
BlogDbContext
UPD
Не знаю важно ли это, но репозиторий с его интерфейсом находится в
одной сборке, а регистрация 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 = "Список заказов";
}
Номер
Дата
Сумма
@foreach (var order in Model)
{
@order.Number
@order.Date
@order.Amount
}
Это уровень представления, где конечный пользователь видит список заказов на сайте.
Чтобы получить этот список, мы обращаемся к хранилищу заказов (это класс уровня предметной
области) и получаем заказы (тоже предметная область). Список этих заказов мы отправляем
в движок рендеринга HTML, который и превратит их в веб-страницу.
А вот как выглядит код уровня доступа к данным, если обращается к данным через Entity
Framework:
public OrderRepository
{
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.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
Имеется класс, который использует Ninject для получения экземпляра репозитория: public class ClosedClass
{
public String DoWork()
{
var repo = AppStatic.Kernel.Get();
return repo.GetData();
} public async Task DoWorkAsync()
{
var repo = AppStatic.Kernel.Get();
return await repo.GetDataAsync();
}
}
Как видно из кода конкретный экземпляр класса, реализующего IRepo каждый экземпляр класса ClosedClass получает от ядра Ninject, экземпляр которого хранится в статическом открытом поле статического класса AppStatic. Вот, собственно, код класса: public static class AppStatic
{
public static IKernel Kernel { get; private set; } static AppStatic()
{
Kernel = new StandardKernel();
} public static void SetKernel(IKernel kernelInstance)
{
Kernel = kernelInstance;
}
}
Класс ClosedClass необходимо протестировать при помощи MSTest + Ninject + Ninject.MockingKernel + Moq, при чём тестируются оба метода (синхронный DoWork() и асинхронный DoWorkAsync()). Собственно, с тестированием синхронного метода проблем не возникает. Создаётся экземпляр класса, затем ядро Niject, которое позвозяет привязывать моки к интерфейсам, на указанном моке, собственно, указывается желаемое значение: [TestMethod]
public void TestSync()
{
var c = new SAMPLEMOQ.ClosedClass();
var kernel = new MoqMockingKernel();
kernel
.GetMock()
.Setup(m => m.GetData())
.Returns("FAKE");
AppStatic.SetKernel(kernel);
Trace.WriteLine(c.DoWork()); }
В результате выполнения теста в output выводится строка FAKE - отлично!
Тем не менее, при тестировании асинхронного метода DoWorkAsync() аналогичным образом, тест просто начинает выполнение и не останавливается, пока я сам не отменяю процесс выполнения. Код юнит-теста для асинхронного метода DoWorkAsync() [TestMethod]
public async Task TestASync()
{
var c = new SAMPLEMOQ.ClosedClass(); var kernel = new MoqMockingKernel(); kernel
.GetMock()
.Setup(m => m.GetDataAsync())
.Returns(new Task(() => "Fake"));
AppStatic.SetKernel(kernel);
Trace.WriteLine(await c.DoWorkAsync());
}
Вопрос: почему этот тест не выполняется, при чём даже не выдаёт никакую ошибку? PS: Тесты запускаю НЕ параллельно, каждый в отдельности.
OS: Windows 10
IDE: VS 2015 Update 3
Platform: .NET Framework 4.6
Ninject: v3.2.2.0 (net46)
Ninject.MockingKernel: v3.2.2.0 (net46)
Ninject.MockingKernel.Moq: v3.2.2.0 (net46)
Moq: v4.5.2.1 (net46)
Ответ
Попробуйте заменить kernel
.GetMock()
.Setup(m => m.GetDataAsync())
.Returns(new Task(() => "Fake"));
на kernel
.GetMock()
.Setup(m => m.GetDataAsync())
.Returns(Task.FromResult("Fake"));
или kernel
.GetMock()
.Setup(m => m.GetDataAsync())
.Returns(
async () =>
{
await Task.Delay(20);
return "Fake";
});
(смотря что требуется). Дело вот в чём. Если убрать всю обвязку, то по сути происходит await new Task(() => "Fake");
Однако new Task(...) создаёт не стартованную задачу (со статусом Created), такой await не завершится.
При попытке создать обьект через контейнер Ninject вылетает exception : Object reference not set to an instance of an object. Подскажите, пожалуйста, в чем может быть проблема? Метод с класса NinjectDependencyResolver private void AddBindings()
{
var mapperConfiguration = new MapperConfiguration(cfg =>
{
cfg.AddProfile(new TagProfile());
});
var mapper = mapperConfiguration.CreateMapper();
_kernel.Bind().ToSelf().InRequestScope();
_kernel.Bind, Repository>();
_kernel.Bind().ToConstant(mapper); var repository = _kernel.Get>();// вылетает exception
_kernel.Bind();
}
Класс Repository public class Repository : IRepository where T : class
{
private readonly BlogDbContext _db; public Repository(BlogDbContext db)
{
_db = db;
}
}
P.S. Не знаю важно ли это, но репозиторий с его интерфейсом находится в одной сборке, а регистрация Ninject в другой.
Ответ
Ошибка говорит о том, что в экземпляре объекта не задана ссылка на объект.
Object reference not set to an instance of an object.
В этих двух строчках _kernel.Bind, Repository>();
_kernel.Bind();
Вы "говорите" IoC-контейнеру, что при запросе IRepository или Repository, необходимо вернуть реализацию, но не указываете ее. Правильным будет следующий вызов. _kernel.Bind>().To>();
_kernel.Bind().To();
Вы сообщаете IoC-контейнеру, что при запросе IRepository создать экземпляр класса Repository. Для второй строки аналогично. И ответ на вопрос из комментария:
для контекста делаю InRequestScope(), нужно ли мне это делать и для
репозитория и для сервиса?
Для репозитория и Сервиса не нужно делать InRequestScope(), только для контекста BlogDbContext
UPD
Не знаю важно ли это, но репозиторий с его интерфейсом находится в
одной сборке, а регистрация Ninject в другой.
Не важно, главное указать ссылка на сборку в которой лежит репозиторий и интерфейсы