关于使用 Ninject 的问题 [英] Questions about using Ninject

查看:37
本文介绍了关于使用 Ninject 的问题的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我按照推荐的步骤将 Ninject 添加到我的 MVC 应用程序中.我在控制器的构造函数中添加了一个 DbContext 参数.

控制器:

公共类 MyController : BaseController{公共 ArticlesController(MyDbContext 上下文):基础(上下文){ }}

基本控制器:

公共类 BaseController : 控制器{受保护的 DbContext MyDbContext;公共 BaseController(MyDbContext 上下文){MyDbContext = 上下文;}}

这似乎运作良好.但是给我留下了几个问题.

  1. Ninject 是否确保我的 DbContext 被及时清理和处置?

  2. 我为所有应用程序的控制器创建了一个基类,以处理任何常见的初始化等.基类在构造函数中接受我的 DbContext 参数的实例.但这要求我还要将此参数添加到我的应用程序中的每个控制器.有什么办法不需要这个?

  3. 我不确定创建我的 DbContext 实例的成本有多大.有没有什么办法可以优化它只在请求实际上需要我访问数据库时才创建.

解决方案

Ninject 是否确保及时清理和处置我的 DbContext?

根据这个答案:

<块引用>

CLR 文档指出,创建 Disposable 对象的人负责调用 Dispose.在这种情况下,对象是由 Ninject 创建的.这意味着您不应显式调用 Dispose.

Ninject 会处理除 InTransientScope 一旦创建对象所绑定的范围对象被 GC 收集.这就是为什么每个 Disposable 对象都应该与不是 InTransientScope() 的范围绑定的原因.例如.您可以使用 NamedScope 扩展 中的 InParentScope() 将对象作为一旦它被注入的对象被垃圾回收.


<块引用>

我为所有应用程序的控制器创建了一个基类,以处理任何常见的初始化等.基类在构造函数中接受我的 DbContext 参数的实例.但这要求我还要将此参数添加到我的应用程序中的每个控制器.有什么办法不需要这个?

简单地说,从不为 MVC 控制器使用公共基类.类继承往往会紧密耦合您的逻辑,并且随着时间的推移变得难以维护.它还倾向于引导您创建一个 god 对象,因为创建多个级别的注入依赖项会意味着每个 Controller 需要更多的依赖项.

如果您有横切关注点,您应该使用全局注册的过滤器.您可以为每条逻辑制作单独的过滤器,这不会像共享基类那样违反单一责任原则.如果您在全局范围内注册过滤器,则可以使用构造函数注入,如 此操作过滤器这个授权过滤器.您也可以制作自己的属性(无行为)如有必要,它们是每个控制器和/或操作的条件.

示例:

由于您明确表示要根据当前用户设置通用 ViewBag 属性,因此可以使用过滤器来完成此操作.

当前用户配置文件过滤器

公共类 CurrentUserProfileFilter : IAuthorizationFilter{私有只读 MyDbContext 上下文;公共 CurrentUserAuthorizationFilter(MyDbContext 上下文){this.context = 上下文;}公共无效 OnAuthorization(AuthorizationContext filterContext){var currentUserName = filterContext.HttpContext.User.Identity.Name;//为请求设置 ViewBag.filterContext.Controller.ViewBag.UserName = 当前用户名;var userBirthdate =从用户作为 this.context.AspNetUsers其中 user.UserName == currentUserName选择生日;if (userBirthdate.Date == DateTime.Now.Date){filterContext.Controller.ViewBag.Message = 生日快乐!";}}}

GlobalFilterProvider

MVC 有一个静态的 GlobalFiltersCollection,您应该在其中全局注册过滤器实例.这不适用于具有由 DI 容器管理的生命周期的依赖项的过滤器(例如 DbContext).

为确保过滤器按需解析(按请求),我们制作了一个 IFilterProvider,通过容器解析它们(假设您的 Ninject 容器已向 MVC 注册为 DependencyResolver);

公共类 GlobalFilterProvider : IFilterProvider{私有只读 IDependencyResolver 依赖解析器;公共 GlobalFilterProvider(IDependencyResolver dependencyResolver){this.dependencyResolver = 依赖解析器;}公共 IEnumerable<过滤器>GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor){foreach(this.dependencyResolver.GetServices() 中的 var 过滤器){yield return new Filter(filter, FilterScope.Global, order: null);}foreach(this.dependencyResolver.GetServices() 中的 var 过滤器){yield return new Filter(filter, FilterScope.Global, order: null);}foreach(this.dependencyResolver.GetServices() 中的 var 过滤器){yield return new Filter(filter, FilterScope.Global, order: null);}foreach(this.dependencyResolver.GetServices() 中的 var 过滤器){yield return new Filter(filter, FilterScope.Global, order: null);}//如果 MVC 5,也添加这些...//foreach (var filter in this.dependencyResolver.GetServices())//{//yield return new Filter(filter, FilterScope.Global, order: null);//}}}

用法

在您的 Ninject 组合根中,向 kernel 注册您的过滤器实例,以获取它实现的过滤器接口的类型.

//自绑定我们的过滤器,因此可以注入依赖项.kernel.Bind().To();

FilterConfig 中,注册您的过滤器提供程序.

公共类FilterConfig{公共静态无效RegisterGlobalFilters(GlobalFilterCollection过滤器){filters.Add(new HandleErrorAttribute());//向 MVC 注册过滤器提供程序.FilterProviders.Providers.Insert(0, new GlobalFilterProvider(DependencyResolver.Current));}}

现在每个请求都会填充您的用户详细信息.

但更重要的是,您的 ArticlesController 不需要 MyDbContext 作为依赖项,其他控制器也不需要.

<块引用>

我不确定创建我的 DbContext 的实例有多昂贵.有没有什么办法可以优化它只在请求实际上需要我访问数据库时才创建.

看看这个问题:每个网络请求一个 DbContext... 为什么?

I went through the recommended steps to add Ninject to my MVC application. And I added a DbContext argument to my controllers' constructors.

Controller:

public class MyController : BaseController
{
    public ArticlesController(MyDbContext context)
        : base(context)
    { }
}

Base Controller:

public class BaseController : Controller
{
    protected DbContext MyDbContext;

    public BaseController(MyDbContext context)
    {
        MyDbContext = context;
    }
}

This seems to work well. But leaves me with a few questions.

  1. Does Ninject ensure my DbContext is cleaned up and disposed in a timely fashion?

  2. I have created a base class for all my application's controllers to handle any common initialization, etc. The base class accepts an instance of my DbContext argument in the constructor. But this requires me to also add this argument to every controller in my app. Is there any way to not require this?

  3. I'm not sure how expensive it is to create an instance of my DbContext. Is there any way to make the optimization that it only gets created if the request actually requires me to access the database.

解决方案

Does Ninject ensure my DbContext is cleaned up and disposed in a timely fashion?

As per this answer:

The CLR documentation states that whoever creates a Disposable object is responsible for calling Dispose. In this case the object is created by Ninject. That means you should not call Dispose explicitly.

Ninject disposes every Disposable object that has another scope other than InTransientScope as soon as the scope object to which the created object is tied is collected by GC. That's why every Disposable object should be Bindd with a scope that is not InTransientScope(). E.g. you can use InParentScope() from the NamedScope extension which will Dispose the object as soon as the object it is injected into is garbage collected.


I have created a base class for all my application's controllers to handle any common initialization, etc. The base class accepts an instance of my DbContext argument in the constructor. But this requires me to also add this argument to every controller in my app. Is there any way to not require this?

Simply put, never use a common base class for MVC Controllers. Class inheritance tends to tightly couple your logic and it becomes difficult to maintain over time. It also tends to lead you to create a god object, because creating multiple levels of injected dependencies would mean even more required dependencies for every Controller.

If you have cross-cutting concerns, you should use globally registered filters instead. You can make a separate filter for each piece of logic, which doesn't violate the Single Responsibility Principle as a shared base class would. And if you register your filters globally, you can use constructor injection as in this action filter or this authorization filter. You can also make your own attributes (without behavior) to make them conditional per controller and/or action, if necessary.

Example:

Since you explicitly said you wanted to set common ViewBag properties based on the current user, here is how that can be done with filters.

CurrentUserProfileFilter

public class CurrentUserProfileFilter : IAuthorizationFilter
{
    private readonly MyDbContext context;

    public CurrentUserAuthorizationFilter(MyDbContext context)
    {
        this.context = context;
    }

    public void OnAuthorization(AuthorizationContext filterContext)
    {
        var currentUserName = filterContext.HttpContext.User.Identity.Name;

        // Set the ViewBag for the request.
        filterContext.Controller.ViewBag.UserName = currentUserName;

        var userBirthdate = 
            from user as this.context.AspNetUsers
            where user.UserName == currentUserName
            select birthdate;
    
        if (userBirthdate.Date == DateTime.Now.Date)
        {
            filterContext.Controller.ViewBag.Message = "Happy Birthday!";
        }
    }
}

GlobalFilterProvider

MVC has a static GlobalFiltersCollection where you are supposed to register filter instances globally. This isn't going to do for filters that have dependencies that have lifetimes that are managed by the DI container (such as DbContext).

To ensure the filters are resolved on demand (per-request), we make an IFilterProvider that resolves them through the container (assuming your Ninject container is registered with MVC as the DependencyResolver);

public class GlobalFilterProvider : IFilterProvider
{
    private readonly IDependencyResolver dependencyResolver;

    public GlobalFilterProvider(IDependencyResolver dependencyResolver)
    {
        this.dependencyResolver = dependencyResolver;
    }

    public IEnumerable<Filter> GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
    {
        foreach (var filter in this.dependencyResolver.GetServices<IActionFilter>())
        {
            yield return new Filter(filter, FilterScope.Global, order: null);
        }
        foreach (var filter in this.dependencyResolver.GetServices<IAuthorizationFilter>())
        {
            yield return new Filter(filter, FilterScope.Global, order: null);
        }
        foreach (var filter in this.dependencyResolver.GetServices<IExceptionFilter>())
        {
            yield return new Filter(filter, FilterScope.Global, order: null);
        }
        foreach (var filter in this.dependencyResolver.GetServices<IResultFilter>())
        {
            yield return new Filter(filter, FilterScope.Global, order: null);
        }
        // If MVC 5, add these as well...
        //foreach (var filter in this.dependencyResolver.GetServices<System.Web.Mvc.Filters.IAuthenticationFilter>())
        //{
        //    yield return new Filter(filter, FilterScope.Global, order: null);
        //}
    }
}

Usage

In your Ninject composition root, register the instance of your filter with the kernel for the type or types of filter interfaces it implements.

// Self-bind our filter, so dependencies can be injected.
kernel.Bind<IAuthorizationFilter>().To<CurrentUserProfileFilter>();

In FilterConfig, register your filter provider.

public class FilterConfig
{
    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        filters.Add(new HandleErrorAttribute());

        // Register the filter provider with MVC.
        FilterProviders.Providers.Insert(0, new GlobalFilterProvider(DependencyResolver.Current));
    }
}

Now on every request, your user details are populated.

But more importantly, your ArticlesController doesn't require MyDbContext as a dependency, nor do the rest of your controllers.

I'm not sure how expensive it is to create an instance of my DbContext. Is there any way to make the optimization that it only gets created if the request actually requires me to access the database.

Have a look at this question: One DbContext per web request... why?

这篇关于关于使用 Ninject 的问题的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆