Forum Xamarin.Forms

How to add custom HTTP headers in WebView (all requests + css + js)?

dimikkdimikk RUMember ✭✭
edited April 2017 in Xamarin.Forms

Hello!
I already did this with custom WebViewRender and MyWebViewClient, but I think that it can be done better for iOS and Android.
I will appreciate any help. If this can be made easier with only the help of the PCL.
Thanks!

    public class WebViewRender : WebViewRenderer
    {
        protected override void OnElementChanged(ElementChangedEventArgs<WebView> e)
        {
            base.OnElementChanged(e);
            if (Control != null)
            {
                var webView = Control as Android.Webkit.WebView;
                Dictionary<string, string> headers = new Dictionary<string, string>
                {
                    ["Name"] = "value"
                };
                webView.SetWebViewClient(new MyWebViewClient());
                webView.LoadUrl(Control.Url, headers);
            }
        }
    }
    public class MyWebViewClient : WebViewClient
    {
        public override bool ShouldOverrideUrlLoading(Android.Webkit.WebView view, string url)
        {
            Dictionary<string, string> headers = new Dictionary<string, string>
            {
                ["Name"] = "value"
            };
            view.LoadUrl(url, headers);
            return true;
        }
    }

Posts

  • dimikkdimikk RUMember ✭✭

    No ideas? I will appreciate any help.

  • john82john82 ITMember ✭✭✭

    Hi,

    I've managed using a custom WebViewClient

        class XtrWebClient : Android.Webkit.WebViewClient
        {
            public override Android.Webkit.WebResourceResponse ShouldInterceptRequest(
                Android.Webkit.WebView view, Android.Webkit.IWebResourceRequest request)
            {
                // Is recursive request? (check for our custom headers)
                if (request.RequestHeaders.ContainsKey("Your Custom Header".ToLower()))
                    return null;
    
                // Add Additional headers
    
                var headers = new Dictionary<string, string>();
    
                foreach (var header in request.RequestHeaders)
                    headers.Add(header.Key, header.Value);
    
                headers.Add("Your Custom Header", "Your Custom Value");
    
                view.LoadUrl(request.Url.ToString(), headers);
    
                return new Android.Webkit.WebResourceResponse("", "", null);
            }
        }
    
  • No any suggestions?

  • buwghdiebuwghdie Member

    So I've spent quite a while trying to get this to work and I hope that my experiences will help others out.
    Getting it working on iOS is trivial, but for Android, not so much, if you need the headers in every request (including POST).

    iOS

    This is actually extremely simple to do on the iOS side of things (the Android devs could learn a thing or two). This includes the custom headers for every single GET and POST reqest (and I'd assume all other request types as well).

    [assembly: ExportRenderer(typeof(WebView), typeof(App.iOS.HybridWebViewRenderer))]
    namespace App.iOS
    {
        public class HybridWebViewRenderer : WebViewRenderer
        {
            protected override void OnElementChanged(VisualElementChangedEventArgs e)
            {
                base.OnElementChanged(e);
                // Set our delegeate.
                Delegate = new HybridUiWebViewDelegate();
            }
        }
        public class HybridUiWebViewDelegate : UIWebViewDelegate
        {
    
            public override bool ShouldStartLoad(UIWebView webView, NSUrlRequest request, UIWebViewNavigationType navigationType)
            {
    
                // Only add the header if it does not exist already.
                if (!request.Headers.ContainsKey(new NSString("Name")))
                {
                    var copy = request.MutableCopy() as NSMutableUrlRequest;
                    NSMutableDictionary dic = new NSMutableDictionary();
                    // Set up the dictionary
                    dic.Add(new NSString("Name"), new NSString("Value"));
                    // Provide the headers
                    copy.Headers = dic;
                    string currentUrl = request.Url.ToString();
                    // Retrigger the request
                    webView.LoadRequest(copy);
    
                    return false;
                }
    
                return true;
            }
        }
    }
    

    Android

    All I can say is that there's not straightforward solution, at least not one that I've found (and I've tried everything short of writing my own network driver). I've tried so many methods (ShouldOverrideUrlLoading, ShouldInterceptRequest, custom LoadUrl and PostUrl etc.) and none of them give a 100% solution. There is a lot of misinformation about this so I think some clarification is needed since I've spent two days on this without success.

    So here's what I've learned:

    If you only need the headers in the GET requests, that's trivial. Simply create an implementation of WebViewClient and override ShouldOverrideUrlLoading like this:

    [assembly: ExportRenderer(typeof(Xamarin.Forms.WebView), typeof(App.Android.HybridWebViewRenderer))]
    namespace App.Android
    {
        public class HybridWebViewRenderer : WebViewRenderer
        {
    
            public HybridWebViewRenderer(Context context) : base(context)
            {
            }
    
            protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.WebView> e)
            {
                base.OnElementChanged(e);
                Control.SetWebViewClient(new CustomWebViewClient());
            }
        }
        public class CustomWebViewClient : WebViewClient
        {
            public override bool ShouldOverrideUrlLoading(Android.Webkit.WebView view, string url)
            {
                Dictionary<string, string> headers = new Dictionary<string, string>
                {
                    ["Name"] = "value"
                };
                view.LoadUrl(url, headers);
                return true;
            }
        }
    }
    

    If, however, you need the headers in other requests (specifically POST requests) there really isn't a perfect solution. Many answers tell you to override ShouldInterceptRequest but this is unlikely to help. ShouldInterceptRequest provides an IWebResourceRequest which contains the URL of the request, the method (i.e. POST) and the headers. There are answers out there which state that adding the headers by doing request.Headers.Add("Name", "Value") is a viable solution but this is wrong. The IWebResourceRequest is not used by the WebView's internal logic so modifying it is useless!

    You can write your own HTTP client in ShouldInterceptRequest which includes your own headers to perform the requests and return a WebResourceResponse object. Again, this works for GET requests, but the problem with this is that even though we can intercept a POST request, we cannot determine the content in the request as the request content is not included in the IWebResourceRequest object. As a result, we cannot accurately perform the request manually. So, unless the content of the POST request is unimportant or can somehow be fetched, this method is not viable.

    An additional note on this method: returning null tells the WebView to handle the request for us. In other words 'I don't want to intercept the request'. If the return is not null however, the WebView will display whatever is in the WebResourceResponse object.

    I also tried overriding the PostUrl and LoadUrl methods in the WebView itself. These methods are not called by the internal logic, so unless you are calling them yourself, this does not work.

    So what can be done? There are a few hacky solutions (see github.com/KeejOow/android-post-webview) to get around this problem, but they rely on javascript and are not suitable in all cases (I have read that they don't work with forms). If you want to use them in Xamarin, you're going to need to adapt the code for C# anyway, and there is no guarantee that it will solve your problem.

    I'm writing this so no one else has to waste countless hours finding a solution that doesn't really exist.
    If only the Android devs had decided to include the POST content in the IWebResourceRequest object...
    And apologies for the length, if you've read to this point, you're probably as desperate as I was.

  • buwghdiebuwghdie Member

    If you only need the headers on every GET request, this can be easily done using the code below. However, if like me, you need to include the headers in every request (including POST requests), it's trivial on iOS but for some ridiculous reason it's not really possible on Android. I've tried to make this answer as informative as possible so that no one else has to waste countless hours trying to find a solution which doesn't exist.

    iOS

    This is actually extremely simple to do on the iOS side of things (the Android devs could learn a thing or two). This includes the custom headers for every single GET and POST request (and I'd assume all other request types as well).

    [assembly: ExportRenderer(typeof(WebView), typeof(App.iOS.HybridWebViewRenderer))]
    namespace App.iOS
    {
        public class HybridWebViewRenderer : WebViewRenderer
        {
            protected override void OnElementChanged(VisualElementChangedEventArgs e)
            {
                base.OnElementChanged(e);
                // Set our delegeate.
                Delegate = new HybridUiWebViewDelegate();
            }
        }
        public class HybridUiWebViewDelegate : UIWebViewDelegate
        {
    
            public override bool ShouldStartLoad(UIWebView webView, NSUrlRequest request, UIWebViewNavigationType navigationType)
            {
    
                // Only add the header if it does not exist already.
                if (!request.Headers.ContainsKey(new NSString("Name")))
                {
                    var copy = request.MutableCopy() as NSMutableUrlRequest;
                    NSMutableDictionary dic = new NSMutableDictionary();
                    // Set up the dictionary
                    dic.Add(new NSString("Name"), new NSString("Value"));
                    // Provide the headers
                    copy.Headers = dic;
                    string currentUrl = request.Url.ToString();
                    // Retrigger the request
                    webView.LoadRequest(copy);
    
                    return false;
                }
    
                return true;
            }
        }
    }
    

    Android

    All I can say is it's not straight forward.
    I've tried so many methods (ShouldOverrideUrlLoading, ShouldInterceptRequest, custom LoadUrl and PostUrl etc.) and none of them give a complete solution. There is a lot of misinformation about this so I think we need some clarification since I've spent two days on this without success.

    So here's what I've learned:

    If you only need the headers in the GET requests, that's trivial. Simply create an implementation of WebViewClient and override ShouldOverrideUrlLoading like this:

    [assembly: ExportRenderer(typeof(Xamarin.Forms.WebView), typeof(App.Android.HybridWebViewRenderer))]
    namespace App.Android
    {
        public class HybridWebViewRenderer : WebViewRenderer
        {
    
            public HybridWebViewRenderer(Context context) : base(context)
            {
            }
    
            protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.WebView> e)
            {
                base.OnElementChanged(e);
                Control.SetWebViewClient(new CustomWebViewClient());
            }
        }
        public class CustomWebViewClient : WebViewClient
        {
            public override bool ShouldOverrideUrlLoading(Android.Webkit.WebView view, string url)
            {
                Dictionary<string, string> headers = new Dictionary<string, string>
                {
                    ["Name"] = "value"
                };
                view.LoadUrl(url, headers);
                return true;
            }
        }
    }
    

    If, however, you need the headers in other requests (specifically POST requests) there really isn't a perfect solution. Many answers tell you to override ShouldInterceptRequest but this is unlikely to help. ShouldInterceptRequest provides an IWebResourceRequest which contains the URL of the request, the method (i.e. POST) and the headers. There are answers out there which state that adding the headers by doing request.Headers.Add("Name", "Value") is a viable solution but this is wrong. The IWebResourceRequest is not used by the WebView's internal logic so modifying it is useless!

    So the bottom line: you can write your own HTTP client inShouldInterceptRequest which includes your own headers to perform the requests and return a WebResourceResponse object. Again, this works for GET requests, but the problem with this is that even though we can intercept a POST request, we cannot determine the content in the request as the request content is not included in the IWebResourceRequest object. As a result, we cannot accurately perform the request manually. So, unless the content of the POST request is unimportant or can somehow be fetched, this method is not viable.

    An additional note on this method: returning null tells the WebView to handle the request for us. In other words 'I don't want to intercept the request'. If the return is not null however, the WebView will display whatever is in the WebResourceResponse object.

    I also tried overriding the PostUrl and LoadUrl methods in the WebView itself. These methods are not called by the internal logic, so unless you are calling them yourself, this does not work.

    So what can be done? There are a few hacky solutions (see github.com/KeejOow/android-post-webview) to get around this problem, but they rely on javascript and are not suitable in all cases (I have read that they don't work with forms). If you want to use them in Xamarin, you're going to need to adapt the code for C# anyway, and there is no guarantee that it will solve your problem.

    If only the Android devs had included the POST content in the IWebResourceRequest object...
    Apologies for the length, and if you've read this far then you're probably as desperate as I was.

Sign In or Register to comment.