从 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 进程:

CefSharp
DotNetBrowser
Cef.Shutdown();
engine.Dispose();

嵌入浏览器 

WPF 

CefSharp 和 DotNetBrowser 均提供可直接嵌入 XAML 的 WPF 控件。

实际上,CefSharp 提供了两种不同的组件。如果您需要离屏渲染,可以使用 WPF 实现。如果您需要更快的渲染速度,可以使用 Wpf.HwndHost 实现,它会将 Chromium 窗口重新设置为您的应用程序的父级。

DotNetBrowser 提供了一个组件,它可以以两种模式渲染内容——大致相当于 CefSharp 的模式。这两种模式分别是 HardwareAcceleratedOffScreen,您可以在创建 IEngine 时选择相应的模式。

MainWindow.xaml:

CefSharp
DotNetBrowser
<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:

CefSharp
DotNetBrowser
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 时配置的渲染模式。

其余部分类似:

CefSharp
DotNetBrowser
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 即可运行。

CefSharp
DotNetBrowser
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)实例,以防资源泄漏。关闭引擎会自动关闭由其创建的所有浏览器实例:

CefSharp
DotNetBrowser
// 您必须仅在主线程中释放 Browser。
cefBrowser.Dispose();
// 您可以在任何线程中释放 Browser。
browser.Dispose();

CefSharp 不提供通用的方法来检测浏览器何时关闭。

在 DotNetBrowser 中,你可以为浏览器和引擎都注册 Disposed 事件处理程序。无论引擎或浏览器因何种原因、以何种方式关闭,该事件都会被触发。

DotNetBrowser
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、存储等内容。若需实现浏览器间的隔离,请在不同配置文件中创建它们:

CefSharp
DotNetBrowser
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 进程会收到通知:

DotNetBrowser
engine.Disposed += (s, e) =>
{
    if (e.ExitCode != 0)
    {
        // 引擎已崩溃。
    }
};

当引擎崩溃时,其所有 IBrowser 实例也会立即关闭。通过在不同的 IEngine 中创建各自的配置文件和浏览器,可实现彼此隔离,避免受到其他引擎崩溃的影响。

请注意,创建并运行额外的 IEngine 实例会占用大量资源,而且通常并不必要。

DotNetBrowser
// 启动公共浏览器的 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 中都能找到一一对应的替代:

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 中,可使用 NavigationStartedNavigationStoppedNavigationFinished 组合来替代 CefSharp 的 LoadingStateChangedLoadError 事件:

CefSharp
DotNetBrowser
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

CefSharp
DotNetBrowser
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:

CefSharp
DotNetBrowser
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 直接加载:

CefSharp
DotNetBrowser
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 替代即可:

CefSharp
DotNetBrowser
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

CefSharp
DotNetBrowser
JavascriptResponse response = await frame.EvaluateScriptAsync("'Hello'");
string response = await frame.ExecuteJavaScript<string>("'Hello'");

虽然两处调用看起来相似,但返回结果却有所不同。CefSharp 与 DotNetBrowser 都会尝试将 JavaScript 类型转换为相应的 C# 类型,但各自的处理方式不同。

CefSharp 可返回原始类型、简单的非循环 JavaScript 对象以及这些类型的数组。无法返回 DOM 元素或 window 对象。

DotNetBrowser 可返回原始类型和任意复杂度的 JavaScript 对象,包括具有循环引用的对象——该库直接使用 V8 对象,而无需在底层对其进行序列化和复制。

CefSharp
DotNetBrowser
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# 代码中:

CefSharp
DotNetBrowser
// 在注册表中注册 .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:

CefSharp
DotNetBrowser
// 一次性获取 .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:// 协议中拦截流量并替换服务器响应:

CefSharp
DotNetBrowser
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.FromStreamResourceHandler.FromFilePath 等便捷方法对应的替代方法。

请求处理程序 

CefSharp 提供了 IRequestHandler 接口,允许您修改传出的请求、处理身份验证以及渲染进程中的某些事件。在 DotNetBrowser 中,相同的功能分布在多个单独的事件处理程序中。

DotNetBrowser 中与 IRequestHandler 等效的方法如下:

IRequestHandler 中的方法DotNetBrowser 中的等效方法
OnSelectClientCertificateSelectCertificateHandler
GetAuthCredentialsAuthenticateHandler
OnCertificateErrorVerifyCertificateHandler
OnRenderProcessTerminatedRenderProcessTerminated
OnDocumentAvailableInMainFrameFrameDocumentLoadFinished
OnOpenUrlFromTab无可用替代方案。如需允许或阻止弹出窗口,请使用 CreatePopupHandler

DotNetBrowser equivalents to IResourceRequestHandler:

IResourceRequestHandler 中的方法DotNetBrowser 中的等效方法
GetResourceHandler使用自定义方案拦截器
OnResourceRedirectRedirectResponseCodeReceived
OnBeforeResourceLoadSendUrlRequestHandler
OnResourceLoadCompleteRequestCompleted
OnResourceResponseStartTransactionHandler
GetCookieAccessFilterStartTransactionHandler
GetResourceResponseFilter
用于读取响应内容
ResponseBytesReceived
GetResourceResponseFilter
用于注入自定义 CSS
InjectCssHandler
GetResourceResponseFilter
用于过滤传入字节
无可用替代方案
OnProtocolExecution无可用替代方案

CefSharp 提供了 ICookieManager 接口,用于在存储中创建、读取、更新和删除 Cookie。所有浏览器共享一个全局 Cookie 管理器,且每个 RequestContext 都有其自己的 Cookie 管理器。

DotNetBrowser 提供了一个类似的实体,称为 ICookieStore。所有 Cookie 存储都绑定到各自的 IProfile,没有可用的全局存储:

CefSharp
DotNetBrowser
// 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 实例之前完成此操作。

CefSharp
DotNetBrowser
// 为所有浏览器指定全局代理设置。
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

CefSharp
DotNetBrowser
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 中如下所示:

CefSharp
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 模式,它会在新窗口中打开弹出窗口;否则,它会抑制弹出窗口:

DotNetBrowser
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 中,您可以将其替换为独立的处理程序

CefSharp
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 进行替换:

CefSharp
DotNetBrowser
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 或暴露远程调试端口:

CefSharp
DotNetBrowser
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(开发者工具):

CefSharp
DotNetBrowser
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 时设置默认用户代理:

DotNetBrowser
IEngine engine = EngineFactory.Create(new EngineOptions.Builder
{
    UserAgent = "<user-agent>"
}.Build());

运行时,您可以为单个浏览器实例覆盖该值:

DotNetBrowser
browser.UserAgent = "<user-agent>";

或者,通过 StartTransactionHandler 为单个请求设置:

DotNetBrowser
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 的过程更加顺畅。

Spinner

发送中。。。

抱歉,发送中断

请再次尝试,如果问题仍然存在,请联系我们 info@teamdev.com.

阅读并同意条款以继续。

您的个人 DotNetBrowser 试用密钥和快速入门指南将在几分钟内发送至您的电子邮箱。