Использование Dependency Injection вне конструктора контроллера - PullRequest
7 голосов
/ 22 февраля 2012

Так вот в чем проблема, мой проект mvc3 использует Dependency Injection и имеет базовый класс Generic IRepository, из которого происходят другие репозитории.

Так что я могу идти вперед и сделать это в контроллере:

public class SomethingController
{
    IOrderRepository repository;

    public SomethingController(IOrderRepository repo)
    {
        this.repository = repo;
    }

    public ActionResult SaveOrder(Order order)
    {
        repository.add(order)
        unitOfWork.CommitChanges();  // THIS works!
    }
}

Но теперь мне нужно использовать один из этих репозиториев в пользовательском статическом неконтроллере, например:

static class OrderParser
{
    private IOrderRepository repo;

    public static DoWork()
    {
        repo = DependencyResolver.Current.GetService<IOrderRepository>();

        var ordersInDB = repo.GetAllOrders(); //THIS works!

        //But!
        var ordersForInsertion = new List<Order>();


        //do some backgroundworker magic                     
        //fetch txt files from an ftp server
        var ordersForInsertion = ParseTextFilesIntoOrders();

        foreach order in ordersForInsertion 
             repo.add(order)
        unitOfWork.CommitChanges();
        // THIS doesnt commit anything into the database
        // It also doesnt throw any exceptions
        // and repo isnt null or any of that
    }
}

Итак, в качестве теста я попытался сделать:

repo = DependencyResolver.Current.GetService<IOrderRepository>();

внутри класса контроллера, как в первом примере, чтобы увидеть, что он также не фиксирует вещи, и это не так. (Делать это правильно [инъекция репозиториев и unitOfWork через конструкторы] работает!)

Так что это должно быть как-то связано с DependencyResolver, верно?

Примечание: если есть еще какой-то код, который вам нужно, чтобы я опубликовал, попросите об этом, и я отредактирую его здесь в одно мгновение!

Примечание 2: Спасибо!

EDIT1:

Относительно быстрого ответа w0lf Вот еще немного информации:

Мой класс OrderParser реализует backgroundWorker, который должен:

  • Спи час
  • Список всех файлов (простые текстовые файлы) на FTP-сервере.
  • Откажитесь от уже проанализированных в БД.
  • Разобрать новые файлы в объекты Order.
  • Зафиксируйте объекты в БД.
  • Начинайте снова и снова, пока не отключится питание или что-то в этом роде:)

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

в моем классе начальной загрузки

Initialise()
{
    //Unrelated stuff
    OrderParser.DoWork()
}

И именно поэтому я реализовал его как статический класс (легко заменяемый на нестатический)

EDIT2:

Это было бы что-то вроде:

class OrderParser
{
    private IOrderRepository repo;

    public OrderParser(IOrderRepository foo)
    {
        this.repo = foo;
    }
    public static DoWork()
    {
        //use repo var!
    }
}

Но затем, когда я создаю его экземпляр в методе начального загрузчика Initialize (), как я это сделаю, например ::106?

class bootstrapper
{
    Initialize()
    {
        var parser = new OrderParser(/*how do i pass the dependency here?*/)
        parser.DoWork();
    }
}

EDIT3:

Вот еще тестирование, пожалуйста, потерпите меня!

Вот снова мой OrderParser:

class OrderParser
{
    public OrderParser(IOrderRepository foo, IContext unitOfWork)
    {
        foo.getall(); 

        foo.add(some_order);
        unitOfWork.commit(); 

    }
}

Test1:

public class SomeController
{
    IOrderRepository repository;

    public SomeController(IOrderRepository repo)
    {
        this.repository = repo;
    }

    public ActionResult SomeMethod(Order order)
    {
        repository.GetAll();    //WORKS

        repository.add(order)
        unitOfWork.CommitChanges();  // WORKS
    }
}

TEST2:

class bootstrapper
{
    Initialize()
    {
        //Build unity container..
        //set resolver..

        var parser = new OrderParser(container.Resolve<IOrderRepository>, container.Resolve<IContext>)
        //can getAll, cant commit.
    }
}

TEST3:

public class SomeController
{
    IOrderRepository controllers_repository;

    public SomeController(IOrderRepository repo)
    {
        this.controllers_repository = repo;
    }

    public ActionResult SomeMethod(Order order)
    {
        var parser = new OrderParser(DependencyResolver.Current.GetService<IOrderRepository>,
        DependencyResolver.Current.GetService<IContext>)   
        //can do getall, no commits


        var parser = new OrderParser(controllers_repository, controllers_icontext)
        // obviously works (can getall and commit)
    }
}

Кстати, когда я говорю «не могу зафиксировать», я не получаю исключение, или репозитории нулевые, нет. код работает так, как будто все в порядке, только БД не изменится.

Ответы [ 3 ]

5 голосов
/ 22 февраля 2012

Одним из возможных решений является сделать класс OrderParser нестатичным и внедрить его экземпляр в конструктор контроллера, который запускает действие (DoWork).

Затем заставьте конструктор OrderParser принять параметр IOrderRepository, и контейнер IoC с радостью позаботится об этом.

Также остерегайтесь таких вещей, как:

DependencyResolver.Current.GetService<ISomeInterface>();

Это называется Service Locator, и оно считается анти-паттерном . Избегайте этого, если это возможно.

По сути, единственное место, где вы должны ссылаться на DependencyResolver.Current.GetService, это ваша реализация IControllerFactory, которая в первую очередь включает DI.

Обновление:

Было бы лучше, если бы вы делали это в другом приложении, чем ваш веб-сайт MVC. Две альтернативы будут:

  • Служба Windows, которая выполняет это действие на основе таймера
  • Консольное приложение, которое запускается с помощью планировщика заданий Windows каждый час

Они, будучи отдельными приложениями, будут иметь свои собственные Корни композиции , которые будут иметь дело с проблемой создания объекта / внедрения зависимости.

Если, однако, вы вынуждены делать это из своего веб-приложения (например, у вас есть хостинг, который допускает только веб-приложения), то вы можете сочтите приемлемым сделать исключение из «Не используйте Правило «Dependencey Resolver напрямую» и выполните что-то вроде этого при запуске приложения:

var runner = DependencyResolver.Current.GetService<OrderParsingRunner>();
runner.StartWorking();

Конечно, класс OrderParsingRunner будет выглядеть примерно так:

public class OrderParsingRunner
{
    private readonly OrderParser orderParser;

    public OrderParsingRunner(OrderParser orderParser)
    {
        this.orderParser = orderParser;
    }

    public StartWorking()
    {
        TaskFactory.StartNew(() => 
            { 
                DoWorkHourly();
            });
    }

    private DoWorkHourly()
    {
        while(true)
        {
            Thread.Sleep(TimeSpan.FromHours(1));

            orderParser.DoWork();
        }
    }
}

Отказ от ответственности: я на самом деле не скомпилировал / запустил этот код, я просто написал его, чтобы проиллюстрировать концепцию.

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

2 голосов
/ 23 февраля 2012

Вам не нужны статические вспомогательные классы при использовании DI. Вы можете рассматривать все как «службу» и объявлять ваши зависимости в вашем конструкторе. Вот как я об этом думаю. Тогда все просто создается для вас, когда вам это нужно.

Так что я бы изменил ваш статический класс на нестатический и вставил его, где это необходимо, через конструктор.

Ответ для редактирования 2

Передайте свой контейнер в класс начальной загрузки.

class bootstrapper
{
    Initialize(DependencyResolver container)
    {
        var parser = new OrderParser(container.Resolve<IOrderRepository>());
        parser.DoWork();
    }
}

Редактировать

Я бы на самом деле сделал это ...

var parser = container.Resolve<OrderParser>();

и пусть решатель зависимостей все выяснит!

1 голос
/ 24 февраля 2012

Поскольку это фоновая задача, не запускайте ее в веб-приложении.Вместо этого используйте службу или запланированное приложение в Windows.Затем вы можете разрешить ссылку во время инициализации приложения или с помощью атрибута [Зависимость]

http://msdn.microsoft.com/en-us/library/zt39148a(v=vs.100).aspx /733155/ispolzuya-unity-framework-dobavte-stranitsu-system-windows-forms-form

...