概述
本文演示了如何通过Ajax访问C# ASP.NET项目中的WebService方法(.asmx文件形式)。
本文的项目配置参见前文:C#构建Web服务项目实战(一)。
环境
Visual Studio 2017 / VS2019
C# ASP.NET Web服务
Win11 / Win10类似
.NET Framework 4.8(服务端)
JQuery-3.6.0.min.js(客户端)
.asmx文件
.asmx的全称是ASMXActive Server Methods
.asmx 是ASP.NET Web 服务 (ASMX)
基于.asmx的服务属于B/S形式,用SOAP方式HTTP访问,默认返回XML格式数据。
可以返回基础类型和PUBLIC结构类型
在Microsoft平台上,一个.asmx通常对应一个.NET Web服务,文件可以是诸如VB、C#等语言编写。
SOAP(百度百科)
简单对象访问协议是交换数据的一种协议规范,是一种轻量的、简单的、基于XML(标准通用标记语言下的一个子集)的协议,它被设计成在WEB上交换结构化的和固化的信息。
注:Web Service +WSDL + SOAP一起构成.NET的Web服务框架规范。
Ajax(异步JavaScript和XML)
Asynchronous Javascript And XML。一种独立于Web服务器的客户端浏览器技术,顾名思义,它能支持客户端异步访问服务器,不需要重载(刷新)整个页面。Ajax基于JavaScript、XML、HTML与 CSS等标准技术。
AJAX 是开发者的梦想,通过Ajax能够:
不刷新页面更新网页
在页面加载后从服务器请求数据
在页面加载后从服务器接收数据
在后台向服务器发送数据
根据W3school的在线文档:
AJAX 并不是编程语言。
AJAX 是一种从网页访问 Web 服务器的技术。
AJAX 代表异步JavaScript 和 XML。
关于Ajax的跨域访问
出于安全原因,现代浏览器不允许跨域访问。
这意味着尝试通过Ajax加载的网页和 XML 文件都必须位于相同服务器上。
.NET框架通过简单的配置可支持Ajax的跨域访问
跨域访问
本例中是从另外一个跨域的服务器(主机)的Web客户端访问本例中的C# 项目创建的Web应用(网站),也就是说,用户需要跨域访问网站。为支持Web客户端的跨域访问,需要在项目的Web.config文件中增加以下内容。
为方便(拷贝)起见,将配置代码附上。
<system.webServer> <defaultDocument> <files> <clear /> <add value="WebService1.asmx"/> </files> </defaultDocument> <directoryBrowse enabled="true" /> <httpProtocol> <customHeaders> <add name="Access-Control-Allow-Origin" value="*" /> <add name="Access-Control-Allow-Methods" value="GET,POST,PUT,DELETE,OPTIONS" /> <add name="Access-Control-Allow-Headers" value="*" /> </customHeaders> </httpProtocol> </system.webServer> |
服务端回顾
根据上一篇文章,我们通过VS2017利用C#创建了一个ASP.NET Web应用项目。然后,我们在项目中添加了一个Web服务,对应生成了一个.asmx文件(WebService1.asmx)。最后,我们通过各种配置操作完成了项目的发布,即通过Windows的IIS服务向Web用户提供相应的Web服务。
Web服务对应的代码如下。
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Services;
namespace MyWebApp { /// <summary> /// WebService1 的摘要说明 /// </summary> [WebService(Namespace = ""http://sgin.pcl.ac.cn/")] [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)] [System.ComponentModel.ToolboxItem(false)] // 若要允许使用 ASP.NET AJAX 从脚本中调用此 Web 服务,请取消注释以下行。 [System.Web.Script.Services.ScriptService] public class WebService1 : System.Web.Services.WebService {
[WebMethod] public string HelloWorld() { return "Hello World"; } [WebMethod(Description = "加法")] public int Add(string a, string b) { int sum = 0; sum = Convert.ToInt32(a) + Convert.ToInt32(b); return sum; } [WebMethod(Description = "乘法")] public int Multiply(string a, string b) { int rsult = 0; rsult = Convert.ToInt32(a) * Convert.ToInt32(b); return rsult; } } } |
可以看出,在上面的Web服务中,提供了三个服务:
HelloWorld服务:向客户端简单返回一个‘Hello World’字符串;
Add服务:客户端提供两个字符串,服务端将字符串转换为数字后,相加(做加法),然后将结果返回给客户端;
Multiply服务:客户端提供两个字符串,服务端将字符串转换为数字后,相乘(做乘法),然后将结果返回给客户端。
此外,根据服务端自动生成的代码中的提示,若要允许使用 ASP.NET AJAX 从脚本中调用此 Web 服务,请取消注释以下行。取消该行注释后,务必重新生成解决方案,然后重新发布项目。
下面演示客户端通过各种方法(协议、标准及接口)访问服务端的相关服务。通过上面的跨域访问配置,客户端不用限于与服务端在一台主机上,客户端可以通过远程(跨域)访问服务端。
标准Ajax方式
本地访问(客户端和服务器端同一台主机或属于同一个域)
一、无参数的方法访问
客户端代码如下
<!DOCTYPE html> <html lang="en"> <meta charset="utf-8" /> <script type="text/javascript" language="javascript"> function callHelloWorld() { var xhttp = new XMLHttpRequest(); xhttp.onreadystatechange = function() { if (this.readyState == 4 && this.status == 200) { document.getElementById("helloworld").innerHTML = this.responseText; } }; xhttp.open("POST", ""http://localhost:8081/WebService1.asmx/HelloWorld", true); xhttp.send(); } </script> <body> <div id="helloworld"> <h2>点击获取服务端返回的测试字符串</h2> <button type="button" οnclick="callHelloWorld()">调用</button> </div> </body> </html> |
注意用的是POST方法,GET方法没有尝试(按照服务端提示,应该是不支持GET方法)。
客户端(浏览器)界面:
点击‘调用’按钮,正确返回结果。
二、带参数的方法访问
客户端代码如下
<!DOCTYPE html> <html lang="en"> <meta charset="utf-8" /> <script type="text/javascript" language="javascript"> function callAdd() { var xhttp = new XMLHttpRequest(); xhttp.onreadystatechange = function() { if (this.readyState == 4 && this.status == 200) { document.getElementById("addValue").innerHTML = this.responseText; } }; xhttp.open("POST", ""http://localhost:8081/WebService1.asmx/Add", true); xhttp.setRequestHeader("content-type", "application/json"); var params = {"a":"3", "b":"4"}; xhttp.send(JSON.stringify(params)); } </script> <body> <div id="addValue"> <h2>点击执行服务端Add()方法</h2> <button type="button" οnclick="callAdd()">调用</button> </div> </body> </html> |
本例中,由于客户端需要上传参数(数据)给服务端,所以需要显式指定‘content-type’,参数名称需要与Add方法中的参数名称保持一致。
客户端(浏览器)界面:
点击‘调用’按钮,正确返回结果。
注意:返回的结果是JSON格式的字符串。由此,建议当服务端方法需要返回多个结果值时,通过JSON格式返回是比较妥当的。
跨域访问(客户端和服务器处于不同的域)
一、无参数的方法访问
上述同样的客户端代码,跨域访问时不能正确访问,点击按钮页面没反应,经调试,xhttp(即Ajax的XMLHttpRequest对象)返回的状态为500。查询w3school(http://w3school.com.cn)官网,对应的解释为‘请求未完成。服务器遇到不可预知的情况。’
经各种网站问题的搜索查询尝试,发现是参数传递的问题,问题最终解决了,但中间的机理还值得深入探讨。
ASP.NET的服务框架基于SOAP协议,SOAP基于XML协议交换数据和指令,在C#默认给出的示例中也是给出了如何基于SOAP协议来访问服务端的接口。
实际应用中,客户端通常通过表单形式提供请求给服务器并返回结果,或者通过更流行的Ajax异步方式,其中由Ajax为客户端请求执行了所需的SOAP封装。
这个问题不深入讨论,一来自己也没有弄得特别清楚,二来有点太费功夫。直接说如何解决问题的(可参见本文的小结帮助解惑)。
首先,需要在前述服务端回顾中按照VS2017自动代码框架生成的代码提示取消相关的注释以允许Ajax访问。
其次,在Ajax的服务请求中添加相应的头部信息。
xhttp.open("POST", ""http://localhost:8081/WebService1.asmx/HelloWorld", true); xhttp.setRequestHeader("Content-type", "application/json"); xhttp.send(); |
增加以上设置后,成功返回(JSON格式)结果。
数据名称为‘d’,是系统自动给出的,值为我们期望返回的‘Hello World’。
尝试了各种content-type,只有‘application/json’能够成功,其他尝试的包括:
text/xml
text/plain
text/html
application/xml
按理‘application/xml’应该是可以的,为何(跨域情况下)反倒是不支持?
经测试,当在本地调用服务时,不受客户端是否设置‘content-type’的影响,都能正确返回结果,只是在设置为‘application/json’返回的是JSON格式,其他设置下直接返回字符串。
也就是说,根据目前的测试,跨域访问的话,客户端只能且仅能将‘content-type’设置为‘application/json’才能正确获取结果。
说这么多,客户端是否能正确访问服务端的方法,跟HTTP请求的头封装密切相关,能够封装为正确的格式,就能获取内容。在跨域情况下,必须正确指定(格式化)相关请求头及请求参数(数据)。
二、带参数的方法访问
参见本地访问的例子。
JQuery + Ajax方式
本地访问
注意需要在客户端引入JQuery代码包(文件)。
利用JQuery + Ajax方式的好处:编写常规的 AJAX 代码并不容易,因为不同的浏览器对 AJAX 的实现并不相同。这意味着您必须编写额外的代码对浏览器进行测试。不过,jQuery团队为我们解决了这个难题,我们只需要一行简单的代码,就可以实现 AJAX 功能。(此处引用了网络原文描述,参见https://www.w3school.com.cn/jquery/jquery_ajax_intro.asp)
注意:在JQuery + Ajax方式下,访问接口、参数及响应与JQuery版本密切相关,本例中的JQuery版本为jquery-3.6.0.min.js
本例中仅对JQuery + Ajax方式下的POST方法进行了测试和验证,jQuery ajax – post()方法接口定义如下:
jQuery.post(url,data,success(data, textStatus, jqXHR),dataType)
详细内容可参见链接:https://www.w3school.com.cn/jquery/ajax_post.asp
一、无参数的方法访问
客户端代码如下:
<!DOCTYPE html> <html lang="en"> <meta charset="utf-8" /> <!-- 注意需要根据JQuery包(文件)的实际路径引入 --> <script type="text/javascript" src="/lib/jquery-3.6.0.min.js"></script> <body> <script type="text/javascript" language="javascript"> $(document).ready(function(){ $("#btnCall").click(function(){ $.post("/WebService1.asmx/HelloWorld", function(data){ $("#HelloWorld").html(data); alert(data); }); }); }); </script> <div id="addValue"> <h2 id="HelloWorld">点击获取服务端返回的测试字符串</h2> <button id="btnCall" type="button">调用</button> </div> </body> </html> |
方法很简单,就是访问C# Web服务提供的无参数接口,然后通过alert方法测试返回的数据。
经测试,点击‘调用’按钮后,服务器返回了结果,在执行了‘$("#HelloWorld").html(data);’语句后,页面无反应,后续的‘alert(data);’语句未能执行。
注释掉语句‘$("#HelloWorld").html(data);’,alert结果如下。
提示返回一个[objectXMLDocument],即一个XMLDocument对象。
这也是‘JQuery+Ajax’方式下,与C# Web服务接口访问后默认返回的对象。
注意jQuery.post()方法的最后一个可选参数‘dataType’,分别指定为xml, json, script和html后的反应如下:
“xml”,返回XMLDocument对象。
“json”,调用失败,无法返回结果。
“script”,返回XML格式的字符串,该字符串通过‘$(selector).html(data);’方法解析后可直接在页面显示结果,即服务器返回的‘HelloWorld’字符串;通过‘$(selector).text(data);’方法则可直接显示原始的XML文本(字符串)。
“html”,与指定为”script”结果一样。
如果需要返回JSON格式数据,需要在客户端请求(post方法)中指定contentType为‘application/jsom’
跨域访问
同样的客户端代码,本地访问均测试通过,跨域完全没反应!
直接给出跨域访问的正确代码,细节请移步小结。
<!DOCTYPE html> <html lang="en"> <meta charset="utf-8" /> <!-- 注意需要根据JQuery包(文件)的实际路径引入 --> <script type="text/javascript" src="/lib/jquery-3.6.0.min.js"></script> <body> <script type="text/javascript" language="javascript"> $(document).ready(function(){ $("#btnCall").click(function(){ $.ajax({ type : "POST", contentType : "application/json; charset:utf-8", url : ""http://192.168.123.24:8081/WebService1.asmx/HelloWorld", dataType : "json", success : function (data) { alert("success"); for(x in data) { $("#HelloWorld").html(data[x]); break; // retrieve only the first element } }, error : function(xhr) { // for test alert("error"); $("#HelloWorld").html(xhr.responseText); } }); }); }); </script> <div id="addValue"> <h2 id="HelloWorld">点击获取服务端返回的测试字符串</h2> <button id="btnCall" type="button">调用</button> </div> </body> </html> |
带参数的访问:
<!DOCTYPE html> <html lang="en"> <meta charset="utf-8" /> <!-- 注意需要根据JQuery包(文件)的实际路径引入 --> <script type="text/javascript" src="/lib/jquery-3.6.0.min.js"></script> <body> <script type="text/javascript" language="javascript"> $(document).ready(function(){ $("#btnCall").click(function(){ var params = {"a":"3", "b":"4"}; $.ajax({ type : "POST", contentType : "application/json; charset:utf-8", url : ""http://192.168.123.24:8081/WebService1.asmx/Add" data : JSON.stringify(params), dataType : "json", success : function (data) { for(x in data) { $("#HelloWorld").html(data[x]); break; // retrieve only the first element } }, error : function(xhr) { // for test alert("error"); $("#HelloWorld").html(xhr.responseText); } }); }); }); </script> <div id="addValue"> <h2 id="HelloWorld">点击获取服务端返回的测试字符串</h2> <button id="btnCall" type="button">调用</button> </div> </body> </html> |
小结
直接总结吧,中间试错的过程全部忽略,下面的总结仅针对跨域访问,本地访问形式灵活且支持的协议更多,可以参照前述的示例。
提示:
由于软件、接口版本不同,本示例和总结仅针对前述环境一节中给出的各软件(或工具)版本。
服务端是基于C#的ASP.NET Web服务应用,客户端基于JQuery+Ajax。
关于本地和跨域访问,本地访问能够成功的代码,跨域会遇到各种各样的问题;反之,跨域能够执行的代码,本地均能执行。在JQuery + Ajax方式下,由于JQuery对Ajax进行了定制的封装,本地访问能够获取XML形式的结果,跨域通过Ajax访问只能获得JSON格式的数据。
总结要点:
根据服务端的服务提示,基于C#的Web服务(.asmx文件形式),服务端只接受来自客户端的POST方式的请求。
基于Ajax跨域访问服务端默认的‘contentType’为‘application/json’,切记!设置为其他类型访问将失败。
返回的data值与请求时设置的‘dataType’有关,dataType的设置不会影响对服务器的请求,其影响的是数据自服务器返回后JQuery执行的针对性的解析操作:
默认(不设置),返回一个JSON对象;
dataType : “json”:dataType的默认值,返回一个JSON对象;
dataType : “xml”:调用服务成功,但在客户端本地解析数据将失败;
dataType : “html”:得到的结果为格式化了的JSON字符串;
dataType : “script”:调用服务成功,但在客户端本地解析数据将失败;
dataType : “text”:得到的结果为格式化了的JSON字符串。
总之,只要在客户端请求数据中正确指定了URL和contentType,则总能成功访问服务端接口(服务),客户端需要根据本地的需求正确指定dataType以便正确解析数据。
好记性不如烂笔头,谨记以备温习。