zoukankan      html  css  js  c++  java
  • 在 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 只要从这个基类派生就可以了。

  • 相关阅读:
    mysql GROUP_CONCAT 查询某个字段(查询结果默认逗号拼接)
    mysql中find_in_set的使用
    Libev源码分析07:Linux下的eventfd简介
    Libev源码分析06:异步信号同步化--sigwait、sigwaitinfo、sigtimedwait和signalfd
    Nova中的Hook机制
    Python深入:stevedore简介
    Libev源码分析05:Libev中的绝对时间定时器
    Python深入:setuptools简介
    Libev源码分析04:Libev中的相对时间定时器
    Libev源码分析02:Libev中的IO监视器
  • 原文地址:https://www.cnblogs.com/haogj/p/4697174.html
Copyright © 2011-2022 走看看