這幾天,有同窗問到爲何在 ASP.NET MVC 應用中,沒法在 .ASMX 中使用 NInject 進行注入。web
好比,咱們定義了一個接口,而後定義了一個實現。mvc
public interface IMessageProvider { string GetMessage(); }
定義一個接口的實現。框架
public class NinjectMessageProvider : IMessageProvider { public string GetMessage() { return "This message was provided by Ninject"; } }
在 ASMX 中進行 NInject 進行注入。less
[WebService(Namespace = "http://tempuri.org/")] [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)] [System.ComponentModel.ToolboxItem(false)] public class MyService { [Ninject.Inject] public IMessageProvider MessageProvider { set; get; } [WebMethod] public string HelloWorld() { var result = MessageProvider.GetMessage();return "Hello World"; } }
你會發現,注入失敗!!!ide
System.NullReferenceException: 未將對象引用設置到對象的實例。
Why?函數
這須要從 ASP.NET MVC 應用的結構提及了,相對與 WebForm 應用,MVC 是微軟從新打造的嶄新 Web 應用框架,雖然已經誕生多年了,沒有那麼新了,可是,從理念到實現確實是革命性的不一樣。這裏面最核心的一個不一樣,就是在 MVC 中從框架級別全面使用了 DI 容器。在 MVC 中,全部對象的建立都使用了容器來獲取,你本身定義的類就看你本身了,反正系統已經作到了。優化
在使用 NInject 的時候,一個重要的步驟就是在 global.asax 中的第一行就替換掉系統默認的容器,這樣保證新建立的對象是從 NInject 中獲取的,以便 NInject 完成依賴注入的實現。this
System.Web.Mvc.DependencyResolver.SetResolver(new NinjectDependencyResolver());
上面的這行代碼你們應該很熟悉了,這樣就把對象建立的全部權轉移到了 NInject 手中。url
可是,這是對 MVC 來講的,對於原來的 WebForm, ASMX 等等,MVC 是無論的,Scott Hanselman 有一篇文章討論了這個問題。spa
Plug-In Hybrids: ASP.NET WebForms and ASP.MVC and ASP.NET Dynamic Data Side By Side
因此,好消息是在 MVC 應用中,能夠繼續使用原有的 ASPX,ASMX 等等類型的特性,壞消息就是,這些類型的對象都不是 MVC 來管理建立和使用的,也就是說,MVC 的 DI 容器無論理這些對象,因此,在使用 NInject 的時候,也就沒法實現注入了。
若是咱們可以獲取剛剛建立的 MyService 對象,而後本身使用 NInject 注入一下,不就解決了嗎?只要咱們可以獲取剛剛建立的對象,也可以獲取 NInject 的容器,調用一下容器提供的注入方法 Inject 就能夠了。
// // Summary: // Injects the specified existing instance, without managing its lifecycle. // // Parameters: // instance: // The instance to inject. // // parameters: // The parameters to pass to the request. void Inject(object instance, params IParameter[] parameters);
在咱們 NInject 管理對象中,就能夠直接獲取容器對象,咱們能夠添加一個注入特定對象的方法。
public void Inject(object target) { this.kernel.Inject(target); }
之後,直接調用這個方法就能夠了。完整的類定義以下:
using System; using System.Collections.Generic; using System.Linq; using System.Web; using Ninject; namespace MvcNinjectAsmx.Models { public class NinjectDependencyResolver : System.Web.Mvc.IDependencyResolver { private Ninject.IKernel kernel; public NinjectDependencyResolver() { this.kernel = new Ninject.StandardKernel(); this.AddBindings(); } private void AddBindings() { this.kernel.Bind<IMessageProvider>() .To<NinjectMessageProvider>(); } public object GetService(Type serviceType) { return this.kernel.TryGet(serviceType); } public IEnumerable<object> GetServices(Type serviceType) { return this.kernel.GetAll(serviceType); } public void Inject(object target) { this.kernel.Inject(target); } } }
這是個實例方法,在整個 MVC 中只有一個實例,就是在 Global.asax 中建立的那個,之後,咱們能夠從 MVC 中直接獲取這個對象,並調用咱們的注入方法。
var resolver = System.Web.Mvc.DependencyResolver.Current as NinjectDependencyResolver; resolver.Inject(this);
如今的問題變成了如何獲取剛剛建立的 MyService 服務對象了。
最爲簡單的方式,是在使用以前,調用咱們的注入方法。好比在調用須要注入的對象以前,手工完成注入。
[Ninject.Inject] public IMessageProvider MessageProvider { set; get; } [WebMethod] public string HelloWorld() { var resolver = System.Web.Mvc.DependencyResolver.Current as NinjectDependencyResolver; resolver.Inject(this); var result = MessageProvider.GetMessage(); return "Hello World"; }
這樣有點太笨了。
在 ASP.NET 中服務對象都是從 HandlerFactory 中建立的,咱們應該能夠替換掉 .asmx 的處理器工廠,如何可以獲取到剛剛建立的 MyService 對象,就能夠完美處理這個問題了。
打開系統的 web.config 文件,能夠找到 .asmx 的處理器管理配置信息。
<add path="*.asmx" verb="*" type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" validate="False" />
StackOverflow 上的一篇文章,描述瞭如何獲取 ScriptHandlerFactory 建立的處理器。
Getting ScriptHandlerFactory handler
public class WebServiceFactory : IHttpHandlerFactory { public IHttpHandler GetHandler(HttpContext context, string requestType, string url, string pathTranslated) { PrivilegedCommand cmd = new PrivilegedCommand(); SecurityCritical.ExecutePrivileged(new PermissionSet(PermissionState.Unrestricted), new SecurityCritical.PrivilegedCallback(cmd.Execute)); var handlerFactory = cmd.Result; var handler = handlerFactory.GetHandler(context, context.Request.RequestType, url, pathTranslated); // Inject var resolver = System.Web.Mvc.DependencyResolver.Current as NinjectDependencyResolver; resolver.Inject(handler); return handler; } public void ReleaseHandler(IHttpHandler handler) { } private class PrivilegedCommand { public IHttpHandlerFactory Result = null; public void Execute() { Type handlerFactoryType = typeof(System.Web.Services.WebService).Assembly.GetType("System.Web.Services.Protocols.WebServiceHandlerFactory"); Result = (IHttpHandlerFactory)Activator.CreateInstance(handlerFactoryType, true); } } }
實際上,仍是注入失敗了,若是檢查一下,能夠發現,咱們獲取的 handler 並非 MyService,而是下面的類型。
System.Web.Services.Protocols.SyncSessionlessHandler
在這個類的內部經過反射來建立 MyService。咱們仍是沒有拿到剛剛建立的 MyService 對象來實現咱們的注入。
因此,這個方法就算了。
換一個思路,咱們能夠給 MyService 對象提供一個構造函數,這個構造函數老是要被調用的,咱們在這裏來實現注入不就能夠了嗎?
另外一篇文章提到這個思路:
Ninject w/ ASMX web service in a MVC3/Ninject 3 environment
public class MyService { [Ninject.Inject] public IMessageProvider MessageProvider { set; get; } public MyService() { var resolver = System.Web.Mvc.DependencyResolver.Current as NinjectDependencyResolver; resolver.Inject(this); } [WebMethod] public string HelloWorld() { var result = MessageProvider.GetMessage(); return "Hello World"; } }
若是咱們定義了多個 WebService ,這樣的話,在每一個構造函數中都要寫上注入的這兩行,仍是再優化一下。
定義一個支持 NInject 注入的基類來完成這個工做。
public class NInjectWebService { public NInjectWebService() { var resolver = System.Web.Mvc.DependencyResolver.Current as NinjectDependencyResolver; resolver.Inject(this); } }
this 就是咱們剛剛建立的對象實例。
而後,將咱們的服務類定義成派生自這個類的基類。
public class MyService : NInjectWebService { [Ninject.Inject] public IMessageProvider MessageProvider { set; get; } [WebMethod] public string HelloWorld() { var result = MessageProvider.GetMessage(); return "Hello World"; } }
這樣,之後的 WebServe 只要從這個基類派生就能夠了。