实际场景中有很多系统交互模块使用Webservice方式。Webservice允许不同语言、平台的相互调用、提供很好的互操作性。Webservice构建在HTTP协议只上。采用HTTP协议发送符合Webservice协议的XML,既Soap文档,服务发布者实现Soap约定,服务调用者按Soap约定通过HTTP发送Soap请求,以此实现跨语言和平台。
平常开发工具比如VS都提供的有在工程上右键添加服务引用的功能。添加引用后VS会自动生成Webservice的客户端调用类。这种模式在固定Webservice的情况是没问题的。但是如果作为平台或者Webservice测试来说这种模式太僵化了。
为了得到动态调用Webservice的功能,这次按Soap协议下载Webservice的MSDL文档,通过MSDL的XML解析Webservice提供的方法,和每个方法的参数,然后渲染出方法和参数页面。再把填的参数组装成Soap调用XML通过HTTP发送给服务端,然后解析服务返回的XML结果,以此实现动态调Webservice,理解Webservice交互机制,也可供没支持Webservice的语言自己实现Webservice调用。
源码地址:https://download.csdn.net/download/zhanglianzhu_91/34445937
效果如下:
主要类(包装了Soap的WSDL解析和构造调用XML及调用)
using System;
using System.IO;
using System.Net;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
using System.Collections.Generic;
using System.Xml;
using System.Net.Http;
using System.Text;
namespace SoapUtil
{
///<summary NoteObject="Class">
/// [功能描述: Webservice动态调用的工具类,实现下载WSDL、解析、组装调用webservice]<br></br>
/// [创建者: zlz]<br></br>
/// [创建时间: 20211024]<br></br>
/// <说明>
///
/// </说明>
/// <修改记录>
/// <修改时间></修改时间>
/// <修改内容>
///
/// </修改内容>
/// </修改记录>
/// </summary>
public class SoapCallUtil
{
/// <summary>
/// 通过URL信息得到Soap信息
/// </summary>
/// <param name="url"></param>
/// <returns></returns>
public static SoapInfo GetSoapInfo(string url)
{
if (url == "")
{
return null;
}
SoapInfo retSoap = new SoapInfo();
retSoap.UserName = "";
retSoap.Password = "";
//提取用户名和密码
if (url.Contains("UserName=") && url.Contains("Password="))
{
//分割数据
string[] arr = url.Split('&');
for (int i = 1; i < arr.Length; i++)
{
string[] oneParaArr = arr[i].Split('=');
if (oneParaArr.Length > 1)
{
if (arr[i].Contains("UserName="))
{
retSoap.UserName = oneParaArr[1];
}
else if (arr[i].Contains("Password="))
{
retSoap.Password = oneParaArr[1];
}
}
}
}
retSoap.Url = url;
retSoap.MethodList = new Dictionary<string, SoapMethodInfo>();
//加载msdl
string xml = LoadMsdlByUrl(url);
XmlDocument doc = new XmlDocument();
doc.LoadXml(xml);
XmlNode xndf = null;
foreach (XmlNode dif in doc.ChildNodes)
{
if (dif.LocalName == "definitions")
{
xndf = dif;
break;
}
}
//得到根节点的所有子节点
XmlNodeList xnl = xndf.ChildNodes;
if (xnl.Count > 0)
{
foreach (XmlNode n in xnl)
{
if (n.LocalName == "types")
{
foreach (XmlNode n1 in n.ChildNodes)
{
if (n1.LocalName == "schema")
{
foreach (XmlNode n2 in n1.ChildNodes)
{
if (n2.LocalName == "element" && n2.ChildNodes.Count > 0)
{
string MethodName = n2.Attributes.GetNamedItem("name").Value;
SoapMethodInfo methodInfo = new SoapMethodInfo();
methodInfo.MethodName = MethodName;
methodInfo.TargetNamespace=n1.Attributes.GetNamedItem("targetNamespace").Value;
methodInfo.ParaList = new List<string>();
XmlNode seqNode = n2.FirstChild.FirstChild;
foreach (XmlNode pn in seqNode.ChildNodes)
{
string ParaName = pn.Attributes.GetNamedItem("name").Value;
methodInfo.ParaList.Add(ParaName);
}
retSoap.MethodList.Add(MethodName, methodInfo);
}
}
}
}
}
if (n.LocalName == "binding")
{
foreach (XmlNode n1 in n.ChildNodes)
{
if (n1.LocalName == "operation")
{
string MethodName = n1.Attributes.GetNamedItem("name").Value;
XmlNode actionNode = n1.FirstChild;
string action = actionNode.Attributes.GetNamedItem("soapAction").Value;
if (retSoap.MethodList.ContainsKey(MethodName))
{
retSoap.MethodList[MethodName].SoapAction = action;
}
}
}
}
if (n.LocalName == "service")
{
XmlNode sNode = n.FirstChild.FirstChild;
retSoap.Url = sNode.Attributes.GetNamedItem("location").Value;
}
}
}
if (retSoap.MethodList.Count > 0)
{
List<string> delList = new List<string>();
foreach (var m in retSoap.MethodList)
{
if (m.Value.SoapAction == null || m.Value.SoapAction == "")
{
delList.Add(m.Key);
}
}
for (int i = 0; i < delList.Count; i++)
{
retSoap.MethodList.Remove(delList[i]);
}
}
return retSoap;
}
/// <summary>
/// 得到Soap调用的xml串
/// </summary>
/// <param name="address">webservice地址</param>
/// <param name="methodInfo">方法信息</param>
/// <param name="para">参数</param>
/// <param name="userName">用户名(有的话就给)</param>
/// <param name="userPass">密码(有的话就给)</param>
/// <returns>调用xml</returns>
public static string GetSoapXml(string address, SoapMethodInfo methodInfo, Dictionary<string, string> para, string userName = "", string userPass = "")
{
StringBuilder sbxml = new StringBuilder();
sbxml.Append("<?xml version=\"1.0\" encoding=\"utf-8\"?>");
sbxml.Append("<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:wsa=\"http://schemas.xmlsoap.org/ws/2004/08/addressing\" xmlns:wsse=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd\" xmlns:wsu=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd\">");
if (userName != "" && userPass != "")
{
sbxml.Append("<soap:Header>");
sbxml.Append("<wsa:Action>" + methodInfo.SoapAction + "</wsa:Action>");
sbxml.Append("<wsa:ReplyTo>");
sbxml.Append("<wsa:Address>http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</wsa:Address>");
sbxml.Append("</wsa:ReplyTo>");
sbxml.Append("<wsa:To>" + address + "</wsa:To>");
sbxml.Append("<wsse:Security soap:mustUnderstand=\"1\">");
sbxml.Append("<wsse:UsernameToken xmlns:wsu=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd\" wsu:Id=\"SecurityToken-29db7c92-1098-4717-b7fb-aac473632fec\">");
sbxml.Append("<wsse:Username>" + userName + "</wsse:Username>");
sbxml.Append("<wsse:Password Type=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText\">" + userPass + "</wsse:Password>");
sbxml.Append("</wsse:UsernameToken>");
sbxml.Append("</wsse:Security>");
sbxml.Append("</soap:Header>");
}
sbxml.Append("<soap:Body>");
if(methodInfo.TargetNamespace!=null&& methodInfo.TargetNamespace!="")
{
sbxml.Append("<" + methodInfo.MethodName + " xmlns=\""+ methodInfo.TargetNamespace + "\">");
}
else
{
sbxml.Append("<" + methodInfo.MethodName + ">");
}
foreach (var m in methodInfo.ParaList)
{
sbxml.Append("<" + m + ">" + para[m].Replace("&", "&").Replace("'", "'").Replace("\"", """).Replace(">", ">").Replace("<", "<") + "</" + m + ">");
}
sbxml.Append("</" + methodInfo.MethodName + ">");
sbxml.Append("</soap:Body>");
sbxml.Append("</soap:Envelope>");
return sbxml.ToString();
}
/// <summary>
/// 到指定地址发送xml执行Soap
/// </summary>
/// <param name="address">webservice地址</param>
/// <param name="xml">soap的xml</param>
/// <returns></returns>
public static string InVokeSoapMethod(string address, string action, string xml)
{
string result = string.Empty;
HttpContent content = new StringContent(xml, Encoding.UTF8, "text/xml");
content.Headers.Add("SOAPAction", action);
IHttpClientFactory factory = HttpClientFactoryUtil.GetFactory();
using (HttpClient client = factory.CreateClient("webservice"))
{
client.Timeout = new TimeSpan(1, 0, 0);
using (var response = client.PostAsync(address, content))
{
result = response.Result.Content.ReadAsStringAsync().Result;
client.Dispose();
//创建一个xml文档
XmlDocument xmlDoc = new XmlDocument();
//为文档导入数据
xmlDoc.LoadXml(result);
result = xmlDoc.InnerText;
}
}
return result;
}
/// <summary>
/// 通过url加载msdl
/// </summary>
/// <param name="url"></param>
/// <returns></returns>
private static string LoadMsdlByUrl(string url)
{
string retStr = "";
HttpWebRequest request = null;
if (url.StartsWith("https", StringComparison.OrdinalIgnoreCase))
{
request = WebRequest.Create(url) as HttpWebRequest;
ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(CheckValidationResult);
request.ProtocolVersion = HttpVersion.Version11;
// 这里设置了协议类型。
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls;
request.KeepAlive = true;
ServicePointManager.CheckCertificateRevocationList = true;
ServicePointManager.DefaultConnectionLimit = 100;
ServicePointManager.Expect100Continue = false;
}
else
{
request = (HttpWebRequest)WebRequest.Create(url);
}
//重要,设置Cookie才能跳转会话不失效
CookieContainer myCookieContainer = new CookieContainer();
request.CookieContainer = myCookieContainer;
HttpWebResponse response = request.GetResponse() as HttpWebResponse;
Stream responseStream = response.GetResponseStream();
StreamReader myread = new StreamReader(responseStream);
retStr = myread.ReadToEnd();
retStr = retStr.Replace("\r\n", "");
responseStream.Close();
return retStr;
}
/// <summary>
/// 回调
/// </summary>
/// <param name="sender">触发者</param>
/// <param name="certificate">证书</param>
/// <param name="chain"></param>
/// <param name="errors"></param>
/// <returns></returns>
private static bool CheckValidationResult(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors errors)
{
//总是接受
return true;
}
}
/// <summary>
/// Soap信息类
/// </summary>
public class SoapInfo
{
/// <summary>
/// URL
/// </summary>
public string Url
{
get;
set;
}
/// <summary>
/// 用户名
/// </summary>
public string UserName
{
get;
set;
}
/// <summary>
/// 密码
/// </summary>
public string Password
{
get;
set;
}
/// <summary>
/// 方法列表
/// </summary>
public Dictionary<string, SoapMethodInfo> MethodList
{
get;
set;
}
}
/// <summary>
/// 方法信息类
/// </summary>
public class SoapMethodInfo
{
/// <summary>
/// 方法名字
/// </summary>
public string MethodName
{
get;
set;
}
/// <summary>
/// 触发路径
/// </summary>
public string SoapAction
{
get;
set;
}
/// <summary>
/// 方法命名空间
/// </summary>
public string TargetNamespace
{
get;
set;
}
/// <summary>
/// 参数列表
/// </summary>
public List<string> ParaList
{
get;
set;
}
}
}
界面代码(根据Soap信息动态渲染调用界面和执行调用)
using SoapUtil;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace SoapCall
{
///<summary NoteObject="Class">
/// [功能描述: 基于动态调用Webservice工具类包装的动态调用Webservice工具]<br></br>
/// [创建者: zlz]<br></br>
/// [创建时间: 20211024]<br></br>
/// <说明>
///
/// </说明>
/// <修改记录>
/// <修改时间></修改时间>
/// <修改内容>
///
/// </修改内容>
/// </修改记录>
/// </summary>
public partial class Form1 : Form
{
/// <summary>
/// 返回信息
/// </summary>
RichTextBox retTxt = new RichTextBox();
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
}
/// <summary>
/// 加载msdl
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnLoad_Click(object sender, EventArgs e)
{
pnlMethod.Controls.Clear();
pnlPara.Controls.Clear();
//得到Soap信息
SoapInfo info=SoapUtil.SoapCallUtil.GetSoapInfo(txtUrl.Text);
pnlMethod.Tag = info;
if (info.MethodList!=null&&info.MethodList.Count>0)
{
int top = 10;
Label labp = new Label();
labp.Left = 20;
labp.Top = top;
labp.ForeColor = Color.Red;
labp.Text = "服务包含以下方法";
labp.Width = pnlMethod.Width - 30;
pnlMethod.Controls.Add(labp);
top += 30;
foreach (string method in info.MethodList.Keys)
{
Label lab = new Label();
lab.Left = 20;
lab.Top = top;
lab.ForeColor = Color.Blue;
top += 30;
lab.Text = method;
lab.Width = pnlMethod.Width - 30;
lab.Tag = info.MethodList[method];
lab.Click += Lab_Click;
pnlMethod.Controls.Add(lab);
}
}
}
/// <summary>
/// 单击渲染方法调用
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Lab_Click(object sender, EventArgs e)
{
pnlPara.Controls.Clear();
Label lab = sender as Label;
SoapMethodInfo method=lab.Tag as SoapMethodInfo;
if (method.ParaList!=null&& method.ParaList.Count>0)
{
int top = 10;
Label labp = new Label();
labp.Left = 20;
labp.Top = top;
labp.ForeColor = Color.Red;
labp.Width = pnlPara.Width - 170;
labp.Text = method.MethodName+"调用测试"+ ":参数本身是xml的用<![CDATA[参数]]>";
pnlPara.Controls.Add(labp);
top += 30;
foreach (string s in method.ParaList)
{
labp = new Label();
labp.Left = 20;
labp.Top = top;
labp.ForeColor = Color.Black;
labp.Text = s;
labp.Width = 130;
pnlPara.Controls.Add(labp);
TextBox txt = new TextBox();
txt.Width = pnlPara.Width - 170;
txt.Left = 150;
txt.Top = top;
txt.Tag = s;
pnlPara.Controls.Add(txt);
top += 30;
}
Button btn= new Button();
btn.Left = 150;
btn.Top = top;
btn.Text = "调用";
btn.Click += Btn_Click;
btn.Tag = method;
pnlPara.Controls.Add(btn);
top += 30;
retTxt.Text = "";
retTxt.Left = 150;
retTxt.Top = top;
retTxt.Width = pnlPara.Width - 170;
retTxt.Height = pnlPara.Height-top-20;
pnlPara.Controls.Add(retTxt);
}
}
/// <summary>
/// 调用方法
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Btn_Click(object sender, EventArgs e)
{
Button btn = sender as Button;
SoapMethodInfo method = btn.Tag as SoapMethodInfo;
SoapInfo soap = pnlMethod.Tag as SoapInfo;
Dictionary<string, string> dicPara = new Dictionary<string, string>();
foreach(Control c in pnlPara.Controls)
{
if(c is TextBox)
{
TextBox txt = c as TextBox;
string paraName = txt.Tag.ToString();
string val = txt.Text;
dicPara.Add(paraName,val);
}
}
string xml = SoapCallUtil.GetSoapXml(soap.Url, method, dicPara, soap.UserName, soap.Password);
string ret=SoapCallUtil.InVokeSoapMethod(soap.Url, method.SoapAction,xml);
retTxt.Text = ret;
}
}
}
更多细节,自己对代码和Webservice协议,这里可以把发送HTTP请求 逻辑不用微软的HttpClient,自己用TCP实现HttpClient的发送部分,下一个实现己用TCP发送Http请求,替换Webservice调用的发送部分。
也可以轻松包装成动态调用Webservice的Web页面-这代码就不对外了