参考:JavaScript SOAP Client - CodeProject
soapclient.js 支持返回DataSet,DataTable(必须有显示名称)
/*****************************************************************************\
Javascript "SOAP Client" library
@version: 2.4 - 2007.12.21
@author: Matteo Casati - http://www.guru4.net/
\*****************************************************************************/
class SOAPClientParameters {
#pp = new Array();
constructor() {
}
add(name, value) {
this.#pp[name] = value;
return this;
}
all() {
return this.#pp;
}
toXml() {
var xml = "";
for (var p in this.#pp) {
switch (typeof (this.#pp[p])) {
case "string":
case "number":
case "boolean":
case "object":
xml += "<" + p + ">" + SOAPClientParameters.#serialize(this.#pp[p]) + "</" + p + ">";
break;
default:
break;
}
}
return xml;
}
static #serialize(o) {
var s = "";
if(o === null || o === undefined) return s;
switch (typeof (o)) {
case "string":
s += o.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">"); break;
case "number":
case "boolean":
s += o.toString(); break;
case "object":
// Date
if (o.constructor.toString().indexOf("function Date()") > -1) {
var year = o.getFullYear().toString();
var month = (o.getMonth() + 1).toString(); month = (month.length == 1) ? "0" + month : month;
var date = o.getDate().toString(); date = (date.length == 1) ? "0" + date : date;
var hours = o.getHours().toString(); hours = (hours.length == 1) ? "0" + hours : hours;
var minutes = o.getMinutes().toString(); minutes = (minutes.length == 1) ? "0" + minutes : minutes;
var seconds = o.getSeconds().toString(); seconds = (seconds.length == 1) ? "0" + seconds : seconds;
var milliseconds = o.getMilliseconds().toString();
var tzminutes = Math.abs(o.getTimezoneOffset());
var tzhours = 0;
while (tzminutes >= 60) {
tzhours++;
tzminutes -= 60;
}
tzminutes = (tzminutes.toString().length == 1) ? "0" + tzminutes.toString() : tzminutes.toString();
tzhours = (tzhours.toString().length == 1) ? "0" + tzhours.toString() : tzhours.toString();
var timezone = ((o.getTimezoneOffset() < 0) ? "+" : "-") + tzhours + ":" + tzminutes;
s += year + "-" + month + "-" + date + "T" + hours + ":" + minutes + ":" + seconds + "." + milliseconds + timezone;
}
// Array
else if (o.constructor.toString().indexOf("function Array()") > -1) {
for (var p in o) {
if (!isNaN(p)) // linear array
{
(/function\s+(\w*)\s*\(/ig).exec(o[p].constructor.toString());
var type = RegExp.$1;
switch (type) {
case "":
type = typeof (o[p]);
case "String":
type = "string"; break;
case "Number":
type = "int"; break;
case "Boolean":
type = "bool"; break;
case "Date":
type = "DateTime"; break;
}
s += "<" + type + ">" + SOAPClientParameters.#serialize(o[p]) + "</" + type + ">"
}
else // associative array
s += "<" + p + ">" + SOAPClientParameters.#serialize(o[p]) + "</" + p + ">"
}
}
// Object or custom function
else
for (var p in o)
s += "<" + p + ">" + SOAPClientParameters.#serialize(o[p]) + "</" + p + ">";
break;
default:
break; // throw new Error(500, "SOAPClientParameters: type '" + typeof(o) + "' is not supported");
}
return s;
}
}
class SOAPClient {
static #cache = new Array();
constructor(url, userName, password) {
this.url = url;
this.userName = userName;
this.password = password;
this.#getWsdl();
}
#getWsdl() {
if (!this.url) return;
var wsdl = SOAPClient.#cache[this.url];
if (wsdl + "" != "" && wsdl + "" != "undefined") {
return;
}
var xmlHttp = SOAPClient.#getXmlHttp();
xmlHttp.open("GET", this.url + "?wsdl", true);
xmlHttp.onreadystatechange = () => {
if (xmlHttp.readyState == 4) {
SOAPClient.#cache[this.url] = xmlHttp.responseXML;
}
}
xmlHttp.send(null);
}
#loadWsdl(method, parameters, async, callback) {
// load from cache?
var wsdl = SOAPClient.#cache[this.url];
if (wsdl + "" != "" && wsdl + "" != "undefined")
return this.#sendSoapRequest(method, parameters, async, callback, wsdl);
// get wsdl
var xmlHttp = SOAPClient.#getXmlHttp();
xmlHttp.open("GET", this.url + "?wsdl", async);
if (async) {
xmlHttp.onreadystatechange = () => {
if (xmlHttp.readyState == 4)
this.#onLoadWsdl(method, parameters, async, callback, xmlHttp);
}
}
xmlHttp.send(null);
if (!async)
return this.#onLoadWsdl(method, parameters, async, callback, xmlHttp);
}
#onLoadWsdl(method, parameters, async, callback, req) {
var wsdl = req.responseXML;
SOAPClient.#cache[this.url] = wsdl; // save a copy in cache
return this.#sendSoapRequest(method, parameters, async, callback, wsdl);
}
#sendSoapRequest(method, parameters, async, callback, wsdl) {
// get namespace
var ns = (wsdl.documentElement.attributes["targetNamespace"] + "" == "undefined") ? wsdl.documentElement.attributes.getNamedItem("targetNamespace").nodeValue : wsdl.documentElement.attributes["targetNamespace"].value;
// build SOAP request
var sr =
"<?xml version=\"1.0\" encoding=\"utf-8\"?>" +
"<soap:Envelope " +
"xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" " +
"xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" " +
"xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">" +
"<soap:Body>" +
"<" + method + " xmlns=\"" + ns + "\">" +
parameters.toXml() +
"</" + method + "></soap:Body></soap:Envelope>";
// send request
var xmlHttp = SOAPClient.#getXmlHttp();
if (this.userName && this.password) {
xmlHttp.open("POST", this.url, async, this.userName, this.password);
// Some WS implementations (i.e. BEA WebLogic Server 10.0 JAX-WS) don't support Challenge/Response HTTP BASIC, so we send authorization headers in the first request
xmlHttp.setRequestHeader("Authorization", "Basic " + SOAPClient.#toBase64(this.userName + ":" + this.password));
}
else
xmlHttp.open("POST", this.url, async);
var soapaction = ((ns.lastIndexOf("/") != ns.length - 1) ? ns + "/" : ns) + method;
xmlHttp.setRequestHeader("SOAPAction", soapaction);
xmlHttp.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
if (async) {
xmlHttp.onreadystatechange = () => {
if (xmlHttp.readyState == 4)
this.#onSendSoapRequest(method, async, callback, wsdl, xmlHttp);
}
}
xmlHttp.send(sr);
if (!async)
return this.#onSendSoapRequest(method, async, callback, wsdl, xmlHttp);
}
#onSendSoapRequest(method, async, callback, wsdl, req) {
var o = null;
var nd = SOAPClient.#getElementsByTagName(req.responseXML, method + "Response");
if (nd.length == 0)
nd = SOAPClient.#getElementsByTagName(req.responseXML, "return"); // PHP web Service?
if (nd.length == 0) {
if (req.responseXML.getElementsByTagName("faultcode").length > 0) {
if (async || callback)
o = new Error(500, req.responseXML.getElementsByTagName("faultstring")[0].childNodes[0].nodeValue);
else
throw new Error(500, req.responseXML.getElementsByTagName("faultstring")[0].childNodes[0].nodeValue);
}
}
else
o = SOAPClient.#soapresult2object(nd[0], wsdl);
if (callback)
callback(o, req.responseXML);
if (!async)
return o;
}
static #soapresult2object(node, wsdl) {
var wsdlTypes = SOAPClient.#getTypesFromWsdl(wsdl);
return SOAPClient.#node2object(node, wsdlTypes);
}
static #node2object(node, wsdlTypes,varTypes) {
// null node
if (node == null)
return null;
// text node
if (node.nodeType == 3 || node.nodeType == 4)
return SOAPClient.#extractValue(node, wsdlTypes, varTypes);
// leaf node
if (node.childNodes.length == 1 && (node.childNodes[0].nodeType == 3 || node.childNodes[0].nodeType == 4))
return SOAPClient.#node2object(node.childNodes[0], wsdlTypes, varTypes);
var isarray = SOAPClient.#getTypeFromWsdl(node.nodeName, wsdlTypes, varTypes).toLowerCase().indexOf("arrayof") != -1;
// object node
if (!isarray) {
var obj = null,n ="childNodes";
if (node.hasChildNodes()) {
if (node[n].length == 2
&& node[n][1].tagName == "diffgr:diffgram") {
//xs:schema,xs:element,xs:complexType,xs:choice
return SOAPClient.#resolveDataSet(node[n][0][n][0][n][0][n][0], node[n][1][n][0], wsdlTypes);
}
obj = new Object();
for (var i = 0; i < node[n].length; i++) {
var p = SOAPClient.#node2object(node[n][i], wsdlTypes,varTypes);
obj[node[n][i].nodeName] = p;
}
}
return obj;
}
// list node
else {
// create node ref
var l = new Array();
for (var i = 0; i < node.childNodes.length; i++)
l[i] = SOAPClient.#node2object(node.childNodes[i], wsdlTypes, varTypes);
return l;
}
return null;
}
static #resolveDataSet(schema,node, wsdlTypes) {
var obj = new Object(), schemaTypes = new Object();
for (var i = 0, j = schema.childNodes.length; i < j; i++) {
var key = schema.childNodes[i].getAttribute("name");
obj[key] = [];
schemaTypes[key] = SOAPClient.#getTypesFromWsdl(schema.childNodes[i], "xs");
}
for (var i = 0, j = node.childNodes.length; i < j; i++) {
var n = node.childNodes[i], key = n.tagName;
obj[key].push(SOAPClient.#node2object(n, wsdlTypes, schemaTypes[key]));
}
return obj;
}
static #extractValue(node, wsdlTypes,varTypes) {
var value = node.nodeValue;
switch (SOAPClient.#getTypeFromWsdl(node.parentNode.nodeName, wsdlTypes, varTypes).toLowerCase()) {
default:
case "s:string":
case "xs:string":
case "s:long":
case "xs:long":
case "s:double":
case "xs:double":
return (value != null) ? value + "" : "";
case "s:boolean":
case "xs:boolean":
return value + "" == "true" || value + "" == "True";
case "s:int":
case "xs:int":
return (value != null) ? parseInt(value + "", 10) : 0;
case "s:datetime":
case "xs:datetime":
if (value == null)
return null;
else {
value = value + "";
value = value.substring(0, (value.lastIndexOf(".") == -1 ? value.length : value.lastIndexOf(".")));
value = value.replace(/T/gi, " ");
value = value.replace(/-/gi, "/");
var d = new Date();
d.setTime(Date.parse(value));
return d;
}
}
}
static #getTypesFromWsdl = function (wsdl, prefix) {
var wsdlTypes = new Array();
prefix = prefix || "s";
// IE
var ell = wsdl.getElementsByTagName(prefix + ":element");
var useNamedItem = true;
// MOZ
if (ell.length == 0) {
ell = wsdl.getElementsByTagName("element");
useNamedItem = false;
}
for (var i = 0; i < ell.length; i++) {
if (useNamedItem) {
if (ell[i].attributes.getNamedItem("name") != null && ell[i].attributes.getNamedItem("type") != null)
wsdlTypes[ell[i].attributes.getNamedItem("name").nodeValue] = ell[i].attributes.getNamedItem("type").nodeValue;
}
else {
if (ell[i].attributes["name"] != null && ell[i].attributes["type"] != null)
wsdlTypes[ell[i].attributes["name"].value] = ell[i].attributes["type"].value;
}
}
return wsdlTypes;
}
static #getTypeFromWsdl = function (elementname, wsdlTypes, varTypes) {
var type = varTypes && varTypes[elementname];
if (!type) {
type = wsdlTypes[elementname] + "";
}
return type == "undefined" ? "" : type;
}
// private: utils
static #getElementsByTagName = function (document, tagName) {
try {
// trying to get node omitting any namespaces (latest versions of MSXML.XMLDocument)
return document.selectNodes(".//*[local-name()=\"" + tagName + "\"]");
}
catch (ex) { }
// old XML parser support
return document.getElementsByTagName(tagName);
}
// private: xmlhttp factory
static #getXmlHttp = function () {
try {
if (window.XMLHttpRequest) {
var req = new XMLHttpRequest();
// some versions of Moz do not support the readyState property and the onreadystate event so we patch it!
if (req.readyState == null) {
req.readyState = 1;
req.addEventListener("load",
function () {
req.readyState = 4;
if (typeof req.onreadystatechange == "function")
req.onreadystatechange();
},
false);
}
return req;
}
if (window.ActiveXObject)
return new ActiveXObject(SOAPClient.#getXmlHttpProgID());
}
catch (ex) { }
throw new Error("Your browser does not support XmlHttp objects");
}
static #getXmlHttpProgID = function () {
if (SOAPClient.#getXmlHttpProgID.progid)
return SOAPClient.#getXmlHttpProgID.progid;
var progids = ["Msxml2.XMLHTTP.5.0", "Msxml2.XMLHTTP.4.0", "MSXML2.XMLHTTP.3.0", "MSXML2.XMLHTTP", "Microsoft.XMLHTTP"];
var o;
for (var i = 0; i < progids.length; i++) {
try {
o = new ActiveXObject(progids[i]);
return SOAPClient.#getXmlHttpProgID.progid = progids[i];
}
catch (ex) { };
}
throw new Error("Could not find an installed XML parser");
}
static #toBase64 = function (input) {
var keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
var output = "";
var chr1, chr2, chr3;
var enc1, enc2, enc3, enc4;
var i = 0;
do {
chr1 = input.charCodeAt(i++);
chr2 = input.charCodeAt(i++);
chr3 = input.charCodeAt(i++);
enc1 = chr1 >> 2;
enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
enc4 = chr3 & 63;
if (isNaN(chr2)) {
enc3 = enc4 = 64;
} else if (isNaN(chr3)) {
enc4 = 64;
}
output = output + keyStr.charAt(enc1) + keyStr.charAt(enc2) +
keyStr.charAt(enc3) + keyStr.charAt(enc4);
} while (i < input.length);
return output;
}
invoke(method, parameters, async, callback) {
if (!parameters) {
parameters = new SOAPClientParameters();
}
if (async)
this.#loadWsdl(method, parameters, async, callback);
else
return this.#loadWsdl(method, parameters, async, callback);
}
}
webservice.asmx
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Web;
using System.Web.Services;
namespace WebApp
{
/// <summary>
/// WebService 的摘要说明
/// </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 WebService : System.Web.Services.WebService
{
[WebMethod(Description = "HelloWorld")]
public string HelloWorld(string id, string name, out string msg, out DataSet ds)
{
msg = "HelloWorld";
ds = new DataSet("data");
DataTable table = new DataTable();
table.Columns.Add("total", typeof(int));
table.Rows.Add(92);
ds.Tables.Add(table);
table = new DataTable();
table.Columns.Add("name");
table.Columns.Add("type");
table.Columns.Add("code");
table.Columns.Add("value",typeof(int));
table.Rows.Add("苹果", "水果", "apple", 11);
table.Rows.Add("香梨", "水果", "pear", 9);
ds.Tables.Add(table);
return "success";
}
[WebMethod(Description = "RetDataSet")]
public DataSet RetDataSet()
{
DataSet ds = new DataSet("data");
DataTable table = new DataTable();
table.Columns.Add("total", typeof(int));
table.Rows.Add(80);
ds.Tables.Add(table);
return ds;
}
[WebMethod(Description = "RetDataTable")]
public DataTable RetDataTable()
{
DataTable table = new DataTable("table");
table.Columns.Add("name");
table.Columns.Add("type");
table.Columns.Add("code");
table.Columns.Add("value", typeof(int));
table.Rows.Add("苹果", "水果", "apple", 11);
table.Rows.Add("香梨", "水果", "pear", 9);
return table;
}
[WebMethod(Description = "GetList")]
public string GetList(int pageNo,int pageSize, out string msg, out List<Fruit> list)
{
msg = "测试接口";
list = new List<Fruit>()
{
new Fruit{ id="1",name="苹果",code="apple",num=10},
new Fruit{ id="2",name="香梨",code="pear",num=3}
};
return "ok";
}
}
public class Fruit
{
public string id { get; set; }
public string name { get; set; }
public string code { get; set; }
public int num { get; set; }
}
}
test.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>测试页面</title>
<script src="soapclient.js"></script>
</head>
<body>
<button onclick="test()">测试</button>
<script>
var url = "WebService.asmx";
var p = new SOAPClientParameters();
p.add("id", "20210610");
p.add("name", "测试");
let client = new SOAPClient(url);
function helloWorldCallback(r) {
console.log(r);
}
function test() {
//client.invoke("HelloWorld", p, true, helloWorldCallback);
//client.invoke("RetDataSet", null, true, helloWorldCallback);
client.invoke("RetDataTable", null, true, helloWorldCallback);
client.invoke("GetList", null, true, helloWorldCallback);
}
</script>
</body>
</html>