将依赖项注入自定义模型绑定器并使用Ninject使用InRequestScope

Joh*_*n H 7 asp.net-mvc dependency-injection ninject model-binding custom-model-binder

我正在使用NInject与NInject.Web.Mvc.

首先,我创建了一个简单的测试项目,我希望IPostRepository在同一Web请求期间在控制器和自定义模型绑定器之间共享一个实例.在我的真实项目中,我需要这个,因为我遇到IEntityChangeTracker问题,我有效地有两个存储库访问相同的对象图.因此,为了简化我的测试项目,我只是想共享一个虚拟存储库.

我遇到的问题是它适用于第一个请求,就是这样.相关代码如下.

NInjectModule:

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

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());
    }
}
Run Code Online (Sandbox Code Playgroud)

Global.asax中:

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

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

    ModelBinders.Binders.Add(typeof(string), kernel.Get<CustomModelBinder>());
}
Run Code Online (Sandbox Code Playgroud)

这样做实际上是创建了2个独立的实例IPostRepository而不是共享实例.这里有一些关于将依赖项注入模型绑定器的问题.我上面的代码是基于NInject.Web.Mvc wiki中描述的第一个设置方法,但我已经尝试了两个.

当我使用第二种方法时,IPostRepository只会为第一个Web请求共享,之后它将默认不共享实例.然而,当我确实工作时,我正在使用默认值,DependencyResolver因为我无法在生活中找出如何使用NInject做同样的事情(因为内核隐藏在NInjectMVC3类中).我这样做是这样的:

ModelBinders.Binders.Add(typeof(string),
    DependencyResolver.Current.GetService<CustomModelBinder>());
Run Code Online (Sandbox Code Playgroud)

我怀疑它第一次运行的原因仅仅是因为它没有通过NInject解决它,所以生命周期实际上是由MVC直接处理的(虽然这意味着我不知道它是如何解决依赖性的).

那么我该如何正确注册我的模型绑定器并让NInject注入依赖?

Rem*_*oor 8

ModelBinder由MVC重用于多个请求.这意味着它们的生命周期比请求范围更长,因此不允许依赖具有较短请求范围生命周期的对象.

使用Factory来为每次执行BindModel创建IPostRepository


Kev*_*ker 5

实际上,让Ninject工厂扩展程序启动并运行起来非常简单,但现有答案对我来说并不清楚.

工厂扩展插件是先决条件,可以通过NUGet安装:

Install-Package Ninject.Extensions.Factory
Run Code Online (Sandbox Code Playgroud)

您只需将工厂注入模型绑定器的某个位置,例如:

private IPostRepositoryFactory _factory;

public CustomModelBinder(IPostRepositoryFactory factory) {
    _factory = factory;
}
Run Code Online (Sandbox Code Playgroud)

然后为工厂创建一个界面.工厂的名称和方法的名称实际上并不重要,只是返回类型.(很高兴知道你是否想要注入一个NHibernate会话,但不想担心引用正确的命名空间ISessionFactory,也知道GetCurrentRepository在上下文中是否实际做得更清楚):

public interface IPostRepositoryFactory { 
    IPostRepository CreatePostRepository();
}
Run Code Online (Sandbox Code Playgroud)

然后,假设您IPostRepository已经被Ninject正确管理,扩展将通过调用.ToFactory()方法为您完成其他所有操作.

kernel.Bind<IPostRepository().To<PostRepository>();
kernel.Bind<IPostRepositoryFactory>().ToFactory();
Run Code Online (Sandbox Code Playgroud)

然后,您只需在您需要的代码中调用您的工厂方法:

var repo = _factory.CreatePostRepository();
repo.DoStuff();
Run Code Online (Sandbox Code Playgroud)

(更新:显然命名你的工厂的功能GetXXX.如果服务不已经在会话中实际上会失败,这样你就需要真的有点.你叫什么名字方法小心)


Joh*_*n H 4

我最终按照建议与工厂解决了这个问题。然而,我只是不知道如何实现这一点,Ninject.Extensions.Factory这正是我所希望的。这是我最终得到的结果:

工厂界面:

public interface IPostRepositoryFactory
{
    IPostRepository CreatePostRepository();
}
Run Code Online (Sandbox Code Playgroud)

工厂实施:

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

工厂的 Ninject 模块:

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

自定义模型活页夹:

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

控制器:

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());
    }
}
Run Code Online (Sandbox Code Playgroud)

Global.asax 连接自定义模型绑定器:

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

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

    ModelBinders.Binders.Add(typeof(string), kernel.Get<CustomModelBinder>());
}
Run Code Online (Sandbox Code Playgroud)

在我看来,这给了我所需的输出:

Model binder
Action method