.NET 7.0调用第三方ASMX服务:登录认证与SOAP客户端实现问题
解决方案:登录逻辑实现与SOAP客户端优化
一、登录逻辑实现(基于Cookie会话维持)
第三方ASMX要求先通过网站登录,本质是通过Cookie维持会话状态,核心思路是先模拟登录请求拿到会话Cookie,再将Cookie附加到SOAP请求头中:
- 用
HttpClient发送登录请求获取Cookie
确认第三方网站的登录接口(比如POST到/Login,参数为用户名密码),发送请求后提取响应的Set-Cookie头信息。 - 通过消息拦截器注入Cookie
给SOAP客户端添加自定义消息拦截器,在每个SOAP请求的HTTP头中带上登录后获取的Cookie。
二、客户端优化建议
- 避免重复创建SOAP客户端:每次调用新建客户端会浪费资源,建议按请求作用域复用实例(注意SOAP客户端非线程安全,不要跨请求复用)
- 移除无意义的
Task.Run:new json_ws_biisSoapClient是同步操作,无需用Task.Run包装 - 缓存登录Cookie:不用每次调用都登录,缓存Cookie并在过期时自动重新登录
- 修复无效的反序列化逻辑:当前代码中反序列化空字符串到DataTable无意义,直接使用SOAP方法返回的结果处理
三、重构后的代码示例
1. 添加Cookie消息拦截器
public class CookieMessageInspector : IClientMessageInspector { private readonly string _cookie; public CookieMessageInspector(string cookie) { _cookie = cookie; } public void AfterReceiveReply(ref Message reply, object correlationState) { // 可选:在此更新Cookie(如果登录后返回新的Cookie) } public object BeforeSendRequest(ref Message request, IClientChannel channel) { if (!string.IsNullOrEmpty(_cookie)) { var httpRequestProperty = new HttpRequestMessageProperty(); httpRequestProperty.Headers.Add(HttpRequestHeader.Cookie, _cookie); request.Properties[HttpRequestMessageProperty.Name] = httpRequestProperty; } return null; } } public class CookieEndpointBehavior : IEndpointBehavior { private readonly string _cookie; public CookieEndpointBehavior(string cookie) { _cookie = cookie; } public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters) { } public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime) { clientRuntime.ClientMessageInspectors.Add(new CookieMessageInspector(_cookie)); } public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher) { } public void Validate(ServiceEndpoint endpoint) { } }
2. 重构Repository类
public class Repository : IRepository { private readonly IConfiguration _configuration; private readonly string _serviceUrl = Constants; private readonly EndpointAddress _endpointAddress; private readonly BasicHttpBinding _basicHttpBinding; private readonly HttpClient _httpClient; private string _authCookie; private DateTime _cookieExpiry; public Repository(IConfiguration configuration, HttpClient httpClient) { _configuration = configuration; _httpClient = httpClient; _endpointAddress = new EndpointAddress(_serviceUrl); _basicHttpBinding = new BasicHttpBinding(_endpointAddress.Uri.Scheme.ToLower() == "http" ? BasicHttpSecurityMode.None : BasicHttpSecurityMode.Transport); // 调整合理的超时时间(不建议设为MaxValue,避免资源泄漏) _basicHttpBinding.OpenTimeout = TimeSpan.FromMinutes(5); _basicHttpBinding.CloseTimeout = TimeSpan.FromMinutes(5); _basicHttpBinding.ReceiveTimeout = TimeSpan.FromMinutes(5); _basicHttpBinding.SendTimeout = TimeSpan.FromMinutes(5); } // 确保已登录,自动处理Cookie过期 private async Task EnsureLoggedInAsync(string user, string password) { if (!string.IsNullOrEmpty(_authCookie) && DateTime.Now < _cookieExpiry) return; // 替换为第三方网站的实际登录接口 var loginUrl = "https://thirdparty-site.com/Login"; var loginFormData = new FormUrlEncodedContent(new[] { new KeyValuePair<string, string>("username", user), new KeyValuePair<string, string>("password", password) }); var loginResponse = await _httpClient.PostAsync(loginUrl, loginFormData); loginResponse.EnsureSuccessStatusCode(); // 提取并拼接Cookie(忽略过期时间等附加属性) if (loginResponse.Headers.TryGetValues("Set-Cookie", out var cookies)) { _authCookie = string.Join(";", cookies.Select(c => c.Split(';')[0])); // 根据第三方Cookie实际有效期调整,这里假设1小时 _cookieExpiry = DateTime.Now.AddHours(1); } else { throw new InvalidOperationException("登录未返回会话Cookie"); } } // 创建带Cookie的SOAP客户端 private json_ws_biisSoapClient CreateClientWithCookie() { var client = new json_ws_biisSoapClient(_basicHttpBinding, _endpointAddress); if (!string.IsNullOrEmpty(_authCookie)) { client.Endpoint.Behaviors.Add(new CookieEndpointBehavior(_authCookie)); } return client; } public Task<json_ws_biisSoapClient> GetInstanceAsync() { // 移除无意义的Task.Run,直接返回实例 return Task.FromResult(CreateClientWithCookie()); } public async Task<Response<string>> BIISAsync(string obj, string lang, string user, string password) { var response = new Response<string>(); try { // 先确保已登录 await EnsureLoggedInAsync(user, password); using var client = CreateClientWithCookie(); // 调用SOAP方法并获取返回结果 var soapResult = await client.BIISAsync(obj, lang, user, password); // 反序列化为DataTable并返回(根据Response<T>定义调整格式) var dataTable = JsonConvert.DeserializeObject<DataTable>(soapResult); response.Data = JsonConvert.SerializeObject(dataTable); response.code = 0; } catch (Exception ex) { response.code = 500; response.message = ex.Message; // 清除失效Cookie,下次调用自动重新登录 _authCookie = null; } return response; } }
3. 接口简化(可选)
如果不需要对外暴露SOAP客户端实例,可以移除GetInstanceAsync方法,简化接口:
public interface IRepository { Task<Response<string>> BIISAsync(string obj, string lang, string user, string password); }
关键注意事项
- Cookie有效期:实际场景中要根据第三方登录返回的
Expires或Max-Age字段准确设置_cookieExpiry,避免提前失效 - 线程安全:如果Repository是单例模式,需要对
_authCookie和_cookieExpiry加锁;如果是Scoped(每个请求一个实例),则无需额外处理 - 超时设置:不要将超时设为
TimeSpan.MaxValue,容易导致资源泄漏,根据业务场景设置合理的超时时间
内容的提问来源于stack exchange,提问作者Franxer
相关产品推荐
相关产品推荐

