Внедрение зависимости в пользовательский связыватель модели и использование InRequestScope с помощью Ninject - PullRequest
7 голосов
/ 08 февраля 2012

Я использую NInject с NInject.Web.Mvc.

Для начала я создал простой тестовый проект, в котором я хочу, чтобы экземпляр IPostRepository был разделен между контроллером ипользовательское связывание модели во время того же веб-запроса.В моем реальном проекте это мне нужно, потому что у меня IEntityChangeTracker проблемы, когда у меня фактически есть два репозитория, обращающихся к одному и тому же графу объектов.Поэтому для простоты моего тестового проекта я просто пытаюсь использовать фиктивный репозиторий.

Проблема, с которой я сталкиваюсь, заключается в том, что он работает по первому запросу и все.Соответствующий код приведен ниже.

NInjectModule:

public class PostRepositoryModule : NinjectModule
{
    public override void Load()
    {
        this.Bind<IPostRepository>().To<PostRepository>().InRequestScope();
    }
}

CustomModelBinder:

public class CustomModelBinder : DefaultModelBinder
{
    [Inject]
    public IPostRepository repository { get; set; }

    public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        repository.Add("Model binder...");

        return base.BindModel(controllerContext, bindingContext);
    }
}

public class HomeController : Controller
{
    private IPostRepository repository;

    public HomeController(IPostRepository repository)
    {
        this.repository = repository;
    }

    public ActionResult Index(string whatever)
    {
        repository.Add("Action...");

        return View(repository.GetList());
    }
}

Global.asax:

protected override void OnApplicationStarted()
{
    AreaRegistration.RegisterAllAreas();

    RegisterGlobalFilters(GlobalFilters.Filters);
    RegisterRoutes(RouteTable.Routes);

    ModelBinders.Binders.Add(typeof(string), kernel.Get<CustomModelBinder>());
}

Делаем этоспособ на самом деле создает 2 отдельных экземпляра IPostRepository, а не общий экземпляр.Здесь есть кое-что, чего мне не хватает в отношении введения зависимости в мое связующее.Мой код выше основан на первом методе настройки, описанном в NInject.Web.Mvc wiki , но я попробовал оба.

Когда я использовал второй метод, IPostRepository будетрасшарен только для самого первого веб-запроса, после которого по умолчанию не будет делиться экземпляром.Однако когда я все заработал, я использовал значение по умолчанию DependencyResolver, так как не мог понять, как сделать то же самое с NInject (поскольку ядро ​​спрятано в классе NInjectMVC3).Я сделал это так:

ModelBinders.Binders.Add(typeof(string),
    DependencyResolver.Current.GetService<CustomModelBinder>());

Я подозреваю, что это сработало только в первый раз, потому что это не решает проблему с помощью NInject, поэтому жизненный цикл действительно обрабатывается MVC напрямую (хотя это означает, чтоЯ понятия не имею, как он разрешает зависимость).

Итак, как мне правильно зарегистрировать связыватель модели и заставить NInject внедрить зависимость?

Ответы [ 3 ]

8 голосов
/ 08 февраля 2012

ModelBinder повторно используются MVC для нескольких запросов. Это означает, что они имеют более длинный жизненный цикл, чем область действия запроса, и поэтому не могут зависеть от объектов с более коротким жизненным циклом области действия запроса.

Вместо этого используйте Factory , чтобы создать IPostRepository для каждого выполнения BindModel

5 голосов
/ 08 октября 2012

На самом деле очень просто запустить заводское расширение Ninject, но мне неясно из существующих ответов.

Плагин заводских расширений является обязательным условием, которое можно установить через NUGet:

Install-Package Ninject.Extensions.Factory

Вам просто нужно где-то впрыснуть фабрику в вашу модель, например:

private IPostRepositoryFactory _factory;

public CustomModelBinder(IPostRepositoryFactory factory) {
    _factory = factory;
}

Затем создайте интерфейс для фабрики. Название фабрики и имя метода на самом деле не имеют значения, просто тип возвращаемого значения. (Полезно знать, если вы хотите внедрить сеанс NHibernate, но не хотите беспокоиться о ссылках на правильное пространство имен для ISessionFactory, также полезно знать, делает ли GetCurrentRepository то, что он фактически делает более понятным в контексте):

public interface IPostRepositoryFactory { 
    IPostRepository CreatePostRepository();
}

Затем, если ваш IPostRepository уже правильно управляется Ninject, расширение сделает все остальное за вас, просто вызвав метод .ToFactory ().

kernel.Bind<IPostRepository().To<PostRepository>();
kernel.Bind<IPostRepositoryFactory>().ToFactory();

Затем вы просто вызываете ваш фабричный метод в коде, где вам это нужно:

var repo = _factory.CreatePostRepository();
repo.DoStuff();

(Обновление: очевидно, что название вашей фабричной функции GetXXX действительно завершится ошибкой, если служба еще не существует в сеансе. Так что вам действительно нужно быть несколько осторожными с тем, как вы называете метод .)

4 голосов
/ 10 февраля 2012

Мне, в конце концов, удалось решить это с фабрикой, как это было предложено.Однако я просто не мог понять, как этого добиться с Ninject.Extensions.Factory, что я бы предпочел.Вот что я закончил:

Заводской интерфейс:

public interface IPostRepositoryFactory
{
    IPostRepository CreatePostRepository();
}

Заводская реализация:

public class PostRepositoryFactory : IPostRepositoryFactory
{
    private readonly string key = "PostRepository";

    public IPostRepository CreatePostRepository()
    {
        IPostRepository repository;

        if (HttpContext.Current.Items[key] == null)
        {
            repository = new PostRepository();
            HttpContext.Current.Items.Add(key, repository);
        }
        else
        {
            repository = HttpContext.Current.Items[key] as PostRepository;
        }

        return repository;
    }
}

Модуль Ninject для фабрики:

public class PostRepositoryFactoryModule : NinjectModule
{
    public override void Load()
    {
        this.Bind<IPostRepositoryFactory>().To<PostRepositoryFactory>();
    }
}

Связыватель пользовательской модели:

public class CustomModelBinder : DefaultModelBinder
{
    private IPostRepositoryFactory factory;

    public CustomModelBinder(IPostRepositoryFactory factory)
    {
        this.factory = factory;
    }

    public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        IPostRepository repository = factory.CreatePostRepository();

        repository.Add("Model binder");

        return base.BindModel(controllerContext, bindingContext);
    }
}

Контроллер:

public class HomeController : Controller
{
    private IPostRepository repository;

    public HomeController(IPostRepositoryFactory factory)
    {
        this.repository = factory.CreatePostRepository();
    }

    public ActionResult Index(string whatever)
    {
        repository.Add("Action method");

        return View(repository.GetList());
    }
}

Global.asax для подключения связывателя пользовательской модели:

protected override void OnApplicationStarted()
{
    AreaRegistration.RegisterAllAreas();

    RegisterGlobalFilters(GlobalFilters.Filters);
    RegisterRoutes(RouteTable.Routes);

    ModelBinders.Binders.Add(typeof(string), kernel.Get<CustomModelBinder>());
}

Что, на мой взгляд, дало мне желаемый результат:

Model binder
Action method

...