Inject a dependency into a custom model binder and using InRequestScope using Ninject

怎甘沉沦 提交于 2019-11-27 15:37:54

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

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.)

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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!