1.配置支持AnyCpu编译模式
CefSharp从51版本以后开始支持AnyCpu编译模式,首先需要在当前项目的csproj文件的PropertyGroup节点下第一行增加一个配置项
true
然后在程序的启动入口配置动态加载目标平台x86/x64的程序集:
[STAThread]static voidMain()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
AppDomain.CurrentDomain.AssemblyResolve+=Resolver;
Application.Run(newForm1());
}private static Assembly Resolver(objectsender, ResolveEventArgs args)
{if (args.Name.StartsWith("CefSharp"))
{string assemblyName = args.Name.Split(new[] { ',' }, 2)[0] + ".dll";string archSpecificPath =Path.Combine(AppDomain.CurrentDomain.SetupInformation.ApplicationBase,
Environment.Is64BitProcess? "x64" : "x86",
assemblyName);returnFile.Exists(archSpecificPath)?Assembly.LoadFile(archSpecificPath)
:null;
}return null;
}
这种方法是根据运行的目标平台动态去加载对应的程序集,如果我们能明确运行平台则可以不用加上面的代码逻辑,在当前项目App.config文件的根节点下加入以下配置即可:
2.使用Http代理服务
网上一些文章介绍的通过添加命令行参数CefCommandLineArgs的方式,我试了一下不管用,通过 CefSharpSettings.Proxy = new ProxyOptions("ipadress", "prot", "username", "password"); 这句是可以配置成功的,但这个是全局配置,不能满足独立我要控制每个browser实例各自使用自己的代理服务器。通过在初始化ChromiumWebBrowser的地方加入以下代码可实现动态设置代理。
var browser = new ChromiumWebBrowser("url", context);
browser.RequestHandler = newDefaultRequestHandler();
Cef.UIThreadTaskFactory.StartNew(delegate{var rc =browser.GetBrowser().GetHost().RequestContext;
rc.GetAllPreferences(true);var dict = new Dictionary();
dict.Add("mode", "fixed_servers");
dict.Add("server", "ipaddress:prot"); //此处替换成实际 ip地址:端口
stringerror;bool success = rc.SetPreference("proxy", dict, outerror);if (!success)
{
Console.WriteLine("something happen with the prerence set up" +error);
}
});
如果代理服务有用户名密码的话,则需要在DefaultRequestHandler类里重写GetAuthCredentials方法,如下:
protected override bool GetAuthCredentials(IWebBrowser chromiumWebBrowser, IBrowser browser, string originUrl, bool isProxy, string host, int port, string realm, stringscheme, IAuthCallback callback)
{if(isProxy)
{
callback.Continue("username", "passwrod");return true;
}return false;
}
这样就可以实现每个ChromiumWebBrowser运行实例独立连接自己的代理服务器了。
3.Cookie隔离,每个IWebBrowser实例的数据不共享
要保证多个browser实例之间cookie不共享,就不要在全局设置CefSettings中设置CachePath值,应该在实例的RequestContextSettings中设置,可以设置成每个browser拥有独立的缓存目录。在RequestContext内添加的cookie只有当前browser才能访问,从而实现cookie隔离。
var setting = newRequestContextSettings()
{
CachePath= Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "CefSharp\\Cache_" +name),
PersistSessionCookies= true,
PersistUserPreferences= true};var context = newRequestContext(setting);var cookieManager = context.GetCookieManager(null);//这样设置的cookie不是全局的,只有当前browser才能访问
cookieManager.SetCookie("domain", newCookie
{
Name= "cookiename",
Value= "cookievalue",
Path= "/"});var browser = new ChromiumWebBrowser("url", context);
4.使用IResponseFilter获取响应数据
要获取ChromiumWebBrowser中每个请求的响应body内容并不是那么方便,拿到Frame的html内容倒是很简单,使用IFrame的GetSourceAsync()方法就行,但有时候我们需要单个请求的响应结果,这就需要自定义实现IResponseFilter接口来实现响应数据的拦截。
//DefaultResourceHandler的构造可以放在IRequestHandler的实现类的GetResourceRequestHandler方法内
classDefaultResourceHandler : ResourceRequestHandler
{protected overrideIResponseFilter GetResourceResponseFilter(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IRequest request, IResponse response)
{if (response.MimeType.Equals("application/json", StringComparison.OrdinalIgnoreCase))
{returnJsonResponseFilter.CreateFilter(request.Identifier.ToString());
}return null;
}protected override void OnResourceLoadComplete(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IRequest request, IResponse response, UrlRequestStatus status, longreceivedContentLength)
{var filter = JsonResponseFilter.GetFileter(request.Identifier.ToString()) asJsonResponseFilter;if (filter != null)
{var encode = !string.IsNullOrEmpty(response.Charset)?Encoding.GetEncoding(response.Charset)
: Encoding.UTF8;using (var read = newStreamReader(filter.GetStream(), encode))
{var text =read.ReadToEnd();
Debug.WriteLine(text);
}
}
}
}public classJsonResponseFilter : IResponseFilter
{privateMemoryStream Stream;publicJsonResponseFilter()
{
Stream= newMemoryStream();
}public FilterStatus Filter(System.IO.Stream dataIn, out long dataInRead, System.IO.Stream dataOut, out longdataOutWritten)
{try{if (dataIn == null || dataIn.Length == 0)
{
dataInRead= 0;
dataOutWritten= 0;returnFilterStatus.Done;
}
dataInRead=dataIn.Length;
dataOutWritten=Math.Min(dataInRead, dataOut.Length);
dataIn.CopyTo(dataOut);
dataIn.Seek(0, SeekOrigin.Begin);byte[] bs = new byte[dataIn.Length];
dataIn.Read(bs,0, bs.Length);
Stream.Write(bs,0, bs.Length);
dataInRead=dataIn.Length;
dataOutWritten=dataIn.Length;returnFilterStatus.NeedMoreData;
}catch(Exception ex)
{
dataInRead=dataIn.Length;
dataOutWritten=dataIn.Length;returnFilterStatus.Done;
}
}public boolInitFilter()
{return true;
}publicStream GetStream()
{
Stream.Seek(0, SeekOrigin.Begin);returnStream;
}public voidDispose()
{
}private static Dictionary _dictionary = new Dictionary();public static IResponseFilter CreateFilter(stringid)
{var filter = newJsonResponseFilter();
_dictionary[id]=filter;returnfilter;
}public static IResponseFilter GetFileter(stringid)
{if(_dictionary.ContainsKey(id))
{var filter =_dictionary[id];
_dictionary.Remove(id);returnfilter;
}return null;
}
}
为了截获响应数据绕了这么一大圈有点费劲,不过人家这种设计也是为了方便外部扩展,可以针对不同响应类型的response来实现IResponseFilter过滤器。