Use Embedded Resources in CefSharp

Applies to: CefSharp, C#

In my last post, Use Local Files in CefSharp, I showed you how to create a CefCustomScheme to pull web files directly from the file system. This post will follow along the same lines except this time I’ll show you how to pull web files directly from your assembly manifest (Embedded Resources). In practical terms, I’m using both. You just call the RegisterScheme method (see below) for each CefCustomScheme you want to use. This is especially helpful during development so that I can quickly tweak things in the file system and then when things are ready to include in my larger project I include everything in the project and embed them. This keeps my files from being browsable/editable outside of my application.

Intro

CefSharp is an open source project which provides Embedded Chromium for .NET (WPF & WinForms). It’s a great way to get Chrome hosted inside of your .NET application. Sometimes you want to load sites/files that aren’t hosted on the web. The easiest way to do that is to provide a custom scheme. You do this by creating an instance of CefSettings and using the RegisterScheme method with a CefCustomScheme wrapper object which requires an implementation of the ISchemeHandlerFactory which will in turn require an implementation of the ISchemeHandler. If you didn’t read my last post about doing this from the file system (or even if you did) you might be a little confused. Fortunately, it isn’t near as complicated as it seems.

Quick Note: This isn’t a tutorial on how to get CefSharp working or integrated into your product. You’ll want to install the packages using NuGet and go to the CefSharp project page for details.

ISchemeHandler

SchemeHandler objects process custom scheme-based requests asynchronously. In other words, you can provide a URL in the form customscheme://folder/yourfile.html”. The SchemeHandler object takes the request and gives you a chance to provide a custom response. In our case, we want to take anything with the scheme resource and pull it from our assembly’s manifest. Here’s what that looks like:

using CefSharp;
using System.IO;
using System.Reflection;
public class ResourceSchemeHandler : ISchemeHandler
{
    public bool ProcessRequestAsync(IRequest request, ISchemeHandlerResponse response, OnRequestCompletedHandler requestCompletedCallback)
    {
        Uri u = new Uri(request.Url);
        String file = u.Authority + u.AbsolutePath;

        Assembly ass = Assembly.GetExecutingAssembly();
        String resourcePath = ass.GetName().Name + "." + file.Replace("/", ".");

        if (ass.GetManifestResourceInfo(resourcePath) != null)
        {
            response.ResponseStream = ass.GetManifestResourceStream(resourcePath);
            switch (Path.GetExtension(file))
            {
                case ".html":
                    response.MimeType = "text/html";
                    break;
                case ".js":
                    response.MimeType = "text/javascript";
                    break;
                case ".png":
                    response.MimeType = "image/png";
                    break;
                case ".appcache":
                case ".manifest":
                    response.MimeType = "text/cache-manifest";
                    break;
                default:
                    response.MimeType = "application/octet-stream";
                    break;
            }
            requestCompletedCallback();
            return true;
        }
        return false;
    }
}

I’ve named my implementation ResourceSchemeHandler. You can see we are implementing the ISchemeHandler interface in line 4 (If this isn’t recognized be sure to include the using statements in lines 1-3 above). This interface only requires a single method, ProcessRequestAsync. Our job is to translate the request into a response stream, call requestCompletedCallback and indicate if we handled the request or not (return true or false).

The first thing we do is translate the request URL into a file path (lines 8-9). Embedded Resources are stored in the form of AppName.Namespace(s).File.Extension. So we need to translate our file path to our resource path. We do this in lines 11-12. First we grab the current assembly (ass, hehe) because we’re going to need it anyway. Next we build our resource path by slapping our AppName (ass.GetName()) followed by a period onto our file path where we replace all the forward slashes with periods.

This allows us to create a folder inside our project (which will get mapped to a namespace in C#) and put our files in there. So if we were to use the address “resource://web/index.html” the file path will be “web/index.html” and the resource path (if our application is named C2Player) will be “C2Player.web.index.html”.

If the resource doesn’t exist (no info returned), there isn’t any way for us to handle the request so we return false (line 39). Otherwise, we set the response.ResponseStream directly to the ManifestResourceStream (line 16). We then guess the Mime Type based on the file’s extension (lines 17-35). This is a pretty limited list but it was all I needed – Just expand as necessary. If you are using both this SchemeHandler and the LocalSchemeHandler created in my last post, you should refactor this to a common function.

Once we’ve got everything we need, we call the requestCompletedCallback and return true to indicate we have handled the request.

ISchemeHandlerFactory

SchemeHandlerFactory objects create the appropriate SchemeHandler objects. I’ve also added a convenience property to help out during registration. Here’s mine:

using CefSharp;
class ResourceSchemeHandlerFactory : ISchemeHandlerFactory
{
    public ISchemeHandler Create()
    {
        return new ResourceSchemeHandler();
    }

    public static string SchemeName { get { return "resource"; } }
}

This one is called ResourceSchemeHandlerFactory (wowzers!) and you can see we are implementing the ISchemeHandlerFactory interface in line 2 (If this isn’t recognized be sure to include the using statement in line 1). This interface also only requires a single method, Create. All we have to do is return a new instance of our custom SchemeHandler (line 6).

The SchemeName property in line 9 is just there to make things easier during registration.

Registering Your Custom Scheme

Now that we have our custom SchemeHandler and the corresponding Factory, how do we get the Chromium web browser controls to take advantage of them? This has to be done before things are initialized (before the controls are loaded). In WPF this can usually be done in your ViewModel’s constructor and in WinForms within the Form Load event. Here’s what it looks like:

CefSettings settings = new CefSettings();
settings.RegisterScheme(new CefCustomScheme()
    {
        SchemeName = ResourceSchemeHandlerFactory.SchemeName,
        SchemeHandlerFactory = new ResourceSchemeHandlerFactory()
    });
Cef.Initialize(settings);

You’ll create a new instance of a CefSettings object (line 1) and call the RegisterScheme method which takes a CefCustomScheme object (lines 2-6). the CefCustomScheme object needs to have its SchemeName set to whatever you are going to be using in the URLs to distinguish your custom scheme (we are using resource) and its SchemeHandlerFactory should be set to a new instance of your custom SchemeHandlerFactory. Then call the Cef.Intialize with the settings object and all the plumbing is hooked up. Again, if you want to use additional custom schemes (such as our LocalSchemeHandler) just add another settings.RegisterScheme call before the Cef.Initialize call.

Giving it a go

In my solution I have created a folder called web. Inside the folder is an html file (index.html) and an images folder with a single image (truck.png). Both the html file and the image have their Build Action set to Embedded Resource. Resource paths are case sensitive. For whatever reason, the request will always come back all lower case (regardless of your address casing) so you’ll need to ensure all your files and folders are lower case as well. Here’s what my test files look like in Solution Explorer:

EmbeddedResources

The html page is very simple:

<!DOCTYPE html>
<html>
<body>
<h1>My First Heading</h1>
<p>My first paragraph.</p>
<img src="images/truck.png"/>
</body>
</html>

Now if I set my ChromiumWebBrowser (WPF) or WebView (WinForms) Address property to resource://web/index.html I get the following:

LocalSchemeHandler

You can see the web page loaded both the HTML and the linked image from the assembly’s manifest. There’s even a couple of dummy buttons underneath the truck to show you that it’s just a standard WPF window.

Since I’m using WPF I also had the option of setting the Build Action to Resource and rewriting a small part of the SchemeHandler to pull from there. I chose Embedded Resource since it’s more widely applicable. Between these two posts hopefully you see that writing a CefCustomScheme is actually really straightforward. Have fun!

Advertisements

One thought on “Use Embedded Resources in CefSharp

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s