<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>caching asp net core &#8211; ASP.NET Hosting Reviews and Guides</title>
	<atom:link href="https://topreviewhostingasp.net/tag/caching-asp-net-core/feed/" rel="self" type="application/rss+xml" />
	<link>https://topreviewhostingasp.net</link>
	<description>Help you to find best ASP.NET Core Hosting</description>
	<lastBuildDate>Tue, 01 Aug 2023 04:26:27 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	

<image>
	<url>https://topreviewhostingasp.net/wp-content/uploads/2017/01/cropped-trhaico-32x32.png</url>
	<title>caching asp net core &#8211; ASP.NET Hosting Reviews and Guides</title>
	<link>https://topreviewhostingasp.net</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>Custom Output Caching in ASP.NET Core</title>
		<link>https://topreviewhostingasp.net/custom-output-caching-in-asp-net-core/</link>
					<comments>https://topreviewhostingasp.net/custom-output-caching-in-asp-net-core/#respond</comments>
		
		<dc:creator><![CDATA[Jacques Hunt]]></dc:creator>
		<pubDate>Tue, 01 Aug 2023 04:26:24 +0000</pubDate>
				<category><![CDATA[Hosting Tips]]></category>
		<category><![CDATA[asp net]]></category>
		<category><![CDATA[asp net core]]></category>
		<category><![CDATA[asp net core tutorial]]></category>
		<category><![CDATA[caching asp net core]]></category>
		<guid isPermaLink="false">https://topreviewhostingasp.net/?p=3648</guid>

					<description><![CDATA[Background Once a page has been cached in the browser (or proxy), the server cannot make the cached copy be utilized in place of the updated version. Pages that are likely to change cannot, therefore, be cached at the client for very long. There is nothing we can do to change this. We should have [&#8230;]]]></description>
										<content:encoded><![CDATA[<h2>Background</h2>
<p>Once a page has been cached in the browser (or proxy), the server cannot make the cached copy be utilized in place of the updated version. Pages that are likely to change cannot, therefore, be cached at the client for very long. There is nothing we can do to change this.</p>
<p>We should have greater control at the server, but sadly, the built-in response caching prevents cache invalidation. Additionally, it employs the same cache duration for asking the browser to cache the page and for server-side caching. Some apps cannot use the built-in response caching due to these limitations.</p>
<p>We are going to investigate developing a unique response caching system. We&#8217;ll presum the following conditions:</p>
<ul>
<li>Allow server and client cache durations to be different</li>
<li>Allow an arbitrary page to be removed from the cache at will</li>
<li>Allow multiple cached pages to be removed based on a common criterion (tags)</li>
</ul>
<p>The fundamental prerequisite is the capacity for endless caching at the server and the ability for application code to invalidate current cache entries when content changes.</p>
<p>Additionally, we&#8217;ll prioritize efficiency and quickness over adaptability. IMemoryCache will be used to implement the cache for a single web server. It is simple to go from using IMemoryCache to IDistributedCache in web farm scenarios, however because of this interface&#8217;s less features, the third criteria (tagged) cannot be met without a redesign.</p>
<h2>Implementation</h2>
<p>We will implement our caching as middleware that runs before MVC and can short-circuit the entire request pipeline if a cached page is found in order to enable it to be as quick as feasible.</p>
<p>We have the following flow for a page that is not in the cache:</p>
<ol>
<li>Fail to retrieve page from the cache</li>
<li>Execute inner middleware (the MVC pipeline) redirecting the response to a buffer</li>
<li>Cache the page (if conditions are met)</li>
<li>Render page</li>
</ol>
<p>The sequence is substantially shorter for a page that has already been cached:</p>
<ol>
<li>Retrieve page from cache</li>
<li>Render page</li>
</ol>
<p>The quickest way to return cached material is using middleware, but it is more challenging to allow specific page cache setup. The middleware might be utilized independently if all pages were cached using the same criteria, but for the majority of real-world sites, we require the ability to control which pages are cached and for how long. Although middleware configuration might be used, using attributes on controller actions is considerably simpler.</p>
<h3>Controlling server caching</h3>
<p>We&#8217;ll use a very basic action filter attribute to let us manage how specific pages are cached:</p>
<pre><code>public class CacheAttribute : ActionFilterAttribute
{
    public int? ClientDuration { get; set; }
    public int? ServerDuration { get; set; }
    public string Tags { get; set; }

    public override void OnActionExecuting(ActionExecutingContext context)
    {
        // validation omitted

        if (ClientDuration.HasValue)
        {
            context.HttpContext.Items[Constants.ClientDuration] = ClientDuration.Value;
        }

        if (ServerDuration.HasValue)
        {
            context.HttpContext.Items[Constants.ServerDuration] = ServerDuration.Value;
        }

        if (!string.IsNullOrWhiteSpace(Tags))
        {
            context.HttpContext.Items[Constants.Tags] = Tags;
        }

        base.OnActionExecuting(context);
    }
}</code></pre>


<p>The filter&#8217;s simple implementation only adds the attribute values to the collection of HttpContext Items. To communicate between the operation and the middleware for caching, we are using the collection.</p>



<h3 class="wp-block-heading">The middleware</h3>



<p>The primary Invoke method of the middleware is quite readable:</p>



<pre class="wp-block-code"><code>public async Task Invoke(HttpContext context)
{
    var key = BuildCacheKey(context);

    if (_cache.TryGet(key, out CachedPage page))
    {
        await WriteResponse(context, page);

        return;
    }

    ApplyClientHeaders(context);

    if (IsNotServerCachable(context))
    {
        await _next.Invoke(context);

        return;
    }            

    page = await CaptureResponse(context);

    if (page != null)
    {
        var serverCacheDuration = GetCacheDuration(context, Constants.ServerDuration);

        if (serverCacheDuration.HasValue)
        {
            var tags = GetCacheTags(context, Constants.Tags);

            _cache.Set(key, page, serverCacheDuration.Value, tags);
        }
    }            
}</code></pre>



<p>Your requirements will determine how you construct the cache key, however it may only require the use of context.Request.Path.</p>



<p>The request is finished if the page can be retrieved from the cache and written to the response.</p>



<pre class="wp-block-code"><code>private async Task WriteResponse(HttpContext context, CachedPage page)
{
    foreach (var header in page.Headers)
    {
        context.Response.Headers.Add(header);
    }

    await context.Response.Body.WriteAsync(page.Content, 0, page.Content.Length);
}</code></pre>



<p>If a cached page cannot be located, additional effort must be done. In order to determine if we may cache the request, we first set any necessary client caching headers. If another method is used, we call the following middleware component and stop processing the request because we only wish to cache GET methods.</p>



<p>Capturing the request output from internal middleware components is the next step. In the part after this, we go over how this is accomplished. If we&#8217;ve set up server side caching (using the action filter mentioned above), the last step is to save the page to the cache.</p>



<h3 class="wp-block-heading">Capturing the response</h3>



<p>You must replace the response body stream&#8217;s default with a MemoryStream in order to capture the page response:</p>



<pre class="wp-block-code"><code>private async Task&lt;CachedPage> CaptureResponse(HttpContext context)
{
    var responseStream = context.Response.Body;

    using (var buffer = new MemoryStream())
    {
        try
        {
            context.Response.Body = buffer;

            await _next.Invoke(context);
        }
        finally
        {
            context.Response.Body = responseStream;
        }

        if (buffer.Length == 0) return null;

        var bytes = buffer.ToArray(); // you could gzip here

        responseStream.Write(bytes, 0, bytes.Length);

        if (context.Response.StatusCode != 200) return null;

        return BuildCachedPage(context, bytes);
    }
}</code></pre>



<p>We return null and make no attempt to cache the response if nothing has been written to it or if the status code is not 200. If not, a CachedPage instance is returned:</p>



<pre class="wp-block-code"><code>internal class CachedPage
{
    public byte&#91;] Content { get; private set; }
    public List&lt;KeyValuePair&lt;string, StringValues>> Headers { get; private set; }

    public CachedPage(byte&#91;] content)
    {
        Content = content;
        Headers = new List&lt;KeyValuePair&lt;string, StringValues>>();
    }
}</code></pre>



<p>The content of the cached page is combined with a subset of the response headers, some of which should be removed (for example, the date header).</p>



<h3 class="wp-block-heading">Controlling client caching</h3>



<p>We choose the straightforward method when it comes to providing caching headers to the browser:</p>



<pre class="wp-block-code"><code>public void ApplyClientHeaders(HttpContext context)
{
    context.Response.OnStarting(() =>
    {
        var clientCacheDuration = GetCacheDuration(context, Constants.ClientDuration);

        if (clientCacheDuration.HasValue &amp;&amp; context.Response.StatusCode == 200)
        {
            if (clientCacheDuration == TimeSpan.Zero)
            {
                context.Response.GetTypedHeaders().CacheControl = new CacheControlHeaderValue
                {
                    NoCache = true,
                    NoStore = true,
                    MustRevalidate = true
                };
                context.Response.Headers&#91;"Expires"] = "0";
            }
            else
            {
                context.Response.GetTypedHeaders().CacheControl = new CacheControlHeaderValue
                {
                    Public = true,
                    MaxAge = clientCacheDuration
                };
            }
        }

        return Task.CompletedTask;
    });            
}</code></pre>



<p>Keep in mind that our action filter determines the ClientDuration value. There are three possibilities:</p>



<ul><li>Unset &#8211; do not send headers</li><li>Zero &#8211; set various headers instructing downstream clients not to cache the response</li><li>Other &#8211; set the cache headers to the provided value</li></ul>



<h3 class="wp-block-heading">The cache</h3>



<p>We haven&#8217;t yet talked about the actual cache, which is one thing. The _cache references in the code above really correspond to a wrapper class that contains the IMemoryCache implementation that is built-in.</p>



<pre class="wp-block-code"><code>public class CacheClient : ICacheClient
{
    private readonly IMemoryCache _cache;
    
    public CacheClient(IMemoryCache cache)
    {
        _cache = cache ?? throw new ArgumentNullException(nameof(cache));
    }

    internal bool TryGet&lt;T>(string key, out T entry)
    {
        return _cache.TryGetValue(Constants.CacheKeyPrefix + key, out entry);
    }

    internal void Set(string key, object entry, TimeSpan expiry, params string&#91;] tags)
    {
        var options = new MemoryCacheEntryOptions
        {
            AbsoluteExpirationRelativeToNow = expiry
        };

        var allTokenSource = _cache.GetOrCreate(Constants.CacheTagPrefix + Constants.AllTag, 
            allTagEntry => new CancellationTokenSource());

        options.AddExpirationToken(new CancellationChangeToken(allTokenSource.Token));

        foreach (var tag in tags)
        {
            var tokenSource = _cache.GetOrCreate(Constants.CacheTagPrefix + tag, tagEntry =>
            {
                tagEntry.AddExpirationToken(new CancellationChangeToken(allTokenSource.Token));

                return new CancellationTokenSource();
            });

            options.AddExpirationToken(new CancellationChangeToken(tokenSource.Token));
        }

        _cache.Set(Constants.CacheKeyPrefix + key, entry, options);
    }</code></pre>



<p>Expiration tokens are used by the Set function to enable bulk removal of cache entries.</p>



<p>The concept is that we store a CancellationTokenSource for every entry in the cache and additional CancellationTokenSources for each tag we define. If you have never used CancellationTokenSource before, this may be a little difficult to understand. When setting the cache entry, we produce a token by using these CancellationTokenSources. In the following part, we&#8217;ll look at using the CancellationTokenSources to invalidate cache items in bulk.</p>



<h2 class="wp-block-heading">Cache invalidation AKA cache busting</h2>



<p>This unique kind of response caching was implemented primarily to meet the need for the ability to delete cache items before they naturally expire. The following methods are exposed by our CacheClient class for this:</p>



<pre class="wp-block-code"><code>public void Remove(string key)
{
    _cache.Remove(Constants.CacheKeyPrefix + key);
}

public void RemoveByTag(string tag)
{
    if (_cache.TryGetValue(Constants.CacheTagPrefix + tag, out CancellationTokenSource tokenSource))
    {
        tokenSource.Cancel();

        _cache.Remove(Constants.CacheTagPrefix + tag);
    }            
}

public void RemoveAll()
{
    RemoveByTag(Constants.AllTag);
}</code></pre>



<p>As you can see, deleting a single cache entry just requires that you know the key. You could want to allow action, controller, and route values to be given instead to make it more user-friendly.</p>



<p>The RemoveAll and RemoveByTag methods invoke the Cancel() function, which expires all tokens issued by the source, after retrieving the CancellationTokenSource from the cache. These cache entries are eliminated as a result.</p>



<h3 class="wp-block-heading">Limitations</h3>



<p>This is a very simple illustration of response caching and is devoid of many features found in native response caching middleware. The lack of the option to alter caching depending on headers, cookies, etc. is perhaps the most noticeable missing. It would not be extremely challenging to incorporate VaryBy. In reality, all we&#8217;re altering is how the cache key is generated and adding support for CacheAttribute setting.</p>



<p>Additionally, we are using IMemoryCache rather than IDistributedCache, which is more scalable. As was already said, modifying the usage is simple but will reduce functionality. Since IDistributedCache does not allow expiration tokens, the method described here cannot be used to remove pages in bulk. Naturally, nothing prevents you from coming up with a different approach, but unsophisticated implementations will almost surely have a negative impact on performance.</p>



<h2 class="wp-block-heading">Summary</h2>



<p>The creation of simple response caching middleware that enables manual invalidation of items both individually and in bulk using tags was covered in this post. To store pages (or other action results like JSON) in an in-memory cache, we combined an action filter with a middleware component. Then, we made a number of methods available on our CacheClient class to enable the deletion of cache entries.</p>



<p>The example code should not be used in place of the built-in response caching, which offers far more capability, although it might be helpful in some circumstances.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://topreviewhostingasp.net/custom-output-caching-in-asp-net-core/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Caching Static Resources Forever with ASP.NET Core</title>
		<link>https://topreviewhostingasp.net/caching-static-resources-forever-with-asp-net-core/</link>
					<comments>https://topreviewhostingasp.net/caching-static-resources-forever-with-asp-net-core/#respond</comments>
		
		<dc:creator><![CDATA[Jacques Hunt]]></dc:creator>
		<pubDate>Thu, 18 Feb 2021 04:11:12 +0000</pubDate>
				<category><![CDATA[Hosting Tips]]></category>
		<category><![CDATA[asp.net core]]></category>
		<category><![CDATA[asp.net core tips]]></category>
		<category><![CDATA[asp.net core tutorial]]></category>
		<category><![CDATA[caching asp net core]]></category>
		<category><![CDATA[net core]]></category>
		<guid isPermaLink="false">https://topreviewhostingasp.net/?p=2924</guid>

					<description><![CDATA[Caching static resources like stylesheet, JavaScript, or image files allows improving the performance of your website. On the client-side, a static file will always be loaded from the cache which reduces the number of requests to the server and so, the time to get the page and its resources. On the server-side, as they are [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p>Caching static resources like stylesheet, JavaScript, or image files allows improving the performance of your website. On the client-side, a static file will always be loaded from the cache which reduces the number of requests to the server and so, the time to get the page and its resources. On the server-side, as they are fewer requests, the server can handle more clients without upgrading the hardware.</p>



<p>While caching is a great thing, you must ensure the client is always running the latest version of the application. I mean, when you deploy the next version of the website, you don&#8217;t want the client to use an obsolete cached version of a file.</p>



<h2 class="wp-block-heading">Versioned URL (Cache buster)</h2>



<p>To ensure the user always uses the latest version of a file, we must have a unique URL per version of a file. There are many strategies:</p>



<ul>
<li>Use the query string: <code>https://sample.com/file.js?v=123</code></li>
<li>Rename the file: <code>https://sample.com/file.123.js</code></li>
<li>Create a directory: <code>https://sample.com/123/file.js</code></li>
</ul>



<p>ASP.NET Core provides a mechanism using a <code>TagHelper</code> to append the version with the query string. It supports the most common HTML tags that target a static resource: <code>script</code>, <code>link</code> and <code>img</code>. All you have to do is append <code>asp-append-version="true"</code> to the tag:</p>



<pre class="wp-block-code"><code>&lt;link rel="stylesheet" href="~/css/site.css" asp-append-version="true" /&gt;
&lt;script src="~/js/site.js" asp-append-version="true"&gt;&lt;/script&gt;
&lt;img src="~/images/banner1.svg" asp-append-version="true" /&gt;</code></pre>



<p>This will produce:</p>



<pre class="wp-block-code"><code>&lt;link rel="stylesheet" href="/css/site.css?v=1wp5zz4e-mOPFx4X2O8seW_DmUtePn5xFJk1vB7JKRc" /&gt;
&lt;script src="/js/site.js?v=EWaMeWsJBYWmL2g_KkgXZQ5nPe-a3Ichp0LEgzXczKo"&gt;&lt;/script&gt;
&lt;img src="/images/banner1.svg?v=GaE_EmkeBf-yBbrJ26lpkGd4jkOSh1eVKJaNOw9I4uk" /&gt;</code></pre>



<p>The version is the SHA256 of the file encoded in base64. Of course, the hash is computed only once per file and stored in an <code>IMemoryCache</code>.</p>



<p>The URL of files are now unique and will change when the file change, so we can add a cache header to the response to indicate the client that the file can be stored in cache forever.</p>



<h2 class="wp-block-heading">Response headers</h2>



<p>To indicate the browser to store the file in its cache, we must send the <code>Cache-control</code> header and the <code>Expires</code> header for <code>HTTP/1.0</code> compatibility. To add these headers, we use the <code>OnPrepareResponse</code> callback of the <code>StaticFilesOptions</code>. Let&#8217;s modify the <code>Startup.cs</code> file:</p>



<pre class="wp-block-code"><code>public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    app.UseStaticFiles(new StaticFileOptions
    {
        OnPrepareResponse = context =&gt;
        {
            // Cache static file for 1 year
            if (!string.IsNullOrEmpty(context.Context.Request.Query["v"]))
            {
                context.Context.Response.Headers.Add("cache-control", new[] { "public,max-age=31536000" });
                context.Context.Response.Headers.Add("Expires", new[] { DateTime.UtcNow.AddYears(1).ToString("R") }); // Format RFC1123
            }
        }
    });

    app.UseMvc(routes =&gt;
    {
        routes.MapRoute(
            name: "default",
            template: "{controller=Home}/{action=Index}/{id?}");
    });
}</code></pre>



<p>You can check the headers are sent by using the Developer Console:</p>



<figure class="wp-block-image size-large"><img fetchpriority="high" decoding="async" width="773" height="275" class="wp-image-2925" src="https://topreviewhostingasp.net/wp-content/uploads/2021/02/developerconsole.png" alt="" srcset="https://topreviewhostingasp.net/wp-content/uploads/2021/02/developerconsole.png 773w, https://topreviewhostingasp.net/wp-content/uploads/2021/02/developerconsole-300x107.png 300w, https://topreviewhostingasp.net/wp-content/uploads/2021/02/developerconsole-768x273.png 768w, https://topreviewhostingasp.net/wp-content/uploads/2021/02/developerconsole-50x18.png 50w" sizes="(max-width: 773px) 100vw, 773px" /></figure>



<h2 class="wp-block-heading" id="conclusion-60e67e">Conclusion</h2>



<p>Using HTTP caching is important for performance reasons (client and server sides). With ASP.NET Core, you can take advantage of the provided <code>TagHelper</code>s to generate a versioned URL, and change the default configuration of the <code>StaticFilesMiddleware</code> to add the <code>Cache-control</code> header for these URLs.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://topreviewhostingasp.net/caching-static-resources-forever-with-asp-net-core/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
