Страницы

Поиск по вопросам

Показаны сообщения с ярлыком ninject. Показать все сообщения
Показаны сообщения с ярлыком ninject. Показать все сообщения

воскресенье, 8 марта 2020 г.

Ninject не может создать обьект

#c_sharp #net #aspnet_mvc #inversion_of_control #ninject


При попытке создать обьект через контейнер 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 в другой. Не важно, главное указать ссылка на сборку в которой лежит репозиторий и интерфейсы

среда, 5 февраля 2020 г.

C#,ASP.NET MVC IoC-контейнер Ninject [закрыт]

#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 = "Список заказов"; } @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 GetServices(Type serviceType) { return kernel.GetAll(serviceType); } void AddBindings() { kernel.Bind().To(); . . . } } Далее, в Global.asax меняем объект для разрешения зависимостей: protected void Application_Start() { DependencyResolver.SetResolver(new NinjectDepedencyResolver()); } После этого при попытке создать OrderController движок обратится к NinjectDependencyResolver, который обнаружит, что в конструкторе ожидается объект, реализующий интерфейс IOrderRepository. Движок NInject пройдёт по графу зарегистрированных объектов и выяснит, что этот интерфейс реализует класс OrderRepository который может быть создан непосредственно (то есть у него пустой конструктор). NInject создаст этот класс и передаст ссылку на него в конструктор OrderController. Этот паттерн называется внедрением через конструктор. Созданный контролер будет передан в движок ASP.NET MVC и обработает запрос Index. Помимо NInject существует большое количество IoC-контейнеров: Castle Windsor, Autofac, Unity. При желании, можно даже обрабатывать зависимости вручную, что иногда делают в небольших проектах. Подытожу основные моменты: Мы вводим интерфейсы между уровнем предметной области и уровнями более низких уровней (классически это уровень доступа к данным, в современных приложениях таких может быть больше, чем один). Объекты предметной области и вышележащих уровней для работы используют только интерфейсы, получая их через конструктор. Например, если нам в контролере нужен репозиторий, мы описываем параметр в конструкторе и сохраняем переданное значение в приватной переменной объекта. Мы реализуем все используемые интерфейсы на нижележащих уровнях. Мы сводим воедино вместе все интерфейсы и их реализации (в классической терминологии IoC — регистрируем (register) интерфейсы в контейнере, в терминологии NInject — назначаем (bind) реализацию). Все зарегистрированные объекты образуют граф объектов. Мы используем IoC-контейнер для создания классов верхнего уровня (в MVC приложении это контролеры). Чтобы это сделать, MVC предоставляет интерфейс IDependencyResolver и статический метод DependencyResolver.SetResolver. Контейнер разрешает все промежуточные зависимости, пользуясь графом объектов. Внизу графа находятся примитивные классы с пустыми конструкторами, которые создаются непосредственно. Всё это позволяет прозрачным образом реализовать инверсию зависимостей. Наш код становится проще. Модульные тесты разрабатывать проще. При необходимости, перейти с одной СУБД на другую СУБД или даже совсем не СУБД — такой переход тоже сделать проще.

Ответ 2



На пальцах (простой тупой пример с картинкой и почти без кода). Об ошибках и уточнениях пишите - поправлю. Чтобы лучше понять просто создайте тестовый проект, который будет из файлов разного формата считывать одни и те же данные (csv, json, xml, txt, любой другой по желанию). Или что-то аналогичное. Пусть я хочу написать сайт. Он будет отображать каталог какой-то продукции. Продукции всего 30 предметов и ожидается, что в ближайший год ее станет максимум 33 предмета. Я решил, что 30 предметов можно легко сохранить в JSON файле. Его можно легко редактировать руками и предметов в нем мало - ничего страшного. Создаю новое ASP.Net приложение. В нем будет контроллер CatalogController, отображение ListAllItems, класс ProductItem для одного продукта, класс для считывания файла ProductFileReader и класс для парсинга (он будет создавать список из ProductItem) ProductParser. Картинка: И тут начальник сказал что он хочет тащить список из 1С... . Придется переписать код, который использует ProductParser и ProductFileReader, потому что они уже не подходят. Тут, к счастью, все это используется только в CatalogController. Можно, конечно, все один раз переписать с мыслью, что ничего точно не поменяется (ага, никогда). Здесь приходит мысль, что в контроллере не требуется вызывать все это. Тут нужно получить только список продукции и откуда он возьмется нам все равно. То есть сюда можно ввести зависимость. От чего? Создадим интерфейс IProductProvider и будем в контроллере им пользоваться. Интерфейс (точнее класс который его реализует) даст нам список продукции и не важно как он получился и откуда он взялся - главное, что он есть в нужном месте. О создании нужного объекта для требуемого интерфейса позаботится, например, NInject. И эта зависимость будет только в одном месте (в настройках NInject). private static void RegisterServices(IKernel kernel) { kernel.Bind() .To(); } ProductProviderFromFile будет считывать файл и парсить. Можем потом поменять на ProductProviderFromDB, который получит список из базы. Тогда блок с контроллером будет выглядеть так: Таким образом переписывать контроллер не придется, если мы захотим перейти на хранение в базе. То же самое с модульным тестированием. Нам надо протестировать только контроллер отдельно от всего, а к нему гвоздями прибиты классы для считывания файлов и парсинга и от них легко не избавиться. Тут с помощью NInject или вообще без него можно просто подсунуть контроллеру любой другой класс который реализует интерфейс IProductProvider (он может просто вручную забитый список из трех тестовых продуктов возвращать - какая разница, хоть пустой).

суббота, 13 июля 2019 г.

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

Имеется класс, который использует 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 не завершится.

воскресенье, 26 мая 2019 г.

Ninject не может создать обьект

При попытке создать обьект через контейнер 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 в другой.
Не важно, главное указать ссылка на сборку в которой лежит репозиторий и интерфейсы