用C#通过反射实现动态调用WebService 告别Web引用(转载)

我们都知道,调用WebService可以在工程中对WebService地址进行WEB引用,但是这确实很不方便。我想能够利用配置文件灵活调用WebService。如何实现呢?

用C#通过反射实现动态调用WebService

 

下面是WebService代码:

复制代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Services;

namespace TestWebService
{
    /// <summary>
    /// Service1 的摘要说明
    /// </summary>
    [WebService(Namespace = "http://tempuri.org/",Description="我的Web服务")]
    [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
    [System.ComponentModel.ToolboxItem(false)]
    // 若要允许使用 ASP.NET AJAX 从脚本中调用此 Web 服务,请取消对下行的注释。
    // [System.Web.Script.Services.ScriptService]
    public class TestWebService : System.Web.Services.WebService
    {

        [WebMethod]
        public string HelloWorld()
        {
            return "测试Hello World";
        }

        [WebMethod]
        public string Test()
        {
            return "测试Test";
        }
      
        [WebMethod(CacheDuration = 60,Description = "测试")]
        public List<String> GetPersons()
        {
            List<String> list = new List<string>();
            list.Add("测试一");
            list.Add("测试二");
            list.Add("测试三");
            return list;
        }  

    }

}
复制代码

 

下面是客户端的代码:

复制代码
using System.Text;
using System.Net;
using System.IO;
using System.Web.Services.Description;
using System.CodeDom;
using Microsoft.CSharp;
using System.CodeDom.Compiler;
using System;

namespace TestCommon
{
    public class Webservice
    {
        /// <summary>
        /// 实例化WebServices
        /// </summary>
        /// <param name="url">WebServices地址</param>
        /// <param name="methodname">调用的方法</param>
        /// <param name="args">把webservices里需要的参数按顺序放到这个object[]里</param>
        public static object InvokeWebService(string url, string methodname, object[] args)
        {
            //这里的namespace是需引用的webservices的命名空间,我没有改过,也可以使用。也可以加一个参数从外面传进来。
            string @namespace = "client";

            try
            {
                //获取WSDL
                WebClient wc = new WebClient();
                Stream stream = wc.OpenRead(url + "?WSDL");
                ServiceDescription sd = ServiceDescription.Read(stream);
                string classname = sd.Services[0].Name;
                ServiceDescriptionImporter sdi = new ServiceDescriptionImporter();
                sdi.AddServiceDescription(sd, "", "");
                CodeNamespace cn = new CodeNamespace(@namespace);

                //生成客户端代理类代码
                CodeCompileUnit ccu = new CodeCompileUnit();
                ccu.Namespaces.Add(cn);
                sdi.Import(cn, ccu);
                CSharpCodeProvider csc = new CSharpCodeProvider();
                //ICodeCompiler icc = csc.CreateCompiler();
                
                //设定编译参数
                CompilerParameters cplist = new CompilerParameters();
                cplist.GenerateExecutable = false;//动态编译后的程序集不生成可执行文件
                cplist.GenerateInMemory = true;//动态编译后的程序集只存在于内存中,不在硬盘的文件上
                cplist.ReferencedAssemblies.Add("System.dll");
                cplist.ReferencedAssemblies.Add("System.XML.dll");
                cplist.ReferencedAssemblies.Add("System.Web.Services.dll");
                cplist.ReferencedAssemblies.Add("System.Data.dll");

                //编译代理类
                CompilerResults cr = csc.CompileAssemblyFromDom(cplist, ccu);
                if (true == cr.Errors.HasErrors)
                {
                    System.Text.StringBuilder sb = new System.Text.StringBuilder();
                    foreach (System.CodeDom.Compiler.CompilerError ce in cr.Errors)
                    {
                        sb.Append(ce.ToString());
                        sb.Append(System.Environment.NewLine);
                    }

                    throw new Exception(sb.ToString());
                }

                //生成代理实例,并调用方法
                System.Reflection.Assembly assembly = cr.CompiledAssembly;
                Type t = assembly.GetType(@namespace + "." + classname, true, true);
                object obj = Activator.CreateInstance(t);
                System.Reflection.MethodInfo mi = t.GetMethod(methodname);

                //注:method.Invoke(o, null)返回的是一个Object,如果你服务端返回的是DataSet,这里也是用(DataSet)method.Invoke(o, null)转一下就行了,method.Invoke(0,null)这里的null可以传调用方法需要的参数,string[]形式的
                return mi.Invoke(obj, args);
            }
            catch
            {
                return null;
            }
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            string url = "http://localhost:3182/Service1.asmx?WSDL";//这个地址可以写在Config文件里面,这里取出来就行了.在原地址后面加上: ?WSDL
            string method = "GetPersons";

            String[] item = (String[])Webservice.InvokeWebService(url, method, null);
            
            foreach (string str in item)
                Console.WriteLine(str);
        }
    }
}
复制代码

 

注意:上述代码需要引用如下四个名称空间:
using System.Web.Services.Description;  //WS的描述
//以下几个用于根据描述动态生成代码并动态编译获取程序集
using System.CodeDom; 
using Microsoft.CSharp;
using System.CodeDom.Compiler;

 

代码相对简单,为什么可以如此调用呢?动态编译后用反射来读取并执行。也许了解反射及如何反射对你会有帮助。

反射提供了封装程序集、模块和类型的对象(Type 类型)。可以使用反射动态创建类型的实例,将类型绑定到现有对象,或从现有对象获取类型并调用其方法或访问其字段和属性。详细请查看:https://msdn.microsoft.com/zh-cn/library/ms173183(VS.80).aspx

为什么WebServices可以通过反射实现?

WebService在传输过程中是通过WSDL来进行描述的(使用SOAP协议)。因此,我们需要获取WebService的WSDL描述,并通过该描述来动态生成程序集。然后通过反射来获取新生成的程序集,并调用其方法!

以下是MSDN对其的描述:

复制代码
XML Web services 的接口通常由 Web 服务描述语言 (WSDL) 文件来说明。例如,若要获取有关使用 http://localhost/service.asmx 处公开的 ASP.NET 的 Web 服务的 WSDL 说明,只需导航到 http://localhost/service.asmx?WSDL。使用 ServiceDescriptionImporter 类可以方便地将 WSDL 说明中包含的信息导入到System.CodeDom.CodeCompileUnit 对象。通过调整 Style 参数的值,可以指示 ServiceDescriptionImporter 实例生成客户端代理类(通过透明调用该类可提供 Web 服务的功能)或生成抽象类(该类封装 Web 服务的功能而不实现该功能)。如果将 Style 属性设置为 Client,则 ServiceDescriptionImporter 生成客户端代理类,通过调用这些类来提供说明的 Web 服务的功能。如果将 Style 属性设置为 Server,则 ServiceDescriptionImporter 实例生成抽象类,这些类表示所说明的 XML Web services 的功能而不进行实现。然后,可以通过编写从这些抽象类继承的类来对其进行实现,并实现相关的方法。
复制代码

 

关于上面代码中CompilerParameters的配置参数,有如下说明:

CodeDom可以动态编译Code代码成为程序集,有时我们只想动态编译的程序集,在内存中或者是硬盘上调用,这就是CodeDom的动态编译。微软在CodeDom中提供了动态编译程序,这是ICodeCompiler的用武之地了,它定义用于调用源代码编译的接口或使用指定编译器的 CodeDOM 树。可以从CodeDomProvider生成引用对象:CodeDomProvider.CreateProvider("").CreateCompiler();

 

在ICodeCompiler中为我们提供了程序集编译的方法有:

CompileAssemblyFromDom :使用指定的编译器设置从指定的 CodeCompileUnit 所包含的 System.CodeDom 树中编译程序集。            

CompileAssemblyFromDomBatch:基于包含在 CodeCompileUnit 对象的指定数组中的 System.CodeDom 树,使用指定的编译器设置编译程序集。              

CompileAssemblyFromFile:从包含在指定文件中的源代码,使用指定的编译器设置编译程序集。              

CompileAssemblyFromFileBatch:从包含在指定文件中的源代码,使用指定的编译器设置编译程序集。              

CompileAssemblyFromSource: 从包含源代码的指定字符串,使用指定的编译器设置编译程序集。            

CompileAssemblyFromSourceBatch:从包含源代码的字符串的指定数组,使用指定的编译器设置编译程序集。

 

在我们的CodeDomProvider也提供了CompileAssemblyFromDom、CompileAssemblyFromFile、CompileAssemblyFromSource。

在他们的编译时候都有一个变异参数CompilerParameters,提供了编译时参数选项:

CompilerOptions:获取或设置调用编译器时使用的可选附加命令行参数字符串。

EmbeddedResources:获取要在编译程序集输出时包含的 .NET Framework 资源文件。

Evidence:指定一个证据对象,该对象表示要授予已编译的程序集的安全策略权限。

GenerateExecutable:获取或设置一个值,该值指示是否生成可执行文件。

GenerateInMemory:获取或设置一个值,该值指示是否在内存中生成输出。

IncludeDebugInformation:获取或设置一个值,该值指示是否在已编译的可执行文件中包含调试信息。

LinkedResources:获取当前源中引用的 .NET Framework 资源文件。

MainClass:获取或设置主类的名称。

OutputAssembly:获取或设置输出程序集的名称。

ReferencedAssemblies:获取当前项目所引用的程序集。

TempFiles:获取或设置包含临时文件的集合.

TreatWarningsAsErrors:获取或设置一个值,该值指示是否将警告视为错误。

UserToken:获取或设置在创建编译器进程时使用的用户标记。

WarningLevel:获取或设置使编译器中止编译的警告级别。

Win32Resource:获取或设置要链接到已编译程序集中的 Win32 资源文件的文件名。

 

他们的结果返回编译结果CompilerResults,提供了编译结果信息:

CompiledAssembly:获取或设置已编译的程序集。

Errors:获取编译器错误和警告的集合。

Evidence:指示证据对象,该对象表示编译的程序集的安全策略权限。

NativeCompilerReturnValue:获取或设置编译器的返回值。

Output:获取编译器输出消息。

PathToAssembly:获取或设置已编译程序集的路径。

TempFiles:获取或设置要使用的临时文件集合。

 

原文链接1

原文链接2

 

 

出处:https://www.cnblogs.com/OpenCoder/p/7677758.html

posted on 2018-07-14 14:48  jack_Meng  阅读(1660)  评论(0编辑  收藏  举报

导航