You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.NET 7.0调用第三方ASMX服务:登录认证与SOAP客户端实现问题

解决方案:登录逻辑实现与SOAP客户端优化

一、登录逻辑实现(基于Cookie会话维持)

第三方ASMX要求先通过网站登录,本质是通过Cookie维持会话状态,核心思路是先模拟登录请求拿到会话Cookie,再将Cookie附加到SOAP请求头中:

  1. 用HttpClient发送登录请求获取Cookie
    确认第三方网站的登录接口(比如POST到/Login,参数为用户名密码),发送请求后提取响应的Set-Cookie头信息。
  2. 通过消息拦截器注入Cookie
    给SOAP客户端添加自定义消息拦截器,在每个SOAP请求的HTTP头中带上登录后获取的Cookie。

二、客户端优化建议

  1. 避免重复创建SOAP客户端:每次调用新建客户端会浪费资源,建议按请求作用域复用实例(注意SOAP客户端非线程安全,不要跨请求复用)
  2. 移除无意义的Task.Run:new json_ws_biisSoapClient是同步操作,无需用Task.Run包装
  3. 缓存登录Cookie:不用每次调用都登录,缓存Cookie并在过期时自动重新登录
  4. 修复无效的反序列化逻辑:当前代码中反序列化空字符串到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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.27 10:12:56