说明:在同一窗口打开链接,只要稍加改造就可以实现,这里实现的是在新Tab页打开链接,并且支持带type="POST" target="_blank"的链接
github和bitbucket上相关问题:
1、WPF empty POST data when using custom popup https://github.com/cefsharp/CefSharp/issues/1267
2、CefLifeSpanHandler, customized OnBeforePopup problem https://bitbucket.org/chromiumembedded/cef/issues/1949/
解决(CefSharp版本75.1.143.0):
一、实现IRequestHandler接口
using CefSharp; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Security.Cryptography.X509Certificates; namespace CefSharpDemo { public class RequestHandler : IRequestHandler { private ExtChromiumBrowser _browser; public RequestHandler(ExtChromiumBrowser browser) { _browser = browser; } public bool GetAuthCredentials(IWebBrowser chromiumWebBrowser, IBrowser browser, string originUrl, bool isProxy, string host, int port, string realm, string scheme, IAuthCallback callback) { return false; } public IResourceRequestHandler GetResourceRequestHandler(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IRequest request, bool isNavigation, bool isDownload, string requestInitiator, ref bool disableDefaultHandling) { if (request.Method.ToUpper() == "POST" && request.PostData != null) { if (request.PostData.Elements.Count > 0) { _browser.PostData = new byte[request.PostData.Elements[0].Bytes.Length]; request.PostData.Elements[0].Bytes.CopyTo(_browser.PostData, 0); } } return null; } public bool OnBeforeBrowse(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IRequest request, bool userGesture, bool isRedirect) { return false; } public bool OnCertificateError(IWebBrowser chromiumWebBrowser, IBrowser browser, CefErrorCode errorCode, string requestUrl, ISslInfo sslInfo, IRequestCallback callback) { return false; } public bool OnOpenUrlFromTab(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, string targetUrl, WindowOpenDisposition targetDisposition, bool userGesture) { return false; } public void OnPluginCrashed(IWebBrowser chromiumWebBrowser, IBrowser browser, string pluginPath) { } public bool OnQuotaRequest(IWebBrowser chromiumWebBrowser, IBrowser browser, string originUrl, long newSize, IRequestCallback callback) { return false; } public void OnRenderProcessTerminated(IWebBrowser chromiumWebBrowser, IBrowser browser, CefTerminationStatus status) { } public void OnRenderViewReady(IWebBrowser chromiumWebBrowser, IBrowser browser) { } public bool OnSelectClientCertificate(IWebBrowser chromiumWebBrowser, IBrowser browser, bool isProxy, string host, int port, X509Certificate2Collection certificates, ISelectClientCertificateCallback callback) { return false; } } }
二、实现ILifeSpanHandler接口
using CefSharp; using System; using System.Collections.Generic; using System.Collections.Specialized; using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows; using System.Windows.Interop; using Utils; namespace CefSharpDemo { public class CefLifeSpanHandler : CefSharp.ILifeSpanHandler { private static LimitedTaskScheduler _scheduler = new LimitedTaskScheduler(2); public CefLifeSpanHandler() { } public bool DoClose(IWebBrowser browserControl, CefSharp.IBrowser browser) { if (browser.IsDisposed || browser.IsPopup) { return false; } return true; } public void OnAfterCreated(IWebBrowser browserControl, IBrowser browser) { } public void OnBeforeClose(IWebBrowser browserControl, IBrowser browser) { } public bool OnBeforePopup(IWebBrowser browserControl, IBrowser browser, IFrame frame, string targetUrl, string targetFrameName, WindowOpenDisposition targetDisposition, bool userGesture, IPopupFeatures popupFeatures, IWindowInfo windowInfo, IBrowserSettings browserSettings, ref bool noJavascriptAccess, out IWebBrowser newBrowser) { var chromiumWebBrowser = (ExtChromiumBrowser)browserControl; chromiumWebBrowser.Dispatcher.Invoke(new Action(() => { BrowserPopupWin win = new BrowserPopupWin(); win.ShowInTaskbar = false; win.Height = 0; win.Width = 0; win.Show(); IntPtr handle = new WindowInteropHelper(win).Handle; windowInfo.SetAsChild(handle); _scheduler.Run(() => { WaitUtil.Wait(() => chromiumWebBrowser.PostData); IRequest request = null; if (chromiumWebBrowser.PostData != null) { request = frame.CreateRequest(); request.Url = targetUrl; request.Method = "POST"; request.InitializePostData(); var element = request.PostData.CreatePostDataElement(); element.Bytes = chromiumWebBrowser.PostData; request.PostData.AddElement(element); chromiumWebBrowser.PostData = null; } chromiumWebBrowser.Dispatcher.Invoke(new Action(() => { NewWindowEventArgs e = new NewWindowEventArgs(targetUrl, request); chromiumWebBrowser.OnNewWindow(e); })); chromiumWebBrowser.Dispatcher.Invoke(new Action(() => { win.Close(); })); }); })); newBrowser = null; return false; } } }
三、扩展ChromiumWebBrowser
using CefSharp.Wpf; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace CefSharpDemo { public class ExtChromiumBrowser : ChromiumWebBrowser { public byte[] PostData { get; set; } public ExtChromiumBrowser() : base() { this.LifeSpanHandler = new CefLifeSpanHandler(); this.DownloadHandler = new DownloadHandler(this); this.MenuHandler = new MenuHandler(); this.KeyboardHandler = new KeyboardHandler(); this.RequestHandler = new RequestHandler(this); } public event EventHandler<NewWindowEventArgs> StartNewWindow; public void OnNewWindow(NewWindowEventArgs e) { if (StartNewWindow != null) { StartNewWindow(this, e); } } public void ClearHandlers() { //如果不清理Handler,会导致子进程CefSharp.BrowserSubprocess.exe无法释放 this.LifeSpanHandler = null; this.DownloadHandler = null; this.MenuHandler = null; this.KeyboardHandler = null; } } }
四、封装ExtChromiumBrowser(BrowserCtrl控件)
using CefSharp; using CefSharp.Wpf; using System; using System.Collections.Generic; using System.ComponentModel; using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Interop; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; using Utils; namespace CefSharpDemo { /// <summary> /// 浏览器用户控件 /// </summary> public partial class BrowserCtrl : UserControl, IDisposable { #region 外部方法 /* [DllImport("user32.dll", SetLastError = true)] private static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent); [DllImport("user32.dll", SetLastError = true)] public static extern IntPtr FindWindowEx(IntPtr parentHandle, IntPtr childAfter, string className, string windowTitle); [DllImport("user32.dll", SetLastError = true)] public static extern int MoveWindow(IntPtr hWnd, int x, int y, int nWidth, int nHeight, bool BRePaint); [DllImport("user32.dll", SetLastError = true)] public static extern int CloseWindow(IntPtr hWnd); [DllImport("User32.dll", EntryPoint = "GetWindowText")] private static extern int GetWindowText(IntPtr hwnd, StringBuilder lpString, int nMaxCount); */ #endregion #region 变量属性事件 private static bool _isCefInited = false; private static object _lockObject = new object(); private JSObject _jsObject; private bool _firstLoad = true; /// <summary> /// 在此事件中设置URL(此事件已在线程中执行,此事件已对错误情况进行处理) /// </summary> public event EventHandler SetUrlEvent; /// <summary> /// URL /// </summary> public string Url { get; set; } public IRequest Request { get; set; } /// <summary> /// 浏览器FrameLoadEnd事件 /// </summary> public event EventHandler FrameLoadEnd; private ExtChromiumBrowser _browser; public ExtChromiumBrowser Browser { get { WaitUtil.Wait(() => this._browser != null && this._browser.IsInitialized && _isCefInited); return this._browser; } } private static LimitedTaskScheduler _scheduler = new LimitedTaskScheduler(2); #endregion #region 构造函数 public BrowserCtrl() { InitializeComponent(); if (DesignerProperties.GetIsInDesignMode(this)) return; this.Loaded += BrowserCtrl_Loaded; lock (_lockObject) { if (!_isCefInited) { _isCefInited = true; InitCef();//初始化CefSharp } } _browser = new ExtChromiumBrowser(); BindBrowser(_browser); grid.Children.Add(_browser); } #endregion #region BrowserCtrl_Loaded private void BrowserCtrl_Loaded(object sender, RoutedEventArgs e) { } #endregion #region SetMapCtrl /// <summary> /// 设置Map控件接口,用于C#和JS互操作 /// </summary> public void SetMapCtrl(IMapCtrl mapCtrl) { _jsObject.MapCtrl = mapCtrl; } #endregion #region Dispose 释放资源 /// <summary> /// 释放资源 /// </summary> public void Dispose() { //如果有弹出窗口则先释放它 //foreach (UIElement item in grid.Children) //{ // if (item is BrowserContainer) // { // (item as BrowserContainer).ClearResource(); // } //} _browser.ClearHandlers(); if (_browser != null && !_browser.IsDisposed) { _browser.Dispose(); } } #endregion #region Load public void Load(string url) { if (!string.IsNullOrWhiteSpace(url)) { loadingWait.Visibility = Visibility.Visible; Url = url; _scheduler.Run(() => { #region Wait WaitUtil.Wait(() => { if (this._browser == null) return false; if (!this._browser.IsInitialized) return false; if (!_isCefInited) return false; bool isBrowserInitialized = false; this.Dispatcher.Invoke(() => { isBrowserInitialized = this._browser.IsBrowserInitialized; }); if (!isBrowserInitialized) return false; return true; }); #endregion _browser.Load(Url); }); } } #endregion #region LoadUrl private void LoadUrl() { if (_firstLoad) { _firstLoad = false; _scheduler.Run(() => { #region Wait WaitUtil.Wait(() => { if (this._browser == null) return false; if (!this._browser.IsInitialized) return false; if (!_isCefInited) return false; bool isBrowserInitialized = false; this.Dispatcher.Invoke(() => { isBrowserInitialized = this._browser.IsBrowserInitialized; }); if (!isBrowserInitialized) return false; return true; }); #endregion if (Url == null && SetUrlEvent != null) { try { SetUrlEvent(this, null); } catch (Exception ex) { LogUtil.Error(ex, "BrowserCtrl LoadUrl error 获取URL失败"); } } else { this.Dispatcher.Invoke(new Action(() => { loadingWait.Visibility = Visibility.Collapsed; })); } if (Url != null) { try { if (Request == null) { _browser.Load(Url); } else { _browser.Load(Url); _browser.GetMainFrame().LoadRequest(Request); Request = null; } } catch (Exception ex) { LogUtil.Error(ex, "BrowserCtrl LoadUrl error Load URL失败"); } } else { this.Dispatcher.Invoke(new Action(() => { loadingWait.Visibility = Visibility.Collapsed; })); } }); } } #endregion #region BindBrowser private void BindBrowser(ExtChromiumBrowser browser) { _jsObject = new JSObject(); browser.RegisterJsObject("jsObj", _jsObject, new CefSharp.BindingOptions { CamelCaseJavascriptNames = false }); browser.IsBrowserInitializedChanged += (ss, ee) => { LoadUrl(); }; browser.FrameLoadStart += (ss, ee) => { this.Dispatcher.BeginInvoke(new Action(() => { (ss as ExtChromiumBrowser).Focus(); })); }; browser.FrameLoadEnd += (ss, ee) => { this.Dispatcher.BeginInvoke(new Action(() => { loadingWait.Visibility = Visibility.Collapsed; })); if (FrameLoadEnd != null) { FrameLoadEnd(null, null); } }; browser.KeyDown += (ss, ee) => { if (ee.Key == Key.F5) { try { browser.Reload(); } catch (Exception ex) { LogUtil.Error(ex, "ExtChromiumBrowser Reload error"); } } }; browser.PreviewTextInput += (o, e) => { foreach (var character in e.Text) { // 把每个字符向浏览器组件发送一遍 browser.GetBrowser().GetHost().SendKeyEvent((int)WM.CHAR, (int)character, 0); } // 不让cef自己处理 e.Handled = true; }; browser.LoadError += (s, e) => { this.Dispatcher.BeginInvoke(new Action(() => { loadingWait.Visibility = Visibility.Collapsed; })); }; } #endregion #region RegisterJsObject public void RegisterJsObject(string name, object objectToBind, BindingOptions options = null) { try { if (_browser != null) { _browser.RegisterJsObject(name, objectToBind, options); } } catch (Exception ex) { LogUtil.Error(ex, "BrowserCtrl RegisterJsObject 错误"); } } #endregion #region 初始化CefSharp public static void InitCef() { string cefsharpFolder = "CefSharp"; var settings = new CefSettings(); //The location where cache data will be stored on disk. If empty an in-memory cache will be used for some features and a temporary disk cache for others. //HTML5 databases such as localStorage will only persist across sessions if a cache path is specified. settings.CachePath = cefsharpFolder + "/cache"; //设置cache目录 settings.MultiThreadedMessageLoop = true; CefSharpSettings.FocusedNodeChangedEnabled = true; CefSharpSettings.LegacyJavascriptBindingEnabled = true; CefSharpSettings.ShutdownOnExit = true; CefSharpSettings.SubprocessExitIfParentProcessClosed = true; string logDir = AppDomain.CurrentDomain.BaseDirectory + cefsharpFolder + "/log/"; if (!Directory.Exists(logDir)) { Directory.CreateDirectory(logDir); } settings.BrowserSubprocessPath = AppDomain.CurrentDomain.BaseDirectory + cefsharpFolder + "/CefSharp.BrowserSubprocess.exe"; settings.LogFile = logDir + DateTime.Now.ToString("yyyyMMdd") + ".log"; settings.LocalesDirPath = AppDomain.CurrentDomain.BaseDirectory + cefsharpFolder + "/locales"; settings.CefCommandLineArgs.Add("disable-gpu", "1"); settings.CefCommandLineArgs.Add("enable-media-stream", "1"); if (!Cef.Initialize(settings, performDependencyCheck: true, browserProcessHandler: new BrowserProcessHandler())) { throw new Exception("Unable to Initialize Cef"); } } #endregion } }
五、MainWindow测试代码
using CefSharp; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; using Utils; namespace CefSharpDemo { /// <summary> /// CefSharp Demo 窗体 /// </summary> public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); tabControl.AddTabItemEvent += tabControl_AddTabItemEvent; Application.Current.MainWindow = this; } private void tabControl_AddTabItemEvent(object sender, EventArgs e) { //CreateTabItem("https://www.cnblogs.com/"); CreateTabItem("file:///D:/_程序/CefSharpDemo/post.html"); } /// <summary> /// 新增Tab页 /// </summary> private void CreateTabItem(string url = null, IRequest request = null) { TabItem tabItem = new TabItem(); tabItem.Header = "新标签页"; BrowserDemoCtrl ctrl = new BrowserDemoCtrl(); ctrl.browserCtrl.Browser.StartNewWindow += (s, e) => { CreateTabItem(e.TargetUrl, e.Request); }; ctrl.browserCtrl.SetUrlEvent += (s, e) => { ctrl.browserCtrl.Url = url; ctrl.browserCtrl.Request = request; }; tabItem.Content = ctrl; tabControl.Items.Add(tabItem); tabControl.SelectedItem = tabItem; ScrollViewer scrollViewer = tabControl.Template.FindName("scrollViewer", tabControl) as ScrollViewer; scrollViewer.ScrollToRightEnd(); } private void Window_Closed(object sender, EventArgs e) { tabControl.CloseAllTabItem(); //关闭窗体清理资源 //程序退出时删除cache CefSharp.Cef.Shutdown(); string cachePath = AppDomain.CurrentDomain.BaseDirectory + "CefSharp\\cache"; if (Directory.Exists(cachePath)) { foreach (string path in Directory.GetDirectories(cachePath)) { Directory.Delete(path, true); } foreach (string file in Directory.GetFiles(cachePath)) { if (!file.ToLower().Contains("cookies")) { File.Delete(file); } } } } } }
六、测试html代码post.html
<!DOCTYPE html> <html> <head> <title>CefSharpDemo</title> <meta charset="utf-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <style type="text/css"> </style> <script type="text/javascript"> </script> </head> <body> <!--enctype="multipart/form-data"--> <form method="post" action="http://localhost:1209/netcms/" target="_blank"> <span>name:</span><input type="text" name="name" value="测试名称" /> <span>code:</span><input type="text" name="code" value="测试编码" /> <button type="submit">Post提交</button> </form> </body> </html>
七、测试后台代码
public ActionResult index() { string name = Request.Params["name"]; string code = Request.Params["code"]; ViewBag.name = name; ViewBag.code = code; return View(); }
八、测试前台cshtml代码
@using Models; @{ Layout = "~/Views/Shared/_SiteLayout.cshtml"; } <div style="font-size:50px; height:1200px;"> <span>name:</span><span>@ViewBag.name</span><br /><span>code:</span><span>@ViewBag.code</span> </div>
九:关键代码段:
1、RequestHandler类中获取并保存PostData
public IResourceRequestHandler GetResourceRequestHandler(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IRequest request, bool isNavigation, bool isDownload, string requestInitiator, ref bool disableDefaultHandling) { if (request.Method.ToUpper() == "POST" && request.PostData != null) { if (request.PostData.Elements.Count > 0) { _browser.PostData = new byte[request.PostData.Elements[0].Bytes.Length]; request.PostData.Elements[0].Bytes.CopyTo(_browser.PostData, 0); } } return null; }
2、CefLifeSpanHandler类中创建IRequest
public bool OnBeforePopup(IWebBrowser browserControl, IBrowser browser, IFrame frame, string targetUrl, string targetFrameName, WindowOpenDisposition targetDisposition, bool userGesture, IPopupFeatures popupFeatures, IWindowInfo windowInfo, IBrowserSettings browserSettings, ref bool noJavascriptAccess, out IWebBrowser newBrowser) { var chromiumWebBrowser = (ExtChromiumBrowser)browserControl; chromiumWebBrowser.Dispatcher.Invoke(new Action(() => { BrowserPopupWin win = new BrowserPopupWin(); win.ShowInTaskbar = false; win.Height = 0; win.Width = 0; win.Show(); IntPtr handle = new WindowInteropHelper(win).Handle; windowInfo.SetAsChild(handle); _scheduler.Run(() => { WaitUtil.Wait(() => chromiumWebBrowser.PostData); IRequest request = null; if (chromiumWebBrowser.PostData != null) { request = frame.CreateRequest(); request.Url = targetUrl; request.Method = "POST"; request.InitializePostData(); var element = request.PostData.CreatePostDataElement(); element.Bytes = chromiumWebBrowser.PostData; request.PostData.AddElement(element); chromiumWebBrowser.PostData = null; } chromiumWebBrowser.Dispatcher.Invoke(new Action(() => { NewWindowEventArgs e = new NewWindowEventArgs(targetUrl, request); chromiumWebBrowser.OnNewWindow(e); })); chromiumWebBrowser.Dispatcher.Invoke(new Action(() => { win.Close(); })); }); })); newBrowser = null; return false; }
说明:OnBeforePopup方法要return false,用BrowserPopupWin和windowInfo.SetAsChild方法弹出一个不可见的窗体,这样才能拿到PostData
3、在BrowserCtrl控件中用LoadRequest方法打开新的URL,并把post数据带过去
if (Request == null) { _browser.Load(Url); } else { _browser.Load(Url); _browser.GetMainFrame().LoadRequest(Request); Request = null; }
十、效果图:
完整代码下载:https://files-cdn.cnblogs.com/files/s0611163/CefSharpDemo.zip
源码说明:为了减少源码压缩包的大小,代码中没有依赖的CefSharp文件,请自己下载,用于测试的网页后台代码也没有,请自己制作测试后台
CefSharp禁止弹出新窗体,在同一窗口打开链接,或者在新Tab页打开链接,并且支持带type="POST" target="_blank"的链接