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

C# VSTO Excel插件连接不受信任HTTPS服务器异常求助

问题描述

使用C#开发Excel VSTO插件,功能为从指定URL下载数据并填充工作表。连接HTTP或证书链受信任的HTTPS服务器时运行正常,但连接证书不受信任的HTTPS服务器时抛出异常,调试发现异常发生在request.GetRequestStream()调用处。已尝试HttpWebRequest和HttpClient两种方式均未解决,替换为可信HTTPS地址(如https://www.google.com)则可正常连接,不想安装目标服务器的证书。

HttpWebRequest代码:

private void BtnCreateDataSource_Click(object sender, EventArgs e)
{
    ServicePointManager.ServerCertificateValidationCallback += (messageSender, cert, chain, sslPolicyErrors) => true;

string data_str = @"{
    ""grant_type"": ""password"",
    ""username"": ""xxx"",
    ""password"": ""xxx"",
    ""scope"": ""openid https://xxxx""
}";
    var data = Encoding.ASCII.GetBytes(data_str);
    HttpWebRequest request = (HttpWebRequest)WebRequest.Create("https://xxx/oidc/grantToken");
    //HttpWebRequest request = (HttpWebRequest)WebRequest.Create("https://www.google.com");

    request.Method = "POST";
    request.ContentType = "application/json";
    request.ContentLength = data.Length;
    StreamWriter requestWriter = new StreamWriter(request.GetRequestStream(), System.Text.Encoding.ASCII);

    using (var stream = request.GetRequestStream())
    {
        stream.Write(data, 0, data.Length);
    }

    var response = (HttpWebResponse)request.GetResponse();

    var responseString = new StreamReader(response.GetResponseStream()).ReadToEnd();

    MessageBox.Show(responseString);
}

异常信息(已翻译):

************** 异常文本 **************
System.Net.WebException: 基础连接已关闭: 发送时发生意外错误。 ---> System.IO.IOException: 验证失败,因为远程方关闭了传输流。
   v System.Net.Security.SslState.StartReadFrame(Byte[] buffer, Int32 readBytes, AsyncProtocolRequest asyncRequest)
   v System.Net.Security.SslState.StartReceiveBlob(Byte[] buffer, AsyncProtocolRequest asyncRequest)
   v System.Net.Security.SslState.CheckCompletionBeforeNextReceive(ProtocolToken message, AsyncProtocolRequest asyncRequest)
   v System.Net.Security.SslState.ForceAuthentication(Boolean receiveFirst, Byte[] buffer, AsyncProtocolRequest asyncRequest, Boolean renegotiation)
   v System.Net.Security.SslState.ProcessAuthentication(LazyAsyncResult lazyResult)
   v System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   v System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   v System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
   v System.Net.TlsStream.ProcessAuthentication(LazyAsyncResult result)
   v System.Net.TlsStream.Write(Byte[] buffer, Int32 offset, Int32 size)
   v System.Net.PooledStream.Write(Byte[] buffer, Int32 offset, Int32 size)
   v System.Net.ConnectStream.WriteHeaders(Boolean async)
   --- 内部异常堆栈跟踪结束 ---
   v System.Net.HttpWebRequest.GetRequestStream(TransportContext& context)
   v System.Net.HttpWebRequest.GetRequestStream()
   v uuTS.FormCreateDataSource.BtnCreateDataSource_Click(Object sender, EventArgs e) v C:\Dropbox\projekty\uuTS\uuTS\FormCreateDataSource.cs:řádek 202
   v System.Windows.Forms.Control.OnClick(EventArgs e)
   v System.Windows.Forms.Button.OnClick(EventArgs e)
   v System.Windows.Forms.Button.OnMouseUp(MouseEventArgs mevent)
   v System.Windows.Forms.Control.WmMouseUp(Message& m, MouseButtons button, Int32 clicks)
   v System.Windows.Forms.Control.WndProc(Message& m)
   v System.Windows.Forms.ButtonBase.WndProc(Message& m)
   v System.Windows.Forms.Button.WndProc(Message& m)
   v System.Windows.Forms.NativeWindow.Callback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)

HttpClient尝试代码:

public bool LoadToken()
{
string data_str = @"{
    ""grant_type"": ""password"",
    ""username"": """" + this.Login + @"""",
    ""password"": """" + this.Password + @"""",
    ""scope"": ""openid "" + this.ApiUrl + @""""
}";
    ServicePointManager.ServerCertificateValidationCallback += (sender, cert, chain, sslPolicyErrors) => true;

    var handler = new HttpClientHandler();
    handler.ClientCertificateOptions = ClientCertificateOption.Manual;
    handler.ServerCertificateCustomValidationCallback = (httpRequestMessage, cert, cetChain, policyErrors) => { return true; };

    using (HttpClient client = new HttpClient(handler))
    {
        ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
        ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls;
        ServicePointManager.ServerCertificateValidationCallback += (sender, cert, chain, sslPolicyErrors) => true;
        

        {
            client.BaseAddress = new Uri(this.OidcUrl);
            HttpResponseMessage response = client.PostAsync(this.OidcUrl, new StringContent(data_str, Encoding.UTF8, "application/json")).Result;

            if (response.IsSuccessStatusCode)
            {
                // Parse the response body.
                string responseStr = response.Content.ReadAsStringAsync().Result;
                this.Token = JsonConvert.DeserializeObject<TokenJson>(responseStr);
                this.TokenValidUntil = DateTime.Now.AddSeconds(this.Token.expires_in - 30);
                return true;
            }
            else
            {
                Log.Error("Error " + ((int)response.StatusCode).ToString() + " (" + response.ReasonPhrase + ")");
                return false;
            }

        }
    }
}
解决方案
  • 全局提前设置证书验证回调
    在VSTO插件的启动入口(如ThisAddIn_Startup方法)中设置证书验证回调,避免Excel安全上下文阻止临时设置生效,同时指定兼容的TLS版本:

    private void ThisAddIn_Startup(object sender, System.EventArgs e)
    {
        // 全局跳过证书验证
        ServicePointManager.ServerCertificateValidationCallback += (sender, cert, chain, sslPolicyErrors) => true;
        // 启用主流TLS版本,避免协议不兼容
        ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls13;
    }
    
  • 修正HttpWebRequest冗余操作
    移除重复调用request.GetRequestStream()的代码,改用UTF-8编码避免字符乱码:

    private void BtnCreateDataSource_Click(object sender, EventArgs e)
    {
        string data_str = @"{
            ""grant_type"": ""password"",
            ""username"": ""xxx"",
            ""password"": ""xxx"",
            ""scope"": ""openid https://xxxx""
        }";
        var data = Encoding.UTF8.GetBytes(data_str);
        HttpWebRequest request = (HttpWebRequest)WebRequest.Create("https://xxx/oidc/grantToken");
    
        request.Method = "POST";
        request.ContentType = "application/json";
        request.ContentLength = data.Length;
    
        // 仅调用一次GetRequestStream
        using (var stream = request.GetRequestStream())
        {
            stream.Write(data, 0, data.Length);
        }
    
        // 用using包裹资源,自动释放
        using (var response = (HttpWebResponse)request.GetResponse())
        using (var reader = new StreamReader(response.GetResponseStream()))
        {
            var responseString = reader.ReadToEnd();
            MessageBox.Show(responseString);
        }
    }
    
  • 优化HttpClient代码逻辑
    移除重复的ServicePointManager设置,修正BaseAddress与请求URL的冲突问题:

    public bool LoadToken()
    {
        // 用字符串插值简化JSON构造
        string data_str = $@"{{
            ""grant_type"": ""password"",
            ""username"": ""{this.Login}"",
            ""password"": ""{this.Password}"",
            ""scope"": ""openid {this.ApiUrl}""
        }}";
    
        var handler = new HttpClientHandler();
        // 明确跳过证书验证
        handler.ServerCertificateCustomValidationCallback = (_, cert, _, _) => true;
    
        using (HttpClient client = new HttpClient(handler))
        {
            // 直接传入完整端点URL,无需设置BaseAddress
            HttpResponseMessage response = client.PostAsync(this.OidcUrl, 
                new StringContent(data_str, Encoding.UTF8, "application/json")).Result;
    
            if (response.IsSuccessStatusCode)
            {
                string responseStr = response.Content.ReadAsStringAsync().Result;
                this.Token = JsonConvert.DeserializeObject<TokenJson>(responseStr);
                this.TokenValidUntil = DateTime.Now.AddSeconds(this.Token.expires_in - 30);
                return true;
            }
            else
            {
                Log.Error($"Error {(int)response.StatusCode} ({response.ReasonPhrase})");
                return false;
            }
        }
    }
    
  • 检查Excel安全策略
    确保Excel允许插件的网络访问权限:

    1. 打开Excel,依次点击「文件」->「选项」->「信任中心」->「信任中心设置」
    2. 进入「宏设置」,确认插件处于信任状态(选择「启用所有宏」或「启用签署的宏」且插件已签名)
    3. 进入「外部内容」,设置允许来自所有位置的外部数据访问

内容的提问来源于stack exchange,提问作者PEV Prague

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.20 15:51:34