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

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

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

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

这是我的新代码,但我的浏览器对象没有ResourceHandler属性。 我看到它有一个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); } } } 

简单方法(一个“文件”,一页)

LoadString()可用于直接从字符串加载:

 ChromiumWebBrowser.LoadString(string html, string url); 

或者, LoadHtml()可以从给定编码中的字符串加载:

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

我试过这两个,他们似乎都工作,至少与CefSharp.Wpf v51.0.0 。 根据WebBrowserExtensions.cs , LoadHtml()使用RegisterHandler()来注册ResourceHandler 。 我不清楚LoadString()是如何工作的,但两个函数似乎都有相同的效果。

请务必使用伪URL的有效URL格式,例如:

 https://myfakeurl.com 

复杂方法(多个“文件”,例如doc + images)

  1. 创建一个派生自IResourceHandlerFactory的类。 使用VS2015,将鼠标hover在带红色下划线的名称上应该提供Implement接口选项。 这个自动完成选项极大地简化了类的创建,因此请务必使用它。

  2. 与步骤1类似,创建一个派生自IResourceHandler的类。 如果可以,请务必使用“ 实施”界面自动完成选项。

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

  4. 正如OP所提到的,浏览器控件有一个名为ResourceHandlerFactory的成员。 将此成员设置为您在步骤1中创建的类的实例(从IResourceHandlerFactory派生)。 这就是将Chromium Web Browser控件链接到接口类的原因。 在第3步中,您链接了两个类,因此我们有一个完整的链。

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

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

  7. 最后一步,再次在第2步中的类中,是在第三个被调用函数内完成请求: ReadResponse() 。 在此函数中,将步骤6中提取的数据写入dataOut流。 如果您的数据超过大约32kB,您可能需要以多个块的forms发送它。 绝对确保将给定调用中写入的数量限制dataOut流的长度。 将bytesRead设置为您在此特定调用中编写的任何内容。 在最后一次调用时,如果没有剩余数据,只需将bytesRead设置为零并返回false 。 由于您可能会多次调用给定文件,因此请务必跟踪当前的读取位置,以便了解自己的位置以及已发送的数据量。

对于那些不熟悉此事的人,可以将直接编译到EXE中的数据文件存储到项目中并将其“Build Action”设置为“Embedded Resource”,然后使用System.Reflection.Assembly.GetManifestResourceStream()编程方式加载数据。 System.Reflection.Assembly.GetManifestResourceStream() 。 使用上述方法, 无需从磁盘创建或读取任何文件

有关为内存中字符串注册ResourceHandler的示例,请参阅https://github.com/cefsharp/CefSharp/blob/v39.0.0-pre02/CefSharp.Example/CefExample.cs#L44 。

正如您所看到的,它仍然具有URL(Web资源通常具有该URL),但它可以是您选择的虚拟URL。

以下是GitHub在WinForms(和WPF)示例应用程序中搜索它的方式: https : //github.com/cefsharp/CefSharp/search? utf8 =%E2%9C%93&q = RegisterTestResources

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

更新 以下评论:

您现在使用的CefSharp版本是什么? 请注意,如果您查看github.com/cefsharp/CefSharp/releases并搜索resource您会看到版本49中更改的API(查看该版本的更改) – 请参阅下面的评论以获取更多信息

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

 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(); } } } 

然后在调用 Cef.Initialize 之前注册它:

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