Внедрение зависимости в пользовательский связыватель модели и использование InRequestScope с помощью Ninject.
Я использую 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, но я попробовал оба.
Когда я использовал второй метод, IPostRepository
будет предоставлен только для самого первого веб-запроса, после которого по умолчанию не будет делиться экземпляром. Тем не менее, когда я получил это работает, я использовал по умолчанию DependencyResolver
поскольку я не мог на всю жизнь выяснить, как сделать то же самое с NInject (быть ядром, спрятанным в классе NInjectMVC3). Я сделал это так:
ModelBinders.Binders.Add(typeof(string),
DependencyResolver.Current.GetService<CustomModelBinder>());
Я подозреваю, что причина этого работала только в первый раз, потому что это не разрешает его через NInject, поэтому жизненный цикл действительно обрабатывается MVC напрямую (хотя это означает, что я понятия не имею, как он разрешает зависимость).
Итак, как мне правильно зарегистрировать связыватель модели и заставить NInject ввести зависимость?
3 ответа
В конце концов мне удалось решить это с фабрикой, как было предложено. Тем не менее, я просто не мог понять, как это сделать с 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
ModelBinder повторно используются MVC для нескольких запросов. Это означает, что они имеют более длинный жизненный цикл, чем область действия запроса, и поэтому не могут зависеть от объектов с более коротким жизненным циклом области запроса.
Вместо этого используйте Factory для создания IPostRepository для каждого выполнения BindModel
На самом деле очень просто получить заводское расширение 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
на самом деле потерпит неудачу, если служба еще не существует в сеансе. Так что вам действительно нужно быть осторожным с тем, что вы называете методом.)