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

这几天,有同学问到为什么在 ASP.NET MVC 应用中,无法在 .ASMX 中使用 NInject 进行注入。

现象

比如,我们定义了一个接口,然后定义了一个实现。

public interface IMessageProvider
{
    string GetMessage();
}

定义一个接口的实现。

public class NinjectMessageProvider : IMessageProvider
{
    public string GetMessage()
    {
        return "This message was provided by Ninject";
    }
}

在 ASMX 中进行 NInject 进行注入。

[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";
    }
}

你会发现,注入失败!!!

System.NullReferenceException: 未将对象引用设置到对象的实例。

分析

Why?

这需要从 ASP.NET MVC 应用的结构说起了,相对与 WebForm 应用,MVC 是微软重新打造的崭新 Web 应用框架,虽然已经诞生多年了,没有那么新了,但是,从理念到实现确实是革命性的不同。这里面最核心的一个不同,就是在 MVC 中从框架级别全面使用了 DI 容器。在 MVC 中,所有对象的创建都使用了容器来获取,你自己定义的类就看你自己了,反正系统已经做到了。

在使用 NInject 的时候,一个重要的步骤就是在 global.asax 中的第一行就替换掉系统默认的容器,这样保证新创建的对象是从 NInject 中获取的,以便 NInject 完成依赖注入的实现。

System.Web.Mvc.DependencyResolver.SetResolver(new NinjectDependencyResolver());

上面的这行代码大家应该很熟悉了,这样就把对象创建的所有权转移到了 NInject 手中。

但是,这是对 MVC 来说的,对于原来的 WebForm, ASMX 等等,MVC 是不管的,Scott Hanselman 有一篇文章讨论了这个问题。

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 只要从这个基类派生就可以了。

时间: 2024-10-26 13:34:48

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

ASP.NET MVC学前篇之Ninject的初步了解

1.介绍 废话几句,Ninject是一种轻量级的.基础.NET的一个开源IoC框架,在对于MVC框架的学习中会用到IoC框架的,因为这种IoC开源框架有很多,本篇的主题只有一个,就是让阅读过本篇幅的朋友逗知道IoC框架在项目中的作用,以及它的重要性. 这样做的目的是以便在以后的学习工作中选择自己中意的一个IoC框架来学习.使用,或者是自己去实现一个.好了,不废话了. 2.环境准备 1.新建个4.0Framework的一个控制台应用程序项目,名称为IoCDemo 2.在http://www.nin

ASP.NET MVC 4 中的JSON数据交互

前台Ajax请求很多时候需要从后台获取JSON格式数据,一般有以下方式: 拼接字符串 return Content("{\"id\":\"1\",\"name\":\"A\"}"); 为了严格符合Json数据格式,对双引号进行了转义. 使用JavaScriptSerialize.Serialize()方法将对象序列化为JSON格式的字符串 MSDN 例如我们有一个匿名对象: var tempObj=new

ASP.NET MVC 3中的路由

准备发布新随笔,才发现草稿里还有几年前这篇烂了尾的,先放上来,有空再补完整吧-- (* 整理自<Pro ASP.NET MVC 3 Framework>学习笔记. *) 路由,正如其名,是决定消息经由何处被传递到何处的过程.也正如网络设备路由器Router一样,ASP.NET MVC框架处理请求URL的方式,同样依赖于一张预定义的路由表.以该路由表为转发依据,请求URL最终被传递给特定Controller的特定Action进行处理.而在相反的方向上,MVC框架的渲染器同样要利用这张路由表,生成

[转]在 ASP.NET MVC 4 中创建为移动设备优化的视图

原文链接 https://msdn.microsoft.com/zh-cn/magazine/dn296507.aspx 如果深入探讨有关编写移动设备网站的常识性考虑因素,会发现其中有一种内在矛盾. 一方面,客户在其编写应用程序和网站的方法中强烈要求(或乐于要求)移动优先. 另一方面,同一些人又经常称赞 CSS 媒体查询和流体布局. 我所发现的矛盾在于经常利用 CSS 媒体查询和流体布局并未在其他内容之前优先处理移动方面,它不是一种移动优先的方法. 在本文中,我将介绍如何使用服务器端逻辑为给定设

在 ASP.NET MVC 项目中使用 WebForm、 HTML

原文地址:http://www.cnblogs.com/snowdream/archive/2009/04/17/winforms-in-mvc.html ASP.NET MVC和WebForm各有各的优点,我们可能需要同时使用ASP.NET MVC和WebForm.本文介绍了如何在ASP.NET MVC项目中使用WebForm.首先新建一个名为WebForms的文件夹用于存放WebForm,并添加一个Web窗体文件Demo.aspx作为演示. Demo.aspx就简单的输出一句话“It’s a

asp.net mvc 客户端(&amp;)中检测到有潜在危险的 Request.Path 值。

出现这个错误后,试过 <pages validateRequest="false"> <httpRuntime requestValidationMode="2.0"/> [ValidateInput(false)] 都不ok,经测试只用以下配置就ok了 <httpRuntime requestPathInvalidCharacters="" /> MSDN解释:在请求路径中无效的字符的列表以逗号分隔). 以下

ASP.NET MVC 4中如何为不同的浏览器自适应布局和视图

在ASP.NET MVC 4中,可以很简单地实现针对不同的浏览器自适应布局和视图.这个得归功于MVC中的"约定甚于配置"的设计理念. 默认的自适应 MVC 4自动地为移动设备浏览器和PC设备浏览器进行自适应.针对布局页面,默认的文件名为_Layout.cshtml,这个默认会被所有的浏览器使用.但如果我们希望在移动设备上面,呈现一个不同的布局,只需要添加一个名称为_Layout.Mobile.cshtml的布局页面就可以了.同样的规则,也适用于普通的视图页面.例如Index.cshtm

【转】在 ASP.NET MVC 项目中使用 WebForm

ASP.NET MVC和WebForm各有各的优点,我们可能需要同时使用ASP.NET MVC和WebForm.本文介绍了如何在ASP.NET MVC项目中使用WebForm. 首先新建一个名为WebForms的文件夹用于存放WebForm,并添加一个Web窗体文件Demo.aspx作为演示. Demo.aspx就简单的输出一句话"It's a WebForm." 关键步骤在于路由设置.如果你希望WebForms这个文件夹名作为URL的一部分,也就是普通WebForm应用程序的方式来访

在ASP.NET MVC环境中使用加密与解密

在.NET Framework 4.5的NET框架中,在程序中加密与解密很方便.现在均学习ASP.NET MVC程序了,因此Insus.NET也在此写个学习的例子.在需要时可以参考与查阅. 写一个Utility类,它包含有加密Encryption与解密Decryption的方法.当然你完全可以自定义你的加密与解密的key. 在ASP.NET MVC演示中,在文本框中输入字符,点加密铵钮,显示加密字符,点解密铵钮,显示原始文本. 在控制器添加三个Action: 根据标记1的Action,添加一个视