在C#中为Webbrowser控件的所有请求添加自定义标头?

4

我正在使用 C# 和 .NET 3.5。如您所知,我们可以像这样向 Web 浏览器控件的 Navigate() 方法添加自定义标头:

var myUrl = "http://example.com/mypage.htm";
System.Uri uri = new Uri(myUrl);

byte[] authData = System.Text.UnicodeEncoding.UTF8.GetBytes("user:password");
string authHeader = 
    "Authorization: Basic " + Convert.ToBase64String(authData) + "\r\n" +
    "User-Agent: MyUserAgent\r\n";

webTDW8961nd.Navigate(uri, "", null, authHeader);

在上面的例子中,我们为单个导航设置了基本授权标头。
现在让我们来谈谈重定向。如果我们想执行 JavaScript 并重定向到另一个页面,基本授权标头将不会被包括在内。
你有什么解决方案?如何添加一个标头,使其适用于所有请求,而不仅仅是一次?
3个回答

5
问题在于,虽然WinForm和WPF的WebBrowser都只是相对较薄的包装器,用于控制ActiveX IE控件,但它们并没有向我们公开所有感兴趣的事件(第二个提供的比第一个还少)。解决这个问题有两种方法:第一种是子类化WF浏览器控件并添加所需内容,或者使用WPF控件并在那里添加钩子。我发现在WPF应用程序中采用第二种方法更加方便。

您只需要相关接口。最简单的方法是添加对Microsoft Internet Controls的引用(您可以在VS的COM标题下找到它)。这将打开一个名为SHDocVw的命名空间,其中包含我们所需的所有内容(如果出于任何原因,您想摆脱此依赖项,可以将所使用的P / Invoke接口简单地复制到自己的代码中)。

您可以使用反射获取底层浏览器。如果您过早调用它,它将返回null,因此我将其放入WebBrowser.Navigating处理程序中:

using SHDocVw;
var ActiveXInstance = (IWebBrowser2)Browser.GetType().InvokeMember("ActiveXInstance", BindingFlags.GetProperty | BindingFlags.Instance | BindingFlags.NonPublic, null, Browser, new object[] { });

一旦您获得它,您可以在浏览器中做很棒的事情。例如,您可以使用各种属性和方法,这些属性和方法不是直接公开的:

ActiveXInstance.Silent = true; // suppresses script error dialogs

并添加缺失的事件钩子:

var SetupEvents2 = (DWebBrowserEvents2_Event)ActiveXInstance;
SetupEvents2.BeforeNavigate2 += OnBeforeNavigate2;

有两种事件接口,2变体包含更新的事件。您可以在MSDN上查找所有这些信息。
回到标题: BeforeNavigate2事件允许您将额外的标头放入提供的对象中:
private void OnBeforeNavigate2(object pDisp, ref object URL, ref object Flags, ref object TargetFrameName, ref object PostData, ref object Headers, ref bool Cancel) {
  Headers = $"Accept-Language: XX;en\r\n";
}

这个精彩的答案让我找到了解决问题的方法。唯一的区别是修改头部对我没有起作用,头部从未传递到实际请求中。为了解决这个问题,我检查了URL和Headers,看看是否缺少我需要的特定头部,以便将其传递给我需要它传递的特定URL。如果 (!(Headers as string).Contains("X-MyHeader") && (URL as string).EndsWith("/UrlThatNeedsTheHeader")) 如果缺少头部,我使用Browser.Stop()来结束导航。然后再次调用Browser.Navigate并传入我的头部。 - GantTheWanderer

2
要为每个请求添加自定义标头,您可以实现扩展方法:

为每个请求添加自定义标头,您可以实现扩展方法:

public static class WebBrowserExtensions
{
    public static void NavigateWithAuthorization(this WebBrowser browser, Uri uri)
    {
        byte[] authData = System.Text.Encoding.UTF8.GetBytes("user:password");
        string authHeader = "Authorization: Basic " + Convert.ToBase64String(authData) + "\r\n" + "User-Agent: MyUserAgent\r\n";
        browser.Navigate(uri, "", null, authHeader);
    }
}

然后调用它而不是标准方法:

//browser.Navigate(uri, "", null, authHeader);
browser.NavigateWithAuthorization(uri);

第二个问题是关于重定向的。但您的情况在简单的浏览器和Fiddler中不起作用。这是Web协议的特性,当您重定向到另一个Uri时,您会发起新的请求并带有新的属性。您可以在js代码中组合您的请求。

它不应该是“WebBrowserExtensions:WebBrowser”吗?我该如何使用它? - asDca21
不应该这样做,因为我建议使用扩展方法 - 我添加了你需要的整个代码。1. 添加一个新类,并将答案中的代码复制进去。2. 在想要使用新方法的地方,添加使用新类命名空间的指令。3. 就像示例中那样调用方法。另请参阅https://msdn.microsoft.com/en-us/library/bb383977.aspx。 - Vadim Martynov
但是通常情况下(当您使用Chrome、Opera或IE时),您的浏览器不会生成带有授权的请求,而是使用cookie。为什么您不想使用它呢? - Vadim Martynov
O.o使用cookie作为基本授权?我已经嗅探了UI的数据包并发现每次浏览器请求页面或提交信息时,都会包含Authorization头。即使有cookies,Webbrowser是否会自动发送它们呢? - asDca21
如果您已经通过身份验证,那么可以在维基百科上阅读更多内容。由于每个HTTP请求的头部都必须发送BA字段,因此Web浏览器需要缓存凭据一段合理的时间,以避免不断提示用户输入用户名和密码。缓存策略因浏览器而异。 - Vadim Martynov
显示剩余7条评论

0

在处理 BeforeNavigate2 时,如果您想要包含其他标头,则需要取消当前导航事件,还需要停止浏览器。然后,您需要再次导航到传递其他标头的URL。

这是我处理基本身份验证的方法,可以使用guest作为用户名和密码进行测试,用于此URL:https://jigsaw.w3.org/HTTP/Basic/

string additionalHeaders;
private void Form1_Load(object sender, EventArgs e)
{
    byte[] authData = System.Text.Encoding.UTF8.GetBytes("guest:guest");
    additionalHeaders = $"Authorization: Basic {Convert.ToBase64String(authData)}\r\n";
    webBrowser1.Navigate("about:blank", null, null, additionalHeaders);
    var wbevents = (DWebBrowserEvents2_Event)webBrowser1.ActiveXInstance;
    wbevents.BeforeNavigate2 += Wbevents_BeforeNavigate2;
    webBrowser1.Navigate("https://jigsaw.w3.org/HTTP/Basic/", null, null, additionalHeaders);
}

private void Wbevents_BeforeNavigate2(object pDisp, ref object URL, ref object Flags,
    ref object TargetFrameName, ref object PostData, ref object Headers, ref bool Cancel)
{
    if (!$"{Headers}".Contains(additionalHeaders) &&
        TargetFrameName == null &&
        $"{URL}".ToLower().StartsWith("http"))
    {
        Cancel = true;
        ((IWebBrowser2)pDisp).Stop();
        object headers = additionalHeaders + $"{Headers}";
        object url = $"{URL}";
        object flags = null;
        object targetFrameName = $"{TargetFrameName}";
        ((IWebBrowser2)pDisp).Navigate2(ref url, ref flags, 
           ref targetFrameName, ref PostData, ref headers);
    }
}

注意 - 刷新页面不会触发导航事件

浏览器在刷新页面时存在一个特性/缺陷。当您刷新浏览器时,导航事件将不会触发,这意味着在刷新的情况下标题将不会被添加。解决方法是禁用快捷方式和上下文菜单:

this.webBrowser1.WebBrowserShortcutsEnabled = false;
this.webBrowser1.IsWebBrowserContextMenuEnabled = false;

对于那些想要设置用户代理的人来说,有一个更好的解决方案。您可以使用本帖底部分享的解决方案:Winform Webbrowser被识别为移动设备。即使您刷新浏览器,它也能很好地工作。 - Reza Aghaei

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接