SMUsamaShah
SMUsamaShah

Reputation: 7880

Set URL of custom HTML loaded in webbrowser

I have loaded some html text in WebBrowser control (its uri becomes "about:blank"). Now I want to set its Uri to something else without navigating to that link.

How can I do that?

Upvotes: 2

Views: 2166

Answers (2)

Reza Aghaei
Reza Aghaei

Reputation: 125197

There are two ways to set a URL for custom html content which you load into web browser control:

  1. Using <base> html tag
  2. By implementing IMoniker interface (Git Repo: [r-aghaei/WebBrowserHtmlContentBaseUrl]

Download

The first one is really simple and straight forward. For the second solution, I created a woring example. You can find it here:

Important Debug Note: To debug the application, disable NotImplementedException by going to Debug menu → Windows → Exception Settings → Search for System.NotImplementedException→ Clear the checkmark. Or if it throws exception, you can uncheck the exception in the exception window. If you press Ctrl+F5 you should not see any exception.

Notes

  • Using <base> tag is pretty easier and more straightforward, however I couldn't load relative address of @font-face with them. Rest of the things were fine.

  • The IMoniker solution needs adding a reference to SHDocVw which could be found as "Microsoft Internet Controls" in COM tab of Reference manager window.

Credits: Thanks to the author of this post, this post and Sheng Jiang for the other answer.

Solution 1 - Using <base> tag

The HTML <base> element specifies the base URL to use for all relative URLs contained within a document. There can be only one element in a document.

So it's enough to inject a <base> tag into <head> like this:

html = html.Replace(@"<head>", $@"<head><base href=""{new Uri(Application.StartupPath)}/""/>");
webBrowser1.DocumentText = html;

It means all the relative addresses will be resolved using href attribute of <base>.

Solution 2 - Implementing IMoniker

You can implement IMoniker interface and providing non-trivial implementation just for GetDisplayName and BindToStorage. Then using IWebBrowser2 from SHDocVw you can load the document and set a base url for that.

Her I've created an extension method for WebBrowser control having the following method:

  • void SetHtmlContent(string html, string baseUrl)

With these parameters:

  • html: HTML content to load in web browser
  • baseUrl: Base url to use for the document. It will be used like a real Url for resolving relative addresses.

You can easily use it like this:

webBrowser1.SetHtmlContent(html, $@"{new Uri(Application.StartupPath)}/");

Just add a reference to SHDocVw and use the paste the following code in your project:

using System;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using SHDocVw;
public static class WebBrowserExtensions
{
    public static void SetHtmlContent(this System.Windows.Forms.WebBrowser webBrowser, string html, string baseUrl)
    {
        webBrowser.Navigate("about:blank");
        var browser = (IWebBrowser2)webBrowser.ActiveXInstance;
        var result = CreateStreamOnHGlobal(Marshal.StringToHGlobalAuto(html), true, out IStream stream);
        if ((result != 0) || (stream == null))
            return;
        var persistentMoniker = browser.Document as IPersistMoniker;
        if (persistentMoniker == null)
            return;
        IBindCtx bindContext = null;
        CreateBindCtx((uint)0, out bindContext);
        if (bindContext == null)
            return;
        var loader = new Moniker(baseUrl, html);
        persistentMoniker.Load(1, loader, bindContext, (uint)0);
        stream = null;
    }

    [DllImport("ole32.dll", ExactSpelling = true, CharSet = CharSet.Auto)]
    static extern int CreateStreamOnHGlobal(IntPtr hGlobal, bool fDeleteOnRelease, out IStream istream);
    [DllImport("ole32.dll", ExactSpelling = true, CharSet = CharSet.Auto)]
    static extern int CreateBindCtx([MarshalAs(UnmanagedType.U4)] uint dwReserved, [Out, MarshalAs(UnmanagedType.Interface)] out IBindCtx ppbc);

    [ComImport, ComVisible(true)]
    [Guid("79EAC9C9-BAF9-11CE-8C82-00AA004BA90B")]
    [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)]
    interface IPersistMoniker
    {
        void GetClassID([In, Out] ref Guid pClassID);
        [return: MarshalAs(UnmanagedType.I4)]
        [PreserveSig]
        int IsDirty();
        void Load([In] int fFullyAvailable, [In, MarshalAs(UnmanagedType.Interface)] IMoniker pmk, [In, MarshalAs(UnmanagedType.Interface)] Object pbc, [In, MarshalAs(UnmanagedType.U4)] uint grfMode);
        void SaveCompleted([In, MarshalAs(UnmanagedType.Interface)] IMoniker pmk, [In, MarshalAs(UnmanagedType.Interface)] Object pbc);
        [return: MarshalAs(UnmanagedType.Interface)]
        IMoniker GetCurMoniker();
    }
    class Moniker : IMoniker
    {
        public static Guid IID_IStream = new Guid("0000000c-0000-0000-C000-000000000046");
        string baseUrl;
        IStream stream;
        public Moniker(string baseUrl, string content)
        {
            this.baseUrl = baseUrl;
            CreateStreamOnHGlobal(Marshal.StringToHGlobalAuto(content), true, out stream);
        }
        public void GetDisplayName(IBindCtx pbc, IMoniker pmkToLeft, out string ppszDisplayName)
        {
            ppszDisplayName = this.baseUrl;
        }
        public void BindToStorage(IBindCtx pbc, IMoniker pmkToLeft, ref Guid riid, out object ppvObj)
        {
            ppvObj = null;
            if (riid.Equals(IID_IStream))
                ppvObj = (IStream)stream; ;
        }
        public void GetClassID(out Guid pClassID)
        {
            throw new NotImplementedException();
        }
        public int IsDirty()
        {
            throw new NotImplementedException();
        }
        public void Load(IStream pStm)
        {
            throw new NotImplementedException();
        }
        public void Save(IStream pStm, bool fClearDirty)
        {
            throw new NotImplementedException();
        }
        public void GetSizeMax(out long pcbSize)
        {
            throw new NotImplementedException();
        }
        public void BindToObject(IBindCtx pbc, IMoniker pmkToLeft, ref Guid riidResult, out object ppvResult)
        {
            throw new NotImplementedException();
        }
        public void Reduce(IBindCtx pbc, int dwReduceHowFar, ref IMoniker ppmkToLeft, out IMoniker ppmkReduced)
        {
            throw new NotImplementedException();
        }
        public void ComposeWith(IMoniker pmkRight, bool fOnlyIfNotGeneric, out IMoniker ppmkComposite)
        {
            throw new NotImplementedException();
        }
        public void Enum(bool fForward, out IEnumMoniker ppenumMoniker)
        {
            throw new NotImplementedException();
        }
        public int IsEqual(IMoniker pmkOtherMoniker)
        {
            throw new NotImplementedException();
        }
        public void Hash(out int pdwHash)
        {
            throw new NotImplementedException();
        }
        public int IsRunning(IBindCtx pbc, IMoniker pmkToLeft, IMoniker pmkNewlyRunning)
        {
            throw new NotImplementedException();
        }
        public void GetTimeOfLastChange(IBindCtx pbc, IMoniker pmkToLeft, out System.Runtime.InteropServices.ComTypes.FILETIME pFileTime)
        {
            throw new NotImplementedException();
        }
        public void Inverse(out IMoniker ppmk)
        {
            throw new NotImplementedException();
        }
        public void CommonPrefixWith(IMoniker pmkOther, out IMoniker ppmkPrefix)
        {
            throw new NotImplementedException();
        }
        public void RelativePathTo(IMoniker pmkOther, out IMoniker ppmkRelPath)
        {
            throw new NotImplementedException();
        }
        public void ParseDisplayName(IBindCtx pbc, IMoniker pmkToLeft, string pszDisplayName, out int pchEaten, out IMoniker ppmkOut)
        {
            throw new NotImplementedException();
        }
        public int IsSystemMoniker(out int pdwMksys)
        {
            throw new NotImplementedException();
        }
    }
}

Upvotes: 2

Sheng Jiang 蒋晟
Sheng Jiang 蒋晟

Reputation: 15261

The webbrowser's document object loads data via a URL moniker. There is a pretty graph in the MSJ 1996 September issue article "Unified Browsing with ActiveX Extensions Brings the Internet to Your Desktop" demonstrating the the relationship about url monikers and the browser.

You can load a moniker or a stream into a document manually via the document's IPersistStreamInit interface. This is what Winform's webbrowser class is doing in its implementation of the DocumentStream and DocumentText properties. The document would call the source's IMoniker::GetDisplayName to get the url. However the load-from-stream implementation in Windows Forms does not implement IMoniker and the loaded document will have a base address of about:blank.

There is a sample on implementing a url moniker at http://www.codeproject.com/KB/miscctrl/csEXWB.aspx. Search LoadHtmlIntoBrowser(string html, string sBaseUrl) on the page.

Upvotes: 3

Related Questions