Add a customized header to Webbrowser control for

2020-03-06 02:15发布

I'm using C# and .NET 3.5. As you know, we can add custom header to Navigate() on the web browser control like this:

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);

In the example above we set a Basic Authorization header for a single navigation.

Now let talk about redirection. If we want to execute javascript which will redirect to another page, the Basic Authorization header won't be included.

What is your solution? How can I add a header which works for all of the requests and not only once?

3条回答
对你真心纯属浪费
2楼-- · 2020-03-06 02:30

The problem is that while both WinForm's and WPF's WebBrowser are nothing else but a relatively thin wrapper around the ActiveX IE control, they don't expose all the events of interest to us (and the second provides even less than the first). There are two ways to solve this: first, to subclass the WF browser control and add what you need or to use the WPF one and add the hooks there. I found the second approach to be more convenient in a WPF application.

You only need the relevant interfaces. The easiest way is to add a reference to Microsoft Internet Controls (you'll find this under the COM heading is VS). This opens up a namespace called SHDocVw that contains all we need (if, for any reason, you want to get rid of this dependency, you can simply copy the P/Invoke interfaces used into your own code).

You can get the underlying browser using reflection. It will return null if you call it too early, so I put it into the WebBrowser.Navigating handler:

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

As soon as you have it, you can do nice things with the browser. For instance, you can use various properties and methods not exposed directly:

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

and add the missing event hooks:

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

There are two event interfaces, the 2 variant contains the newer events. You can look all this up on MSDN.

And, back to the headers: the BeforeNavigate2 event allows you to put your extra headers into the provided object:

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";
}
查看更多
beautiful°
3楼-- · 2020-03-06 02:34

To add custom header for each request you can implement extension method:

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);
    }
}

And then call it instead of standard method:

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

The second question is about redirecting. But your scenario will not work in a simple browser and fiddler. It is the feature of web protocol, when you redirect to another Uri you initiate new request with new properties. You can compose your request in js code.

查看更多
Viruses.
4楼-- · 2020-03-06 02:39

When handling BeforeNavigate2 if you want to include additional headers, you need to cancel the current navigating event, you also need to stop the browser. Then you need to navigate again to the URL passing additional headers.

Here is the way that I handled the basic authentication which can be tested by guest as username and password for this 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);
    }
}

Note - Navigating will not raise on Refresh

There is a feature/flaw in browser with refresh. When you refresh the browser, Navigating event will not raise, which means the header will not be added in case of refresh. A workaround for that is disabling shortcut and context menu:

this.webBrowser1.WebBrowserShortcutsEnabled = false;
this.webBrowser1.IsWebBrowserContextMenuEnabled = false;
查看更多
登录 后发表回答