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

发布于 2025-01-03 06:10:47 字数 2275 浏览 1 评论 0原文

我将 NInject 与 NInject.Web.Mvc 一起使用。

首先,我创建了一个简单的测试项目,其中我希望在同一 Web 请求期间在控制器和自定义模型绑定器之间共享 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 将仅在第一个 Web 请求时共享,此后它将默认不共享实例。然而,当我真正开始工作时,我使用的是默认的 DependencyResolver,因为我一生都无法弄清楚如何使用 NInject 做同样的事情(因为内核隐藏在NInjectMVC3 类)。我这样做是这样的:

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

我怀疑这第一次起作用的原因是因为这不是通过 NInject 解决它,所以生命周期实际上是由 MVC 直接处理的(尽管这意味着我不知道它是如何解决依赖关系的) )。

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

I'm using NInject with NInject.Web.Mvc.

To start with, I've created a simple test project in which I want an instance of IPostRepository to be shared between a controller and a custom model binder during the same web request. In my real project, I need this because I'm getting IEntityChangeTracker problems where I effectively have two repositories accessing the same object graph. So to keep my test project simple, I'm just trying to share a dummy repository.

The problem I'm having is that it works on the first request and that's it. The relevant code is below.

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>());
}

Doing it this way is actually creating 2 separate instances of IPostRepository rather than the shared instance. There's something here that I'm missing with regards to injecting a dependency into my model binder. My code above is based on the first setup method described in the NInject.Web.Mvc wiki but I have tried both.

When I did use the second method, IPostRepository would be shared only for the very first web request, after which it would default to not sharing the instance. However, when I did get that working, I was using the default DependencyResolver as I couldn't for the life of me figure out how to do the same with NInject (being as the kernel is tucked away in the NInjectMVC3 class). I did that like so:

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

I suspect the reason this worked the first time only is because this isn't resolving it via NInject, so the lifecycle is really being handled by MVC directly (although that means I have no idea how it's resolving the dependency).

So how do I go about properly registering my model binder and getting NInject to inject the dependency?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

寂寞陪衬 2025-01-10 06:10:47

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

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

The ModelBinders are reused by MVC for multiple requests. This means they have a longer lifecycle than request scope and therefore aren't allowed to depend on objects with the shorter request scope life cycle.

Use a Factory instead to create the IPostRepository for every execution of BindModel

幼儿园老大 2025-01-10 06:10:47

实际上,启动并运行 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 实际上会失败。所以您实际上这样做必须稍微小心地命名该方法。)

It's actually really simple to get the Ninject factory extension up and running, but that wasn't clear to me from the existing answers.

The factory extensions plugin is a prerequisite, which can be installed via NUGet:

Install-Package Ninject.Extensions.Factory

You just need the factory injected into your model binder somewhere, eg:

private IPostRepositoryFactory _factory;

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

Then create an interface for the factory. The name of the factory and the name of the method doesn't actually matter at all, just the return type. (Good to know if you want to inject an NHibernate session but don't want to have to worry about referencing the correct namespace for ISessionFactory, also useful to know if GetCurrentRepository makes what it actually does more clear in context):

public interface IPostRepositoryFactory { 
    IPostRepository CreatePostRepository();
}

Then, assuming your IPostRepository is already being managed by Ninject correctly, the extension will do everything else for you just by calling the .ToFactory() method.

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

Then you just call your factory method in the code where you need it:

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

(Update: Apparently naming your factory function GetXXX will actually fail if the service doesn't already exist in the session. So you do actually have to be somewhat careful with what you name the method.)

蓝眼泪 2025-01-10 06:10:47

我最终按照建议与工厂解决了这个问题。但是,我只是不知道如何使用 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>());
}

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

模型绑定器
操作方法

I eventually managed to solve it with a factory as suggested. However, I just could not figure out how to accomplish this with Ninject.Extensions.Factory which is what I would've preferred. Here is what I ended up with:

The factory interface:

public interface IPostRepositoryFactory
{
    IPostRepository CreatePostRepository();
}

The factory implementation:

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;
    }
}

The Ninject module for the factory:

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

The custom model binder:

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);
    }
}

The controller:

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 to wire up the custom model binder:

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

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

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

Which in my view, gave me the desired output of:

Model binder
Action method

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文