关于使用 Ninject 的问题

Questions about using Ninject

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

控制器:

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

基地控制器:

public class BaseController : Controller
{
    protected DbContext MyDbContext;

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

这似乎很有效。但是留给我几个问题。

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

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

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

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

根据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?

简单地说,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,因为创建多级注入依赖项意味着每个控制器需要更多的依赖项。

如果您有 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 . You can also make your own attributes (without behavior) 使它们成为每个控制器的条件 and/or 操作,如有必要。

示例:

既然您明确表示要根据当前用户设置通用 ViewBag 属性,下面是如何使用过滤器完成的。

当前用户配置文件过滤器

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

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

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

用法

在您的 Ninject 组合根目录中,使用 kernel 注册您的过滤器实例,以获取它实现的一种或多种过滤器接口类型。

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

FilterConfig 中,注册您的过滤器提供商。

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

现在,根据每个请求,您的用户详细信息都会被填充。

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

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.

看看这个问题:One DbContext per web request... why?