在 ASP.NET MVC 应用中使用 NInject 注入 ASMX 类型的 Web Service

这几天,有同窗问到为何在 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 容器

在咱们  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 只要从这个基类派生就能够了。

相关文章
相关标签/搜索