Ninject是一款.Net平台下的开源依赖注入框架.按照官方说法,它快如闪电、超级轻量,且充分利用了.Net的最新语法,使用Lambda表达式代替Xml文件完成类型绑定.Ninject结构精巧,功能强大,越来越受到各类开发者的关注,其开源社区非常活跃,众多开发者为它开发了各种各样的扩展应用.其中有一款名叫Ninject.Web.Common,是所有将Ninject应用于Web项目的基框架,而Ninject.MVC3则是将Ninject应用于Asp.Net Mvc中的框架.这两者是本文分析的主角.

书写本文时,Ninject的版本号为3.0.1,Ninject.Web.Common的版本号为3.0.0.3.,Ninject.MVC3的版本号为3.0.0.6.

OnePerRequestHttpModule是与对象生命周期有关的类,表示在同一次请求内同一类型会解析出相同的实例.这个是Web程序特有的生命周期,能在部分应用中节约资源,提高性能.此类实现了IHttpModule接口,如下:

public void Init(HttpApplication application)
{
application.EndRequest += (o, e) => this.DeactivateInstancesForCurrentHttpRequest();
} public void DeactivateInstancesForCurrentHttpRequest()
{
if (this.ReleaseScopeAtRequestEnd)
{
var context = HttpContext.Current;
this.MapKernels(kernel => kernel.Components.Get<ICache>().Clear(context));
}
}

可以看到,在WebApplication生命周期的最后,其会将所有在请求过程中生成的且缓存于HttpContext.Current的对象手动清空.还可以看到,其实这个类只涉及到OnePerRequest生命周期的部分实现,即清空部分.对象的分配部分则内置于Ninject框架中.

IBootstrapper接口的实现类为Bootstrapper,如下所示:

private static IKernel kernelInstance;

public void Initialize(Func<IKernel> createKernelCallback)
{
kernelInstance = createKernelCallback(); kernelInstance.Components.GetAll<INinjectHttpApplicationPlugin>().Map(c => c.Start());
kernelInstance.Bind<IHttpModule>().To<HttpApplicationInitializationHttpModule>();
kernelInstance.Inject(this);
} public void ShutDown()
{
if (kernelInstance != null)
{
kernelInstance.Components.GetAll<INinjectHttpApplicationPlugin>().Map(c => c.Stop());
kernelInstance.Dispose();
kernelInstance = null;
}
} public void InitializeHttpApplication(HttpApplication httpApplication)
{
    kernelInstance.Inject(httpApplication);
}

Bootstrapper的中文意思是启动加载器,即管理程序启动时所需资源.IOC核心最终是保存在此对象的静态字段kernelInstance中.Initialize方法接受一个Func<IKernel>类型的委托,即通过它获取IOC核心.然后从中获取所有实现了INinjectHttpApplicationPlugin接口的类并立刻调用此接口Start方法.可以从名字猜出,这是一个简易的插件系统,在系统启动前首先启动所有插件.第二句先放一下.第三句则是将自己重新注入.只有由从IOC中获取的对象才能被注入,而Inject方法专门用于不是由IOC生成的对象,可以让其标记了[Inject]的且为空的属性重新获得对象注入.可以看到在默认情况下这一句不会产生实际效果.

InitializeHttpApplication则是将HttpApplication对象重注入.HttpApplication对象是由Asp.Net自动生成的.如果在实际使用的子类中进行了属性注入,则需要通过重注入的方式为属性获取合适的值.

ShutDown方法在系统关闭时被调用,用于插件的逐个卸载.

分析完这两个类我们再来分析Ninject.Web.Common的核心类:NinjectHttpApplication

private readonly OnePerRequestHttpModule onePerRequestHttpModule;

private readonly IBootstrapper bootstrapper;

protected NinjectHttpApplication()
{
this.onePerRequestHttpModule = new OnePerRequestHttpModule();
this.onePerRequestHttpModule.Init(this);
this.bootstrapper = new Bootstrapper();
} public void Application_Start()
{
lock (this)
{
this.bootstrapper.Initialize(this.CreateKernel);
this.onePerRequestHttpModule.ReleaseScopeAtRequestEnd = this.bootstrapper.Kernel.Settings.Get("ReleaseScopeAtRequestEnd", true);
this.OnApplicationStarted();
}
} public void Application_End()
{
this.bootstrapper.ShutDown();
this.OnApplicationStopped();
} protected abstract IKernel CreateKernel(); protected virtual void OnApplicationStarted() { } protected virtual void OnApplicationStopped() { } public override void Init()
{
    base.Init();
    this.bootstrapper.InitializeHttpApplication(this);
}

在此对象构造函数中,构造了上面所述的两个对象,并手动调用了OnePerRequestHttpModule对象的Init方法完成了此IHttpModule的挂载.然后在Application_Start()方法中调用了IBootstrapper对象的Initialize方法完成了插件的挂载.并设置了OnePerRequestHttpModule对象是否开启资源清理.最后调用OnApplicationStarted方法.Application_End()则完成插件的卸载和OnApplicationStopped()方法.

可以看到,此对象改变了传统HttpApplication对象的一般处理过程.按照其设计意图,应用程序开启时执行的方法由默认的Application_Start方法转移动OnApplicationStarted(),而应用程序终止时执行的方法则由Application_End方法转移到OnApplicationStopped方法中.

此对象实际是一个抽象对象,包含唯一一个抽象方法CreateKernel,方法最终将被用户重写,用于提供IOC核心.

在Init方法中调用Bootstrapper对象的InitializeHttpApplication方法完成对自身的重注入.Init方法与Application_Start方法不同.Init方法将在每个HttpApplication对象构造完成并加载了所有IHttpModule之后被调用,而Application_Start方法则是在应用程序启动时被调用,在整个应用程序中Init方法会被调用多次,而Application_Start方法只会被调用一次.如果HttpApplication对象池有剩余对象,则会取出一个来处理请求,这时不会触发Init方法,否则则会建立新的HttpApplication对象来处理新的请求,这时则会调用Init方法.

至此,Ninject.Web.Common的主体工程已分析完毕.但其仍提供了两个额外的HttoModule.这也是最容易让人引起困惑的地方.

NinjectHttpModule类提供了一种功能,即通过类型绑定而不是Web.Config来配置IHttpModule,如下:

private IList<IHttpModule> httpModules;

public void Init(HttpApplication context)
{
this.httpModules = new Bootstrapper().Kernel.GetAll<IHttpModule>().ToList();
foreach (var httpModule in this.httpModules)
{
httpModule.Init(context);
}
} public void Dispose()
{
foreach (var httpModule in this.httpModules)
{
httpModule.Dispose();
} this.httpModules.Clear();
}

代码比较简单,即在Init处获取所有已绑定的实现了IHttpModule接口的对象并循环调用它们的Init方法,在Dispose方法处循环调用它们各自的Dispose方法.

HttpApplicationInitializationHttpModule对象如下:

private readonly Func<IKernel> lazyKernel;

public HttpApplicationInitializationHttpModule(Func<IKernel> lazyKernel)
{
this.lazyKernel = lazyKernel;
} public void Init(HttpApplication context)
{
this.lazyKernel().Inject(context);
}

这个更简单,就是将HttpApplication对象重新注入,只不过实现为了IHttpModule接口.还记得Bootstrapper对象的Initialize方法吗?在那里将IHttpModule与HttpApplicationInitializationHttpModule进行了绑定.

在Ninject.Web.Common官网上展示了使用这个框架的两种方式,一种是继承,一种是动态注入.现在问题来了:

1.如何使用HttpApplicationInitializationHttpModule对象?可以看到,其没有无参构造函数,如果直接配置于Web.Config,Asp.Net则会因为无法构造此对象而报错.此对象需配合NinjectHttpModule对象使用.

2.如果使用继承方式使用,同时在Web.Config中配置了NinjectHttpModule,则HttpApplication对象将被注入两次.一次在Bootstrapper对象的InitializeHttpApplication方法中,一次在HttpApplicationInitializationHttpModule对象的Init方法中.一开始我百思不得其解,后来看到一篇官方的反馈我才恍然大悟.原来HttpApplicationInitializationHttpModule并不应该用于使用继承方式中,而是用于动态注入方式中的.

3.同上,如果使用继承方式使用,同时在Web.Config中配置了NinjectHttpModule,则程序会报错,因为是HttpApplicationInitializationHttpModule对象是由IOC核心构造的,其需要知道Func<IKernel>类型的实际类型.

最后,我们来看一看官方推荐的动态注入使用方式

[assembly: WebActivator.PreApplicationStartMethod(typeof(WebApplication1.App_Start.NinjectWebCommon), "Start")]
[assembly: WebActivator.ApplicationShutdownMethodAttribute(typeof(WebApplication1.App_Start.NinjectWebCommon), "Stop")]
public static class NinjectWebCommon
{
private static readonly Bootstrapper bootstrapper = new Bootstrapper(); /// <summary>
/// Starts the application
/// </summary>
public static void Start()
{
DynamicModuleUtility.RegisterModule(typeof(OnePerRequestHttpModule));
DynamicModuleUtility.RegisterModule(typeof(NinjectHttpModule));
bootstrapper.Initialize(CreateKernel);
} public static void Stop()
{
bootstrapper.ShutDown();
} private static IKernel CreateKernel()
{
var kernel = new StandardKernel();
kernel.Bind<Func<IKernel>>().ToMethod(ctx => () => new Bootstrapper().Kernel);
kernel.Bind<IHttpModule>().To<HttpApplicationInitializationHttpModule>(); RegisterServices(kernel);
return kernel;
} private static void RegisterServices(IKernel kernel) { }
}

它充分使用Asp.Net 4.0的新技术,并使用了WebActivator框架,在Web程序启动前动态调用Start函数,在关闭前调用Stop函数.在Start函数中,动态注册了OnePerRequestHttpModule和NinjectHttpModule.而对于CreateKernel函数,首先新建了IOC核心,然后绑定了Func<IKernel>类型与IHttpModule类型.可以看到,由于在Bootstrapper对象的Initialize方法里已绑定过一次,这里重复绑定了.然后调用RegisterServices方法获取应用程序的各类具体类型绑定,最后将IOC核心返回以保存至Bootstrapper的静态变量kernelInstance中.

Ninject.MVC3的代码则比较简单

MvcModule类继承自GlobalKernelRegistrationModule<OnePerRequestHttpModule>类.GlobalKernelRegistrationModule<T>类继承自NinjectModule类.完成了各种类型绑定.GlobalKernelRegistrationModule<T>类与GlobalKernelRegistration类配合使用的,实现了一种反向绑定.一般的正向绑定,即某一个IOC核心绑定了多个接口到类的映射,而反向绑定则是记录某个接口在哪些IOC核心里进行了绑定.这个功能我暂时没有看到在哪里进行了使用,不过应该是有所目的的.

MvcModule类定义如下:

public override void Load()
{
base.Load();
this.Kernel.Components.Add<INinjectHttpApplicationPlugin, NinjectMvcHttpApplicationPlugin>();
this.Kernel.Bind<IDependencyResolver>().To<NinjectDependencyResolver>();
this.Kernel.Bind<IFilterProvider>().To<NinjectFilterAttributeFilterProvider>();
this.Kernel.Bind<IFilterProvider>().To<NinjectFilterProvider>();
this.Kernel.Bind<RouteCollection>().ToConstant(RouteTable.Routes);
this.Kernel.Bind<HttpContext>().ToMethod(ctx => HttpContext.Current).InTransientScope();
this.Kernel.Bind<HttpContextBase>().ToMethod(ctx => new HttpContextWrapper(HttpContext.Current)).InTransientScope();
this.Kernel.Bind<ModelValidatorProvider>().To<NinjectDataAnnotationsModelValidatorProvider>();
//this.Kernel.Bind<IModelBinderProvider>().To<NinjectModelBinderProvider>();
//this.Kernel.Bind<IModelBinder>().To<NinjectModelBinder>();
}

那这个Load函数是何时执行的呢?这就要谈到Ninject的加载策略了.核心对象StandardKernel与其基类KernelBase的构造函数如下

public class StandardKernel : KernelBase
{
public StandardKernel(params INinjectModule[] modules) : base(modules)
{
}
} public abstract class KernelBase : BindingRoot, IKernel
{
protected KernelBase()
: this(new ComponentContainer(), new NinjectSettings(), new INinjectModule[0])
{} protected KernelBase(IComponentContainer components, INinjectSettings settings, params INinjectModule[] modules)
{
if (this.Settings.LoadExtensions)
{
this.Load(this.Settings.ExtensionSearchPatterns);
}
}
}

可以看到,在KernelBase的构造函数中,有一个LoadExtensions的判断,默认在NinjectSettings类中,意思是加载本地扩展

public class NinjectSettings : INinjectSettings
{
public bool LoadExtensions
{
get { return Get("LoadExtensions", true); }
set { Set("LoadExtensions", value); }
} public string[] ExtensionSearchPatterns
{
get { return Get("ExtensionSearchPatterns", new [] { "Ninject.Extensions.*.dll", "Ninject.Web*.dll" }); }
set { Set("ExtensionSearchPatterns", value); }
}
}

可以猜测,默认会去Bin目录下搜索所有以Ninject.Extensions或Ninject.Web开头的程序集.

具体的加载过程则比较简单

public void Load(IEnumerable<string> filePatterns)
{
var moduleLoader = this.Components.Get<IModuleLoader>();
moduleLoader.LoadModules(filePatterns);
}

加载管理器接口IModuleLoader的实现类为ModuleLoader

public void LoadModules(IEnumerable<string> patterns)
{
var plugins = Kernel.Components.GetAll<IModuleLoaderPlugin>(); var fileGroups = patterns
.SelectMany(pattern => GetFilesMatchingPattern(pattern))
.GroupBy(filename => Path.GetExtension(filename).ToLowerInvariant()); foreach (var fileGroup in fileGroups)
{
string extension = fileGroup.Key;
IModuleLoaderPlugin plugin = plugins.Where(p => p.SupportedExtensions.Contains(extension)).FirstOrDefault(); if (plugin != null)
plugin.LoadModules(fileGroup);
}
}

第一句获取所有的加载分析器.程序自带了加载以dll为扩展名的文件型加载分析器,实现类为CompiledModuleLoaderPlugin.第二句获取传入的程序集全路径.第三句则是循环文件,为每一个文件获取一个加载分析器并进行操作.

public class CompiledModuleLoaderPlugin : NinjectComponent, IModuleLoaderPlugin
{
public void LoadModules(IEnumerable<string> filenames)
{
var assembliesWithModules = this.assemblyNameRetriever.GetAssemblyNames(filenames, asm => asm.HasNinjectModules());
this.Kernel.Load(assembliesWithModules.Select(asm => Assembly.Load(asm)));
}
}

这里将实际功能委托给AssemblyNameRetriever类完成.

public class AssemblyNameRetriever : NinjectComponent, IAssemblyNameRetriever
{
public IEnumerable<AssemblyName> GetAssemblyNames(IEnumerable<string> filenames, Predicate<Assembly> filter)
{
var assemblyCheckerType = typeof(AssemblyChecker);
var temporaryDomain = CreateTemporaryAppDomain();
try
{
var checker = (AssemblyChecker)temporaryDomain.CreateInstanceAndUnwrap(
assemblyCheckerType.Assembly.FullName,
assemblyCheckerType.FullName ?? string.Empty); return checker.GetAssemblyNames(filenames.ToArray(), filter);
}
}
}

这里创建了一个临时应用程序域,然后在其中创建AssemblyChecker对象,最终通过它来完成最后的任务

private class AssemblyChecker : MarshalByRefObject
{
public IEnumerable<AssemblyName> GetAssemblyNames(IEnumerable<string> filenames, Predicate<Assembly> filter)
{
var result = new List<AssemblyName>();
foreach (var filename in filenames)
{
Assembly assembly = Assembly.Load(filename); if (filter(assembly))
{
result.Add(assembly.GetName(false));
}
} return result;
}
}

这里在临时应用程序域中创建程序集后,使用了一个判断来决定是否在此程序集中搜索,是由之前传入的,定义在ExtensionsForAssembly类中.

internal static class ExtensionsForAssembly
{
public static bool HasNinjectModules(this Assembly assembly)
{
return assembly.GetExportedTypes().Any(IsLoadableModule);
} public static IEnumerable<INinjectModule> GetNinjectModules(this Assembly assembly)
{
return assembly.GetExportedTypes()
.Where(IsLoadableModule)
.Select(type => Activator.CreateInstance(type) as INinjectModule);
} private static bool IsLoadableModule(Type type)
{
return typeof(INinjectModule).IsAssignableFrom(type)
&& !type.IsAbstract
&& !type.IsInterface
&& type.GetConstructor(Type.EmptyTypes) != null;
}
}

可以看到,这里就是说,在某一程序集中是否有实现了INinjectModule接口的类,且不是接口,不是抽象类,且还拥有无参构建函数.

获取到程序集名后,由KernelBase完全加载

public void Load(IEnumerable<Assembly> assemblies)
{
this.Load(assemblies.SelectMany(asm => asm.GetNinjectModules()));
} public void Load(IEnumerable<INinjectModule> m)
{
Ensure.ArgumentNotNull(m, "modules"); m = m.ToList();
foreach (INinjectModule module in m)
{
module.OnLoad(this); this.modules.Add(module.Name, module);
}
}

NinjectDependencyResolver类则实现了接口IDependencyResolver,其本质是一个配置器模式,即让Asp.Net MVC从IOC核心的获取资源.定义如下:

private readonly IResolutionRoot resolutionRoot;

public object GetService(Type serviceType)
{
var request = this.resolutionRoot.CreateRequest(serviceType, null, new Parameter[0], true, true);
return this.resolutionRoot.Resolve(request).SingleOrDefault();
} public IEnumerable<object> GetServices(Type serviceType)
{
return this.resolutionRoot.GetAll(serviceType).ToList();
}

NinjectMvcHttpApplicationPlugin类则是利用Ninject.Web.Common的插件机制,完成IOC与Asp.Net MVC的对接,其关键代码如下:

public void Start()
{
ModelValidatorProviders.Providers.Remove(ModelValidatorProviders.Providers.OfType<DataAnnotationsModelValidatorProvider>().Single());
DependencyResolver.SetResolver(this.CreateDependencyResolver());
RemoveDefaultAttributeFilterProvider();
} protected IDependencyResolver CreateDependencyResolver()
{
return this.kernel.Get<IDependencyResolver>();
}

在CreateDependencyResolver方法中从IOC核心中获取IOC实例,在KernelBase对象的构造函数中完成了IKernel接口与IKernel实例的隐式绑定.如下

this.Bind<IKernel>().ToConstant(this).InTransientScope();
this.Bind<IResolutionRoot>().ToConstant(this).InTransientScope();

回到NinjectMvcHttpApplicationPlugin类,最后在其Start方法的第二行将获取到的IOC实例作为参数传入DependencyResolver对象的SetResolver方法,完成与Asp.Net MVC的对接.

至此,Ninject.MVC3的工作已全部完成!

参考的文章

Ninject官网

Ninject.Web.Common官网

Ninject.MVC3官网

How to inject dependencies in an HttpModule with a NinjectHttpApplication (no nuget)?

RC2, issue with HttpApplicationInitializationHttpModule Injection

IOC框架Ninject实践总结

Ninject 学习例子代码(一)

Ninject 学习例子代码(二)

ASP.NET MVC3 让依赖注入来的更简单(新补充了Ninject示例

ASP.NET MVC3 + Ninject.Mvc3 依赖注入原来可以这么简单

ASP.NET MVC 3 Service Location, Part

Ninject是一款.Net平台下的开源依赖注入框架的更多相关文章

  1. C#下IOC/依赖注入框架Grace介绍

    对依赖注入或控制反转不了解的童鞋请先自行学习一下这一设计,这里直接介绍项目和实现步骤. Grace是一个开源.轻巧.易用同时特性丰富.性能优秀的依赖注入容器框架.从这篇IOC容器评测文章找到的Grac ...

  2. 依赖注入框架Ninject

    为什么需要依赖注入 我们提到MVC的一个重要特征是关注点分离(separation of concerns).我们希望应用程序的各部分组件尽可能多的相互独立.尽可能少的相互依赖. 我们的理想情况是:一 ...

  3. 八款你不得不知的开源前端JS框架

    angular.js Angular.JS是一个开源的JavaScript框架,最适于开发客户端的单页面应用.它实现了前端MVC架构,专注于扩展HTML功能,提供动态数据绑定(Data Binding ...

  4. (依赖注入框架:Ninject ) 一 手写依赖注入

    什么是依赖注入? 这里有一个场景:战士拿着刀去战斗: 刀: class Sword { public void Hit(string target) { Console.WriteLine($&quo ...

  5. Sparrow-WiFi:一款Linux平台下的图形化WiFi及蓝牙分析工具

    工具概述 Sparrow-wifi本质上一款针对下一代2.4GHz和5GHz的WiFi频谱感知工具,它不仅提供了GUI图形化用户界面,而且功能更加全面,可以代替类似inSSIDer和linssid之类 ...

  6. HttpServer:一款Windows平台下基于IOCP模型的高并发轻量级web服务器

    HttpServer的特点1.完全采用IOCP模型,实现真正的异步IO,高并发.高可靠: 2.支持4G以上文件下载: 3.支持断点续传: 4.轻量级,体积小,服务器文件仅200多K,无任何依赖库: 5 ...

  7. .NET平台下开源框架

    一.AOP框架Encase 是C#编写开发的为.NET平台提供的AOP框架.Encase 独特的提供了把方面(aspects)部署到运行时代码,而其它AOP框架依赖配置文件的方式.这种部署方面(asp ...

  8. 使用Ninject进行DI(依赖注入)

    Ninject是一个快如闪电.超轻量级的基于.Net平台的依赖注入框架.它能够帮助你把应用程序分离成一个个松耦合.高内聚的模块,然后用一种灵活的方式组装起来.通过使用Ninject配套你的软件架构,那 ...

  9. Ninject依赖注入——构造函数的注入

    1.Ninject简介 Ninject是基于.Net平台的依赖注入框架,它能够将应用程序分离成一个个高内聚.低耦合(loosely-coupled, highly-cohesive)的模块,然后以一种 ...

随机推荐

  1. ORACLE 根据上表不同的字段值的统计

    select p.id comperitorId,p.compcorp competitorName, sum(case when c.kindname = 'ATM' then c.num else ...

  2. Visual Studio Code开发TypeScript

    [Tool] 使用Visual Studio Code开发TypeScript   [Tool] 使用Visual Studio Code开发TypeScript 注意 依照本篇操作步骤实作,就可以在 ...

  3. SICP 练习 (2.12)解决摘要 :不同的实现时间

    SICP 2.12 要求我们定义一个构造函数make-center-percent,它接收两个參数,分别代表中心点和一个误差百分比.我们须要通过这个构造函数产生一个区间.此外还须要定义一个选择函数pe ...

  4. 组态ORACLE 11G ADG

    一旦载10g的,没有票据.昨天使用duplicate方法一安装11g ADG,过程艰辛,记录: 一.环境配置 主图书馆 IP地址:192.168.233.128/24 操作系统版本号:rhel5.8 ...

  5. poj 3744 Scout YYF I (可能性DP+矩阵高速功率)

    Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 5062   Accepted: 1370 Description YYF i ...

  6. iOS10收集IDFA,植入第三方广告[终结]--ADMob

    [PS: 前段时间,公司做ASO推广,需要在应用中收集IDFA值,跟广告平台做交互!于是有了这个需求--] 1.首先,考虑了一下情况(自己懒 -_-#),就直接在首页上写了一个Banner,循环加载广 ...

  7. 【高德地图API】从零开始学高德JS API(三)覆盖物——标注|折线|多边形|信息窗口|聚合marker|麻点图|图片覆盖物

    原文:[高德地图API]从零开始学高德JS API(三)覆盖物——标注|折线|多边形|信息窗口|聚合marker|麻点图|图片覆盖物 摘要:覆盖物,是一张地图的灵魂.有覆盖物的地图,才是完整的地图.在 ...

  8. jquery 仅仅读

    大家都理解这是什么,正常的写法例如以下: if (status == true) { $("#minDelistStr").val(totalAmount);// 去掉首部的&qu ...

  9. 我在Github上的flare-spark项目

    Flare-Spark 介绍 我在自己的github上建了个flare-spark项目,本身是Apache Spark项目Master分支的镜像.在Spark的基础上,添加了flare子项目. 估计大 ...

  10. OracleServiceORCL服务不见了怎么办

    用管理员身份运行命令提示符(CMD) 然后输入“oradim -new -sid orcl”即可