C# WebService的动态调用(一)

2021-01-21  乐帮网

c# webservice

使用Web Service服务在C#中非常方便,使用静态调用尤其简单。但是静态调也有它的弊端,就是当接口改动时不管你用没有用到新方法新实体,都需要重新生成然后再编译再部署。这个过程不容易掌控。所以就有了动态调用这一说。这篇文章只讨论动态调用。目前WCF也算 比较特殊的Web Service,这里也包含在内。

Web Service的动态调用也有两种常用方法,

方法一:通过C#内部工具动态生成dll类库,然后利用反射调用(推荐)
方法二:可以直接使用http 请求通过合成符合soap协议的消息体来调用方法。
两种方法各有利弊,如何就是在C#平台开发那建议你使用方法一,如何是在其它平台非.net平台,那么建议你使用方法二。

下面开始介绍方法一,在这里我们不仅实现调用,还要把服务中所有的方法以及参数描述信息保存在类中,以方便查看。到最后会提供示例源码下载。
(1)首先建两个测试用的Web Service,一个是asmx一个是wcf方式。我们分别是添加代码如下:

Models

  [DataContract]
    public class WxBase
    {

        [DataMember]
        public int errcode { get; set; }


        [DataMember]
        public string errmsg { get; set; }
    }
 [DataContract]
    public class WxResUser : WxBase
    {
        [DataMember]
        public string access_token { get; set; }

        [DataMember]
        public int expires_in { get; set; }

        [DataMember]
        public string refresh_token { get; set; }

        [DataMember]
        public string openid { get; set; }

        [DataMember]
        public string scope { get; set; }

    }

WxTest.asmx

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

        [WebMethod(Description = "获取用户")]
        public WxResUser GetUser(string openid)
        {
            return new WxResUser()
            {
                openid = openid,
                access_token = "202101171203ef76ab",
                expires_in = 7200,
                scope = "lebang2020.cn",
                refresh_token=string.Empty
            };
        }
    }

WxTest.svc

  public class WxTest : IWxTest
    {
        [OperationBehavior(Impersonation = ImpersonationOption.Allowed)]
        public WxResUser GetUser(string openid)
        {
            return new WxResUser()
            {
                openid = openid,
                access_token="202101171203ef76ab",
                expires_in=7200,
                scope="lebang2020.cn",
                refresh_token="roodke334dswssqwz"
            };
        }

    }

IWxTest.cs

 [ServiceContract]
    //[WebService(Namespace = "vkt.soap.demo.wcf.wxtest")]
    public interface IWxTest
    {

        [OperationContract]
        WxResUser GetUser(string openid);
    }

(2)下面写我们的主要实现逻辑,我把核心代码粘贴到这里。

 public class WebServiceInfo
    {
        const string _serviceNamespace = "vkt.test";
        WebMethodInfoCollection _webMethods = new WebMethodInfoCollection();
        Uri _url;
        static Dictionary<string, WebServiceInfo> _webServiceInfos =
            new Dictionary<string, WebServiceInfo>();

        Assembly _assembly;
        string _className;
        object _service;


        /// <summary>
        /// Constructor creates the web service info from the given url.
        /// </summary>
        /// <param name="url">
        private WebServiceInfo(Uri url)
        {
            if (url == null)
                throw new ArgumentNullException("url");
            _url = url;
            _webMethods = GetWebServiceDescription(url);
        }

        /// <summary>
        /// Factory method for WebServiceInfo. Maintains a hashtable WebServiceInfo objects
        /// keyed by url in order to cache previously accessed wsdl files.
        /// </summary>
        /// <param name="url">
        /// <returns></returns>
        public static WebServiceInfo OpenWsdl(Uri url)
        {
            WebServiceInfo webServiceInfo;
            if (!_webServiceInfos.TryGetValue(url.ToString(), out webServiceInfo))
            {
                webServiceInfo = new WebServiceInfo(url);
                _webServiceInfos.Add(url.ToString(), webServiceInfo);
            }
            return webServiceInfo;
        }

        /// <summary>
        /// Convenience overload that takes a string url
        /// </summary>
        /// <param name="url">
        /// <returns></returns>
        public static WebServiceInfo OpenWsdl(string url)
        {
            Uri uri = new Uri(url);
            return OpenWsdl(uri);
        }

        private void BuildLib(ServiceDescription description)
        {
            try
            {
                _className = description.Services[0].Name;
                //ServiceDescriptionImporter descriptionImporter = new ServiceDescriptionImporter();
                ServiceDescriptionImporter descriptionImporter = new ServiceDescriptionImporter()
                {
                    ProtocolName = "Soap",
                    Style = ServiceDescriptionImportStyle.Client,
                    CodeGenerationOptions =
                              CodeGenerationOptions.GenerateProperties | CodeGenerationOptions.GenerateNewAsync
                };

                descriptionImporter.AddServiceDescription(description, string.Empty, string.Empty);
                CodeNamespace nameSpace = new CodeNamespace(_serviceNamespace);

                CodeCompileUnit compile = new CodeCompileUnit();
                compile.Namespaces.Add(nameSpace);
                descriptionImporter.Import(nameSpace, compile);

                CompilerParameters paras = new CompilerParameters();
                paras.GenerateExecutable = false;
                paras.GenerateInMemory = true;
                paras.ReferencedAssemblies.Add("System.dll");
                paras.ReferencedAssemblies.Add("System.XML.dll");
                paras.ReferencedAssemblies.Add("System.Web.Services.dll");
                paras.ReferencedAssemblies.Add("System.Data.dll");

                using (CSharpCodeProvider codeProvider = new CSharpCodeProvider())
                {
                    CompilerResults compilerResults = codeProvider.CompileAssemblyFromDom(paras, compile);
                    if (true == compilerResults.Errors.HasErrors)
                    {
                        throw new Exception(compilerResults.Errors[0].ErrorText);
                    }

                    _assembly = compilerResults.CompiledAssembly;
                    if (_assembly != null)
                    {
                        _service = _assembly.CreateInstance($"{ _serviceNamespace}.{_className}", true);
                    }
                }
                    
                
            }
            catch(Exception ex)
            {
                throw ex;
            }

        
        }

        /// <summary>
        /// Load the WSDL file from the given url.
        /// Use the ServiceDescription class to walk the wsdl and create the WebServiceInfo
        /// instance.
        /// </summary>
        /// <param name="url">
        private WebMethodInfoCollection GetWebServiceDescription(Uri url)
        {
            UriBuilder uriBuilder = new UriBuilder(url);
            //uriBuilder.Query = "WSDL";

            WebMethodInfoCollection webMethodInfos = new WebMethodInfoCollection();

            HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(uriBuilder.Uri);
            //webRequest.ContentType = "text/xml;charset=\"utf-8\"";
            webRequest.ContentType = "application/soap+xml;charset=\"utf-8\"";
            
            webRequest.Method = "GET";
            webRequest.Accept = "text/xml";

            ServiceDescription serviceDescription;

            using (System.Net.WebResponse response = webRequest.GetResponse())
            using (System.IO.Stream stream = response.GetResponseStream())
            {
                serviceDescription = ServiceDescription.Read(stream);
            }

            if(serviceDescription !=null)
            {
                BuildLib(serviceDescription);
            }
           

            foreach (PortType portType in serviceDescription.PortTypes)
            {
                foreach (Operation operation in portType.Operations)
                {
                    string operationName = operation.Name;
                    string inputMessageName = operation.Messages.Input.Message.Name;
                    string outputMessageName = operation.Messages.Output.Message.Name;

                    // get the message part
                    string inputMessagePartName =
                        serviceDescription.Messages[inputMessageName].Parts[0].Element.Name;
                    string outputMessagePartName =
                        serviceDescription.Messages[outputMessageName].Parts[0].Element.Name;

                    // get the parameter name and type
                    Parameter[] inputParameters = GetParameters(serviceDescription, inputMessagePartName);
                    Parameter[] outputParameters = GetParameters(serviceDescription, outputMessagePartName);

                    WebMethodInfo webMethodInfo = new WebMethodInfo(
                        operation.Name, inputParameters, outputParameters);
                    webMethodInfos.Add(webMethodInfo);
                }
            }

            return webMethodInfos;
        }

        /// <summary>
        /// Walk the schema definition to find the parameters of the given message.
        /// </summary>
        /// <param name="serviceDescription">
        /// <param name="messagePartName">
        /// <returns></returns>
        private static Parameter[] GetParameters(ServiceDescription serviceDescription, string messagePartName)
        {
            List<Parameter> parameters = new List<Parameter>();

            Types types = serviceDescription.Types;
            System.Xml.Schema.XmlSchema xmlSchema = types.Schemas[0];

            foreach (object item in xmlSchema.Items)
            {
                System.Xml.Schema.XmlSchemaElement schemaElement = item as System.Xml.Schema.XmlSchemaElement;
                if (schemaElement != null)
                {
                    if (schemaElement.Name == messagePartName)
                    {
                        System.Xml.Schema.XmlSchemaType schemaType = schemaElement.SchemaType;
                        System.Xml.Schema.XmlSchemaComplexType complexType = schemaType as System.Xml.Schema.XmlSchemaComplexType;
                        if (complexType != null)
                        {
                            System.Xml.Schema.XmlSchemaParticle particle = complexType.Particle;
                            System.Xml.Schema.XmlSchemaSequence sequence = particle as System.Xml.Schema.XmlSchemaSequence;
                            if (sequence != null)
                            {
                                foreach (System.Xml.Schema.XmlSchemaElement childElement in sequence.Items)
                                {
                                    string parameterName = childElement.Name;
                                    string parameterType = childElement.SchemaTypeName.Name;
                                    parameters.Add(new Parameter(parameterName, parameterType));
                                }
                            }
                        }
                    }
                }
            }
            return parameters.ToArray();
        }

        public string Invoke(string methodName,params object[] args)
        {
            object result = null; 
            if (_service != null)
            {
                if (_webMethods.Contains(methodName))
                {
                    IList<Type> types = new List<Type>();
                    foreach( var p in args)
                    {
                        types.Add(p.GetType());
                    }
                    MethodInfo mi = _service.GetType().GetMethod(methodName, types.ToArray());

                    if (mi == null)
                        throw new Exception("服务中未找到方法" + methodName);
                    result = mi.Invoke(_service, args);
                    //类型的特殊处理可写到这里
                    if (result is string)
                        return result as string;
                    
                }
            }
            return JsonConvert.SerializeObject(result);
        }

        /// <summary>
        /// WebMethodInfo
        /// </summary>
        public WebMethodInfoCollection WebMethods
        {
            get { return _webMethods; }
        }

        /// <summary>
        /// Url
        /// </summary>
        public Uri Url
        {
            get { return _url; }
            set { _url = value; }
        }
    }

(3)使用方法如下 :

  [TestMethod]
        public void TestMethod1()
        {
            string wsdlAddress =  "http://localhost/wcftest2/WxTest.asmx?wsdl";
            WebServiceInfo webServiceInfo = WebServiceInfo.OpenWsdl(wsdlAddress);
            var result = webServiceInfo.Invoke("GetUser", "9887445dfe"); 
        }

写在最后,以上的方法是返回的实体序列化的Json格式,当然你可以优化成实体。核心思想就是动态下载服务描述,根据描述生成dll然后再加载。通过反射调用WebService中的方法。对于wcf和asmx的服务都通用。其中wcf是指绑定方式是basicHttpBinding的配置。

下一篇将会带来通过Http方式调用的代码。

可以下载我的示例代码链接:https://pan.baidu.com/s/1fyQzqQtZMfb00zCT9Ak-WQ 

提取码请点击下方的捐赠按钮获取。

包含两种调用方式的示例代码,以及webservice 和 wcf接口示例。
 

参考:

C# WebService动态调用

Dynamic call of SOAP base Web Service

C# 动态调取 soap 接口

The ChannelAdam SOAP Library

Dynamic Web Service Invoker

Calling Web Service Using SOAP Request In Console Application

How to send/receive SOAP request and response using C# in Windows Phone 8

 

 

公众号二维码

关注我的微信公众号
在公众号里留言交流
投稿邮箱:1052839972@qq.com

庭院深深深几许?杨柳堆烟,帘幕无重数。
玉勒雕鞍游冶处,楼高不见章台路。
雨横风狂三月暮。门掩黄昏,无计留春住。
泪眼问花花不语,乱红飞过秋千去。

欧阳修

付款二维码

如果感觉对您有帮助
欢迎向作者提供捐赠
这将是创作的最大动力