C# 在CefSharp中使用本地构建的网页

C# 在CefSharp中使用本地构建的网页,c#,chromium-embedded,cefsharp,C#,Chromium Embedded,Cefsharp,我在Winform中创建了一个CefSharp浏览器,我需要在内存中动态构建一个HTML页面,然后让CefSharp渲染它 理想情况下,我希望向构造函数传递一个包含HTML的字符串,但它需要一个URL。答案可能是否定的,但是否有一个可以在字符串前面加上前缀的指令,让CefSharp知道它是一个包含网页的字符串?那你会创建一个临时文件吗 如果没有,Chromium temp文件夹设置在哪里?如果我在那里写入一个文件,然后将其作为完全限定路径传递,它会工作吗?我知道Chrome会支持类似的功能fil

我在Winform中创建了一个CefSharp浏览器,我需要在内存中动态构建一个HTML页面,然后让CefSharp渲染它

理想情况下,我希望向构造函数传递一个包含HTML的字符串,但它需要一个URL。答案可能是否定的,但是否有一个可以在字符串前面加上前缀的指令,让CefSharp知道它是一个包含网页的字符串?那你会创建一个临时文件吗

如果没有,Chromium temp文件夹设置在哪里?如果我在那里写入一个文件,然后将其作为完全限定路径传递,它会工作吗?我知道Chrome会支持类似的功能file:///Users/dmacdonald/Documents/myFile.htm 作为URL,但如果使用临时结构,则不确定如何形成URL

这是我的新代码,但我的浏览器对象没有ResourceHandler属性。我看到它有一个资源处理厂

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);
        }
    }
}
有关为内存中字符串注册
ResourceHandler
的示例,请参阅

正如您所看到的,它仍然有一个URL(web资源通常都有),但它可以是您选择的一个虚拟URL

下面是GitHub搜索,了解它在WinForms(和WPF)示例应用程序中的调用方式:

在本地文件系统中使用临时文件(任何地方?)的另一个可能不太有利的选项是使用

根据以下评论更新

你现在在看什么版本?注意:如果您查看github.com/cefsharp/cefsharp/releases并搜索
资源
,您会看到API在版本49中发生了更改(请查看该版本的突破性更改)-请参阅下面的评论,以了解更多的gotcha的

简单方法(一个“文件”,一页) LoadString()可用于直接从字符串加载:

ChromiumWebBrowser.LoadString(string html, string url);
或者,LoadHtml()可以从给定编码的字符串加载:

ChromiumWebBrowser.LoadHtml(string html, string url, Encoding encoding);
我尝试了这两种方法,它们似乎都有效,至少在CefSharp.wpfv51.0.0中是如此。根据,
LoadHtml()
使用
RegisterHandler()
注册
ResourceHandler
。我不清楚
LoadString()
是如何工作的,但这两个函数似乎具有相同的效果

请确保为假URL使用有效的URL格式,例如:

https://myfakeurl.com
复杂的方法(多个“文件”,如文档+图像)
  • 创建从
    IResourceHandlerFactory
    派生的类。使用VS2015,将鼠标悬停在带红色下划线的名称上,应该可以选择ImplementInterface。这个自动完成选项大大简化了类的创建,所以一定要使用它

  • 与步骤1类似,创建一个派生自
    IResourceHandler
    的类。如果可以,请确保使用机具接口自动完成选项

  • 在步骤1中创建的类(派生自
    IResourceHandlerFactory
    )中,有一个名为
    GetResourceHandler()
    的函数。在此函数中,从步骤2中返回派生类的新实例(基于
    IResourceHandler
    )。在此处使用
    new
    非常重要,因为Web浏览器可能会同时请求多个文件。每个
    IResourceHandler
    实例都应该处理来自浏览器的一个请求(不用担心,这是为您完成的)

  • 正如OP所提到的,浏览器控件有一个名为
    ResourceHandlerFactory
    的成员。将此成员设置为步骤1中创建的类的新实例(派生自
    IResourceHandlerFactory
    )。这是将Chromium Web浏览器控件链接到界面类的内容。在步骤3中,您链接了两个类,因此我们有一个完整的链

  • 在步骤2中的类中,有一个名为
    ProcessRequest()
    的函数。这是网页发出请求时调用的第一个函数。您的目标是记录请求的URL和任何POST数据,然后决定是否允许请求,调用
    callback.Continue()
    callback.Cancel()
    。返回true以继续

  • 同样在步骤2中的类中,有一个名为
    GetResponseHeaders()
    的函数。这是调用的第二个函数。这里的目标是检查URL,可能从存储它的任何位置获取文件数据(但尚未发送),确定响应长度(文件或字符串大小),并在响应对象内设置适当的状态代码。确保设置所有这些变量,以便请求能够正确进行

  • 最后一步,也是在步骤2中的类中,是在第三个调用的函数中完成请求:
    ReadResponse()
    。在此函数中,将步骤6中获取的数据写入
    dataOut
    流。如果您的数据超过约32kB,则可能需要将其分为多个数据块发送。绝对要确保将您在给定调用中写入的量限制为
    dataOut
    流的长度。将
    bytesRead
    设置为您在此特定调用中编写的内容。在最后一次调用中,当没有更多数据保留时,只需将
    bytesRead
    设置为零并返回
    false
    。由于给定文件可能会多次调用您,因此请确保跟踪您当前的读取位置,以便知道您所在的位置以及发送了多少数据


  • 对于那些不熟悉此问题的人,您可以通过将数据文件添加到项目中并将其“构建操作”设置为“嵌入式资源”,然后使用
    System.Reflection.Assembly.GetManifestResourceStream()
    以编程方式加载其数据,来存储直接编译到EXE中的数据文件。使用上述方法,无需从磁盘创建或读取任何文件

    以下是从文件系统加载资源的自定义工厂示例:

    public class FileResourceHandlerFactory : ISchemeHandlerFactory {
        private string scheme, host, folder, default_filename;
    
        public string Scheme => scheme;
    
        public FileResourceHandlerFactory(string scheme, string host, string folder, string default_filename = "index.html") {
            this.scheme = scheme;
            this.host = host;
            this.folder = folder;
            this.default_filename = default_filename;
        }
    
        private string get_content(Uri uri, out string extension) {
            var path = uri.LocalPath.Substring(1);
            path = string.IsNullOrWhiteSpace(path) ? this.default_filename : path;
            extension = Path.GetExtension(path);
            return File.ReadAllText(Path.Combine(this.folder, path));
        }
    
        IResourceHandler ISchemeHandlerFactory.Create(IBrowser browser, IFrame frame, string schemeName, IRequest request) {
            var uri = new Uri(request.Url);
            return ResourceHandler.FromString(get_content(uri, out var extension), extension);
        }
    }
    
    下面是您将如何应用它:

    var settings = new CefSettings();
    settings.RegisterScheme(new CefCustomScheme {
        SchemeName = "app",
        SchemeHandlerFactory = fileResourceHandlerFactory,
        IsSecure = true //treated with the same security rules as those applied to "https" URLs
    });
    var chromeBrowser = new ChromiumWebBrowser();
    chromeBrowser.Load("app://local");
    

    您可能需要使用自定义方案处理程序,以便为本地文件提供服务,并“绕过”有关文件的安全性
    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();
            }
        }
    }
    
    var settings = new CefSettings
    {
      BrowserSubprocessPath = GetCefExecutablePath()
    };
    
    settings.RegisterScheme(new CefCustomScheme
    {
      SchemeName = CustomProtocolSchemeHandlerFactory.SchemeName,
      SchemeHandlerFactory = new CustomProtocolSchemeHandlerFactory()
    });