Hello everyone, I'm Wolf at the End of the Desert.
In the previous article, I introduced 《C#使用CefSharp内嵌网页-并给出C#与JS的交互示例》. This article introduces the cache implementation of CefSharp. First, let's discuss the benefits of adding caching:
- Improve page loading speed: CefSharp cache can cache already loaded pages and resources. When the user visits the same page again, it can be loaded directly from the cache without having to re-download and parse the page and resources, thus speeding up page loading.
- Reduce network traffic: Using caching can reduce network traffic because already downloaded resources can be read directly from the cache without needing to be re-downloaded.
- Enhance user experience: Because caching can improve page loading speed, user experience is enhanced. Users can access pages and resources faster, making the application more enjoyable to use.
- Reduce server load: Using caching can reduce server load because already downloaded resources can be read directly from the cache without needing to be regenerated and sent.
- Offline access: It can enable the application to support offline access because it caches already downloaded pages and resources. When the user has no network connection, pages and resources can be loaded directly from the cache.
In summary, using caching can improve application performance and user experience, reduce network traffic and server load, and support offline access. It is a very useful feature.
Example in this article: GitHub
Demonstration of loading cached pages Baidu, Baidu Translate, Dotnet9 Home, Dotnet9 About without a network:

Next, I'll explain the cache implementation methods.
1. Default Cache Implementation
The default cache implementation of CefSharp is based on Chromium's caching mechanism. Chromium uses two types of caching: memory cache and disk cache.
1.1. Memory Cache
Memory cache is a cache based on the LRU (Least Recently Used) algorithm. It caches recently accessed pages and resources. The size of the memory cache is limited. When the cache reaches its maximum size, the least recently used pages and resources are deleted.
The memory cache cannot be set through the CefSharp.WPF API. Specifically, Chromium maintains an LRU cache in memory to store recently accessed web page data. When cache space is insufficient, Chromium automatically clears the least recently used cached data according to the LRU algorithm to free up space for new data.
In CefSharp.WPF, we can clear data in the memory cache by calling the Cef.GetGlobalRequestContext().ClearCacheAsync() method. This method clears all cached data, including both memory and disk caches. If you only need to clear the memory cache, you can call Cef.GetGlobalRequestContext().ClearCache(CefCacheType.MemoryCache).
It is important to note that because the memory cache is managed by Chromium itself, we cannot directly control its size. If you need to control the cache size, you can indirectly control the memory cache size by setting the disk cache size.
1.2. Disk Cache
Disk cache is a file system-based cache that caches already downloaded pages and resources. The size of the disk cache is also limited. When the cache reaches its maximum size, the oldest pages and resources are deleted.
The disk cache in CefSharp.WPF is implemented by setting the CachePath property in CefSettings. Specifically, we can set the disk cache path with the following code:
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
// CachePath needs to be an absolute path
var settings = new CefSettings
{
CachePath = $"{AppDomain.CurrentDomain.BaseDirectory}DefaultCaches"
};
Cef.Initialize(settings);
}
}
The cache directory structure is as follows:

Here, the CachePath property specifies the path of the disk cache (absolute path). If this property is not set, Chromium will store the cached data in the default path (usually the AppData\Local\CefSharp directory under the user's directory).
It should be noted that the size of the disk cache is controlled by Chromium itself. We can control the maximum disk space occupied by cached data by setting the SetCacheLimit method of CacheController. This method accepts a long type parameter representing the maximum size of the cached data (in bytes). For example, the following code sets the maximum size of the disk cache to 100MB:
var cacheController = Cef.GetGlobalRequestContext().CacheController;
cacheController.SetCacheLimit(100 * 1024 * 1024); // 100MB
It should be noted that Chromium automatically clears the least recently used cached data according to the LRU algorithm to free up space for new data. Therefore, even if the cache size is set, it does not guarantee that all data will be cached. If you need to clear the data in the disk cache, you can call the Cef.GetGlobalRequestContext().ClearCacheAsync() method.
The webmaster has not studied the default cache extensively. The above code and description were searched via ChatGPT. Let's now look at the implementation of custom caching. The default cache is just an introduction.
2. Custom Cache
This is the focus of this article. Compared to the default cache, custom cache has the following advantages:
- More flexible: You can flexibly configure caching strategies and cache sizes based on the application's needs, thereby better meeting the application's requirements.
- Better performance: You can configure it according to the application's needs and specific scenarios to achieve better performance. The default cache may not be suitable for certain specific scenarios or your application's requirements, while custom cache can be adjusted according to your needs to achieve better performance.
- Better security: You can better protect users' privacy and security because you can control what is stored in the cache and the cache's lifecycle.
- More controllable: You can better control the behavior of the cache, such as controlling when to clear the cache and the clearing strategy, thereby better managing the cache.
- Better compatibility: You can better adapt to different browsers and devices. The default cache may not provide sufficient compatibility, while custom cache can be adjusted according to your needs to provide better compatibility.
- More efficient: You can better utilize system resources, for example, using faster storage devices to store the cache, thereby improving the read and write speed of the cache.
Summary: Custom cache can provide better performance, responsiveness, security, and compatibility, thereby improving the quality and user experience of the application. In plain terms, it's better control.
2.1. Code Implementation
Note: Default cache code is commented out above.
2.1.1. Register Resource Request Interception Handler
First, in the code-behind of the control using ChromiumWebBrowser, register the request interception handler. CefBrowser is the control name, and CefRequestHandlerc is the handler:
public TestCefCacheView()
{
InitializeComponent();
var handler = new CefRequestHandlerc();
CefBrowser.RequestHandler = handler;
}
2.1.2. Request Interception Handler
IRequestHandler in CefSharp is an interface used to handle requests sent by the browser. It defines some methods that can process the request before or after it is sent to the server.
The implementation class of IRequestHandler can be used in the following aspects:
Intercept requests: You can intercept requests by implementing the
OnBeforeBrowsemethod to control browser behavior. For example, you can check the URL of the request before it is sent to the server; if it does not meet requirements, you can cancel the request or redirect to another page.Modify requests: You can modify requests by implementing the
OnBeforeResourceLoadmethod, such as adding custom HTTP headers or modifying the request URL.Handle responses: You can handle server responses by implementing the
OnResourceResponsemethod, such as checking the status code and content of the response to decide whether to continue loading the page.Cache control: You can control the cache size and clearing strategy by implementing the
OnQuotaRequestmethod, thereby optimizing cache usage.
In summary, an implementation class of IRequestHandler can be used to control browser behavior, optimize network requests and cache usage, thereby improving application performance and user experience.
Instead of directly implementing the IRequestHandler interface, we inherit its default implementation class RequestHandler, which simplifies our development since implementing the interface requires listing a series of interface methods.
We override the GetResourceRequestHandler method. In this method, we return a CefResourceRequestHandler instance. This method is called when resources are requested on the page:
using CefSharp;
using CefSharp.Handler;
namespace WpfWithCefSharpCacheDemo.Caches;
internal class CefRequestHandlerc : RequestHandler
{
protected override IResourceRequestHandler GetResourceRequestHandler(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame,
IRequest request, bool isNavigation, bool isDownload, string requestInitiator, ref bool disableDefaultHandling)
{
// One request uses one CefResourceRequestHandler
return new CefResourceRequestHandler();
}
}
2.1.3. Resource Request Interceptor
In CefSharp, the IResourceRequestHandler interface is used to handle resource requests. It can intercept resource requests made by the browser, such as images, CSS, JavaScript, etc., thereby enabling control and optimization of resource requests.
Specifically, the IResourceRequestHandler interface defines methods such as OnBeforeResourceLoad, OnResourceResponse, etc. These methods can be used to intercept requests, modify requests, handle responses, etc. For example:
OnBeforeResourceLoad: Called before the browser requests a resource. It can be used to modify the request, such as adding custom HTTP headers or modifying the request URL.OnResourceResponse: Called after the browser receives the response from the server. It can be used to handle the response, such as checking the status code and content to decide whether to continue loading the page.OnResourceLoadComplete: Called after the resource is loaded. It can be used to handle operations after resource loading, such as saving the resource to the local cache.
By implementing the IResourceRequestHandler interface, you can intercept and optimize resource requests, thereby improving application performance and user experience.
Here, we also do not directly implement the IResourceRequestHandler interface. We define the CefResourceRequestHandler class, inheriting the default implementation class ResourceRequestHandler of this interface.
In the CefResourceRequestHandler class below:
GetResourceHandlermethod: Handles whether the resource needs to be cached. Returnsnullif no caching is needed; returnsCefResourceHandlerto indicate caching is needed. Cross-origin handling is done in this class.GetResourceResponseFiltermethod: Registers the operation class for resource caching, i.e., the implementation of resource download.OnBeforeResourceLoadmethod: In this method, we can pass header parameters to the page.
using System.Collections.Specialized;
using CefSharp;
using CefSharp.Handler;
namespace WpfWithCefSharpCacheDemo.Caches;
internal class CefResourceRequestHandler : ResourceRequestHandler
{
private string _localCacheFilePath;
private bool IsLocalCacheFileExist => System.IO.File.Exists(_localCacheFilePath);
protected override IResourceHandler? GetResourceHandler(IWebBrowser chromiumWebBrowser, IBrowser browser,
IFrame frame, IRequest request)
{
try
{
_localCacheFilePath = CacheFileHelper.CalculateResourceFileName(request.Url, request.ResourceType);
if (string.IsNullOrWhiteSpace(_localCacheFilePath))
{
return null;
}
}
catch
{
return null;
}
if (!IsLocalCacheFileExist)
{
return null;
}
return new CefResourceHandler(_localCacheFilePath);
}
protected override IResponseFilter? GetResourceResponseFilter(IWebBrowser chromiumWebBrowser, IBrowser browser,
IFrame frame,
IRequest request, IResponse response)
{
return IsLocalCacheFileExist ? null : new CefResponseFilter { LocalCacheFilePath = _localCacheFilePath };
}
protected override CefReturnValue OnBeforeResourceLoad(IWebBrowser chromiumWebBrowser, IBrowser browser,
IFrame frame, IRequest request,
IRequestCallback callback)
{
var headers = new NameValueCollection(request.Headers);
headers["Authorization"] = "Bearer xxxxxx.xxxxx.xxx";
request.Headers = headers;
return CefReturnValue.Continue;
}
}
2.1.4. CefResourceHandler
In CefSharp, the IResourceHandler interface is used to process resources. It can intercept resource requests made by the browser and return custom resource content, thereby enabling control and optimization of resources.
Specifically, the IResourceHandler interface defines methods such as ProcessRequest, GetResponseHeaders, ReadResponse, etc. These methods can be used to handle resource requests, get response header information, read response content, etc. For example:
ProcessRequest: Called when the browser requests a resource. It can be used to handle the resource request, such as reading resource content from local cache or downloading resource content from the network.GetResponseHeaders: Called when the browser requests a resource. It can be used to get response header information, such as setting the MIME type of the response, caching strategy, etc.ReadResponse: Called when the browser requests a resource. It can be used to read response content, such as reading resource content from local cache or downloading resource content from the network.
By implementing the IResourceHandler interface, you can perform custom processing on resources, such as reading resource content from local cache, thereby improving application performance and user experience.
Here, we also do not directly implement the IResourceHandler interface. We define the CefResourceHandler class, inheriting the default implementation class ResourceHandler of this interface.
In the constructor of CefResourceHandler, only cross-origin issues are handled. Other requirements can be handled by searching for information through the methods of the above interface.
using CefSharp;
using System.IO;
namespace WpfWithCefSharpCacheDemo.Caches;
internal class CefResourceHandler : ResourceHandler
{
public CefResourceHandler(string filePath, string mimeType = null, bool autoDisposeStream = false,
string charset = null) : base()
{
if (string.IsNullOrWhiteSpace(mimeType))
{
var fileExtension = Path.GetExtension(filePath);
mimeType = Cef.GetMimeType(fileExtension);
mimeType = mimeType ?? DefaultMimeType;
}
var stream = File.OpenRead(filePath);
StatusCode = 200;
StatusText = "OK";
MimeType = mimeType;
Stream = stream;
AutoDisposeStream = autoDisposeStream;
Charset = charset;
Headers.Add("Access-Control-Allow-Origin", "*");
}
}
2.1.5. CefResponseFilter
In CefSharp, the IResponseFilter interface is used to filter response content. It can intercept response content received by the browser and modify or filter it, thereby enabling control and optimization of response content.
Specifically, the IResponseFilter interface defines methods such as InitFilter, Filter, GetSize, etc. These methods can be used to initialize the filter, filter response content, get the size of the filtered response content, etc. For example:
InitFilter: Called when the browser receives the response content. It can be used to initialize the filter, such as setting the state of the filter, getting response header information, etc.Filter: Called when the browser receives the response content. It can be used to filter the response content, such as modifying the response content, deleting response content, etc.GetSize: Called when the browser receives the response content. It can be used to get the size of the filtered response content, for example, to calculate the compression ratio of the response content.
In the version 89.0.170.0 of CefSharp.Wpf used by the webmaster, the IResponseFilter interface does not have the GetSize method. In that version, the IResponseFilter interface only defines two methods: InitFilter and Filter.
If you need to get the size of the filtered response content in that version, you can calculate it yourself in the Filter method. For example, in the Filter method, you can write the filtered response content into a buffer, record the size of the buffer, and finally return the filtered response content and the buffer size.
public class MyResponseFilter : IResponseFilter
{
private MemoryStream outputStream = new MemoryStream();
public void Dispose()
{
outputStream.Dispose();
}
public bool InitFilter()
{
return true;
}
public FilterStatus Filter(Stream dataIn, out long dataInRead, Stream dataOut, out long dataOutWritten)
{
dataInRead = 0;
dataOutWritten = 0;
byte[] buffer = new byte[4096];
int bytesRead = 0;
do
{
bytesRead = dataIn.Read(buffer, 0, buffer.Length);
if (bytesRead > 0)
{
outputStream.Write(buffer, 0, bytesRead);
}
} while (bytesRead > 0);
byte[] outputBytes = outputStream.ToArray();
dataOut.Write(outputBytes, 0, outputBytes.Length);
dataInRead = outputBytes.Length;
dataOutWritten = outputBytes.Length;
return FilterStatus.Done;
}
public int GetResponseFilterBufferSize()
{
return 0;
}
public int GetResponseFilterDelay()
{
return 0;
}
}
In the example code above, we wrote the filtered response content into a MemoryStream object in the Filter method and recorded the size of the buffer. Finally, we returned the filtered response content and the buffer size in the return value of the Filter method.
In summary, by implementing the IResponseFilter interface, you can perform custom processing on response content, such as compressing, encrypting, etc., thereby improving application performance and security.
In this example, we define the class CefResponseFilter to directly implement the interface and handle the actual file caching operation, i.e., the resource download implementation:
using CefSharp;
using System.IO;
namespace WpfWithCefSharpCacheDemo.Caches;
internal class CefResponseFilter : IResponseFilter
{
public string LocalCacheFilePath { get; set; }
private const int BUFFER_LENGTH = 1024;
private bool isFailCacheFile;
public FilterStatus Filter(Stream? dataIn, out long dataInRead, Stream? dataOut, out long dataOutWritten)
{
dataInRead = 0;
dataOutWritten = 0;
if (dataIn == null)
{
return FilterStatus.NeedMoreData;
}
var length = dataIn.Length;
var data = new byte[BUFFER_LENGTH];
var count = dataIn.Read(data, 0, BUFFER_LENGTH);
dataInRead = count;
dataOutWritten = count;
dataOut?.Write(data, 0, count);
try
{
CacheFile(data, count);
}
catch
{
// ignored
}
return length == dataIn.Position ? FilterStatus.Done : FilterStatus.NeedMoreData;
}
public bool InitFilter()
{
try
{
var dirPath = Path.GetDirectoryName(LocalCacheFilePath);
if (!string.IsNullOrWhiteSpace(dirPath) && !Directory.Exists(dirPath))
{
Directory.CreateDirectory(dirPath);
}
}
catch
{
// ignored
}
return true;
}
public void Dispose()
{
}
private void CacheFile(byte[] data, int count)
{
if (isFailCacheFile)
{
return;
}
try
{
if (!File.Exists(LocalCacheFilePath))
{
using var fs = File.Create(LocalCacheFilePath);
fs.Write(data, 0, count);
}
else
{
using var fs = File.Open(LocalCacheFilePath, FileMode.Append);
fs.Write(data,0,count);
}
}
catch
{
isFailCacheFile = true;
File.Delete(LocalCacheFilePath);
}
}
}
2.1.6. CacheFileHelper
Cache file helper class. It is used to manage the whitelist for cached AJAX interfaces of pages, cache file path specifications, etc.:
using CefSharp;
using System;
using System.Collections.Generic;
using System.IO;
namespace WpfWithCefSharpCacheDemo.Caches;
internal static class CacheFileHelper
{
private const string DEV_TOOLS_SCHEME = "devtools";
private const string DEFAULT_INDEX_FILE = "index.html";
private static HashSet<string> needInterceptedAjaxInterfaces = new();
private static string CachePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "caches");
public static void AddInterceptedAjaxInterfaces(string url)
{
if (needInterceptedAjaxInterfaces.Contains(url))
{
return;
}
needInterceptedAjaxInterfaces.Add(url);
}
private static bool IsNeedInterceptedAjaxInterface(string url, ResourceType resourceType)
{
var uri = new Uri(url);
if (DEV_TOOLS_SCHEME == url)
{
return false;
}
if (ResourceType.Xhr == resourceType && !needInterceptedAjaxInterfaces.Contains(url))
{
return false;
}
return true;
}
public static string? CalculateResourceFileName(string url, ResourceType resourceType)
{
if (!IsNeedInterceptedAjaxInterface(url, resourceType))
{
return default;
}
var uri = new Uri(url);
var urlPath = uri.LocalPath;
if (urlPath.StartsWith("/"))
{
urlPath = urlPath.Substring(1);
}
var subFilePath = urlPath;
if (ResourceType.MainFrame == resourceType || string.IsNullOrWhiteSpace(urlPath))
{
subFilePath = Path.Combine(urlPath, DEFAULT_INDEX_FILE);
}
var hostCachePath = Path.Combine(CachePath, uri.Host);
var fullFilePath = Path.Combine(hostCachePath, subFilePath);
return fullFilePath;
}
}
The subdirectory for custom cache is created with the resource's domain name (Host) as the directory name:

Open the cached dotnet9.com directory. By looking at the directory structure, it is basically consistent with the program's publish directory. This is more user-friendly, isn't it?

2.2. Possible Issues
First point: issues the webmaster has encountered. Points 2-5 are explanations provided by Token AI.
2.2.1. Poor support for resource URLs with QueryString
It is recommended to use Route (routing method: https://dotnet9.com/albums/wpf) instead of QueryString (query parameter method: https://dotnet9.com/albums?slug=wpf). The webmaster will research the caching method for QueryString when he has time.
If resources must have QueryString, then disable caching for such resources and use direct network requests.
2.2.2. Cache Consistency Issues
If the custom cache does not handle cache consistency correctly, the browser may display expired or inconsistent content. For example, if a web page is cached but has been updated on the server, the custom cache may cause the browser to show stale content.
2.2.3. Cache Space Issues
If the custom cache does not manage cache space properly, the browser may consume excessive memory or disk space. For example, if the custom cache stores a large amount of data without timely cleaning expired data or limiting the cache size, the browser may use too much memory or disk space.
2.2.4. Cache Performance Issues
If the custom cache does not handle cache performance correctly, browser performance may degrade. For example, if the custom cache does not handle cache reads and writes properly, the browser's response speed may slow down.
2.2.5. Cache Security Issues
If the custom cache does not handle cache security correctly, browser security may be threatened. For example, if the custom cache caches sensitive data but does not properly handle encryption and decryption of the cache, sensitive data may leak.
Therefore, when customizing the cache, it is necessary to pay attention to issues such as cache consistency, cache space, cache performance, and cache security to ensure the normal operation and security of the browser.
References:
WeChat Technical Communication Group: Add WeChat (codewf) with note "join group"
QQ Technical Communication Group: 771992300.
