根据指定WSDL文件,创建客户端,调用Webservice接口
- IDE: Microsoft Visual Studio Community 2022 (64bit)
- 平台:C#.Net6
- 协议:Soap协议(Xml格式)
功能
- 对方是一个WebService服务端,己方是客户端,双方要遵守对方提供的WSDL规范。
实现方式1
- 使用BasicHttpBinding
通过WSDL文件,生成WEB服务
注意,这里,我的VS2022的提示步骤和网上查到的操作步骤略有不同,下面的步骤是我的VS2022的操作步骤:
-
首先,在 VS2022 中打开 .NET 6 项目或者新建一个 .NET 6 项目。
-
在“解决方案资源管理器”中,右键单击项目名称,然后选择“添加”->“服务引用”。
-
在“添加服务引用”窗口中,有三个选项,
- OpenAPI,
- gRPC
- WCF Web Service
这里选择 WCF Web Service,进入窗口“添加新的WCF Web Service服务引用”
-
点击“浏览”按钮,然后从文件系统中选择你的 WSDL 文件。
-
选中 WSDL 文件后,服务引用的配置将在窗口下方显示出来。这里可以在下面的输入框,修改命名空间为你想要的名称,点击下一步,在新窗口中,可以指定数据类型选项,默认不需要改,点击“完成”。
-
此时,VS2022 会根据你的 WSDL 文件生成 Web 服务的代码,即:原Webservice服务的代理类。
-
还有一个生成Webservice服务代理类的方法:
- 用wsdl.exe工具的方式
- 指令是:wsdl /language:c# /n:Fu /out:d:/MyService.cs C:\Users\Administrator\Desktop\MyService.wsdl)
- “d:/MyService.cs”是输出目录
- “C:\Users\Administrator\Desktop\MyService.wsdl”是源wsdl文件的位置
- 具体用法,可以使用搜索引擎查询,这里不再赘述。
调用该WebService的方法
public static async Task TestCallWs()
{
mydemo.TestMyClient serv = null;
try
{
var binding = new BasicHttpBinding();
var endpoint = new EndpointAddress(UrlString); // url地址
serv = new mydemo.TestMyClient(binding, endpoint);
var result = await serv.invokeAsync(ParamsString); // xml格式的字符串参数
Console.WriteLine("返回接口数据:\n " + result);
}
catch(HttpRequestException e)
{
Console.WriteLine(e.Message);
}
finally
{
if (serv != null)
{
serv.Close();
}
}
}
实现方式2
使用HttpClient
简单做一下封装
- 定义:WsClient
public class WsClient
{
// 接口地址
private string mUrl = string.Empty;
// 请求参数
private string mParam = string.Empty;
namespace命名空间
//private string ns = "http://XXX:xx/xx/xx"; // TODO填写上你需要的命名空间名称
请求方法名
//private string funName = string.Empty;
public WsClient()
{
throw new NotImplementedException();
}
public WsClient(string url)
{
this.mUrl = url;
}
public async Task<string> Request(string param)
{
if (string.IsNullOrEmpty(mUrl))
{
return "接口地址为空!";
}
if (!mUrl.Contains("http"))
{
return "非法接口地址!";
}
if (string.IsNullOrEmpty(param))
{
return "请求参数为空!";
}
this.mParam = param;
try
{
HttpClient httpClient = new();
// soap参数
string payload =
@"<?xml version=""1.0""?>
<soap:Envelope xmlns:soap=""http://schemas.xmlsoap.org/soap/envelope/"">
<soap:Body>
<invoke xmlns=""http://xxxxx.yyyyyyyy.com"">
<xmlData>" + mParam +
@"</xmlData>
</invoke>
</soap:Body>
</soap:Envelope>";
// 发送 SOAP 请求并获取响应
HttpResponseMessage response = await httpClient.PostAsync(mUrl, new StringContent(payload, Encoding.UTF8, "text/xml"));
// 解析 SOAP 响应
string respStr = await response.Content.ReadAsStringAsync();
return respStr;
}
catch (HttpRequestException e)
{
Console.WriteLine("WsClient -> Request()异常 : {0} ", e.Message);
}
return "";
}
}
调用方式:
string result = await new WsClient(wsUrl).Request(xmlData);
wsUrl: 是你的Webservice接口地址;
xmlData: 是你拼装好协议里约定的xml格式的数据参数,示例如下:
result: 是请求接口,返回的数据,也是xml格式。
<?xml version=""1.0"" encoding=""UTF-8""?>
<Request>
<Name>
QueryUsers
</Name>
<Info>
<ID>21202012211211</ID>
</Info>
</Request>
实现方式3 - 方式2的优化
来自朋友的优化版
public class WsClient
{
/// <summary>
/// 声明 HttpClient
/// </summary>
private HttpClient httpClient;
/// <summary>
/// 超时设定
/// </summary>
private TimeSpan Timeout = new TimeSpan(0, 0, 15);
#region 事件声明
/// <summary>
/// 接收消息事件通知
/// </summary>
public event EventHandler<RespBean> RecvDataEvent;
/// <summary>
/// 请求响应异常事件通知
/// </summary>
public event EventHandler<ReqError> RespErrorEvent;
#endregion
#region 单例
private static readonly Lazy<WsClient> _instance = new Lazy<WsClient>(() => new WsClient());
public static WsClient Instance
{
get { return _instance.Value; }
}
private WsClient()
{
if (httpClient == null)
{
httpClient = new HttpClient(new HttpClientHandler { UseCookies = false });
httpClient.Timeout = Timeout;
}
}
#endregion
/// <summary>
/// 生成SOAP协议
/// </summary>
/// <param name="reqData"></param>
/// <returns>完整SOAP协议,用于请求</returns>
private string GetSoapData(string reqData)
{
StringBuilder soapBuilder = new StringBuilder();
soapBuilder.Append("<?xml version=\"1.0\"?>")
.Append("<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">")
.Append("<soap:Body>")
.AppendFormat("<invoke xmlns=\"http://specialmap.chinamap.com\"><data>{0}</data></invoke>", reqData)
.Append("</soap:Body>")
.Append("</soap:Envelope>");
return soapBuilder.ToString();
}
/// <summary>
/// Post 请求
/// </summary>
/// <param name="url">请求地址</param>
/// <param name="data">请求数据体</param>
/// <param name="header">请求头</param>
public async Task PostData(string url, string data, List<KeyValuePair<string, string>> headers = null)
{
StringContent content = new StringContent(data, Encoding.UTF8, "text/xml");
if (headers != null && headers.Count > 0)
{
content.Headers.Clear();
foreach (var header in headers)
{
content.Headers.Add(header.Key, header.Value);
}
}
Uri uri = new Uri(url);
try
{
HttpResponseMessage response = await httpClient.PostAsync(uri, content);
if (response.StatusCode == HttpStatusCode.OK)
{
// 接收数据
string soapStr = await response.Content.ReadAsStringAsync();
// 实际的数据协议内容
string realStr = GetNodeValue(soapStr, "return");
// 具体的数据类
BaseInfo baseInfo = DeserializeObject<BaseInfo>(realStr);
if (baseInfo == null || baseInfo.Data == null)
{
RespErrorEvent?.Invoke(this, new ReqError()
{
IpAddr = new IPEndPoint(IPAddress.Parse(uri.Host), uri.Port),
ErrorMsg = "协议解析失败"
});
return;
}
RecvDataEvent?.Invoke(this, new RespBean { Ip = url, Data = soapStr });
}
else // HTTP 响应错误
{
RespErrorEvent?.Invoke(this, new ReqError()
{
IpAddr = new IPEndPoint(IPAddress.Parse(uri.Host), uri.Port),
ErrorMsg = $"响应错误 HttpCode={response.StatusCode}"
});
}
}
catch (TaskCanceledException ex)
{
RespErrorEvent?.Invoke(this, new ReqError()
{
IpAddr = new IPEndPoint(IPAddress.Parse(uri.Host), uri.Port),
ErrorMsg = "请求超时"
});
}
catch (Exception ex)
{
RespErrorEvent?.Invoke(this, new ReqError()
{
IpAddr = new IPEndPoint(IPAddress.Parse(uri.Host), uri.Port),
ErrorMsg = "请求错误"
});
}
}
/// <summary>
/// 发送 Http 请求数据
/// </summary>
public void Request(string host, int port, string content)
{
Task.Run(async () =>
{
string url = $"http://{host}:{port}/services/FSUService";
string soapData = GetSoapData(content);
await PostData(url, soapData);
});
}
/// <summary>
/// 从{xmlStr}中获取节点{nodeName}的值
/// </summary>
public static string GetNodeValue(string xmlStr, string nodeName)
{
try
{
XDocument doc = XDocument.Parse(xmlStr);
XElement element = doc.Descendants(nodeName).FirstOrDefault();
return element != null ? element.Value : null;
}
catch (Exception)
{
throw;
}
}
/// <summary>
/// XML 反序列化
/// </summary>
public static T DeserializeObject<T>(string xmlStr)
{
try
{
XmlSerializer xmlSerializer = new XmlSerializer(typeof(T));
using (StringReader textReader = new StringReader(xmlStr))
{
return (T)xmlSerializer.Deserialize(textReader);
}
}
catch (Exception ex)
{
Console.WriteLine("反序列化失败:"+ex.Message);
throw;
}
}
}
调用方式:
// 1 监听事件
WsClient.Instance.RecvDataEvent+= Recv_DataEvent;
WsClient.Instance.RespErrorEvent+= Resp_ErrorEvent;
// 2 发送请求
WsClient.Instance.Request(ip, port, paramsXml);