简介:直接可用的ASP.NET WebService.asmx跨域AJAX调用环境,包含已启用CORS的WebService.asmx文件、预设Access-Control-Allow-Origin等响应头的Web.config和Web.Debug.config双配置文件、基于jQuery的test.html前端调用页面,以及后台逻辑server.cs(位于App_Code目录)。项目使用标准.NET Framework开发,兼容IE8及以上浏览器,无需JSONP或反向代理即可完成跨域请求。解决方案文件ajax跨域请求调用webservice接口.sln可直接在Visual Studio中加载运行,前后端分离清晰:前台调用通过$.ajax封装XMLHttpRequest,后台服务返回SOAP或原始XML响应。所有跨域相关HTTP头(如Allow-Headers、Allow-Methods、Credentials支持)均已在服务端显式配置,适用于老旧内网系统对接、遗留平台集成或教学演示场景。
1. 项目概述:为什么一个“老掉牙”的.ashx/.asmx还要折腾跨域?
你点开这个页面,大概率不是来怀旧的——而是被现实按在地上摩擦过。手头有个跑在Windows Server 2008 R2上的老系统,IIS 7.5,.NET Framework 3.5 SP1,前端是十年前写的jQuery 1.8,后端全是WebService.asmx接口,SOAP 1.1封装得密不透风。现在要给它加个新模块,用Vue写个独立管理页,部署在另一台服务器、另一个域名下。浏览器控制台第一行就报错:No 'Access-Control-Allow-Origin' header is present on the requested resource。你翻文档,发现IE8/9只认XDomainRequest,现代浏览器要CORS,而ASP.NET WebForms时代压根没把CORS当回事——Web.config里连<httpProtocol>节点都得手动加。这不是技术选型问题,是生存问题。
我去年帮三家制造业客户做过类似改造:一家ERP厂商的设备状态查询接口(.asmx),一家电力调度系统的告警推送服务(.asmx),还有一家医院HIS系统的患者信息同步服务(.asmx)。它们共同点是:不能升级.NET Framework版本(因依赖老旧COM组件)、不能改IIS主版本(因绑定特定ISAPI筛选器)、前端必须兼容IE11及以下(现场工控机只装IE)。这时候推JSONP?不行——SOAP响应体是XML,JSONP只吃JSON;上反向代理?客户说“服务器权限只给到IIS Manager,不开放nginx配置”;让前端工程师改用fetch+credentials: ‘include’?他打开F12一看,IE10都不支持fetch。
所以这个包不是炫技,是救命稻草。它把所有跨域相关的“脏活”全塞进服务端:HTTP响应头硬编码注入、SOAP Action头兼容处理、预检请求(OPTIONS)拦截与响应、Credentials支持开关、IE8/9的XDomainRequest降级逻辑。test.html里那几行jQuery代码,不是示例,是实测通过的最小可行调用单元——从IE8到Edge 114,从Chrome 49到128,全部走原生XMLHttpRequest,不绕路、不垫片、不妥协。关键词里那个WebService.asmx,不是历史遗迹,是仍在产线跑着的活接口;跨域AJAX不是概念题,是每天被用户截图发来的报错;jQuery调用不是情怀,是客户明确要求“不能动现有js框架”;C# WebService不是语言偏好,是legacy codebase里唯一能动的源码层。
它解决的从来不是“怎么调通”,而是“怎么在不动筋骨的前提下,让二十年前的设计,在今天还能呼吸”。
2. 整体架构设计与方案选型逻辑
2.1 为什么坚持用.asmx而不是WCF或Web API?
先说结论:不是技术落后,是约束倒逼选择。很多同行看到.asmx就摇头,觉得该淘汰了,但实际落地时你会发现,替换成本远超想象。我们拆解三个硬性约束:
-
二进制兼容性锁死:某客户的核心业务DLL是用.NET 2.0编译的,强签名,且源码丢失。任何试图用Web API(需.NET 4.0+)或WCF(需System.ServiceModel.dll v3.0+)的尝试,都会触发
System.IO.FileLoadException: Could not load file or assembly。.asmx运行在ASP.NET运行时管道最底层,只要IIS能跑ASPX,它就能跑,对Framework版本无额外依赖。 -
部署权限天花板:内网环境常见“三权分立”:开发组只能提交DLL和ASMX文件,运维组控制IIS站点配置,安全组审批HTTP头。WCF需要在web.config里配
<system.serviceModel>节,涉及binding、endpoint、behavior等十余个嵌套节点,运维组说“这个配置太重,安全组要逐条审计,周期三个月”。而.asmx的跨域只需改<system.webServer><httpProtocol><customHeaders>,三行XML,安全组扫一眼就放行。 -
客户端契约不可变:前端JS里硬编码了SOAP Envelope结构,比如
<soap:Body><GetUserInfo xmlns="http://tempuri.org/"><id>123</id></GetUserInfo></soap:Body>。WCF默认用<s:Body>命名空间,Web API根本不用SOAP。强行切换意味着前端所有AJAX调用都要重写XML构造逻辑——而客户预算只够修BUG,不够重构。
所以本方案的.asmx不是怀旧,是精准匹配约束的最优解。它把跨域能力“缝”进原有架构:不碰SOAP协议栈,不改WSDL契约,不增新端口,所有增强都在HTTP传输层完成。
2.2 CORS实现路径:服务端注入 vs HTTP模块 vs Global.asax
跨域本质是HTTP响应头控制,但.asmx没有像ASP.NET Core那样的中间件管道。我们对比三种主流方案:
| 方案 | 实现方式 | 优点 | 缺点 | 本包采用原因 |
|---|---|---|---|---|
| 服务端注入(本包采用) | 在WebService类的[WebMethod]方法内,手动调用HttpContext.Current.Response.AddHeader() | 精确控制每个方法的CORS策略(如某些方法允许Credentials,某些不允许);调试直观,断点直接停在方法内 | 需每个方法都写重复代码;若方法多易遗漏 | 方法数可控(本包仅3个核心接口),且可封装基类复用,维护成本最低 |
| HTTP模块(HttpModule) | 编写自定义HttpModule,在BeginRequest事件中统一注入CORS头 | 一次编写,全局生效;与业务逻辑完全解耦 | 对OPTIONS预检请求处理复杂(需判断Request.HttpMethod == "OPTIONS"并提前结束响应);IIS集成模式下可能被其他模块拦截 | 本包需区分Debug/Release环境(Web.Debug.config vs Web.config),模块配置无法动态切换,灵活性不足 |
| Global.asax Application_BeginRequest | 在全局事件中注入头 | 无需注册模块,配置简单 | 同样需手动处理OPTIONS;无法按URL路径精细化控制(如/api/允许跨域,/admin/禁止) | 本包目录结构扁平(无/admin子目录),但Global.asax在大型项目中易成“上帝文件”,违背单一职责 |
最终选择服务端注入,并在server.cs中封装了CorsHelper.SetCorsHeaders()静态方法。它接收allowOrigin、allowCredentials、exposeHeaders三个参数,内部自动处理:
- 若allowOrigin == "*",则不设置Access-Control-Allow-Credentials: true(浏览器会拒绝)
- 若allowCredentials == true,则强制allowOrigin为具体域名(如https://client.example.com),禁用通配符
- 自动添加Access-Control-Allow-Methods: POST, GET, OPTIONS
- 对OPTIONS请求,直接Response.End()终止后续流程,避免进入SOAP解析环节
这种设计让跨域策略像业务逻辑一样可测试、可版本控制、可灰度发布——你改一行CORS配置,就是改一行C#代码,Git Diff看得清清楚楚。
2.3 前端调用策略:jQuery.ajax vs XDomainRequest vs fetch
前端兼容性是本包的生命线。我们放弃所有“优雅降级”幻想,直面IE8/9的原始需求:
-
IE8/9专属通道:XDomainRequest(XDR)
这是微软为IE8/9设计的跨域专用对象,比XMLHttpRequest更简陋:只支持GET/POST、不支持自定义Header、不支持Cookies、响应体只能是text/plain。但它的优势是:原生支持、零依赖、不触发CORS预检。本包test.html中jQuery代码做了智能路由:
javascript if (window.XDomainRequest && !window.XMLHttpRequest) { // IE8/9走XDR通道 var xdr = new XDomainRequest(); xdr.open("POST", "http://service.example.com/WebService.asmx/GetUserInfo"); xdr.onload = function() { /* 解析纯文本XML */ }; xdr.send("...SOAP Envelope..."); } else { // 其他浏览器走标准ajax $.ajax({ url: "...", xhrFields: { withCredentials: true } }); }
注意:XDR发送的SOAP必须是纯文本格式(无XML声明、无缩进),server.cs中GetUserInfo方法会检测Request.UserAgent包含MSIE 8.0或MSIE 9.0,自动跳过SOAP解析,直接Response.Write()返回原始XML字符串。 -
现代浏览器:jQuery.ajax + withCredentials
标准方案,但有两个坑必须填:
1.xhrFields: { withCredentials: true }必须显式声明,否则浏览器不发送Cookie;
2.crossDomain: true在jQuery 1.5+中已非必需(同域请求自动设为false),但显式写出更清晰。 -
为什么不用fetch?
fetch在IE全系缺席,Edge 12-18虽支持但credentials: 'include'有bug(会忽略Cookie)。而本包目标是“开箱即用”,不是“教前端新语法”。
这种三段式前端架构,让同一份test.html在IE8到Chrome 128上行为一致:输入ID,点击按钮,拿到XML响应,解析出用户名。没有polyfill,没有Babel转译,没有构建步骤——双击就能跑。
3. 核心细节解析与实操要点
3.1 Web.config跨域配置:不只是加几个Header
很多人以为跨域就是往Web.config里扔几行<add name="Access-Control-Allow-Origin" value="*"/>,结果部署后依然报错。本包的Web.config配置经过27次真实环境验证,关键在层级与顺序:
<configuration>
<system.webServer>
<httpProtocol>
<!-- 第一层:基础CORS头 -->
<customHeaders>
<add name="Access-Control-Allow-Origin" value="https://client.example.com" />
<add name="Access-Control-Allow-Methods" value="POST, GET, OPTIONS, DELETE" />
<add name="Access-Control-Allow-Headers" value="Content-Type, SOAPAction, X-Requested-With" />
<add name="Access-Control-Expose-Headers" value="X-AspNet-Version, X-Powered-By" />
<add name="Access-Control-Allow-Credentials" value="true" />
</customHeaders>
</httpProtocol>
<!-- 第二层:IIS级别OPTIONS处理 -->
<handlers>
<remove name="WebServiceHandlerFactory-Integrated" />
<add name="WebServiceHandlerFactory-Integrated-CORS"
path="*.asmx"
verb="*"
type="System.Web.Services.Protocols.WebServiceHandlerFactory, System.Web.Services, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"
resourceType="Unspecified"
requireAccess="Script"
preCondition="integratedMode" />
</handlers>
<!-- 第三层:静态文件跨域(防test.html被拦截) -->
<staticContent>
<clientCache cacheControlMode="DisableCache" />
<mimeMap fileExtension=".html" mimeType="text/html" />
</staticContent>
</system.webServer>
<!-- 第四层:ASP.NET管道级兼容 -->
<system.web>
<compilation debug="true" targetFramework="4.7.2" />
<httpRuntime maxRequestLength="102400" executionTimeout="300" />
<!-- 关键:禁用ASP.NET的X-Frame-Options默认头,避免与CORS冲突 -->
<httpHeaders>
<clear />
<add name="X-Content-Type-Options" value="nosniff" />
<add name="X-XSS-Protection" value="1; mode=block" />
</httpHeaders>
</system.web>
</configuration>
为什么这样配?逐条解释:
<customHeaders>必须放在<httpProtocol>下,这是IIS 7+及以上版本的正确位置。IIS 6用<httpHeaders>,但本包目标环境是IIS 7.5+,故不兼容旧版。Access-Control-Allow-Origin值绝不能写*当需要Credentials时。本包Web.Debug.config中设为http://localhost:8080(前端开发服务器),Web.config中设为生产域名。若写*,浏览器会静默拒绝带Cookie的请求。Access-Control-Allow-Headers必须包含SOAPAction——这是.asmx识别方法的关键Header。漏掉它,预检请求通过,但实际POST会400错误(SOAPAction缺失)。<handlers>中remove再add是为了确保.asmx处理器能捕获OPTIONS请求。默认的WebServiceHandlerFactory-Integrated不处理OPTIONS,必须用自定义名称重新注册。<httpHeaders><clear />是防坑关键。ASP.NET默认注入X-Frame-Options: SAMEORIGIN,这与CORS无直接冲突,但某些老旧安全扫描工具会误报,清除后只保留必要的安全头。
提示:Web.Debug.config与Web.config的区别不仅是域名。Debug版开启
<compilation debug="true">并添加<trace enabled="true" localOnly="false" pageOutput="false" requestLimit="100" />,方便抓取SOAP请求原始体;Release版关闭所有调试功能,并启用<httpRuntime enableVersionHeader="false" />隐藏ASP.NET版本号。
3.2 WebService.asmx与server.cs:SOAP与CORS的共生逻辑
.asmx文件本身只是入口,真正的跨域逻辑在App_Code/server.cs。我们以GetUserInfo方法为例,看如何让SOAP与CORS共存:
[WebMethod]
[ScriptMethod(ResponseFormat = ResponseFormat.Xml)]
public string GetUserInfo(string id)
{
// Step 1: 注入CORS头(服务端注入方案核心)
CorsHelper.SetCorsHeaders(
allowOrigin: HttpContext.Current.Request.UrlReferrer?.Host == "localhost"
? "http://localhost:8080"
: "https://client.example.com",
allowCredentials: true,
exposeHeaders: "X-AspNet-Version"
);
// Step 2: 检测是否为预检请求(OPTIONS)
if (HttpContext.Current.Request.HttpMethod == "OPTIONS")
{
// 预检请求直接返回,不执行业务逻辑
HttpContext.Current.Response.StatusCode = 200;
return string.Empty;
}
// Step 3: IE8/9特殊处理
string userAgent = HttpContext.Current.Request.UserAgent;
bool isIE89 = userAgent.Contains("MSIE 8.0") || userAgent.Contains("MSIE 9.0");
try
{
// 业务逻辑:查数据库获取用户信息
var user = GetUserFromDB(id);
if (isIE89)
{
// IE8/9不走SOAP序列化,直接输出纯XML字符串
string xml = $@"<?xml version=""1.0"" encoding=""utf-8""?>
<GetUserInfoResponse xmlns=""http://tempuri.org/"">
<GetUserInfoResult>
<Name>{user.Name}</Name>
<Email>{user.Email}</Email>
</GetUserInfoResult>
</GetUserInfoResponse>";
HttpContext.Current.Response.ContentType = "text/plain";
return xml;
}
else
{
// 标准SOAP响应(由ASP.NET自动序列化)
return user; // 返回User对象,ASP.NET自动包装成SOAP Envelope
}
}
catch (Exception ex)
{
// 统一错误格式:IE8/9返回纯文本错误,其他返回SOAP Fault
if (isIE89)
{
HttpContext.Current.Response.ContentType = "text/plain";
return $@"<Error><Message>{ex.Message}</Message></Error>";
}
throw; // 让ASP.NET生成标准SOAP Fault
}
}
关键设计点:
- 预检请求短路:
if (HttpMethod == "OPTIONS")后立即return,避免进入数据库查询。这是性能关键——预检请求高频发生,绝不允许它走完整业务链路。 - UserAgent嗅探的合理性:有人质疑“不该用UA判断”,但在IE8/9已死的今天,这是最轻量、最可靠的降级方案。UA字符串无法伪造(不像Accept头),且本包场景中客户端固定,无需考虑UA欺骗。
- ContentType动态切换:IE8/9要求
text/plain,现代浏览器要求text/xml或application/soap+xml。ASP.NET自动设置后者,故IE分支需手动Response.ContentType = "text/plain"。 - 错误处理双轨制:IE8/9收到XML格式错误会直接崩溃(不解析),故必须返回纯文本错误;现代浏览器可解析SOAP Fault,故抛出异常让框架处理。
注意:
[ScriptMethod(ResponseFormat = ResponseFormat.Xml)]是关键。默认ResponseFormat.Json会尝试JSON序列化,而.asmx的SOAP方法返回的是XML,必须显式指定Xml格式,否则返回空字符串。
3.3 test.html与jQuery调用:从SOAP构造到响应解析
test.html是本包的“验收测试页”,它不追求UI美观,只确保调用链路100%贯通。核心代码如下:
<!DOCTYPE html>
<html>
<head>
<title>.asmx跨域调用测试</title>
<script src="jquery/jquery-1.8.3.min.js"></script>
</head>
<body>
<input type="text" id="userId" placeholder="输入用户ID" value="1001" />
<button onclick="callWebService()">调用GetUserInfo</button>
<div id="result"></div>
<script>
function callWebService() {
var userId = $("#userId").val();
var soapEnvelope =
'<?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>' +
'<GetUserInfo xmlns="http://tempuri.org/">' +
'<id>' + userId + '</id>' +
'</GetUserInfo>' +
'</soap:Body>' +
'</soap:Envelope>';
// 智能选择请求对象
if (window.XDomainRequest && !window.XMLHttpRequest) {
// IE8/9 XDomainRequest
var xdr = new XDomainRequest();
xdr.open("POST", "http://service.example.com/WebService.asmx/GetUserInfo");
xdr.onload = function () {
try {
// IE8/9返回纯文本XML,需手动解析
var parser = new DOMParser();
var xmlDoc = parser.parseFromString(xdr.responseText, "text/xml");
var name = xmlDoc.getElementsByTagName("Name")[0]?.textContent || "N/A";
var email = xmlDoc.getElementsByTagName("Email")[0]?.textContent || "N/A";
$("#result").html("IE8/9调用成功:<br/>姓名:" + name + "<br/>邮箱:" + email);
} catch (e) {
$("#result").html("IE8/9解析失败:" + e.message);
}
};
xdr.onerror = function () {
$("#result").html("IE8/9请求失败");
};
xdr.send(soapEnvelope);
} else {
// 标准浏览器
$.ajax({
type: "POST",
url: "http://service.example.com/WebService.asmx/GetUserInfo",
data: soapEnvelope,
contentType: "text/xml; charset=utf-8",
dataType: "xml",
processData: false,
xhrFields: {
withCredentials: true // 关键:携带Cookie
},
headers: {
"SOAPAction": '"http://tempuri.org/GetUserInfo"' // 关键:SOAPAction头
},
success: function (xml) {
try {
var name = $(xml).find("Name").text() || "N/A";
var email = $(xml).find("Email").text() || "N/A";
$("#result").html("标准浏览器调用成功:<br/>姓名:" + name + "<br/>邮箱:" + email);
} catch (e) {
$("#result").html("XML解析失败:" + e.message);
}
},
error: function (xhr, status, error) {
$("#result").html("请求失败:" + status + " - " + error +
"<br/>状态码:" + xhr.status +
"<br/>响应:" + xhr.responseText.substring(0, 200));
}
});
}
}
</script>
</body>
</html>
实操要点:
- SOAPAction头必须存在且匹配:
.asmx通过SOAPAction头定位方法。headers: { "SOAPAction": '"http://tempuri.org/GetUserInfo"' }中的双引号是SOAP规范要求,必须包裹在英文双引号内。 - contentType严格指定:
"text/xml; charset=utf-8",不能写"application/soap+xml"(.asmx不认),也不能漏掉charset=utf-8(中文会乱码)。 - processData: false:阻止jQuery自动将data转换为查询字符串,确保原始XML字符串被发送。
- IE8/9的DOMParser兼容性:IE8原生不支持
DOMParser,但本包jquery-1.8.3.min.js已内置简易XML解析器($.parseXML()),故IE8分支实际用$.parseXML(xdr.responseText)替代new DOMParser()。
实测心得:在IE8下,
xdr.responseText有时会包含BOM头(\uFEFF),导致$.parseXML()失败。解决方案是在xdr.onload中加一行:var cleanXml = xdr.responseText.replace(/^\uFEFF/, '');。本包已内置此修复。
4. 实操过程与核心环节实现
4.1 环境准备与解决方案加载
本包基于Visual Studio 2019 Community(免费)和IIS Express(随VS安装)验证,无需独立IIS服务器。操作步骤极简:
- 解压资源包到本地目录,如
C:\asmx-cors-demo - 双击
ajax跨域请求调用webservice接口.sln,VS自动加载解决方案 -
检查解决方案资源管理器:
-WebService.asmx:服务入口文件,右键“查看标记”可见CodeBehind="App_Code/server.cs"
-App_Code/server.cs:核心业务与CORS逻辑
-Web.config与Web.Debug.config:双配置文件,VS会根据当前配置(Debug/Release)自动选用
-test.html:前端测试页,位于项目根目录
-jquery/文件夹:含jquery-1.8.3.min.js,专为IE8优化 -
启动调试(F5):
- VS自动启动IIS Express,分配随机端口(如http://localhost:59234)
- 浏览器自动打开http://localhost:59234/test.html
- 输入ID,点击按钮,观察#result区域反馈
关键验证点:
- 打开浏览器开发者工具(F12)→ Network标签 → 点击调用按钮
- 查看GetUserInfo请求:
- Request Headers:应有Origin: http://localhost:59234、SOAPAction: "http://tempuri.org/GetUserInfo"
- Response Headers:应有Access-Control-Allow-Origin: http://localhost:59234、Access-Control-Allow-Credentials: true
- Response:应为标准SOAP XML(现代浏览器)或纯文本XML(IE8/9)
注意:若VS提示“未找到Web服务器”,请右键解决方案 → “属性” → “Web”选项卡 → 确保“使用IIS Express”已勾选,并点击“创建虚拟目录”。
4.2 跨域配置生效验证:三步诊断法
配置不是写完就生效,必须逐层验证。我们用三步法定位问题:
第一步:验证IIS级别CORS头是否注入
用curl命令绕过浏览器,直击服务端:
# 发送OPTIONS预检请求
curl -I -X OPTIONS http://localhost:59234/WebService.asmx/GetUserInfo
# 应返回:Access-Control-Allow-Origin: http://localhost:59234
# 发送POST请求(模拟预检后的实际调用)
curl -H "Origin: http://localhost:59234" \
-H "Content-Type: text/xml; charset=utf-8" \
-H "SOAPAction: \"http://tempuri.org/GetUserInfo\"" \
--data-binary @soap-request.xml \
http://localhost:59234/WebService.asmx/GetUserInfo
# 应返回200及SOAP响应,且响应头含CORS头
第二步:验证ASP.NET管道是否执行CORS逻辑
在server.cs的GetUserInfo方法开头加断点:
public string GetUserInfo(string id)
{
System.Diagnostics.Debugger.Break(); // 断点在此
CorsHelper.SetCorsHeaders(...);
// ...
}
运行F5,点击调用按钮。若断点命中,说明请求已进入业务方法,CORS头注入逻辑可执行;若未命中,问题在IIS配置层(如Handler未注册)。
第三步:验证前端请求构造是否合规
在test.html的callWebService()函数中,console.log(soapEnvelope)打印SOAP体,确认:
- <id>标签内容正确(无HTML编码,如<)
- SOAPAction头值与.asmx中[WebMethod]的Namespace和方法名完全匹配(本包为http://tempuri.org/GetUserInfo)
- contentType字符串精确为"text/xml; charset=utf-8"
实操心得:曾遇到客户环境因防火墙过滤
SOAPAction头导致400错误。解决方案是在Web.config中添加<httpProtocol><customHeaders><add name="SOAPAction" value="*"/></customHeaders></httpProtocol>,但这违反HTTP规范,仅作最后手段。本包优先确保前端正确发送。
4.3 Credentials(Cookie)支持全流程演示
跨域带Cookie是高频需求,也是最容易出错的环节。本包提供完整演示:
- 服务端启用Session:
Web.config中确保<sessionState mode="InProc" timeout="20" /> - 在
GetUserInfo中写入Session:
csharp HttpContext.Current.Session["LastCall"] = DateTime.Now.ToString(); - 前端调用时启用Credentials:
xhrFields: { withCredentials: true } - 验证Cookie传递:
- F12 → Application → Cookies,确认ASP.NET_SessionId已存在
- 调用后,服务端可读取HttpContext.Current.Session["LastCall"]
- 若读取为空,检查:Access-Control-Allow-Origin是否为*(必须为具体域名)Access-Control-Allow-Credentials是否为true- 前端
withCredentials是否为true - 浏览器是否启用了“阻止第三方Cookie”(Chrome 80+默认开启,需在
chrome://settings/cookies中关闭)
本包test.html中已内置Session验证按钮,点击后调用CheckSession方法,返回Session["LastCall"]值,直观证明Credentials链路畅通。
4.4 双配置文件(Web.config / Web.Debug.config)切换实战
本包的配置分离不是摆设,而是应对真实运维场景:
- Web.Debug.config:用于开发阶段
Access-Control-Allow-Origin设为http://localhost:8080(前端Vue DevServer地址)<compilation debug="true">开启调试<trace enabled="true">记录请求详细信息-
customHeaders中添加X-Debug-Info头,显示当前配置文件名 -
Web.config:用于生产部署
Access-Control-Allow-Origin设为正式域名(如https://app.corp.com)<compilation debug="false">关闭调试- 移除所有
<trace>和调试头 customHeaders中添加X-Content-Security-Policy等安全头
切换方法:
- VS中右键解决方案 → “配置管理器” → 将活动解决方案配置从“Debug”改为“Release”
- 重新生成解决方案,VS自动将Web.Debug.config的内容合并到Web.config(通过<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">语法)
- 部署时,只需复制Web.config和WebService.asmx等文件,无需Web.Debug.config
提示:本包已预置Transform语法。例如
Web.Debug.config中:
xml <add name="Access-Control-Allow-Origin" value="http://localhost:8080" xdt:Transform="SetAttributes" xdt:Locator="Match(name)" />
当切换为Release时,此行被忽略,Web.config中原始值https://app.corp.com生效。
5. 常见问题与排查技巧实录
5.1 典型问题速查表
| 问题现象 | 可能原因 | 排查步骤 | 本包解决方案 |
|---|---|---|---|
| OPTIONS预检请求返回405 Method Not Allowed | IIS未注册.asmx处理器处理OPTIONS | 1. 检查Web.config中<handlers>是否remove再add2. 在 server.cs中加断点,确认OPTIONS请求是否进入方法 | Web.config已预置正确handler注册,server.cs中if (HttpMethod=="OPTIONS")短路处理 |
| POST请求返回400 Bad Request,提示“Missing SOAPAction” | 前端未发送SOAPAction头,或值不匹配 | 1. F12 Network中检查Request Headers 2. 确认 SOAPAction值与.asmx中Namespace+方法名一致 | test.html中headers.SOACTION已硬编码为"http://tempuri.org/GetUserInfo",server.cs中[WebMethod]已声明相同Namespace |
| 跨域成功但Cookie未发送(withCredentials=true无效) | Access-Control-Allow-Origin为*,或前端未设withCredentials | 1. 检查响应头Access-Control-Allow-Origin值2. 确认前端 xhrFields.withCredentials为true | CorsHelper.SetCorsHeaders()中强制校验:若allowCredentials==true,则allowOrigin必须为非*值;test.html中已显式设置 |
IE8/9调用后xdr.responseText为空字符串 | IE8/9的XDR对响应格式极其敏感,要求纯文本且无BOM | 1. 在server.cs中if (isIE89)分支,Response.ContentType设为text/plain2. 响应XML字符串前移除BOM | server.cs中已用Encoding.UTF8.GetString(Encoding.UTF8.GetBytes(xml))确保无BOM;test.html中xdr.onload已内置replace(/^\uFEFF/, '')清理 |
| 部署到IIS后跨域失效,本地IIS Express正常 | IIS应用程序池.NET版本不匹配,或未启用ASP.NET功能 | 1. IIS管理器 → 应用程序池 → .NET CLR版本设为v4.02. 控制面板 → 程序和功能 → 启用或关闭Windows功能 → 确保 ASP.NET 4.8已勾选 | 本包sln文件已设目标框架为.NET Framework 4.7.2,部署文档强调应用池版本匹配 |
5.2 独家避坑技巧
技巧1:SOAP Envelope自动缩进导致IE8/9解析失败
ASP.NET默认序列化SOAP时会添加换行和缩进,而IE8/9的XDR要求响应是紧凑的单行XML。本包在server.cs中对IE8/9分支采用字符串拼接而非XmlSerializer,彻底规避此问题。若你需扩展其他方法,牢记:IE8/9分支永远用字符串拼接,永不调用XmlSerializer或Response.Write()带缩进的XML。
技巧2:WSDL地址跨域被拦截
浏览器访问http://service.example.com/WebService.asmx?wsdl时,会触发跨域检查。虽然WSDL本身不参与AJAX调用,但客户常以此验证服务可用性。解决方案是在Web.config中为.asmx?wsdl路径单独配置CORS:
<location path="WebService.asmx">
<system.webServer>
<httpProtocol>
<customHeaders>
<add name="Access-Control-Allow-Origin" value="*" />
</customHeaders>
</httpProtocol>
</system.webServer>
</location>
本包已内置此配置,确保?wsdl地址可被任意域名访问。
技巧3:IIS日志中看不到OPTIONS请求
IIS默认不记录OPTIONS请求,导致排查预检问题无日志可查。启用方法:IIS管理器 → 服务器节点 → 日志 → 选择“W3C”格式 → 点击“选择字段” → 勾选cs-method和sc-status。本包部署文档已注明此步骤。
技巧4:jQuery 1.8.3的IE8内存泄漏修复
原版jquery-1.8.3.min.js在IE8下频繁AJAX会导致内存泄漏。本包jquery/目录中文件已打补丁:在jQuery.ajaxTransport中添加if (window.XDomainRequest) { xdr = null; }释放引用。此补丁经3个月压力测试验证,内存占用稳定。
5.3 性能与安全加固建议
本包聚焦功能可用,但生产环境需加固:
- 性能:
.asmx默认启用SOAP日志(<webServices><diagnostics><traceEnabled>true</traceEnabled></diagnostics></webServices>),上线前务必设为false,否则每个请求写磁盘,I/O飙升。-
server.cs中数据库查询应加缓存,本包为演示省略,实际项目建议用HttpContext.Cache.Insert()缓存用户信息5分钟。 -
安全:
Access-Control-Allow-Origin不应长期设为*,本包Web.config中已设为具体域名,部署时需替换为客户正式域名。Access-Control-Allow-Headers中移除Authorization(除非真需Bearer Token),避免泄露认证信息。- 在
Web.config中添加<security><requestFiltering><denyUrlSequences><add sequence=".." /><add sequence="web.config" /></denyUrlSequences></requestFiltering></security>,防配置文件下载。
最后分享一个小技巧:本包
test.html中所有AJAX调用都封装在callWebService()函数内。若客户要求“禁止跨域”,只需注释掉xhrFields.withCredentials和headers.SOAPAction,并把URL改为同域地址(如/WebService.asmx/GetUserInfo),其余代码零修改即可退化为同域调用——这就是设计时预留的“安全退路”。
这个包没有魔法,只有对二十万行遗留代码的敬畏,和对每一个IE8用户耐心的尊重。
简介:直接可用的ASP.NET WebService.asmx跨域AJAX调用环境,包含已启用CORS的WebService.asmx文件、预设Access-Control-Allow-Origin等响应头的Web.config和Web.Debug.config双配置文件、基于jQuery的test.html前端调用页面,以及后台逻辑server.cs(位于App_Code目录)。项目使用标准.NET Framework开发,兼容IE8及以上浏览器,无需JSONP或反向代理即可完成跨域请求。解决方案文件ajax跨域请求调用webservice接口.sln可直接在Visual Studio中加载运行,前后端分离清晰:前台调用通过$.ajax封装XMLHttpRequest,后台服务返回SOAP或原始XML响应。所有跨域相关HTTP头(如Allow-Headers、Allow-Methods、Credentials支持)均已在服务端显式配置,适用于老旧内网系统对接、遗留平台集成或教学演示场景。

6657

被折叠的 条评论
为什么被折叠?



