从 CefSharp 迁移至 DotNetBrowser
本指南面向已有 CefSharp 代码、正在考虑迁移至 DotNetBrowser 的 .NET 开发者。
文中各节将针对 CefSharp 的具体功能特性,说明如何用 DotNetBrowser 进行替换。
指南提供了对 CefSharp 调用与事件的直接替代方案,指出 DotNetBrowser 与 CefSharp 之间的关键差异,并附上相关文档链接以供参考。
安装依赖项
将 DotNetBrowser 添加到项目的最简方式是通过 NuGet。只需安装适合您 UI framework 的包,其余工作 NuGet 会自动完成,包括下载核心库及所需的 Chromium 文件。
请务必从一开始就选择正确的配置——匹配架构(x86、x64 或 aarch64)与 UI 框架(如 WPF 或 Avalonia)。
所需软件包:
DotNetBrowser.Wpf.x64
— 适用于 Windows x64 的 WPF 集成包。- (传递引用)
DotNetBrowser.Win-x64
— 核心 API。 - (传递引用)
DotNetBrowser.Chromium.Win-x64
— 内置 Chromium。
您可在安装指南中查看所有支持的 UI framework 及平台的完整 NuGet 包列表。
若不使用 NuGet,也可手动添加:引用 DLL 文件 或 VSIX 包即可。
Chromium 引擎
初始化
在 CefSharp 中,您需要先初始化底层的 Chromium Embedded Framework(简称 CEF):
Cef.Initialize(new CefSettings());
在 WPF 和 WinForms 中,CefSharp 会自动调用此方法。但若您需要自定义配置,则需手动预先调用。
注意:此方法必须从主线程调用,且每个进程生命周期内仅允许调用一次。这些限制源于 CefSharp 的进程内架构,其将 Chromium 引擎运行在 .NET 进程中,导致每个进程仅能承载一个引擎(Engine)实例。
在 DotNetBrowser 中,您同样需要初始化 Chromium 引擎,但无上述限制:
IEngine engine = EngineFactory.Create(RenderingMode.HardwareAccelerated);
DotNetBrowser 采用进程外架构,在独立进程中启动 Chromium 引擎。因此,您可以在任何线程的任何时刻创建多个 Engine 实例。
为何需要多个引擎?详见本指南的浏览器隔离部分。
关闭
在 CefSharp 中,当不再需要 Chromium 引擎时,必须将其关闭,否则应用程序将挂起。
同样地,该方法必须在应用程序的主线程中调用;若使用 WPF 或 WinForms,CefSharp 会自动为你执行此操作。
在 DotNetBrowser 中,同样需要显式关闭 Chromium 引擎,但可以在任何线程的任何时刻进行。若未释放 IEngine
,只会造成资源泄漏,不会阻塞 .NET 进程:
Cef.Shutdown();
engine.Dispose();
嵌入浏览器
WPF
CefSharp 和 DotNetBrowser 均提供可直接嵌入 XAML 的 WPF 控件。
实际上,CefSharp 提供了两种不同的组件。如果您需要离屏渲染,可以使用 WPF
实现。如果您需要更快的渲染速度,可以使用 Wpf.HwndHost
实现,它会将 Chromium 窗口重新设置为您的应用程序的父级。
DotNetBrowser 提供了一个组件,它可以以两种模式渲染内容——大致相当于 CefSharp 的模式。这两种模式分别是 HardwareAccelerated
和 OffScreen
,您可以在创建 IEngine
时选择相应的模式。
MainWindow.xaml:
<Window xmlns:wpf="clr-namespace:CefSharp.Wpf;assembly=CefSharp.Wpf"
x:Class="CefSharp.Wpf.MainWindow"
Title="CefSharp" Width="800" Height="600">
<Grid>
<wpf:ChromiumWebBrowser x:Name="Browser"
Address="https://www.teamdev.com" />
</Grid>
</Window>
<Window xmlns:wpf="clr-namespace:DotNetBrowser.Wpf;assembly=DotNetBrowser.Wpf"
x:Class="DotNetBrowser.Wpf.MainWindow"
Title="DotNetBrowser" Width="800" Height="600" Closed="Window_Closed">
<Grid>
<wpf:BrowserView Name="browserView" />
</Grid>
</Window>
MainWindow.xaml.cs:
public partial class MainWindow : Window
{
static MainWindow()
{
CefSettings settings = new CefSettings();
Cef.Initialize(settings);
}
public MainWindow()
{
InitializeComponent();
}
}
public partial class MainWindow : Window
{
private readonly IEngine engine;
public MainWindow()
{
// 在此配置渲染模式。
engine = EngineFactory.Create(RenderingMode.HardwareAccelerated);
IBrowser browser = engine.CreateBrowser();
InitializeComponent();
browserView.InitializeFrom(browser);
browser.Navigation.LoadUrl("https://teamdev.com");
}
private void Window_Closed(object sender, EventArgs e)
{
engine?.Dispose();
}
}
请参阅 BrowserView
指南,了解更多关于渲染模式、其性能以及如何选择的信息。
WinForms
在 WinForms 中,CefSharp 始终使用重量级渲染,与 Wpf.Hwnd
中相同。
在 DotNetBrowser 中,WinForms 的 BrowserView
控件将使用您在创建 IEngine
时配置的渲染模式。
其余部分类似:
using CefSharp.WinForms;
...
public partial class BrowserForm : Form
{
private readonly ChromiumWebBrowser browser;
public BrowserForm()
{
InitializeComponent();
browser = new ChromiumWebBrowser("https://teamdev.com");
Controls.Add(browser);
}
}
using DotNetBrowser.WinForms;
...
public partial class BrowserForm : Form
{
private readonly IEngine engine;
public BrowserForm()
{
// 您可以在此处配置渲染模式。
engine = EngineFactory.Create(RenderingMode.HardwareAccelerated);
IBrowser browser = engine.CreateBrowser();
InitializeComponent();
BrowserView browserView = new BrowserView();
Controls.Add(browserView);
FormClosed += Form1_FormClosed;
browserView.InitializeFrom(browser);
browser.Navigation.LoadUrl("https://teamdev.com");
}
private void Form1_FormClosed(object sender, FormClosedEventArgs e)
{
engine?.Dispose();
}
}
无头模式
CefSharp 为无头使用场景提供了特殊的 OffScreen
实现,即 ChromiumWebBrowser
,此时浏览器不会显示在 UI 中。
在 DotNetBrowser 中,每个 IBrowser
实例都是一个功能齐全的浏览器,无需 UI 即可运行。
using CefSharp.OffScreen;
...
public static class Program
{
public static int Main(string[] args)
{
var settings = new CefSettings();
Cef.Initialize(settings);
var browser = new ChromiumWebBrowser("https://www.google.com/");
...
// 仅等待,以保持进程运行。
Console.ReadKey();
Cef.Shutdown();
return 0;
}
}
using DotNetBrowser.Browser;
using DotNetBrowser.Engine;
...
public static class Program
{
public static int Main(string[] args)
{
using (IEngine engine =
EngineFactory.Create(RenderingMode.HardwareAccelerated))
{
IBrowser browser = engine.CreateBrowser();
browser.Navigation
.LoadUrl("https://www.google.com/").Wait();
...
// 仅等待,以保持进程运行。
Console.ReadKey();
return 0;
}
}
}
释放浏览器
在 CefSharp 和 DotNetBrowser 中,都必须关闭 Chromium 引擎(Engine)和浏览器(Browser)实例,以防资源泄漏。关闭引擎会自动关闭由其创建的所有浏览器实例:
// 您必须仅在主线程中释放 Browser。
cefBrowser.Dispose();
// 您可以在任何线程中释放 Browser。
browser.Dispose();
CefSharp 不提供通用的方法来检测浏览器何时关闭。
在 DotNetBrowser 中,你可以为浏览器和引擎都注册 Disposed
事件处理程序。无论引擎或浏览器因何种原因、以何种方式关闭,该事件都会被触发。
engine.Disposed += (sender, args) =>
{
Console.WriteLine("The engine was closed!");
};
browser.Disposed += (sender, args) =>
{
Console.WriteLine("The browser was closed!");
};
线程
在 CefSharp 中,调用方法时必须始终留意所处的线程。许多方法对线程有明确要求,文档中常见如下说明:
此方法必须在 CEF XYZ 线程上调用。
DotNetBrowser 无任何线程限制。您可以从任意线程调用该库的 API。
浏览器隔离
数据与网络隔离
在 CefSharp 中,您可以通过将浏览器置于不同的 RequestContext
实现彼此隔离。同一请求上下文内的浏览器会共享所有内容:偏好设置、Cookie、缓存、Service Worker。相应地,不同请求上下文中的浏览器则相互隔离。
在 DotNetBrowser 中,请求上下文的替代方案是配置文件(Profile)。同一配置文件下创建的浏览器会共享偏好设置、网络层、缓存、Cookie、存储等内容。若需实现浏览器间的隔离,请在不同配置文件中创建它们:
var publicContext = RequestContext.Configure()
.WithCachePath("<path to a public cache location>")
.Create();
var publicBrowser = new ChromiumWebBrowser("https://teamdev.com");
publicBrowser.RequestContext = publicContext;
var secureContext = RequestContext.Configure()
.WithCachePath("<path to a secure cache location>")
.Create();
var secureBrowser = new ChromiumWebBrowser("https://internal.system");
secureBrowser.RequestContext = secureContext;
var publicProfile = engine.Profiles.Default;
var publicBrowser = defaultProfile.CreateBrowser();
publicBrowser.Navigation.LoadUrl("https://teamdev.com");
var secureProfile = engine.Profiles.Create("secure-profile");
var secureBrowser = secureProfile.CreateBrowser();
secureBrowser.Navigation.LoadUrl("https://internal.system");
运行时隔离
CefSharp 将 Chromium 引擎嵌入 .NET 进程内部。该引擎是单例,每个进程生命周期只能创建一次。若引擎崩溃,不仅浏览器实例会终止,整个应用程序都会随之崩溃。
DotNetBrowser 则在独立进程中运行 Chromium 引擎,因此即使引擎崩溃,.NET 进程也不会受影响。当发生此类崩溃时,.NET 进程会收到通知:
engine.Disposed += (s, e) =>
{
if (e.ExitCode != 0)
{
// 引擎已崩溃。
}
};
当引擎崩溃时,其所有 IBrowser
实例也会立即关闭。通过在不同的 IEngine
中创建各自的配置文件和浏览器,可实现彼此隔离,避免受到其他引擎崩溃的影响。
请注意,创建并运行额外的
IEngine
实例会占用大量资源,而且通常并不必要。
// 启动公共浏览器的 Chromium 主进程。
var publicEngine = EngineFactory.Create(RenderingMode.HardwareAccelerated);
var publicProfile = publicEngine.Profiles.Default;
var publicBrowser = defaultProfile.CreateBrowser();
publicBrowser.Navigation.LoadUrl("https://teamdev.com");
// 启动安全浏览器的 Chromium 主进程。
var secureEngine = EngineFactory.Create(RenderingMode.HardwareAccelerated);
var secureProfile = secureEngine.Profiles.Create("secure-profile");
var secureBrowser = secureProfile.CreateBrowser();
secureBrowser.Navigation.LoadUrl("https://internal.system");
导航方法
在 CefSharp 中,大多数导航方法在 DotNetBrowser 中都能找到一一对应的替代:
ChromiumWebBrowser chromiumBrowser = new ChromiumWebBrowser();
chromiumBrowser.LoadUrl("https://teamdev.com");
IBrowser browser = chromiumBrowser.GetBrowser();
browser.GoBack();
browser.GoForward();
browser.Reload(true);
browser.StopLoad();
bool canGoBack = browser.CanGoBack;
bool canGoForward = browser.CanGoForward;
INavigation navigation = browser.Navigation;
navigation.LoadUrl("https://teamdev.com");
navigation.GoBack();
navigation.GoForward();
navigation.Reload();
navigation.ReloadIgnoringCache();
navigation.Stop();
bool canGoBack = navigation.CanGoBack();
bool canGoForward = navigation.CanGoForward();
导航事件
CefSharp 与 DotNetBrowser 都提供了追踪导航状态的机制。在 DotNetBrowser 中,可使用 NavigationStarted
、NavigationStopped
和 NavigationFinished
组合来替代 CefSharp 的 LoadingStateChanged
与 LoadError
事件:
browser.LoadingStateChanged += (sender, args) =>
{
if (args.IsLoading)
{
Console.WriteLine("Navigation started.");
}
else
{
Console.WriteLine("Navigation completed.");
}
};
browser.LoadError += (sender, args) =>
{
if (args.ErrorCode == CefErrorCode.Aborted)
{
Console.WriteLine("Navigation aborted");
}
}
navigation.NavigationStarted += (s, e) =>
{
Console.WriteLine("Navigation started.");
};
navigation.NavigationStopped += (s, e) =>
{
Console.WriteLine("Navigation stopped.");
};
navigation.NavigationFinished += (sender, args) =>
{
if (args.ErrorCode == NetError.Aborted)
{
Console.WriteLine("Navigation aborted");
}
};
若需判断某个 Frame 的 JavaScript 上下文与 DOM 树何时就绪,可用 FrameDocumentLoadFinished
替代 FrameLoadEnd
。
browser.FrameLoadEnd += (sender, args) =>
{
if (args.Frame.IsMain)
{
Console.WriteLine("DOM and V8 context are ready in the main frame.");
}
else
{
Console.WriteLine("DOM and V8 context are in an iframe.");
}
};
navigation.FrameDocumentLoadFinished += (s, args) =>
{
if (args.frame.IsMain)
{
Console.WriteLine("DOM and V8 context are ready in the main frame.");
}
else
{
Console.WriteLine("DOM and V8 context are in an iframe.");
}
};
DotNetBrowser 中有 10 个细粒度的导航事件。可查看完整列表。
使用 POST 数据加载
在 CefSharp 中,无法直接使用 POST 参数加载 URL。要实现这一功能,您需要注册一个全局请求处理器,筛选出要修改的请求,然后手动添加 POST 数据。
在 DotNetBrowser 中,您可以直接使用任意 POST 数据加载 URL:
class MyRequestHandler : RequestHandler
{
protected override IResourceRequestHandler GetResourceRequestHandler(...)
{
if (request.Url == "https://my.post.endpoint")
{
return new MyResourceRequestHandler();
}
return null;
}
}
class MyResourceRequestHandler : ResourceRequestHandler
{
protected override CefReturnValue OnBeforeResourceLoad(...)
{
var postData = new PostData();
postData.AddData("username=test&password=1234");
request.Method = "POST";
request.PostData = postData;
request.SetHeaderByName(
"Content-Type", "application/x-www-form-urlencoded", true);
return CefReturnValue.Continue;
}
}
browser = new ChromiumWebBrowser("https://my.post.endpoint");
browser.RequestHandler = new CustomRequestHandler();
// 创建包含 POST 数据参数的数组。
KeyValuePair<string, string>[] formData =
{
new KeyValuePair<string, string>("username", "test"),
new KeyValuePair<string, string>("password", "1234")
};
LoadUrlParameters request = new LoadUrlParameters("https://my.post.endpoint")
{
// DotNetBrowser 将根据数据类型自动推断内容类型(Content-Type)。
UploadData = new FormData(formData)
};
// 使用自定义请求进行导航。
browser.Navigation.LoadUrl(request);
DotNetBrowser 会自动填充 POST 请求的 Content-Type
(内容类型)和 Content-Length
(内容长度)标头。对于分段上传,它还会添加表单边界,并设置每个分段的 Content-Disposition
(内容部署)和 Content-Type
标头。
您可以在使用 POST 数据导航指南中了解更多关于如何发送 POST 请求的信息。
加载 HTML 内容
在 CefSharp 和 DotNetBrowser 中,加载 HTML 字符串最简便的方式是将其作为 data:
URL 直接加载:
var html = "<html>Html Encoded in URL!</html>";
var encodedHtml = Convert.ToBase64String(Encoding.UTF8.GetBytes(html));
browser.Load("data:text/html;base64," + encodedHtml);
var html = "<html>Html Encoded in URL!</html>";
var encodedHtml = Convert.ToBase64String(Encoding.UTF8.GetBytes(html));
browser.Navigation.LoadUrl("data:text/html;base64," + encodedHtml);
此方法的 URL 大小上限约为 2 MB。如果您使用 CefSharp 的 ResourceHandler
加载更多内容,请将其替换为自定义方案请求拦截器。
JavaScript
何时开始执行 JavaScript
若要在最早的时机运行 JavaScript,需要先确定 Chromium 已完成 V8 JavaScript 上下文的初始化。
在 CefSharp 中,可通过 OnContextCreated
事件获知某个 frame 的 JavaScript 上下文已创建;此时 frame 内的其他脚本尚未运行。在 DotNetBrowser 中,用语义相同的 InjectJsHandler
替代即可:
class RenderProcessMessageHandler : IRenderProcessMessageHandler
{
void OnContextCreated(IWebBrowser browserControl,
IBrowser browser, IFrame frame)
{
frame.ExecuteJavaScriptAsync("...");
}
}
browser.RenderProcessMessageHandler = new RenderProcessMessageHandler();
browser.InjectJsHandler = new Handler<InjectJsParameters>(args =>
{
args.Frame.ExecuteJavaScript<IJsObject>("...");
});
从 .NET 调用 JavaScript
两个库在 JavaScript 交互方面提供了类似的能力,区别主要体现在类型转换以及将 .NET 对象注入到 JavaScript 中的方式。
要执行脚本,请将 EvaluateScriptAsync
调用替换为 ExecuteJavaScript
:
JavascriptResponse response = await frame.EvaluateScriptAsync("'Hello'");
string response = await frame.ExecuteJavaScript<string>("'Hello'");
虽然两处调用看起来相似,但返回结果却有所不同。CefSharp 与 DotNetBrowser 都会尝试将 JavaScript 类型转换为相应的 C# 类型,但各自的处理方式不同。
CefSharp 可返回原始类型、简单的非循环 JavaScript 对象以及这些类型的数组。无法返回 DOM 元素或 window
对象。
DotNetBrowser 可返回原始类型和任意复杂度的 JavaScript 对象,包括具有循环引用的对象——该库直接使用 V8 对象,而无需在底层对其进行序列化和复制。
JavascriptResponse numberResponse = await frame.EvaluateScriptAsync("123");
int number = (int) numberResponse.Result;
JavascriptResponse booleanResponse = await frame.EvaluateScriptAsync("true");
bool boolean = (bool) booleanResponse.Result;
JavascriptResponse strResponse = await frame.EvaluateScriptAsync("'Hello'");
string str = (string) strResponse.Result;
JavascriptResponse objResponse =
await frame.EvaluateScriptAsync("({'foo': 'bar'})");
dynamic obj = objResponse.Result;
var foo = obj.foo;
// 不能这样做:
JavascriptResponse windowResponse = await frame.EvaluateScriptAsync("window");
object window = strResponse.Result; // Null.
// DotNetBrowser 与 CefSharp 一样可直接转换原始类型:
double number = await frame.ExecuteJavaScript<double>("123");
bool boolean = await frame.ExecuteJavaScript<bool>("true");
string str = await frame.ExecuteJavaScript<string>("'Hello'");
IJsObject obj =
await frame.ExecuteJavaScript<IJsObject>("{'foo': 'bar'}");
var foo = obj.Properties["foo"];
// 这在 DotNetBrowser 中可行。
IJsObject window = await frame.ExecuteJavaScript<IJsObject>("window");
// 在可能的情况下,DotNetBrowser 会选择更具体的类型:
IElement body = await frame.ExecuteJavaScript<IElement>("document.body");
如需了解更多信息,请参阅指南从 .NET 调用 JavaScript。
从 JavaScript 调用 .NET
在这两种解决方案中,从 JavaScript 调用 .NET 代码都需要将 .NET 对象注入到 JavaScript 环境中。
在 CefSharp 中,此过程分两步:首先在 .NET 端把对象放入 JavascriptObjectRepository
,然后在 JavaScript 端异步“获取”该对象。
在 DotNetBrowser 中,方法更简单:将任意 .NET 对象分配给任意 JavaScript 属性即可。
在 C# 代码中:
// 在注册表中注册 .NET 对象:
browser.JavascriptObjectRepository.ResolveObject += (sender, e) =>
{
var repo = e.ObjectRepository;
if (e.ObjectName == "myObject")
{
repo.Register("myObject", new MyObject(), null);
}
};
// 将 .NET 对象分配给任意 JavaScript 对象的属性。
IJsObject window = frame.ExecuteJavaScript<IJsObject>("window").Result;
window.Properties["myObject"] = new MyObject();
In JavaScript code:
// 一次性获取 .NET 对象:
await CefSharp.BindObjectAsync("myObject");
// 使用注入的 .NET 对象。
myObject.add(40, 2).then((sum) => console.log("Sum is: " + sum));
// 可以直接使用该 JavaScript 对象:
const sum = myObject.add(40, 2);
console.log("Sum is: " + sum);
更多信息请参阅在 DotNetBrowser 中从 JavaScript 调用 .NET 的指南。
拦截流量
CefSharp 与 DotNetBrowser 都支持在标准协议和 custom://
协议中拦截流量并替换服务器响应:
public class MySchemeHandlerFactory : ISchemeHandlerFactory
{
IResourceHandler Create(IBrowser browser, IFrame frame,
string schemeName, IRequest request)
{
var uri = new Uri(request.Url);
var fileName = uri.AbsolutePath;
var responseContent = ReadMyCustomResponse(uri);
return ResourceHandler.FromString(responseContent, ...);
return null;
}
}
settings.RegisterScheme(new CefCustomScheme
{
// 对应 custom-sceme://
SchemeName = "custom-scheme",
// 您必须为该 scheme 与 host 的组合注册一个处理程序。
DomainName = "my-app.com",
SchemeHandlerFactory = new MySchemeHandlerFactory()
});
Cef.Initialize(settings);
var handler =
new Handler<InterceptRequestParameters, InterceptRequestResponse>(p =>
{
var url = p.UrlRequest.Url;
if (GetDomain(url) == "my-app.com")
{
var opts = new UrlRequestJobOptions
{
HttpStatusCode = HttpStatusCode.OK,
Headers = new List<HttpHeader>
{
new HttpHeader("Content-Type", "text/html", "charset=utf-8")
}
};
var job = p.Network.CreateUrlRequestJob(p.UrlRequest, opts);
Task.Run(() =>
{
var responseContent = ReadMyCustomResponse(url);
job.Write(Encoding.UTF8.GetBytes(responseContent));
job.Complete();
});
// 返回用于填充响应数据的任务。
return InterceptRequestResponse.Intercept(job);
}
else
{
// 允许请求继续。
return InterceptRequestResponse.Proceed();
}
});
var opts = new EngineOptions.Builder
{
// 为整个 scheme 注册请求拦截器。
Schemes =
{
{ Scheme.Create("custom-scheme"), handler }
}
}.Build();
var engine = EngineFactory.Create(opts);
DotNetBrowser 没有提供与 CefSharp 中 ResourceHandler.FromStream
或 ResourceHandler.FromFilePath
等便捷方法对应的替代方法。
请求处理程序
CefSharp 提供了 IRequestHandler
接口,允许您修改传出的请求、处理身份验证以及渲染进程中的某些事件。在 DotNetBrowser 中,相同的功能分布在多个单独的事件处理程序中。
DotNetBrowser 中与 IRequestHandler
等效的方法如下:
IRequestHandler 中的方法 | DotNetBrowser 中的等效方法 |
---|---|
OnSelectClientCertificate | SelectCertificateHandler |
GetAuthCredentials | AuthenticateHandler |
OnCertificateError | VerifyCertificateHandler |
OnRenderProcessTerminated | RenderProcessTerminated |
OnDocumentAvailableInMainFrame | FrameDocumentLoadFinished |
OnOpenUrlFromTab | 无可用替代方案。如需允许或阻止弹出窗口,请使用 CreatePopupHandler 。 |
DotNetBrowser equivalents to IResourceRequestHandler
:
IResourceRequestHandler 中的方法 | DotNetBrowser 中的等效方法 |
---|---|
GetResourceHandler | 使用自定义方案拦截器。 |
OnResourceRedirect | RedirectResponseCodeReceived |
OnBeforeResourceLoad | SendUrlRequestHandler |
OnResourceLoadComplete | RequestCompleted |
OnResourceResponse | StartTransactionHandler |
GetCookieAccessFilter | StartTransactionHandler |
GetResourceResponseFilter 用于读取响应内容 | ResponseBytesReceived |
GetResourceResponseFilter 用于注入自定义 CSS | InjectCssHandler |
GetResourceResponseFilter 用于过滤传入字节 | 无可用替代方案 |
OnProtocolExecution | 无可用替代方案 |
Cookie 管理器
CefSharp 提供了 ICookieManager
接口,用于在存储中创建、读取、更新和删除 Cookie。所有浏览器共享一个全局 Cookie 管理器,且每个 RequestContext
都有其自己的 Cookie 管理器。
DotNetBrowser 提供了一个类似的实体,称为 ICookieStore
。所有 Cookie 存储都绑定到各自的 IProfile
,没有可用的全局存储:
// Cookie 管理器以异步方式创建。
// 使用 IBrowserProcessHandler.OnContextInitialized 了解其何时可使用。
var cookieManager = Cef.GetGlobalCookieManager();
// 创建 Cookie。
var success = cookieManager.SetCookie("google.com", new Cookie
{
Name = "name",
Value = "value",
Expires = expirationTime,
Path = "/"
});
// 删除 Cookie。
cookieManager.DeleteCookies("https://google.com", "cookie-name");
// 立即持久化 Cookie 更改。
cookieManager.FlushStore();
// 遍历所有现有 Cookie。
cookieManager.VisitAllCookies(new CookieVisitor());
// 遍历指定 URL 的所有 Cookie。
bool withHttpOnly = false;
cookieManager.VisitUrlCookies(
"httos://google.com", withHttpOnly, new CookieVisitor());
// 无需初始化,可直接使用 Cookie 存储。
var cookieStore = profile.CookieStore;
// 创建 Cookie。
Cookie cookie = new Cookie.Builder("google.com")
{
Name = "name",
Value = "value",
ExpirationTime = expirationTime,
Path = "/"
}.Build();
await engine.Profiles.Default.CookieStore.SetCookie(cookie);
// 删除 Cookie。
await cookieStore.CookieStore.Delete(cookie);
// 立即持久化 Cookie 更改。
cookieStore.Flush();
// 读取所有 Cookie。
IEnumerable<Cookie> allCookies =
await cookieStore.CookieStore.GetAllCookies();
// 读取指定 URL 的 Cookie。
IEnumerable<Cookie> urlCookies =
await cookieStore.CookieStore.GetAllCookies("https://google.com");
代理
CefSharp 允许您通过两种方式配置代理:
- 在初始化引擎时使用特殊的 Chromium 开关;
- 在运行时更改
IRequestContext
中的代理。
在 DotNetBrowser 中,首选方法是使用 IProfile
实例在运行时配置代理,您可以在创建任何 IBrowser
实例之前完成此操作。
// 为所有浏览器指定全局代理设置。
var settings = new CefSettings();
// 继承系统代理设置。
settings.CefCommandLineArgs.Add("no-proxy-server");
// 使用 Web 代理自动发现(Web Proxy Auto-Discovery)。
settings.CefCommandLineArgs.Add("proxy-auto-detect");
// 手动配置代理。
settings.CefCommandLineArgs.Add(
"proxy-server=http=proxy.com:8080;https=proxy.com:8081");
// 使用 PAC 文件。
settings.CefCommandLineArgs.Add("proxy-pac-url=URL");
Cef.Initialize(settings);
// 或者,为特定的 RequestContext 配置代理:
var requestContext = RequestContext
.Configure()
.WithProxyServer("proxy.com", 8080)
.Create();
IProxy proxy = profile.Proxy;
// 继承系统代理设置。
proxy.Settings = new SystemProxySettings();
// 不使用任何代理服务器。
proxy.Settings = new DirectProxySettings();
// 使用 Web 代理自动发现。
proxy.Settings = new AutoDetectProxySettings();
// 手动配置代理。
string proxyRules = "http=proxy.com:8080;https=proxy.com:8081";
proxy.Settings = new CustomProxySettings(proxyRules);
// 使用 PAC 文件。
proxy.Settings = new PacProxySettings("<pac-file-url>");
要验证代理,请将 CefSharp 的 IRequestHandler.GetAuthCredentials
替换为 AuthenticateHandler
:
class CustomRequestHandler : CefSharp.Handler.RequestHandler
{
override bool GetAuthCredentials(...)
{
if (isProxy)
{
using (callback)
{
callback.Continue(username: "user", password: "pass");
}
return true;
}
// 取消身份验证请求。
return false;
}
}
browser.RequestHandler = new CustomRequestHandler();
network.AuthenticateHandler =
new Handler<AuthenticateParameters, AuthenticateResponse>(p =>
{
if (p.IsProxy)
{
return AuthenticateResponse.Continue("user", "pass"));
}
return AuthenticateResponse.Cancel();
};
弹出窗口
在 CefSharp 中,弹出浏览器使用 ILifeSpanHandler
进行控制。通过使用此接口的 WPF 或 WinForms 实现,您可以在新窗口中显示新创建的浏览器。例如,在 WinForms 中如下所示:
browser.LifeSpanHandler = CefSharp.WinForms.Handler.LifeSpanHandler
.Create()
.OnBeforePopupCreated((...) => {
if (...)
{
return PopupCreation.Continue;
}
else
{
return PopupCreation.Cancel;
}
})
.OnPopupCreated((ctrl, targetUrl) =>
{
parent.Controls.Add(ctrl);
})
.OnPopupDestroyed((ctrl, popupBrowser) =>
{
if (!ctrl.IsDisposed && ctrl.IsHandleCreated)
{
parent.Controls.Remove(ctrl);
}
}).Build();
在 DotNetBrowser 中,您需要用两个处理器来替代:
CreatePopupHandler
用于启用 / 禁用弹出窗口的创建。OpenPopupHandler
用于在需要时显示弹出窗口。
在 OpenPopupHandler
中,您将收到新创建的 IBrowser
实例,
并且您可以在无头模式下使用它,或者使用 BrowserView
在 UI 中显示它。默认情况下,如果浏览器处于 BrowserView
模式,它会在新窗口中打开弹出窗口;否则,它会抑制弹出窗口:
class MyOpenPopupHandler : IHandler<OpenPopupParameters>
{
private readonly Control parent;
public MyOpenPopupHandler(Control parent)
{
this.parent = parent;
}
public void Handle(OpenPopupParameters parameters)
{
Action showPopupAction = () =>
{
ShowPopup(parameters.PopupBrowser,
parameters.Rectangle);
};
parent.BeginInvoke(showPopupAction);
}
private void ShowPopup(IBrowser popupBrowser, Rectangle rectangle)
{
BrowserView browserView = new BrowserView
{
Dock = DockStyle.Fill
};
browserView.InitializeFrom(popupBrowser);
// 将浏览器视图添加到父控件并显示。
}
}
browser.CreatePopupHandler =
new Handler<CreatePopupParameters, CreatePopupResponse>(p =>
{
if (...)
{
return CreatePopupResponse.Create();
}
else
{
return CreatePopupResponse.Suppress();
}
});
// 如果您不想让 DotNetBrowser 的默认行为:
// 在新窗口中显示弹出窗口,请设置此处理程序。
browser.OpenPopupHandler = new MyOpenPopupHandler(parent);
JavaScript 对话框
CefSharp 提供了 IJsDialogHandler
接口,允许您处理 JavaScript 对话框。在 DotNetBrowser 中,您可以将其替换为独立的处理程序:
class MyDialogHandler : IJsDialogHandler
{
// 在此方法中处理 alert、prompt 和 confirm 对话框。
bool OnJSDialog(IWebBrowser chromiumWebBrowser, ...)
{
...
}
// 在此方法中处理 onBeforeUnload 对话框。
bool OnBeforeUnloadDialog(IWebBrowser chromiumWebBrowser, ...)
{
...
}
// 以下方法在 DotNetBrowser 中没有直接替代,
// 因为 DotNetBrowser 允许在 .NET 代码中完全控制对话框状态。
void OnResetDialogState(IWebBrowser chromiumWebBrowser, IBrowser browser)
{
...
}
void OnDialogClosed(IWebBrowser chromiumWebBrowser, IBrowser browser)
{
...
}
}
browser.JsDialogHandler = new MyDialogHandler();
var dialogs = browser.JsDialogs;
dialogs.AlertHandler = new Handler<AlertParameters>(p => { });
dialogs.ConfirmHandler =
new Handler<ConfirmParameters, ConfirmResponse>(p =>
{
...
return ConfirmResponse.Ok();
});
dialogs.PromptHandler =
new Handler<PromptParameters, PromptResponse>(p =>
{
...
return PromptResponse.SubmitText("some response");
});
dialogs.BeforeUnloadHandler =
new Handler<BeforeUnloadParameters, BeforeUnloadResponse>(p =>
{
...
return BeforeUnloadResponse.Leave();
});
如果您未设置处理器,DotNetBrowser 会自动管理对话框。在 BrowserView
中,它会使用应用程序的 UI 库显示默认对话框实现。否则,它会跳过这些对话框,就像用户按下了“取消”一样。
下载
CefSharp 提供 IDownloadHandler
用于控制下载过程。在 DotNetBrowser 中,可直接使用 StartDownloadHandler
进行替换:
class MyDownloadHandler : IDownloadHandler
{
bool OnBeforeDownload(IWebBrowser chromiumWebBrowser, IBrowser browser,
DownloadItem downloadItem,
IBeforeDownloadCallback callback)
{
if (downloadItem.IsValid)
{
string downloadPath =
Path.GetFullPath(downloadItem.SuggestedFileName);
if (!callback.IsDisposed)
{
using (callback)
{
callback.Continue(
downloadPath: downloadPath,
// 显示默认的保存对话框。
showDialog: true
);
}
}
return true;
}
// 让 CefSharp 处理下载。
// 在 Alloy 中,下载默认会被取消。
return false;
}
void OnDownloadUpdated(IWebBrowser chromiumWebBrowser, IBrowser browser,
DownloadItem downloadItem,
IDownloadItemCallback callback)
{
if (downloadItem.IsValid)
{
if (downloadItem.IsComplete)
{
Console.WriteLine("Download completed");
}
}
}
}
browser.DownloadHandler = new MyDownloadHandler();
browser.StartDownloadHandler =
new Handler<StartDownloadParameters, StartDownloadResponse>(p =>
{
var fileName = p.Download.Info.SuggestedFileName;
var targetPath = Path.GetFullPath(fileName);
p.Download.Finished += (sender, args) =>
{
Console.WriteLine("Download completed");
};
// 返回 StartDownloadResponse.Cancel() 以取消下载。
return StartDownloadResponse.DownloadTo(targetPath);
});
如果您未配置此处理程序,DotNetBrowser 将自动处理下载。在 BrowserView
中,它会使用应用的 UI 库显示“Save As(另存为)”对话框。在该上下文之外,它会取消下载。
有关更多详细信息,请参阅下载指南。
屏幕截图
在 CefSharp 中,只能通过 DevTools 协议捕获截图。而 DotNetBrowser 提供了专门的方法来实现这一功能,无需使用 DevTools 或暴露远程调试端口:
using (var devToolsClient = chromiumWebBrowser.GetDevToolsClient())
{
var result = await devToolsClient.Page.CaptureScreenshotAsync();
byte[] pixels = result.Data;
}
// 获取当前视口的原始像素。
// 无需 DevTools 或窗口。
DotNetBrowser.Ui.Bitmap image = browser.TakeImage();
// 将图像保存到文件。
Bitmap bitmap = ToBitmap(image);
bitmap.Save("screenshot.png", ImageFormat.Png);
查看 DotNetBrowser 中的高级截屏教程。
开发者工具
在 CefSharp 和 DotNetBrowser 中,您都可以配置远程调试端口,并为每个浏览器打开 DevTools(开发者工具):
var settings = new CefSettings();
settings.RemoteDebuggingPort = 9222;
settings.CefCommandLineArgs.Add("remote-allow-origins=http://localhost:9222");
Cef.Initialize(settings);
...
// 打开 DevTools 窗口。
browser.ShowDevTools();
// 关闭它。
browser.HideDevTools();
IEngine engine = EngineFactory.Create(new EngineOptions.Builder
{
ChromiumSwitches = { "--remote-allow-origins=http://localhost:9222" },
RemoteDebuggingPort = 9222
}.Build());
...
// 打开 DevTools 窗口。
browser.DevTools.Show();
// 关闭它。
browser.DevTools.Hide();
// 或通过 URL 在另一个浏览器中打开 DevTools。
string url = browser.DevTools.RemoteDebuggingUrl;
DotNetBrowser 没有提供 CefSharp 中 browser.GetDevToolsClient()
对应的替代方案。
用户代理
在 CefSharp 中,您可以通过三种方式配置用户代理:
- 在初始化 CEF 之前配置;
- 在运行时,使用 DevTools 协议配置;
- 在运行时,通过重写请求的
User-Agent
头部配置。
在 DotNetBrowser 中,您可以在创建 IEngine
时设置默认用户代理:
IEngine engine = EngineFactory.Create(new EngineOptions.Builder
{
UserAgent = "<user-agent>"
}.Build());
运行时,您可以为单个浏览器实例覆盖该值:
browser.UserAgent = "<user-agent>";
或者,通过 StartTransactionHandler
为单个请求设置:
network.StartTransactionHandler =
new Handler<StartTransactionParameters, StartTransactionResponse>(p =>
{
var newHeaders = p.headers.Cast<HttpHeader>().ToList();
newHeaders.Add(
new HttpHeader("User-Agent", "Mozilla/5.0 ..."));
return StartTransactionResponse.OverrideHeaders(newHttpHeaders);
});
或者,您也可以指定用户代理客户端提示(User Agent Client Hints)。
结论
如果您曾使用过 CefSharp,您会发现 DotNetBrowser 的工作方式与之相似。大多数功能都可用,但在使用方式上略有不同。
在本指南中,我们介绍了浏览器生命周期管理、导航、弹窗与对话框、JavaScript 等多项内容的迁移流程。
希望本指南能为您提供有力帮助,让从 CefSharp 迁移至 DotNetBrowser 的过程更加顺畅。
发送中。。。
您的个人 DotNetBrowser 试用密钥和快速入门指南将在几分钟内发送至您的电子邮箱。