在WinForms应用程序中使用WebView2控件执行带有Content-Type: multipart/form-data的HTTP POST请求,是实现文件上传、表单提交等功能的常见需求。本文将详细介绍如何在WinForms中使用WebView2控件执行multipart/form-data类型的POST请求,包括使用NavigateWithWebResourceRequest方法和通过JavaScript执行POST请求的两种主要方法。


目录

  1. 前提条件

  2. 方法一:使用 NavigateWithWebResourceRequest 执行multipart/form-data POST请求

  3. 方法二:通过JavaScript在WebView2中执行multipart/form-data POST请求

  4. 完整示例:在WinForms中集成WebView2并执行multipart/form-data POST请求

  5. 注意事项

  6. 常见问题及解决方法

  7. 参考资料

  8. 总结


1. 前提条件

在开始之前,请确保已经完成以下准备工作:

  • WebView2 控件已成功添加到 WinForms 项目中:确保WebView2控件已正确集成。

  • 安装WebView2运行时:确保系统中安装了WebView2运行时。

  • 项目目标框架:建议使用 .NET Framework 4.6.2 及以上,或 .NET Core/5+/6+。


2. 方法一:使用 NavigateWithWebResourceRequest 执行multipart/form-data POST请求

NavigateWithWebResourceRequest 方法允许构建一个完整的HTTP请求,包括请求方法(如POST)、请求头和请求正文,从而实现更灵活的导航和数据提交。对于multipart/form-data,需要正确设置边界和格式。

步骤概述

  1. 构建multipart/form-data的请求体,包括必要的边界分隔符。

  2. 设置请求头,包括Content-Type,并指定边界。

  3. 创建CoreWebView2WebResourceRequest对象,配置HTTP方法、URL、请求头和请求内容。

  4. 调用NavigateWithWebResourceRequest方法,传递构建的请求对象。

  5. 处理导航完成后的响应,如获取响应状态或内容(需要进一步实现)。

示例代码

以下示例展示如何在WinForms应用程序中使用WebView2控件执行一个multipart/form-data类型的POST请求,上传一个简单的文本字段和一个文件。

代码示例(C#)

using System;
using System.IO;
using System.Text;
using System.Text.Json;
using System.Windows.Forms;
using Microsoft.Web.WebView2.WinForms;
using Microsoft.Web.WebView2.Core;

namespace WebView2MultipartPostExample
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            InitializeAsync();
        }

        private async void InitializeAsync()
        {
            try
            {
                // 初始化 WebView2
                await webView21.EnsureCoreWebView2Async(null);

                // 可选:订阅导航完成事件
                webView21.CoreWebView2.NavigationCompleted += CoreWebView2_NavigationCompleted;

                // 导航到一个空白页面,准备执行POST请求
                webView21.CoreWebView2.Navigate("about:blank");
            }
            catch (Exception ex)
            {
                MessageBox.Show($"WebView2 初始化失败: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        private async void btnPost_Click(object sender, EventArgs e)
        {
            try
            {
                // 确保WebView2已初始化
                if (webView21.CoreWebView2 == null)
                {
                    MessageBox.Show("WebView2尚未初始化。请稍候再试。", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
                    return;
                }

                // 构建multipart/form-data内容
                string boundary = "----WebView2Boundary" + DateTime.Now.Ticks.ToString("x");
                StringBuilder sb = new StringBuilder();

                // 添加文本字段
                sb.AppendLine($"--{boundary}");
                sb.AppendLine("Content-Disposition: form-data; name="username"");
                sb.AppendLine();
                sb.AppendLine("张三");

                // 添加文件字段
                sb.AppendLine($"--{boundary}");
                sb.AppendLine("Content-Disposition: form-data; name="file"; filename="test.txt"");
                sb.AppendLine("Content-Type: text/plain");
                sb.AppendLine();
                sb.AppendLine("这是一个测试文件。");

                // 结束边界
                sb.AppendLine($"--{boundary}--");

                string multipartContent = sb.ToString();
                byte[] dataBytes = Encoding.UTF8.GetBytes(multipartContent);
                var stream = new MemoryStream(dataBytes);

                // 设置请求头
                string contentType = $"multipart/form-data; boundary={boundary}";

                // 创建请求
                var request = webView21.CoreWebView2.Environment.CreateWebResourceRequest(
                    "https://www.example.com/upload", // 替换为目标URL
                    "POST",
                    stream,
                    $"Content-Type: {contentType}");

                // 执行POST请求
                webView21.CoreWebView2.NavigateWithWebResourceRequest(request);
            }
            catch (Exception ex)
            {
                MessageBox.Show($"执行POST请求失败: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        private void CoreWebView2_NavigationCompleted(object sender, CoreWebView2NavigationCompletedEventArgs e)
        {
            if (e.IsSuccess)
            {
                MessageBox.Show("POST请求执行完成,导航成功。", "成功", MessageBoxButtons.OK, MessageBoxIcon.Information);
                // 这里可以进一步处理响应内容,需要使用WebResourceRequested事件来拦截响应
            }
            else
            {
                MessageBox.Show($"导航失败,错误代码: {e.WebErrorStatus}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }
    }
}

设计器文件(Form1.Designer.cs)

确保Form1.Designer.cs包含WebView2控件和一个按钮用于触发POST请求。

namespace WebView2MultipartPostExample
{
    partial class Form1
    {
        private Microsoft.Web.WebView2.WinForms.WebView2 webView21;
        private System.Windows.Forms.Button btnPost;

        /// <summary>
        /// 设计器支持所需的方法 - 不要修改
        /// 使用代码编辑器修改此方法的内容。
        /// </summary>
        private void InitializeComponent()
        {
            this.webView21 = new Microsoft.Web.WebView2.WinForms.WebView2();
            this.btnPost = new System.Windows.Forms.Button();
            ((System.ComponentModel.ISupportInitialize)(this.webView21)).BeginInit();
            this.SuspendLayout();
            // 
            // webView21
            // 
            this.webView21.CreationProperties = null;
            this.webView21.DefaultBackgroundColor = System.Drawing.Color.White;
            this.webView21.Location = new System.Drawing.Point(12, 12);
            this.webView21.Name = "webView21";
            this.webView21.Size = new System.Drawing.Size(776, 400);
            this.webView21.TabIndex = 0;
            this.webView21.ZoomFactor = 1D;
            // 
            // btnPost
            // 
            this.btnPost.Location = new System.Drawing.Point(12, 420);
            this.btnPost.Name = "btnPost";
            this.btnPost.Size = new System.Drawing.Size(100, 30);
            this.btnPost.TabIndex = 1;
            this.btnPost.Text = "执行POST";
            this.btnPost.UseVisualStyleBackColor = true;
            this.btnPost.Click += new System.EventHandler(this.btnPost_Click);
            // 
            // Form1
            // 
            this.ClientSize = new System.Drawing.Size(800, 462);
            this.Controls.Add(this.btnPost);
            this.Controls.Add(this.webView21);
            this.Name = "Form1";
            this.Text = "WebView2 执行multipart/form-data POST请求示例";
            ((System.ComponentModel.ISupportInitialize)(this.webView21)).EndInit();
            this.ResumeLayout(false);
        }
    }
}

代码解释

  1. 构建multipart/form-data内容

    string boundary = "----WebView2Boundary" + DateTime.Now.Ticks.ToString("x");
    StringBuilder sb = new StringBuilder();
    
    // 添加文本字段
    sb.AppendLine($"--{boundary}");
    sb.AppendLine("Content-Disposition: form-data; name="username"");
    sb.AppendLine();
    sb.AppendLine("张三");
    
    // 添加文件字段
    sb.AppendLine($"--{boundary}");
    sb.AppendLine("Content-Disposition: form-data; name="file"; filename="test.txt"");
    sb.AppendLine("Content-Type: text/plain");
    sb.AppendLine();
    sb.AppendLine("这是一个测试文件。");
    
    // 结束边界
    sb.AppendLine($"--{boundary}--");
    
    string multipartContent = sb.ToString();
    byte[] dataBytes = Encoding.UTF8.GetBytes(multipartContent);
    var stream = new MemoryStream(dataBytes);
    
    • 边界字符串boundary用于分隔不同的表单部分。需要在Content-Type中指定。

    • 文本字段:如username

    • 文件字段:如file,包括文件名和内容类型。

    • 结束边界:标识multipart内容的结束。

  2. 设置请求头

    string contentType = $"multipart/form-data; boundary={boundary}";
    
    • Content-Type:必须包含boundary,用于解析不同的表单部分。

  3. 创建并执行POST请求

    var request = webView21.CoreWebView2.Environment.CreateWebResourceRequest(
        "https://www.example.com/upload", // 替换为目标URL
        "POST",
        stream,
        $"Content-Type: {contentType}");
    
    webView21.CoreWebView2.NavigateWithWebResourceRequest(request);
    
    • 目标URL:替换为需要发送POST请求的API端点。

    • HTTP方法:设置为POST

    • 请求体:传递构建的multipart/form-data内容。

    • 请求头:设置Content-Type

  4. 处理导航完成事件

    private void CoreWebView2_NavigationCompleted(object sender, CoreWebView2NavigationCompletedEventArgs e)
    {
        if (e.IsSuccess)
        {
            MessageBox.Show("POST请求执行完成,导航成功。", "成功", MessageBoxButtons.OK, MessageBoxIcon.Information);
            // 这里可以进一步处理响应内容,需要使用WebResourceRequested事件来拦截响应
        }
        else
        {
            MessageBox.Show($"导航失败,错误代码: {e.WebErrorStatus}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
        }
    }
    
    • 导航成功:表示POST请求已发送并接收到响应。

    • 获取响应内容:需要使用WebResourceRequested事件拦截和读取响应内容(需进一步实现)。

处理响应内容

NavigateWithWebResourceRequest 方法本身不直接提供响应内容。要获取响应内容,需要使用WebResourceRequested事件拦截响应。以下是如何实现这一功能的示例:

private async void CoreWebView2_WebResourceRequested(object sender, CoreWebView2WebResourceRequestedEventArgs e)
{
    if (e.Request.Uri == "https://www.example.com/upload" && e.Request.Method == "POST")
    {
        var response = e.Response;
        if (response != null)
        {
            // 读取响应内容
            using (var stream = response.Content)
            using (var reader = new StreamReader(stream))
            {
                string responseBody = await reader.ReadToEndAsync();
                MessageBox.Show($"响应内容: {responseBody}", "响应", MessageBoxButtons.OK, MessageBoxIcon.Information);
            }
        }
    }
}
  • 订阅WebResourceRequested事件

    webView21.CoreWebView2.WebResourceRequested += CoreWebView2_WebResourceRequested;
    
  • 实现事件处理程序

    • 检查请求的URL和方法。

    • 读取响应内容并进行处理。


3. 方法二:通过JavaScript在WebView2中执行multipart/form-data POST请求

另一种方法是在WebView2控件中通过JavaScript(如使用Fetch API或XMLHttpRequest)执行multipart/form-data类型的POST请求。这种方法适用于需要在网页上下文中执行复杂交互或获取响应内容的场景。

步骤概述

  1. 在WebView2中注入并执行JavaScript代码,使用Fetch API发送multipart/form-data POST请求。

  2. 通过WebView2的WebMessageReceived事件接收从网页传回的响应数据。

  3. 处理接收到的数据,如显示在WinForms应用中或进行其他操作。

示例代码

以下示例展示如何在WinForms应用程序中使用WebView2控件,通过JavaScript执行multipart/form-data POST请求,并将响应结果传回应用程序。

代码示例(C#)

using System;
using System.Collections.Generic;
using System.Text.Json;
using System.Windows.Forms;
using Microsoft.Web.WebView2.WinForms;
using Microsoft.Web.WebView2.Core;

namespace WebView2MultipartPostExample
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            InitializeAsync();
        }

        private async void InitializeAsync()
        {
            try
            {
                // 初始化 WebView2
                await webView21.EnsureCoreWebView2Async(null);

                // 订阅 WebMessageReceived 事件
                webView21.CoreWebView2.WebMessageReceived += CoreWebView2_WebMessageReceived;

                // 导航到一个空白页面,准备执行JavaScript
                webView21.CoreWebView2.NavigateToString("<html><body></body></html>");
            }
            catch (Exception ex)
            {
                MessageBox.Show($"WebView2 初始化失败: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        private async void btnPost_Click(object sender, EventArgs e)
        {
            try
            {
                // 确保WebView2已初始化
                if (webView21.CoreWebView2 == null)
                {
                    MessageBox.Show("WebView2尚未初始化。请稍候再试。", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
                    return;
                }

                // 构建JavaScript代码,使用Fetch API发送multipart/form-data POST请求
                string script = @"
                    (async () => {
                        try {
                            const formData = new FormData();
                            formData.append('username', '张三');
                            formData.append('file', new Blob(['这是一个测试文件。'], { type: 'text/plain' }), 'test.txt');

                            const response = await fetch('https://www.example.com/api', { // 替换为目标URL
                                method: 'POST',
                                body: formData
                            });

                            const result = await response.text(); // 根据需要选择解析方式
                            window.chrome.webview.postMessage(JSON.stringify({ success: true, data: result }));
                        } catch (error) {
                            window.chrome.webview.postMessage(JSON.stringify({ success: false, error: error.message }));
                        }
                    })();
                ";

                // 执行JavaScript代码
                await webView21.CoreWebView2.ExecuteScriptAsync(script);
            }
            catch (Exception ex)
            {
                MessageBox.Show($"执行POST请求失败: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        private void CoreWebView2_WebMessageReceived(object sender, CoreWebView2WebMessageReceivedEventArgs e)
        {
            try
            {
                string message = e.TryGetWebMessageAsString();
                var response = JsonSerializer.Deserialize<Dictionary<string, object>>(message);

                if (response.ContainsKey("success") && (bool)response["success"])
                {
                    string data = response["data"].ToString();
                    textBoxHtml.Text = data;
                    MessageBox.Show("成功获取POST响应!", "成功", MessageBoxButtons.OK, MessageBoxIcon.Information);
                }
                else if (response.ContainsKey("error"))
                {
                    string error = response["error"].ToString();
                    MessageBox.Show($"POST请求错误: {error}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show($"处理响应失败: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }
    }
}

设计器文件(Form1.Designer.cs)

确保Form1.Designer.cs包含WebView2控件、一个按钮用于触发POST请求和一个文本框用于显示响应内容。

namespace WebView2MultipartPostExample
{
    partial class Form1
    {
        private Microsoft.Web.WebView2.WinForms.WebView2 webView21;
        private System.Windows.Forms.Button btnPost;
        private System.Windows.Forms.TextBox textBoxHtml;

        /// <summary>
        /// 设计器支持所需的方法 - 不要修改
        /// 使用代码编辑器修改此方法的内容。
        /// </summary>
        private void InitializeComponent()
        {
            this.webView21 = new Microsoft.Web.WebView2.WinForms.WebView2();
            this.btnPost = new System.Windows.Forms.Button();
            this.textBoxHtml = new System.Windows.Forms.TextBox();
            ((System.ComponentModel.ISupportInitialize)(this.webView21)).BeginInit();
            this.SuspendLayout();
            // 
            // webView21
            // 
            this.webView21.CreationProperties = null;
            this.webView21.DefaultBackgroundColor = System.Drawing.Color.White;
            this.webView21.Location = new System.Drawing.Point(12, 12);
            this.webView21.Name = "webView21";
            this.webView21.Size = new System.Drawing.Size(776, 300);
            this.webView21.TabIndex = 0;
            this.webView21.ZoomFactor = 1D;
            // 
            // btnPost
            // 
            this.btnPost.Location = new System.Drawing.Point(12, 318);
            this.btnPost.Name = "btnPost";
            this.btnPost.Size = new System.Drawing.Size(100, 30);
            this.btnPost.TabIndex = 1;
            this.btnPost.Text = "执行POST";
            this.btnPost.UseVisualStyleBackColor = true;
            this.btnPost.Click += new System.EventHandler(this.btnPost_Click);
            // 
            // textBoxHtml
            // 
            this.textBoxHtml.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 
                                                                            | System.Windows.Forms.AnchorStyles.Left) 
                                                                            | System.Windows.Forms.AnchorStyles.Right)));
            this.textBoxHtml.Location = new System.Drawing.Point(12, 354);
            this.textBoxHtml.Multiline = true;
            this.textBoxHtml.Name = "textBoxHtml";
            this.textBoxHtml.ScrollBars = System.Windows.Forms.ScrollBars.Vertical;
            this.textBoxHtml.Size = new System.Drawing.Size(776, 84);
            this.textBoxHtml.TabIndex = 2;
            // 
            // Form1
            // 
            this.ClientSize = new System.Drawing.Size(800, 450);
            this.Controls.Add(this.textBoxHtml);
            this.Controls.Add(this.btnPost);
            this.Controls.Add(this.webView21);
            this.Name = "Form1";
            this.Text = "WebView2 执行multipart/form-data POST请求示例";
            ((System.ComponentModel.ISupportInitialize)(this.webView21)).EndInit();
            this.ResumeLayout(false);
            this.PerformLayout();
        }
    }
}

代码解释

  1. 构建JavaScript代码

    (async () => {
        try {
            const formData = new FormData();
            formData.append('username', '张三');
            formData.append('file', new Blob(['这是一个测试文件。'], { type: 'text/plain' }), 'test.txt');
    
            const response = await fetch('https://www.example.com/api', { // 替换为目标URL
                method: 'POST',
                body: formData
            });
    
            const result = await response.text(); // 根据需要选择解析方式
            window.chrome.webview.postMessage(JSON.stringify({ success: true, data: result }));
        } catch (error) {
            window.chrome.webview.postMessage(JSON.stringify({ success: false, error: error.message }));
        }
    })();
    
    • FormData:构建包含文本字段和文件字段的表单数据。

    • Fetch API:发送POST请求,bodyformData

    • 处理响应:将响应结果通过postMessage发送回C#应用程序。

    • 错误处理:捕获错误并通过postMessage发送错误信息。

  2. 按钮点击事件处理

    private async void btnPost_Click(object sender, EventArgs e)
    {
        try
        {
            // 确保WebView2已初始化
            if (webView21.CoreWebView2 == null)
            {
                MessageBox.Show("WebView2尚未初始化。请稍候再试。", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
                return;
            }
    
            // 构建JavaScript代码,使用Fetch API发送multipart/form-data POST请求
            string script = @"
                (async () => {
                    try {
                        const formData = new FormData();
                        formData.append('username', '张三');
                        formData.append('file', new Blob(['这是一个测试文件。'], { type: 'text/plain' }), 'test.txt');
    
                        const response = await fetch('https://www.example.com/api', { // 替换为目标URL
                            method: 'POST',
                            body: formData
                        });
    
                        const result = await response.text(); // 根据需要选择解析方式
                        window.chrome.webview.postMessage(JSON.stringify({ success: true, data: result }));
                    } catch (error) {
                        window.chrome.webview.postMessage(JSON.stringify({ success: false, error: error.message }));
                    }
                })();
            ";
    
            // 执行JavaScript代码
            await webView21.CoreWebView2.ExecuteScriptAsync(script);
        }
        catch (Exception ex)
        {
            MessageBox.Show($"执行POST请求失败: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
        }
    }
    
    • 执行脚本:使用ExecuteScriptAsync方法在WebView2中执行JavaScript代码。

    • 安全性:确保仅向受信任的API端点发送请求,避免潜在的安全风险。

  3. 处理WebMessageReceived事件

    private void CoreWebView2_WebMessageReceived(object sender, CoreWebView2WebMessageReceivedEventArgs e)
    {
        try
        {
            string message = e.TryGetWebMessageAsString();
            var response = JsonSerializer.Deserialize<Dictionary<string, object>>(message);
    
            if (response.ContainsKey("success") && (bool)response["success"])
            {
                string data = response["data"].ToString();
                textBoxHtml.Text = data;
                MessageBox.Show("成功获取POST响应!", "成功", MessageBoxButtons.OK, MessageBoxIcon.Information);
            }
            else if (response.ContainsKey("error"))
            {
                string error = response["error"].ToString();
                MessageBox.Show($"POST请求错误: {error}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }
        catch (Exception ex)
        {
            MessageBox.Show($"处理响应失败: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
        }
    }
    
    • 接收消息:通过WebMessageReceived事件接收来自网页的消息。

    • 解析消息:将接收到的JSON字符串反序列化为字典对象。

    • 处理响应:根据响应内容显示消息或错误信息。

优点与缺点

优点

  • 灵活性高:可以在网页上下文中执行复杂的逻辑和交互。

  • 响应内容易于获取:通过postMessage直接获取响应内容,无需拦截网络请求。

缺点

  • 依赖JavaScript:需要在网页中执行JavaScript代码,可能受限于网页的安全策略(如CORS)。

  • 复杂性增加:需要在C#和JavaScript之间进行通信,增加了实现的复杂性。


4. 完整示例:在WinForms中集成WebView2并执行multipart/form-data POST请求

为了更清晰地展示如何在WinForms应用程序中集成WebView2控件并执行multipart/form-data POST请求,以下是一个完整的示例项目。该示例包括使用两种方法发送POST请求:通过NavigateWithWebResourceRequest和通过JavaScript。

步骤1:创建新的WinForms项目

  1. 打开Visual Studio

  2. 创建新项目

    • 选择 “Windows Forms 应用程序 (.NET Framework)”“.NET Core”,点击 “下一步”

    • 配置项目名称(例如 WebView2MultipartPostExample)和位置,点击 “创建”

步骤2:安装WebView2 NuGet包

  1. 打开NuGet包管理器

    • 右键点击项目,选择 “管理NuGet程序包…”

  2. 搜索并安装Microsoft.Web.WebView2

    • “浏览” 标签下,搜索 Microsoft.Web.WebView2

    • 选择 Microsoft.Web.WebView2 包(由Microsoft发布),点击 “安装”

    • 接受许可协议,等待安装完成。

步骤3:设计界面

  1. 打开设计器视图

    • 在解决方案资源管理器中,双击 Form1.cs,进入设计器视图。

  2. 添加WebView2控件

    • 如果WebView2控件已显示在工具箱中,直接拖放到窗体上。

    • 否则,手动添加:

      • 右键点击 “工具箱”,选择 “选择项…”

      • “.NET Framework 组件” 选项卡中,找到 Microsoft.Web.WebView2.WinForms.WebView2

      • 勾选该控件,点击 “确定”

    • 将WebView2控件调整为适合窗体的大小(例如填满顶部区域)。

  3. 添加按钮控件

    • 从工具箱中拖放两个 Button 控件到窗体上。

    • 设置按钮的属性:

      • Button1

        • NamebtnNavigatePost

        • Text执行NavigateWithWebResourceRequest POST

      • Button2

        • NamebtnJavaScriptPost

        • Text执行JavaScript POST

  4. 添加文本框控件

    • 从工具箱中拖放一个 TextBox 控件到窗体上。

    • 设置文本框的属性:

      • NametextBoxResponse

      • MultilineTrue

      • ScrollBarsVertical

      • AnchorTop, Bottom, Left, Right

    • 调整文本框的大小以适应显示响应内容。

步骤4:编写代码

Form1.cs

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Text.Json;
using System.Windows.Forms;
using Microsoft.Web.WebView2.WinForms;
using Microsoft.Web.WebView2.Core;

namespace WebView2MultipartPostExample
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            InitializeAsync();
        }

        private async void InitializeAsync()
        {
            try
            {
                // 初始化 WebView2
                await webView21.EnsureCoreWebView2Async(null);

                // 可选:订阅导航完成事件
                webView21.CoreWebView2.NavigationCompleted += CoreWebView2_NavigationCompleted;

                // 可选:订阅 WebResourceRequested 事件以拦截响应(仅用于方法一)
                webView21.CoreWebView2.WebResourceRequested += CoreWebView2_WebResourceRequested;

                // 订阅 WebMessageReceived 事件以接收JavaScript发送的消息(仅用于方法二)
                webView21.CoreWebView2.WebMessageReceived += CoreWebView2_WebMessageReceived;

                // 导航到一个空白页面,准备执行POST请求
                webView21.CoreWebView2.Navigate("about:blank");
            }
            catch (Exception ex)
            {
                MessageBox.Show($"WebView2 初始化失败: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        // 方法一:使用 NavigateWithWebResourceRequest 执行multipart/form-data POST请求
        private async void btnNavigatePost_Click(object sender, EventArgs e)
        {
            try
            {
                // 确保WebView2已初始化
                if (webView21.CoreWebView2 == null)
                {
                    MessageBox.Show("WebView2尚未初始化。请稍候再试。", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
                    return;
                }

                // 构建multipart/form-data内容
                string boundary = "----WebView2Boundary" + DateTime.Now.Ticks.ToString("x");
                StringBuilder sb = new StringBuilder();

                // 添加文本字段
                sb.AppendLine($"--{boundary}");
                sb.AppendLine("Content-Disposition: form-data; name="username"");
                sb.AppendLine();
                sb.AppendLine("张三");

                // 添加文件字段
                sb.AppendLine($"--{boundary}");
                sb.AppendLine("Content-Disposition: form-data; name="file"; filename="test.txt"");
                sb.AppendLine("Content-Type: text/plain");
                sb.AppendLine();
                sb.AppendLine("这是一个测试文件。");

                // 结束边界
                sb.AppendLine($"--{boundary}--");

                string multipartContent = sb.ToString();
                byte[] dataBytes = Encoding.UTF8.GetBytes(multipartContent);
                var stream = new MemoryStream(dataBytes);

                // 设置请求头
                string contentType = $"multipart/form-data; boundary={boundary}";

                // 创建请求
                var request = webView21.CoreWebView2.Environment.CreateWebResourceRequest(
                    "https://www.example.com/upload", // 替换为目标URL
                    "POST",
                    stream,
                    $"Content-Type: {contentType}");

                // 执行POST请求
                webView21.CoreWebView2.NavigateWithWebResourceRequest(request);
            }
            catch (Exception ex)
            {
                MessageBox.Show($"执行POST请求失败: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        // 方法二:通过JavaScript执行multipart/form-data POST请求
        private async void btnJavaScriptPost_Click(object sender, EventArgs e)
        {
            try
            {
                // 确保WebView2已初始化
                if (webView21.CoreWebView2 == null)
                {
                    MessageBox.Show("WebView2尚未初始化。请稍候再试。", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
                    return;
                }

                // 构建JavaScript代码,使用Fetch API发送multipart/form-data POST请求
                string script = @"
                    (async () => {
                        try {
                            const formData = new FormData();
                            formData.append('username', '张三');
                            formData.append('file', new Blob(['这是一个测试文件。'], { type: 'text/plain' }), 'test.txt');

                            const response = await fetch('https://www.example.com/api', { // 替换为目标URL
                                method: 'POST',
                                body: formData
                            });

                            if (!response.ok) {
                                throw new Error('网络响应不是OK');
                            }

                            const result = await response.text(); // 根据需要选择解析方式
                            window.chrome.webview.postMessage(JSON.stringify({ success: true, data: result }));
                        } catch (error) {
                            window.chrome.webview.postMessage(JSON.stringify({ success: false, error: error.message }));
                        }
                    })();
                ";

                // 执行JavaScript代码
                await webView21.CoreWebView2.ExecuteScriptAsync(script);
            }
            catch (Exception ex)
            {
                MessageBox.Show($"执行POST请求失败: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        // 处理方法一的导航完成事件
        private async void CoreWebView2_NavigationCompleted(object sender, CoreWebView2NavigationCompletedEventArgs e)
        {
            if (e.IsSuccess)
            {
                MessageBox.Show("方法一:POST请求执行完成,导航成功。", "成功", MessageBoxButtons.OK, MessageBoxIcon.Information);

                // 可选:获取响应内容(需使用拦截器)
                // 这里可以进一步实现响应内容的拦截和处理
            }
            else
            {
                MessageBox.Show($"方法一:导航失败,错误代码: {e.WebErrorStatus}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        // 拦截响应内容(方法一)
        private async void CoreWebView2_WebResourceRequested(object sender, CoreWebView2WebResourceRequestedEventArgs e)
        {
            if (e.Request.Uri == "https://www.example.com/upload" && e.Request.Method == "POST")
            {
                var response = e.Response;
                if (response != null)
                {
                    // 读取响应内容
                    using (var stream = response.Content)
                    using (var reader = new StreamReader(stream))
                    {
                        string responseBody = await reader.ReadToEndAsync();
                        MessageBox.Show($"方法一:响应内容: {responseBody}", "响应", MessageBoxButtons.OK, MessageBoxIcon.Information);
                    }
                }
            }
        }

        // 处理方法二的WebMessageReceived事件
        private void CoreWebView2_WebMessageReceived(object sender, CoreWebView2WebMessageReceivedEventArgs e)
        {
            try
            {
                string message = e.TryGetWebMessageAsString();
                var response = JsonSerializer.Deserialize<Dictionary<string, object>>(message);

                if (response.ContainsKey("success") && (bool)response["success"])
                {
                    string data = response["data"].ToString();
                    textBoxResponse.Text = data;
                    MessageBox.Show("方法二:成功获取POST响应!", "成功", MessageBoxButtons.OK, MessageBoxIcon.Information);
                }
                else if (response.ContainsKey("error"))
                {
                    string error = response["error"].ToString();
                    MessageBox.Show($"方法二:POST请求错误: {error}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show($"方法二:处理响应失败: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }
    }
}

Form1.Designer.cs

namespace WebView2MultipartPostExample
{
    partial class Form1
    {
        private Microsoft.Web.WebView2.WinForms.WebView2 webView21;
        private System.Windows.Forms.Button btnNavigatePost;
        private System.Windows.Forms.Button btnJavaScriptPost;
        private System.Windows.Forms.TextBox textBoxResponse;

        /// <summary>
        /// 设计器支持所需的方法 - 不要修改
        /// 使用代码编辑器修改此方法的内容。
        /// </summary>
        private void InitializeComponent()
        {
            this.webView21 = new Microsoft.Web.WebView2.WinForms.WebView2();
            this.btnNavigatePost = new System.Windows.Forms.Button();
            this.btnJavaScriptPost = new System.Windows.Forms.Button();
            this.textBoxResponse = new System.Windows.Forms.TextBox();
            ((System.ComponentModel.ISupportInitialize)(this.webView21)).BeginInit();
            this.SuspendLayout();
            // 
            // webView21
            // 
            this.webView21.CreationProperties = null;
            this.webView21.DefaultBackgroundColor = System.Drawing.Color.White;
            this.webView21.Location = new System.Drawing.Point(12, 12);
            this.webView21.Name = "webView21";
            this.webView21.Size = new System.Drawing.Size(776, 300);
            this.webView21.TabIndex = 0;
            this.webView21.ZoomFactor = 1D;
            // 
            // btnNavigatePost
            // 
            this.btnNavigatePost.Location = new System.Drawing.Point(12, 318);
            this.btnNavigatePost.Name = "btnNavigatePost";
            this.btnNavigatePost.Size = new System.Drawing.Size(200, 30);
            this.btnNavigatePost.TabIndex = 1;
            this.btnNavigatePost.Text = "执行NavigateWithWebResourceRequest POST";
            this.btnNavigatePost.UseVisualStyleBackColor = true;
            this.btnNavigatePost.Click += new System.EventHandler(this.btnNavigatePost_Click);
            // 
            // btnJavaScriptPost
            // 
            this.btnJavaScriptPost.Location = new System.Drawing.Point(218, 318);
            this.btnJavaScriptPost.Name = "btnJavaScriptPost";
            this.btnJavaScriptPost.Size = new System.Drawing.Size(200, 30);
            this.btnJavaScriptPost.TabIndex = 2;
            this.btnJavaScriptPost.Text = "执行JavaScript multipart/form-data POST";
            this.btnJavaScriptPost.UseVisualStyleBackColor = true;
            this.btnJavaScriptPost.Click += new System.EventHandler(this.btnJavaScriptPost_Click);
            // 
            // textBoxResponse
            // 
            this.textBoxResponse.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 
                                                                            | System.Windows.Forms.AnchorStyles.Left) 
                                                                            | System.Windows.Forms.AnchorStyles.Right)));
            this.textBoxResponse.Location = new System.Drawing.Point(12, 354);
            this.textBoxResponse.Multiline = true;
            this.textBoxResponse.Name = "textBoxResponse";
            this.textBoxResponse.ScrollBars = System.Windows.Forms.ScrollBars.Vertical;
            this.textBoxResponse.Size = new System.Drawing.Size(776, 84);
            this.textBoxResponse.TabIndex = 3;
            // 
            // Form1
            // 
            this.ClientSize = new System.Drawing.Size(800, 450);
            this.Controls.Add(this.textBoxResponse);
            this.Controls.Add(this.btnJavaScriptPost);
            this.Controls.Add(this.btnNavigatePost);
            this.Controls.Add(this.webView21);
            this.Name = "Form1";
            this.Text = "WebView2 执行multipart/form-data POST请求示例";
            ((System.ComponentModel.ISupportInitialize)(this.webView21)).EndInit();
            this.ResumeLayout(false);
            this.PerformLayout();
        }
    }
}

运行应用程序

  1. 按 F5 运行应用程序。

  2. 观察结果

    • WebView2控件会导航到指定的初始URL(如 about:blank)。

    • 点击 “执行NavigateWithWebResourceRequest POST” 按钮,将构建并执行一个multipart/form-data POST请求。

      • 成功后,将弹出消息框显示导航成功,并通过WebResourceRequested事件拦截并显示响应内容。

    • 点击 “执行JavaScript multipart/form-data POST” 按钮,将在WebView2中通过JavaScript执行一个multipart/form-data POST请求。

      • 成功后,将响应内容显示在下方的文本框中,并弹出消息框提示成功。


5. 注意事项

1. 跨域请求(CORS)

  • 问题:如果POST请求的目标URL与当前加载的页面不同域,可能会遇到CORS(跨域资源共享)限制,导致请求失败。

  • 解决方法

    • 确保目标服务器已正确设置CORS头,允许来自应用程序的请求。

    • 如果控制目标服务器,可以在服务器端添加必要的CORS配置。

    • 使用同域策略,或通过代理服务器中转请求。

2. 响应内容处理

  • 方法一NavigateWithWebResourceRequest 方法不直接提供响应内容。要获取响应内容,需要使用WebView2的拦截器功能。

    • 实现:使用WebResourceRequested事件拦截请求,并读取响应内容。

  • 方法二:通过JavaScript执行POST请求,可以更方便地获取响应内容,并通过postMessage将其传回WinForms应用程序。

3. 安全性

  • 信任来源:确保仅向受信任的API端点发送POST请求,避免潜在的安全风险。

  • 数据验证:在应用程序中验证和处理从网页或API接收的数据,防止潜在的注入攻击。

4. 错误处理

  • 捕获异常:在执行POST请求和处理响应时,确保捕获并处理可能出现的异常,提升应用程序的稳定性。

  • 反馈用户:在发生错误时,向用户提供明确的错误信息和可能的解决方案。

5. 性能优化

  • 避免频繁请求:合理安排POST请求的频率,避免过于频繁的请求导致性能下降或被目标服务器限制。

  • 资源管理:确保在不需要时释放WebView2控件的资源,避免内存泄漏。

6. 字符转义

  • 构建JavaScript代码时:确保JSON字符串中的特殊字符(如单引号)已正确转义,以避免JavaScript语法错误。

    • 使用@字符串字面量或转义字符来处理特殊字符。


6. 常见问题及解决方法

问题1:使用 NavigateWithWebResourceRequest 方法后,无法获取POST响应内容

原因

  • NavigateWithWebResourceRequest 方法用于导航,而非直接获取响应内容。要获取响应内容,需要使用WebView2的拦截器功能。

解决方法

  1. 使用WebResourceRequested事件拦截响应

    webView21.CoreWebView2.WebResourceRequested += CoreWebView2_WebResourceRequested;
    
  2. 实现事件处理程序以捕获响应内容

    private async void CoreWebView2_WebResourceRequested(object sender, CoreWebView2WebResourceRequestedEventArgs e)
    {
        if (e.Request.Uri == "https://www.example.com/upload" && e.Request.Method == "POST")
        {
            var response = e.Response;
            if (response != null)
            {
                using (var stream = response.Content)
                using (var reader = new StreamReader(stream))
                {
                    string responseBody = await reader.ReadToEndAsync();
                    MessageBox.Show($"响应内容: {responseBody}", "响应", MessageBoxButtons.OK, MessageBoxIcon.Information);
                }
            }
        }
    }
    

注意:读取响应内容需要处理流的异步读取和线程安全问题,建议使用异步方法。

问题2:通过JavaScript执行POST请求时,postMessage未接收响应

原因

  • JavaScript代码存在错误。

  • WebMessageReceived事件未正确订阅或处理。

  • CORS限制导致请求失败。

解决方法

  1. 检查JavaScript代码是否正确

    • 使用浏览器的开发者工具(通过WebView2的DevTools),检查控制台是否有错误。

  2. 确认WebMessageReceived事件已正确订阅

    webView21.CoreWebView2.WebMessageReceived += CoreWebView2_WebMessageReceived;
    
  3. 处理CORS问题

    • 确保目标服务器允许来自应用程序的跨域请求。

    • 检查网络响应,确认请求是否成功。

问题3:JSON反序列化失败或返回的HTML包含转义字符

原因

  • 返回的JSON格式不正确。

  • 未正确解析JSON字符串。

解决方法

  • 使用正确的JSON解析方法,例如:

    string htmlContent = JsonSerializer.Deserialize<string>(result);
    
  • 确保JavaScript返回的字符串已正确序列化:

    window.chrome.webview.postMessage(JSON.stringify(data));
    

问题4:WebView2控件报错“未能解析类型 Microsoft.Web.WebView2.WinForms.WebView2”

解决方法

  1. 确认NuGet包已正确安装

    • 在解决方案资源管理器中,展开 “依赖项” > “NuGet”,确保 Microsoft.Web.WebView2 包已列出。

  2. 检查项目目标框架

    • 在项目属性中,确保目标框架为 .NET Framework 4.6.2 及以上,或 .NET Core/5+/6+。

  3. 重启Visual Studio

    • 有时,安装NuGet包后需要重启Visual Studio才能识别新引用。

  4. 清理并重建解决方案

    • 选择 “生成” > “清理解決方案”,然后 “生成” > “生成解决方案”

  5. 手动添加引用

    • 在设计器视图中,确保WebView2控件已添加到工具箱并拖放到窗体上。


7. 参考资料


8. 总结

在WinForms应用程序中使用WebView2控件执行带有Content-Type: multipart/form-data的HTTP POST请求,可以通过两种主要方法实现:

  1. 方法一:使用NavigateWithWebResourceRequest方法

    • 构建完整的multipart/form-data请求体,包括边界分隔符。

    • 设置正确的Content-Type头。

    • 使用NavigateWithWebResourceRequest方法发送POST请求。

    • 使用WebResourceRequested事件拦截并处理响应内容。

  2. 方法二:通过JavaScript执行POST请求

    • 在WebView2中通过JavaScript(如Fetch API)构建并发送multipart/form-data POST请求。

    • 使用postMessage将响应数据传回C#应用程序。

    • 在C#应用程序中处理WebMessageReceived事件,获取并显示响应内容。

关键要点

  • 正确构建multipart/form-data请求:包括边界分隔符、表单字段和文件字段。

  • 设置正确的请求头:特别是Content-Type,确保包含边界信息。

  • 处理跨域请求:确保目标服务器允许跨域请求,避免CORS限制。

  • 异步编程:使用asyncawait确保不会阻塞UI线程,提升应用程序的响应性。

  • 错误处理:捕获并处理可能出现的异常,确保应用程序的稳定性。

  • 安全性:仅向受信任的API端点发送请求,避免潜在的安全风险。