一、控制反轉和依賴注入
Ninject是一個輕量級的基於.Net平台的依賴注入(IOC)框架。所謂的IOC,即控制反轉(Inversion of Control),它是一個經典的面向對象編程法則,它的作用主要是用來幫助應用程序解耦,並把程序分離成一個個松耦合高內聚的模塊。控制反轉還有一個名字叫依賴注入(Dependency Injection),簡稱DI。
二、快速無xml配置注入
1、定義應用程序Module
ServiceModuleusing LogService;
using LogService.Impl;
using Ninject.Modules;
using NinjectApp.Warrior;
using NinjectApp.Weapon;
namespace NinjectApp
{
internal class ServiceModule : NinjectModule
{
public override void Load()
{
Bind<ILogService>().To<DbLogService>();
Bind<IWeapon>().To<Sword>().InSingletonScope();
//Bind<IWeapon>().To<Shuriken>();
Bind<Shuriken>().ToSelf().WhenInjectedInto<IWeapon>();
Bind<IWarrior>().To<FootSoldier>();
//Bind<IWarrior>().To<Samurai>();
}
}
}
2、手動調用服務
InjectManual /// <summary>
/// 手動注入
/// </summary>
static void InjectManual()
{
using (var kernel = new StandardKernel(module))
{
var dbLogger = kernel.Get<ILogService>();
dbLogger.AppendLog("hello world");
var weapon = kernel.Get<IWeapon>();
Console.WriteLine(weapon.GetType());
Console.WriteLine(weapon.Name);
//weapon = kernel.Get<Shuriken>();
//Console.WriteLine(weapon.GetType());
//Console.WriteLine(weapon.Name);
var weapon1 = kernel.Get<IWeapon>();
Console.WriteLine(weapon1.GetType());
Console.WriteLine(weapon1.Name);
Console.WriteLine(object.ReferenceEquals(weapon, weapon1));
var warrior = kernel.Get<IWarrior>();
Console.WriteLine(warrior.GetType());
}
}
注:Ninject的綁定對象作用域有多種,本文的demo中有具體的單元測試,具體可以直接查看源碼或者參考官方文檔。。
三、配置文件注入
通過Ninject的xml擴展,可以實現傳統的類似於Spring.net、Unity等IOC容器的注入方式。
1、配置文件
<?xml version="1.0" encoding="utf-8" ?>
<module name="ServiceModule">
<bind name="Txtlog" service="LogService.ILogService,LogService" to="LogService.Impl.TxtLogService,LogService"/>
<!--<bind name="Dblog" service="LogService.ILogService,LogService" to="LogService.Impl.DbLogService,LogService"/>-->
<bind name="Sword" service="NinjectApp.Weapon.IWeapon,NinjectApp" to="NinjectApp.Weapon.Sword,NinjectApp"/>
<bind name="FootSoldier" service="NinjectApp.Warrior.IWarrior,NinjectApp" to="NinjectApp.Warrior.FootSoldier,NinjectApp"/>
</module>
2、利用擴展加載服務
XmlServiceModuleusing System.Collections.Generic;
using System.Xml.Linq;
using Ninject;
using Ninject.Extensions.Xml;
using Ninject.Extensions.Xml.Handlers;
namespace NinjectApp
{
public class XmlModuleContext
{
protected readonly IKernel kernel;
protected readonly IDictionary<string, IXmlElementHandler> elementHandlers;
public XmlModuleContext()
{
kernel = new StandardKernel();
elementHandlers = new Dictionary<string, IXmlElementHandler> { { "bind", new BindElementHandler(kernel) } };
}
}
public class XmlServiceModule : XmlModuleContext
{
private static readonly XmlServiceModule instance = new XmlServiceModule();
protected readonly XmlModule module = null;
public XmlServiceModule()
{
var document = XDocument.Load("Config/NinjectServiceModule.config");
module = new XmlModule(document.Element("module"), elementHandlers);
module.OnLoad(kernel);
}
public static IKernel GetKernel()
{
return instance.kernel;
}
}
}
3、調用服務
InjectByConfig /// <summary>
/// 通過xml配置注入
/// </summary>
static void InjectByConfig()
{
var kernel = XmlServiceModule.GetKernel();
var logger = kernel.Get<ILogService>();
Console.WriteLine(logger.GetType());
logger.AppendLog("hello world");
var weapon = kernel.Get<IWeapon>();
Console.WriteLine(weapon.GetType());
Console.WriteLine(weapon.Name);
var warrior = kernel.Get<IWarrior>();
Console.WriteLine(warrior.GetType());
}
雖然配置注入看上去更容易擴展應對外部變化,但是項目龐大臃腫之后,配置文件並不好管理。固然有一些可視化的工具,但是仍然容易出現偏差。Ninject最擅長的基本注入功能就是無配置簡單快速注入,達到free yourself from xml的目的,對於一般的中小型應用程序完全可以零配置。
四、MVC項目的依賴注入
通過Ninject的MVC擴展可以輕松實現MVC項目的依賴注入。
1、NinjectHttpApplication
在Global.asax.cs文件里重新定義MvcApplication繼承自NinjectHttpApplication,重寫OnApplicationStarted事件和CreateKernel方法:
MvcApplication
protected override void OnApplicationStarted()
{
base.OnApplicationStarted();
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
}
protected override IKernel CreateKernel()
{
var kernel = new StandardKernel();
kernel.Bind<IUserService>().To<UserInfoService>();
kernel.Bind<ILogService>().To<TxtLogService>();
//kernel.Bind<ILogService>().To<TxtLogService>().InSingletonScope();//單例
return kernel;
}
2、通過構造函數或者屬性或者Module實現注入
(1)、Controller實現注入
a、構造函數注入
Account/Index private readonly IUserService userService = null;
public AccountController(IUserService userService)
{
this.userService = userService;
//var userInfo = userService.GetCurrentUser();//do sth
}
b、屬性注入
定義屬性,加上Inject特性即可實現注入。
[Inject]
public IUserService CurrentUserService { get; set; }
c、module注入
定義Module:
MvcServiceModuleusing LogService;
using LogService.Impl;
using Ninject.Modules;
using UserService;
using UserService.Impl;
namespace MVCApp.Helper
{
internal class ServiceModule : NinjectModule
{
public override void Load()
{
Bind<ILogService>().To<TxtLogService>();
Bind<IUserService>().To<UserInfoService>();
}
}
}
接着調用即可:
MvcServiceModule using (var kernel = new StandardKernel(new ServiceModule()))
{
var logger = kernel.Get<ILogService>();
}
(2)、自定義Attribute實現注入
ExceptionHandleAttributeusing System.Web.Mvc;
using LogService;
using Ninject;
namespace MVCApp.Helper
{
/// <summary>
/// 異常處理特性
/// </summary>
public class ExceptionHandleAttribute : HandleErrorAttribute
{
[Inject]
public ILogService Logger { get; set; }
public override void OnException(ExceptionContext filterContext)
{
Logger.AppendLog(filterContext.Exception.ToString());//記錄日志
}
}
}
和Controller非常相似,示例使用屬性加上Inject特性的方式實現注入,其他注入方式略過。
到這里,你應該已經可以看到,這可以算是web應用程序中非常干凈利落的注入方式,簡單的令人發指。
五、組合還是繼承
通過IOC框架實現服務依賴注入本來不難,但是這里或多或少會牽扯到一個問題:注入服務調用是使用組合還是繼承?
舉例來說,最基礎的用戶服務(或者日志服務),一般的web應用程序幾乎每個控制器(或者頁面)都或多或少和用戶有關系。
問題來了,不同的控制器或者頁面要調用用戶服務該怎么做?
下面以MVC項目為例來說明一下通常的注入方法。
如你所知,通常的做法,所謂組合優於繼承(繼承被認為是一種強耦合),我們只要在需要調用服務的控制器中定義一個服務變量或者屬性,通過構造函數注入,類似下面這樣:
controllerconstructor private readonly IUserService userService = null;
public AccountController(IUserService userService)
{
this.userService = userService;
}
然后在對應的Action中就可以調用用戶服務了。
如果你的Controller很少,這種方式當然可以接受。但是,實際項目中控制器真的比較多的時候,有一些幾乎每個控制器必然用到的公共服務,我們是不是不得不哼哧哼哧寫很多構造函數實現依賴注入呢?
到這里,你一定想到,是啊,都調用一樣的服務,幾乎都類似的代碼,重構吧!
最簡單的方式,利用繼承,集中在一個地方(通常就叫BaseController吧)寫一次,
BaseControllerusing System.Web.Mvc;
using LogService;
using Ninject;
using UserService;
using UserService.Model;
namespace MVCApp.Helper
{
[ExceptionHandle]
public class BaseController : Controller
{
/// <summary>
/// 構造函數
/// </summary>
public BaseController()
{
if (CurrentLogService==null)
{
using (var kernel = new StandardKernel(new ServiceModule()))
{
var logger = kernel.Get<ILogService>();
logger.AppendLog("As you see,in constructor log service is not initilized by inject attribute.");
}
}
}
protected UserInfo CurrentUser
{
get { return CurrentUserService.CurrentUser; }
}
#region 服務
[Inject]
public IUserService CurrentUserService { get; set; }
[Inject]
public ILogService CurrentLogService { get; set; }
#endregion
}
}
然后,在相應的Controller下this點服務屬性名調用一下,多么優雅干凈簡潔。但是這種方式有一點需要特別需要注意,在Controller的構造函數里調用服務初始化一些數據可能不能讓你那么隨心所欲,因為在構造函數內,服務還沒有初始化。
如上代碼所示,通過Inject特性實現服務注入,通過繼承實現公共服務調用,不管哪種表現形式的應用程序都可以使用,有AOP和繼承的世界看上去是多么美好啊。當然了,具體使用哪種方式好每個人肯定都有自己的看法,實際項目中,我們通常選擇組合和繼承相結合的方式,這樣就可以兼顧兩者的優點實現注入。
最后還有幾個困擾人的問題需要思考:如何划分服務?服務和服務之間是否應該依賴注入?如果僅僅是在表現層實現依賴注入,難道不覺得IOC的作用有點太醬油了嗎?
domo下載:NinjectApp
參考:http://www.ninject.org/wiki.html
https://github.com/ninject/
http://ninject.codeplex.com/
http://www.cnblogs.com/cnmaxu/archive/2011/10/25/2224375.html
http://martinfowler.com/articles/injection.html