CefSharp custom SchemeHandler
Asked Answered
H

2

5

Iam using CefSharp's SchemeHandler in order to grab resources from my C# project like .css, .js or .png files using a custom url for example custom://cefsharp/assets/css/style.css

I've 2 custom classes in order to archive this.

First class, MyCustomSchemeHandlerFactory will be the one that handles the custom Scheme and it looks like this, where "custom" will be the custom scheme:

internal class MyCustomSchemeHandlerFactory : ISchemeHandlerFactory
{
    public const string SchemeName = "custom";

    public IResourceHandler Create(IBrowser browser, IFrame frame, string schemeName, IRequest request)
    {
        return new MyCustomSchemeHandler();
    }
}

The next class I've implemented is MyCustomSchemeHandler which will receive the call and output a response and it looks like this:

internal class MyCustomSchemeHandler : IResourceHandler
{

    private static readonly IDictionary<string, string> ResourceDictionary;

    private string mimeType;
    private MemoryStream stream;

    static MyCustomSchemeHandler()
    {
        ResourceDictionary = new Dictionary<string, string>
        {
            { "/home.html", Properties.Resources.index},
            { "/assets/css/style.css", Properties.Resources.style}
        };
    }

    public Stream Stream { get; set; }
    public int StatusCode { get; set; }
    public string StatusText { get; set; }
    public string MimeType { get; set; }
    public NameValueCollection Headers { get; private set; }

    public Stream GetResponse(IResponse response, out long responseLength, out string redirectUrl)
    {
        redirectUrl = null;
        responseLength = -1;

        response.MimeType = MimeType;
        response.StatusCode = StatusCode;
        response.StatusText = StatusText;
        response.ResponseHeaders = Headers;

        var memoryStream = Stream as MemoryStream;
        if (memoryStream != null)
        {
            responseLength = memoryStream.Length;
        }

        return Stream;
    }

    public bool ProcessRequestAsync(IRequest request, ICallback callback)
    {
        // The 'host' portion is entirely ignored by this scheme handler.
        var uri = new Uri(request.Url);
        var fileName = uri.AbsolutePath;
        string resource;

        if (ResourceDictionary.TryGetValue(fileName, out resource) && !string.IsNullOrEmpty(resource))
        {
            var resourceHandler = ResourceHandler.FromString(resource);
            stream = (MemoryStream)resourceHandler.Stream;

            var fileExtension = Path.GetExtension(fileName);
            mimeType = ResourceHandler.GetMimeType(fileExtension);

            callback.Continue();
            return true;
        }
        else
        {
            callback.Dispose();
        }

        return false;
    }

    void GetResponseHeaders(IResponse response, out long responseLength, out string redirectUrl)
    {
        responseLength = stream == null ? 0 : stream.Length;
        redirectUrl = null;

        response.StatusCode = (int)HttpStatusCode.OK;
        response.StatusText = "OK";
        response.MimeType = mimeType;
    }

    bool ReadResponse(Stream dataOut, out int bytesRead, ICallback callback)
    {
        //Dispose the callback as it's an unmanaged resource, we don't need it in this case
        callback.Dispose();

        if (stream == null)
        {
            bytesRead = 0;
            return false;
        }

        //Data out represents an underlying buffer (typically 32kb in size).
        var buffer = new byte[dataOut.Length];
        bytesRead = stream.Read(buffer, 0, buffer.Length);

        dataOut.Write(buffer, 0, buffer.Length);

        return bytesRead > 0;
    }

    bool CanGetCookie(Cookie cookie)
    {
        return true;
    }

    bool CanSetCookie(Cookie cookie)
    {
        return true;
    }

    void Cancel()
    {

    }

}

Inside this class I've defined a custom resource dictionary which will dictate what file from the resources will be used, so as I stated in the first example, custom://cefsharp/assets/css/style.css should load the resource Properties.Resources.style, the problem is that nothing gets loaded once I enter to the specific url, I've tried to output the mimeType and It works but somehow the file itself won't output correctly. Is there something wrong with my implementation?

Additionaly I've tried to output the raw file in the form of:

if (ResourceDictionary.TryGetValue(fileName, out resource) && !string.IsNullOrEmpty(resource))
{
    MessageBox.Show(resource);
}

And it outputs the correct file without any problems.

To load the custom Scheme I use the following code before initializing CefSharp:

var settings = new CefSettings();
settings.RegisterScheme(new CefCustomScheme
{
    SchemeName = MyCustomSchemeHandlerFactory.SchemeName,
    SchemeHandlerFactory = new MyCustomSchemeHandlerFactory()
});

The above classes were based on the following links: MyCustomSchemeHandlerFactory: FlashResourceHandlerFactory.cs MyCustomSchemeHandler: CefSharpSchemeHandler.cs and ResourceHandler.cs

Hamlen answered 13/3, 2016 at 2:54 Comment(1)
Use the examples from the branch that matches your version, if your using 47.0.3 then use the cefsharp/47 branch, the ones from master won't work until 49.0.0 is released. github.com/cefsharp/CefSharp/tree/cefsharp/47Pelagia
P
4

If you simply need to return a string, then you can use ResourceHandler.FromString(html, mimeType). For this you just need to implement the ISchemeHandlerFactory.

https://github.com/cefsharp/CefSharp/blob/cefsharp/47/CefSharp/ResourceHandler.cs#L98

Example reading from a file https://github.com/cefsharp/CefSharp/blob/cefsharp/47/CefSharp.Example/CefSharpSchemeHandlerFactory.cs#L17 which can be translated to reading from a string quite simply.

Pelagia answered 13/3, 2016 at 9:0 Comment(0)
E
5

Since Cefsharp changed a bit in last few months here is an updated and easier way of handling 'file' protocol. I wrote blog post on this matter.

What you want to add is your scheme handler and its factory:

using System;
using System.IO;
using CefSharp;

namespace MyProject.CustomProtocol
{
    public class CustomProtocolSchemeHandler : ResourceHandler
    {
        // Specifies where you bundled app resides.
        // Basically path to your index.html
        private string frontendFolderPath;

        public CustomProtocolSchemeHandler()
        {
            frontendFolderPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "./bundle/");
        }

        // Process request and craft response.
        public override bool ProcessRequestAsync(IRequest request, ICallback callback)
        {
            var uri = new Uri(request.Url);
            var fileName = uri.AbsolutePath;

            var requestedFilePath = frontendFolderPath + fileName;

            if (File.Exists(requestedFilePath))
            {
                byte[] bytes = File.ReadAllBytes(requestedFilePath);
                Stream = new MemoryStream(bytes);

                var fileExtension = Path.GetExtension(fileName);
                MimeType = GetMimeType(fileExtension);

                callback.Continue();
                return true;
            }

            callback.Dispose();
            return false;
        }
    }

    public class CustomProtocolSchemeHandlerFactory : ISchemeHandlerFactory
    {
        public const string SchemeName = "customFileProtocol";

        public IResourceHandler Create(IBrowser browser, IFrame frame, string schemeName, IRequest request)
        {
            return new CustomProtocolSchemeHandler();
        }
    }
}

And then register it before calling Cef.Initialize:

var settings = new CefSettings
{
  BrowserSubprocessPath = GetCefExecutablePath()
};

settings.RegisterScheme(new CefCustomScheme
{
  SchemeName = CustomProtocolSchemeHandlerFactory.SchemeName,
  SchemeHandlerFactory = new CustomProtocolSchemeHandlerFactory()
});
Evenhanded answered 24/4, 2018 at 19:54 Comment(1)
I am getting this error: "'CustomProtocolSchemeHandler.ProcessRequestAsync(IRequest, ICallback)': return type must be 'CefReturnValue' to match overridden member 'ResourceHandler.ProcessRequestAsync(IRequest, ICallback)'"Drinker
P
4

If you simply need to return a string, then you can use ResourceHandler.FromString(html, mimeType). For this you just need to implement the ISchemeHandlerFactory.

https://github.com/cefsharp/CefSharp/blob/cefsharp/47/CefSharp/ResourceHandler.cs#L98

Example reading from a file https://github.com/cefsharp/CefSharp/blob/cefsharp/47/CefSharp.Example/CefSharpSchemeHandlerFactory.cs#L17 which can be translated to reading from a string quite simply.

Pelagia answered 13/3, 2016 at 9:0 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.