Working with locally built web page in CefSharp

user461051 picture user461051 · Feb 24, 2015 · Viewed 22.2k times · Source

I have a CefSharp browser created in my Winform and I need to dynamically build an HTML page in memory and then have CefSharp render it.

Ideally I would like to pass the constructor a string with the HTML in it but it is expecting a URL. The answer is probably no, but is there a directive you can prepend the string with to let CefSharp know it is a string that contains a web page? Then CefSharp will create a temp file?

If not, where is the Chromium temp folder set to? Will it work if I write a file to there and then pass that as a fully qualified path? I know Chrome will support something like file:///Users/dmacdonald/Documents/myFile.htm as a URL but not sure how to form a URL if using the temp structure.

Here is my new code but my browser object doesn't have a ResourceHandler property. I see it has a ResourceHandlerFactory

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using CefSharp.WinForms;
using CefSharp;


namespace DanCefWinForm
{
    public partial class Form1 : Form
    {
        public const string TestResourceUrl = "http://maps/resource/load";

        public Form1()
        {
            InitializeComponent();


        }

        private void Form1_Load(object sender, EventArgs e)
        {
            ChromiumWebBrowser browser = new ChromiumWebBrowser("http://maps/resource/load")
            {
                Dock = DockStyle.Fill,
            };

            var handler = browser.ResourceHandler;

           browser.Location = new Point(20, 20);
           browser.Size = new Size(100, 100);
            this.Controls.Add(browser);
        }
    }
}

Answer

Michael picture Michael · Sep 18, 2016

The Simple Approach (one "file", one page)

LoadString() can be used to load directly from a string:

ChromiumWebBrowser.LoadString(string html, string url);

Alternatively, LoadHtml() can load from a string in a given encoding:

ChromiumWebBrowser.LoadHtml(string html, string url, Encoding encoding);

I tried both, and they both seem to work, at least with CefSharp.Wpf v51.0.0. According to WebBrowserExtensions.cs, LoadHtml() uses RegisterHandler() to register a ResourceHandler. It is not clear to me how LoadString() works, but both functions seem to have the same effect.

Be sure to use a valid URL format for the fake URL, such as:

https://myfakeurl.com

The Complex Approach (multiple "files", such as doc + images)

  1. Create a class deriving from IResourceHandlerFactory. Using VS2015, mousing over the red-underlined name should give the option of Implement interface. This auto-complete option vastly simplifies creation of the class, so be sure to use it.

  2. Similar to in step 1, create a class deriving from IResourceHandler. Be sure to use the Implement interface auto-complete option if you can.

  3. In the class created in step 1 (derived from IResourceHandlerFactory), there is a function called GetResourceHandler(). Within this function, return a new instance of your derived class from step 2 (based on IResourceHandler). Using new here is essential since the Web browser may request multiple files simultaneously. Each IResourceHandler instance should handle one request from the browser (no worries, this is done for you).

  4. As mentioned by OP, the browser control has a member called ResourceHandlerFactory. Set this member equal to a new instance of your class you created in step 1 (deriving from IResourceHandlerFactory). This is what links the Chromium Web Browser control to your interface classes. In step 3 you linked both your classes, so we have a full chain.

  5. Within the class from step 2, there is a function called ProcessRequest(). This is the first function called when a request is made by a Web page. Your goal here is to record the requested URL and any POST data, then decide whether to allow the request, calling either callback.Continue() or callback.Cancel(). Return true to continue.

  6. Again in the class from step 2, there is a function called GetResponseHeaders(). This is the second function called. Your goal here is to check the URL, possibly fetching file data from wherever you store it (but not yet sending it), determine the response length (file or string size), and set an appropriate status code within the response object. Be sure to set all these variables so the request can proceed correctly.

  7. Your final step, again in the class from step 2, is to complete the request within the third called function: ReadResponse(). Within this function, write your data fetched in step 6 to the dataOut stream. If your data exceeds about 32kB, you may need to send it in multiple chunks. Be absolutely sure to limit the amount you write in a given call to the length of the dataOut stream. Set bytesRead to whatever you wrote in this particular call. On the last call, when no more data remains, simply set bytesRead to zero and return false. Because you may be called upon multiple times for a given file, be sure to track your current read location so you know where you are and how much data has been sent.

For those unfamiliar with the matter, you can store data files directly compiled into your EXE by adding them to your project and setting their "Build Action" to "Embedded Resource", followed by loading their data programmatically using System.Reflection.Assembly.GetManifestResourceStream(). Using the above methods, there is no need to create or read any files from disk.