<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>cache on Marcin Zabłocki blog</title>
    <link>https://zablo.net/tags/cache/</link>
    <description>Recent content in cache on Marcin Zabłocki blog</description>
    <generator>Hugo</generator>
    <language>en-us</language>
    <managingEditor>Marcin Zabłocki</managingEditor>
    <webMaster>Marcin Zabłocki</webMaster>
    <copyright>2026 Marcin Zabłocki</copyright>
    <lastBuildDate>Tue, 08 Mar 2016 22:51:42 +0000</lastBuildDate>
    <atom:link href="https://zablo.net/tags/cache/index.xml" rel="self" type="application/rss+xml"/>
    <item>
      <title>ASP.NET Core Redis HTML Cache</title>
      <link>https://zablo.net/blog/post/asp-net-core-redis-html-cache/</link>
      <pubDate>Tue, 08 Mar 2016 22:51:42 +0000</pubDate>
      <author>Marcin Zabłocki</author>
      <guid>https://zablo.net/blog/post/asp-net-core-redis-html-cache/</guid>
      <category>asp.net-5</category>
      <category>asp.net-core</category>
      <category>html-cache</category>
      <category>redis</category>
      <category>cache</category>
      <description>&lt;div&gt;&#xA;    &lt;p&gt;Previous versions of ASP.NET MVC framework had out-of-the-box &lt;strong&gt;OutputCache&lt;/strong&gt;&#xA;        attribute, which could be used to improve MVC aplications performance by - as the name describes&#xA;        - caching the output of actions to which the attribute was added.&#xA;    &lt;/p&gt;&#xA;    &lt;p&gt;&#xA;        In ASP.NET Core 1.0 (formerly ASP.NET 5) the attribute no longer exists. The ASP.NET team&#xA;        provided &lt;em&gt;ResponseCache&lt;/em&gt; attribute, however it&#39;s purpose is only to set HTTP response headers, so&#xA;        the proxy servers behind which the web application could be running, could cache the response.&#xA;        What about local caching? So far, I didn&#39;t found out-of-the-box solution in the edge version of&#xA;        the framework (which is as of writing this post in RC1 state).&#xA;    &lt;/p&gt;&#xA;    &lt;p&gt;So... the need for that functionality became the code in my Visual Studio. I will show you how&#xA;        I&#39;ve implemented &lt;strong&gt;full HTML cache for ASP.NET Core 1.0 using REDIS&lt;/strong&gt; cache (and&#xA;        also in-proc memory), and I&#39;ll be glad to read comments about my solution and also discuss about&#xA;        it. Here it goes!&#xA;    &lt;/p&gt;&#xA;    &lt;h2&gt;Implementing ASP.NET Core HTML cache&lt;/h2&gt;&#xA;    &lt;h3&gt;Prerequisites:&lt;/h3&gt;&#xA;    &lt;ul&gt;&#xA;        &lt;li&gt;ASP.NET 5 (ASP.NET Core 1.0) RC1 installed&lt;/li&gt;&#xA;        &lt;li&gt;Visual Studio 2015&lt;/li&gt;&#xA;        &lt;li&gt;access to any Redis server*&lt;/li&gt;&#xA;        &lt;li&gt;ASP.NET dnx451 targeted project (there is no .NET Core support for Redis Cache yet)&lt;/li&gt;&#xA;    &lt;/ul&gt;&#xA;    &lt;p&gt;&#xA;        *Redis is obviously optional when you use memory cache&#xA;    &lt;/p&gt;&#xA;    &lt;h3&gt;Preparation&lt;/h3&gt;&#xA;    &lt;p&gt;&#xA;        My cache solution will be a tandem of ASP.NET Middleware and C# Attribute usage.&#xA;        Let&#39;s start with creating new Web Application project. You will also need some additional&#xA;        packages, so open project.json and add those:&#xA;        &lt;br&gt;&#xA;    &lt;/p&gt;&#xA;    &lt;pre class=&#34;language-markup&#34;&gt;&lt;code&gt;&#34;Microsoft.Extensions.Caching.Memory&#34;: &#34;1.0.0-rc1-final&#34;,&#xA;&#34;Microsoft.Extensions.Caching.Redis&#34;: &#34;1.0.0-rc1-final&#34;&lt;/code&gt;&lt;/pre&gt;&#xA;    If Visual Studio fails to restore packages, make sure that you have proper package sources. Those&#xA;    are mine:&#xA;    (Nuget.config)&#xA;    &lt;pre&gt;&lt;code class=&#34;language-markup&#34;&gt;&amp;lt;?xml version=&#34;1.0&#34; encoding=&#34;utf-8&#34;?&amp;gt;&#xA;&amp;lt;configuration&amp;gt;&#xA;    &amp;lt;packageSources&amp;gt;&#xA;        &amp;lt;add key=&#34;api.nuget.org&#34; value=&#34;https://api.nuget.org/v3/index.json&#34; /&amp;gt;&#xA;        &amp;lt;add key=&#34;AspNetVNext&#34; value=&#34;https://www.myget.org/F/aspnetvnext/api/v2/&#34;&#xA;        /&amp;gt;&#xA;        &amp;lt;add key=&#34;nuget.org&#34; value=&#34;https://www.nuget.org/api/v2/&#34; /&amp;gt;&#xA;        &amp;lt;add key=&#34;Roslyn&#34; value=&#34;https://www.myget.org/F/roslyn-nightly/&#34; /&amp;gt;&#xA;        &amp;lt;add key=&#34;DotNet&#34; value=&#34;https://www.myget.org/F/dotnet-corefx/&#34; /&amp;gt;&#xA;    &amp;lt;/packageSources&amp;gt;&#xA;&amp;lt;/configuration&amp;gt;&lt;/code&gt;&#xA;                    &lt;/pre&gt;&#xA;    &lt;h3&gt;How it will work&lt;/h3&gt;&#xA;    &lt;p&gt;&#xA;        Before writing the code, here is a description how my solution will work:&#xA;    &lt;/p&gt;&#xA;    &lt;ol&gt;&#xA;        &lt;li&gt;Adding custom created &lt;strong&gt;Cache attribute&lt;/strong&gt; adds action filter to marked action&lt;/li&gt;&#xA;        &lt;li&gt;Action filter handles those events:&#xA;            &lt;ol&gt;&#xA;                &lt;li&gt;&#xA;                    &lt;b&gt;OnActionExecuting&lt;/b&gt; - to prevent action executing if cached response exists, and to write&#xA;                    cached response into current request result&#xA;                &lt;/li&gt;&#xA;                &lt;li&gt;&#xA;                    &lt;b&gt;OnResultExecuting&lt;/b&gt; - to finish request handling, when cache is hit&#xA;                &lt;/li&gt;&#xA;                &lt;li&gt;&#xA;                    &lt;b&gt;OnResultExecuted&lt;/b&gt; - when there was a cache miss, to save response into cache&#xA;                &lt;/li&gt;&#xA;            &lt;/ol&gt;&#xA;        &lt;/li&gt;&#xA;        &lt;li&gt;&#xA;            Custom middleware to intercept response stream and provide own MemoryStream to which custom code will be&#xA;            able to write and read.w&#xA;        &lt;/li&gt;&#xA;    &lt;/ol&gt;&#xA;    &lt;h3&gt;Implementation - abstract layer&lt;/h3&gt;&#xA;    &lt;p&gt;&#xA;        After restoring packages and setting target framework - add new folder to your project, call it Cache - all of&#xA;        cache related code will land there.&#xA;    &lt;/p&gt;&#xA;    &lt;p&gt;&#xA;        In order to have some abstraction and avoid hard dependencies, I&#39;ve created &lt;em&gt;ICacheService&lt;/em&gt; interface,&#xA;        which exposes general purpose cache methods:&#xA;    &lt;/p&gt;&#xA;    &lt;pre class=&#34;language-csharp&#34;&gt;&lt;code&gt;public interface ICacheService&#xA;{&#xA;    void Store(string key, object content);&#xA;&#xA;    void Store(string key, object content, int duration);&#xA;&#xA;    T Get&lt;t&gt;(string key) where T : class;&#xA;}&lt;/t&gt;&lt;/code&gt;&#xA;                &lt;/pre&gt;&#xA;    &lt;p&gt;&#xA;        Implementation of this service will be described later in this post.&lt;br&gt;&#xA;        Next - let&#39;s add &lt;em&gt;CacheAttribute&lt;/em&gt; class, which will implement &lt;em&gt;IActionFilter&lt;/em&gt; and inherit from&#xA;        &lt;em&gt;ResultFilterAttribute&lt;/em&gt;.&#xA;    &lt;/p&gt;&#xA;    &lt;pre class=&#34;language-csharp&#34;&gt;&lt;code&gt;public class CacheAttribute : ResultFilterAttribute, IActionFilter&#xA;{&#xA;//...&#xA;}&lt;/code&gt;&#xA;                &lt;/pre&gt;&#xA;    &lt;h3&gt;Custom middleware for accessing response stream&lt;/h3&gt;&#xA;    &lt;p&gt;&#xA;        Before writing the implementation of this attribute, we will need access to the response stream (which is&#xA;        somehow locked to write/read by default). In order to obtain this functionality, add the middleware class&#xA;        CacheMiddleware.cs. Every middleware class requires non-interfaced method with the signature as follows:&#xA;        &lt;br&gt;&#xA;        &lt;code class=&#34;language-csharp&#34;&gt;public async Task Invoke(HttpContext httpContext)&lt;/code&gt;&#xA;    &lt;/p&gt;&#xA;    &lt;p&gt;&#xA;        By desing, every middleware must invoke further registered middlewares, there is no exception for this one.&#xA;        Easiest way to do this is to add &lt;em&gt;RequestDelegate&lt;/em&gt; dependency in constructor, so build-in ASP.NET DI&#xA;        container will inject this object into our middleware.&#xA;    &lt;/p&gt;&#xA;    &lt;p&gt;&#xA;        Purpose of &lt;strong&gt;&lt;em&gt;CacheMiddleware&lt;/em&gt;&lt;/strong&gt; is to intercept request, and provide custom stream to which&#xA;        further middlewares could write (and read). When the middleware invocation process will return to&#xA;        &lt;em&gt;CacheMiddleware&lt;/em&gt;, this stream&#39;s content will be copied to original response stream. Full implementation&#xA;        of this class:&#xA;    &lt;/p&gt;&#xA;    &lt;pre class=&#34;language-csharp&#34;&gt;&lt;code&gt;public class CacheMiddleware&#xA;{&#xA;&#x9;protected RequestDelegate NextMiddleware;&#xA;&#xA;&#x9;public CacheMiddleware(RequestDelegate nextMiddleware)&#xA;&#x9;{&#xA;&#x9;&#x9;NextMiddleware = nextMiddleware;&#xA;&#x9;}&#xA;&#xA;&#x9;public async Task Invoke(HttpContext httpContext)&#xA;&#x9;{&#xA;&#x9;&#x9;using (var responseStream = new MemoryStream())&#xA;&#x9;&#x9;{&#xA;&#x9;&#x9;&#x9;var fullResponse = httpContext.Response.Body;&#xA;&#x9;&#x9;&#x9;httpContext.Response.Body = responseStream;&#xA;&#x9;&#x9;&#x9;await NextMiddleware.Invoke(httpContext);&#xA;&#x9;&#x9;&#x9;responseStream.Seek(0, SeekOrigin.Begin);&#xA;&#x9;&#x9;&#x9;await responseStream.CopyToAsync(fullResponse);&#xA;&#x9;&#x9;}&#xA;&#x9;}&#xA;}&lt;/code&gt;&lt;/pre&gt;&#xA;    &lt;p&gt;&#xA;        To make use of this class, we need to add it to the request pipeline. In &lt;em&gt;Startup.cs&lt;/em&gt; file, in Configure&#xA;        method, just before &lt;em&gt;app.UseMvc()&lt;/em&gt;, add:&#xA;        &lt;br&gt;&#xA;        &lt;code class=&#34;language-csharp&#34;&gt;app.UseMiddleware&amp;lt;CacheMiddleware&amp;gt;();&lt;/code&gt;&#xA;    &lt;/p&gt;&#xA;    &lt;h3&gt;Cache attribute&lt;/h3&gt;&#xA;    &lt;p&gt;&#xA;        We are now set to write CacheAttribute implementation. First, add those properties to the&#xA;        &lt;em&gt;CacheAttribute.cs&lt;/em&gt; class:&#xA;    &lt;/p&gt;&#xA;    &lt;pre class=&#34;language-csharp&#34;&gt;&lt;code&gt;protected ICacheService CacheService { set; get; }&#xA;&#xA;public int Duration { set; get; }&lt;/code&gt;&lt;/pre&gt;&#xA;    &lt;p&gt;&#xA;        Duration will be handy if there is any need for adjustment of cache time. Usage of CacheService property will be&#xA;        described shortly.&#xA;    &lt;/p&gt;&#xA;    &lt;p&gt;&#xA;        Let&#39;s move along with caching output in &lt;em&gt;OnResultExecuted&lt;/em&gt; method:&#xA;    &lt;/p&gt;&#xA;    &lt;pre class=&#34;language-csharp&#34;&gt;&lt;code&gt;public override void OnResultExecuted(ResultExecutedContext context)&#xA;{&#xA;&#x9;GetServices(context);&#xA;&#xA;&#x9;var cacheKey = context.HttpContext.Request.GetEncodedUrl().ToMd5();&#xA;&#x9;var httpResponse = context.HttpContext.Response;&#xA;&#x9;var responseStream = httpResponse.Body;&#xA;&#xA;&#x9;responseStream.Seek(0, SeekOrigin.Begin);&#xA;&#xA;&#x9;using (var streamReader = new StreamReader(responseStream, Encoding.UTF8, true, 512, true))&#xA;&#x9;{&#xA;&#x9;&#x9;var toCache = streamReader.ReadToEnd();&#xA;&#x9;&#x9;var contentType = httpResponse.ContentType;&#xA;&#x9;&#x9;var statusCode = httpResponse.StatusCode.ToString();&#xA;&#x9;&#x9;Task.Factory.StartNew(() =&amp;gt;&#xA;&#x9;&#x9;{&#xA;&#x9;&#x9;&#x9;CacheService.Store(cacheKey + &#34;_contentType&#34;, contentType, Duration);&#xA;&#x9;&#x9;&#x9;CacheService.Store(cacheKey + &#34;_statusCode&#34;, statusCode, Duration);&#xA;&#x9;&#x9;&#x9;CacheService.Store(cacheKey, toCache, Duration);&#xA;&#x9;&#x9;});&#xA;&#xA;&#x9;}&#xA;&#x9;base.OnResultExecuted(context);&#xA;}&lt;/code&gt;&lt;/pre&gt;&#xA;    &lt;p&gt;&#xA;        &lt;b&gt;What it does?&lt;/b&gt; First - it uses &lt;em&gt;Service Locator Pattern&lt;/em&gt; (which you could obviously replace with&#xA;        proper DI-way, it was used here for the quick implementation purposes) to get access to &lt;em&gt;CacheService&lt;/em&gt;.&#xA;        Then, it takes the response stream (which was provided by our custom middleware) and reads the whole of it (with&#xA;        leaving the stream open, thus the monstrous StreamReader constructor usage). After all of this, method stores&#xA;        &lt;strong&gt;response, response type and status code&lt;/strong&gt; in the Cache - this process is run in the background,&#xA;        so the request could process further without any blocking operations. At last, the &lt;em&gt;OnResultExecuted&lt;/em&gt;&#xA;        method invokes base method, so any remaining filters can execute.&#xA;    &lt;/p&gt;&#xA;    &lt;p&gt;&#xA;        Now, when we have response cached, we need a way to intercept request and write it to the response. There goes&#xA;        the implementation of &lt;em&gt;OnActionExecuting&lt;/em&gt; method:&#xA;    &lt;/p&gt;&#xA;    &lt;pre class=&#34;language-csharp&#34;&gt;&lt;code&gt;public void OnActionExecuting(ActionExecutingContext context)&#xA;{&#xA;&#x9;GetServices(context);&#xA;&#x9;var requestUrl = context.HttpContext.Request.GetEncodedUrl();&#xA;&#x9;var cacheKey = requestUrl.ToMd5();&#xA;&#x9;var cachedResult = CacheService.Get&amp;lt;string&amp;gt;(cacheKey);&#xA;&#x9;var contentType = CacheService.Get&amp;lt;string&amp;gt;(cacheKey + &#34;_contentType&#34;);&#xA;&#x9;var statusCode = CacheService.Get&amp;lt;string&amp;gt;(cacheKey + &#34;_statusCode&#34;);&#xA;&#x9;if (!string.IsNullOrEmpty(cachedResult) &amp;amp;&amp;amp; !string.IsNullOrEmpty(contentType) &amp;amp;&amp;amp;&#xA;&#x9;&#x9;!string.IsNullOrEmpty(statusCode))&#xA;&#x9;{&#xA;&#x9;&#x9;//cache hit&#xA;&#x9;&#x9;var httpResponse = context.HttpContext.Response;&#xA;&#x9;&#x9;httpResponse.ContentType = contentType;&#xA;&#x9;&#x9;httpResponse.StatusCode = Convert.ToInt32(statusCode);&#xA;&#xA;&#x9;&#x9;var responseStream = httpResponse.Body;&#xA;&#x9;&#x9;responseStream.Seek(0, SeekOrigin.Begin);&#xA;&#x9;&#x9;if (responseStream.Length &amp;lt;= cachedResult.Length)&#xA;&#x9;&#x9;{&#xA;&#x9;&#x9;&#x9;responseStream.SetLength((long)cachedResult.Length &amp;lt;&amp;lt; 1);&#xA;&#x9;&#x9;}&#xA;&#x9;&#x9;using (var writer = new StreamWriter(responseStream, Encoding.UTF8, 4096, true))&#xA;&#x9;&#x9;{&#xA;&#x9;&#x9;&#x9;writer.Write(cachedResult);&#xA;&#x9;&#x9;&#x9;writer.Flush();&#xA;&#x9;&#x9;&#x9;responseStream.Flush();&#xA;&#x9;&#x9;&#x9;context.Result = new ContentResult { Content = cachedResult };&#xA;&#x9;&#x9;}&#xA;&#x9;}&#xA;&#x9;else&#xA;&#x9;{&#xA;&#x9;&#x9;//cache miss&#xA;&#x9;}&#xA;}&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;    &lt;p&gt;&#xA;        As you can see, again - I take &lt;strong&gt;CacheService&lt;/strong&gt;. This time, the code searches for the same cache&#xA;        keys used to store response. If all keys are found (cache hit). From this point, we set response Content Type,&#xA;        Status Code and finally - we &lt;strong&gt;write cached HTML&lt;/strong&gt; into response stream. As you can see, there is a&#xA;        part of code which extends stream&#39;s length. I&#39;ve encountered an issue in which I could not write to the stream&#xA;        (because of the length issues), so I&#39;ve just extended the length of it.&#xA;        The line which sets &lt;strong&gt;context.Result&lt;/strong&gt; is very important, as the implementation of next method -&#xA;        &lt;em&gt;OnResultExecuting&lt;/em&gt; - relays on it:&#xA;    &lt;/p&gt;&#xA;    &lt;pre class=&#34;language-csharp&#34;&gt;&lt;code&gt;public override void OnResultExecuting(ResultExecutingContext context)&#xA;{&#xA;&#x9;if (context.Result is ContentResult)&#xA;&#x9;{&#xA;&#x9;&#x9;context.Cancel = true;&#xA;&#x9;}&#xA;}&lt;/code&gt;&lt;/pre&gt;&#xA;    &lt;p&gt;&#xA;        This code cancels the request pipeline, if there was a Cache Hit. Of course, you can create more sophisticated&#xA;        condition here - I&#39;m sure that it will not work in some cases, but this post is about general idea of HTML&#xA;        cache, not &lt;i&gt;perfect-error-resistant-uber-implemntation.&lt;/i&gt;&#xA;    &lt;/p&gt;&#xA;    &lt;p&gt;&lt;em&gt;OnResultExecuted&lt;/em&gt; method will be left blank.&lt;/p&gt;&#xA;    &lt;h3&gt;Implementing Cache Service&lt;/h3&gt;&#xA;    &lt;p&gt;&#xA;        We are almost done. The only missing part is the actual &lt;strong&gt;CacheService&lt;/strong&gt;. As promised in the title&#xA;        of this post - &lt;strong&gt;Redis cache&lt;/strong&gt; will be used. I will also provide my implementation of&#xA;        &lt;strong&gt;MemoryCache&lt;/strong&gt;, so you can test HTML cache without Redis server.&#xA;    &lt;/p&gt;&#xA;    &lt;pre class=&#34;language-csharp&#34;&gt;&lt;code&gt;public class RedisCacheService : ICacheService&#xA;{&#xA;&#x9;protected IDistributedCache Cache;&#xA;&#x9;private static int DefaultCacheDuration =&amp;gt; 60;&#xA;&#xA;&#x9;public RedisCacheService(IDistributedCache cache)&#xA;&#x9;{&#xA;&#x9;&#x9;Cache = cache;&#xA;&#x9;}&#xA;&#xA;&#x9;public void Store(string key, object content)&#xA;&#x9;{&#xA;&#x9;&#x9;Store(key, content, DefaultCacheDuration);&#xA;&#x9;}&#xA;&#xA;&#x9;public void Store(string key, object content, int duration)&#xA;&#x9;{&#xA;&#x9;&#x9;string toStore;&#xA;&#x9;&#x9;if (content is string)&#xA;&#x9;&#x9;{&#xA;&#x9;&#x9;&#x9;toStore = (string)content;&#xA;&#x9;&#x9;}&#xA;&#x9;&#x9;else&#xA;&#x9;&#x9;{&#xA;&#x9;&#x9;&#x9;toStore = JsonConvert.SerializeObject(content);&#xA;&#x9;&#x9;}&#xA;&#xA;&#x9;&#x9;duration = duration &amp;lt;= 0 ? DefaultCacheDuration : duration;&#xA;&#x9;&#x9;Cache.Set(key, Encoding.UTF8.GetBytes(toStore), new DistributedCacheEntryOptions()&#xA;&#x9;&#x9;{&#xA;&#x9;&#x9;&#x9;AbsoluteExpiration = DateTime.Now + TimeSpan.FromSeconds(duration)&#xA;&#x9;&#x9;});&#xA;&#x9;}&#xA;&#xA;&#x9;public T Get&amp;lt;T&amp;gt;(string key) where T : class&#xA;&#x9;{&#xA;&#x9;&#x9;var fromCache = Cache.Get(key);&#xA;&#x9;&#x9;if (fromCache == null)&#xA;&#x9;&#x9;{&#xA;&#x9;&#x9;&#x9;return null;&#xA;&#x9;&#x9;}&#xA;&#xA;&#x9;&#x9;var str = Encoding.UTF8.GetString(fromCache);&#xA;&#x9;&#x9;if (typeof(T) == typeof(string))&#xA;&#x9;&#x9;{&#xA;&#x9;&#x9;&#x9;return str as T;&#xA;&#x9;&#x9;}&#xA;&#xA;&#x9;&#x9;return JsonConvert.DeserializeObject&amp;lt;T&amp;gt;(str);&#xA;&#x9;}&#xA;}&lt;/code&gt;&lt;/pre&gt;&#xA;    &lt;p&gt;&#xA;        Implementation depends on &lt;em&gt;IDistributedCache&lt;/em&gt; - interface provided by the ASP.NET Core 1.0 framework&#xA;        itself. To use it, we need to configure it. Head to the &lt;em&gt;Startup.cs&lt;/em&gt; file again, but this time, go to&#xA;        &lt;em&gt;ConfigureServices&lt;/em&gt; method and add this code:&#xA;    &lt;/p&gt;&#xA;    &lt;pre class=&#34;language-csharp&#34;&gt;&lt;code&gt;services.AddCaching();&#xA;string redisConnection = Configuration[&#34;Redis:ConnectionString&#34;],&#xA;&#x9;   redisInstance = Configuration[&#34;Redis:InstanceName&#34;];&#xA;&#xA;services.AddSingleton&amp;lt;IDistributedCache&amp;gt;(factory =&amp;gt;&#xA;{&#xA;&#x9;var cache = new RedisCache(new RedisCacheOptions&#xA;&#x9;{&#xA;&#x9;&#x9;Configuration = redisConnection,&#xA;&#x9;&#x9;InstanceName = redisInstance&#xA;&#x9;});&#xA;&#xA;&#x9;return cache;&#xA;});&lt;/code&gt;&lt;/pre&gt;&#xA;    &lt;p&gt;&#xA;        You can see that this code uses some configuration data. By default, ASP.NET Core 1.0 configuration is stored in&#xA;        &lt;em&gt;appsettings.json&lt;/em&gt; file. Open it and configure your Redis instance connection strings:&#xA;    &lt;/p&gt;&#xA;    &lt;pre class=&#34;language-markup&#34;&gt;&lt;code&gt;&#34;Redis&#34;: {&#xA;    &#34;ConnectionString&#34;: &#34;redis-server:7331,password=correcthorsebatterystaple&#34;,&#xA;    &#34;InstanceName&#34;: &#34;redis&#34;&#xA;}&lt;/code&gt;&lt;/pre&gt;&#xA;    &lt;p&gt;&#xA;        Last thing to do is to add implemented &lt;em&gt;CacheAttribute&lt;/em&gt; and &lt;em&gt;RedisCacheService&lt;/em&gt; to the&#xA;        application. Just add those lines bellow the previous Redis-related ones in &lt;em&gt;Startup.cs&lt;/em&gt;:&#xA;    &lt;/p&gt;&#xA;    &lt;pre class=&#34;language-csharp&#34;&gt;&lt;code&gt;services.AddSingleton&amp;lt;ICacheService, RedisCacheService&amp;gt;();&#xA;services.AddTransient&amp;lt;CacheAttribute&amp;gt;();&lt;/code&gt;&lt;/pre&gt;&#xA;    &lt;h3&gt;Testing the solution&lt;/h3&gt;&#xA;    &lt;p&gt;&#xA;        OK, &lt;b&gt;let&#39;s test it!&lt;/b&gt; I&#39;ve replaced original &lt;em&gt;Home/Index.cshtml&lt;/em&gt; with this code:&#xA;    &lt;/p&gt;&#xA;    &lt;pre class=&#34;language-markup&#34;&gt;&lt;code&gt;@model dynamic&#xA;@DateTime.Now&#xA;&amp;lt;script type=&#34;text/javascript&#34;&amp;gt;&#xA;    document.write(&#34;&amp;lt;p&amp;gt;&#34; + new Date().toLocaleString() + &#34;&amp;lt;/p&amp;gt;&#34;);&#xA;&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;&#xA;    &lt;p&gt;&#xA;        and then I&#39;ve added &lt;strong&gt;CacheAttribute&lt;/strong&gt; to &lt;em&gt;HomeController&lt;/em&gt; Index method:&#xA;    &lt;/p&gt;&#xA;    &lt;pre class=&#34;language-csharp&#34;&gt;&lt;code&gt;[Cache(Duration = 30)]&#xA;public IActionResult Index()&#xA;{&#xA;&#x9;return View();&#xA;}&lt;/code&gt;&lt;/pre&gt;&#xA;    &lt;p&gt;&#xA;        Now - let&#39;s start the application using &lt;em&gt;dnx web&lt;/em&gt; (or &lt;em&gt;dnx-watch web&lt;/em&gt;) command and open&#xA;        &lt;em&gt;http://localhost:5000&lt;/em&gt; (default URL). First request processes along the whole pipeline, but the second&#xA;        one responds from cache, as you can see in the screenshots bellow.&#xA;    &lt;/p&gt;&#xA;    &lt;div class=&#34;row&#34;&gt;&#xA;        &lt;div class=&#34;column six&#34;&gt;&#xA;            &lt;h3&gt;Full request&lt;/h3&gt;&#xA;            &lt;img src=&#34;https://zablo.net/resources/d58c4af9-c712-4e60-8ef4-0e17beeecd33.png&#34; alt=&#34;ASP.NET Core 1.0 Redis Cached HTML&#34;&gt;&#xA;        &lt;/div&gt;&#xA;        &lt;div class=&#34;column six&#34;&gt;&#xA;            &lt;h3&gt;Cached request&lt;/h3&gt;&#xA;            &lt;img src=&#34;https://zablo.net/resources/ea52f328-dadd-4a3d-831e-fb2e4d63d96f.png&#34; alt=&#34;ASP.NET Core 1.0 Redis Cached HTML&#34;&gt;&#xA;        &lt;/div&gt;&#xA;        &lt;div class=&#34;column twelve&#34;&gt;&#xA;            &lt;h3&gt;Redis server view&lt;/h3&gt;&#xA;            &lt;img src=&#34;https://zablo.net/resources/73d5b618-443a-454f-90d2-89a9d2776f3d.png&#34; alt=&#34;ASP.NET Core 1.0 Redis Cached HTML&#34;&gt;&#xA;        &lt;/div&gt;&#xA;    &lt;/div&gt;&#xA;    &lt;h2&gt;Using memory to cache HTML instead of Redis&lt;/h2&gt;&#xA;    &lt;p&gt;&#xA;        As my implementation is interface-only dependant, the &lt;em&gt;RedisCacheService&lt;/em&gt; could be easily replaced by&#xA;        In-memory Cache. Here is the implementation:&#xA;    &lt;/p&gt;&#xA;    &lt;pre class=&#34;language-csharp&#34;&gt;&lt;code&gt;public class MemoryCacheService : ICacheService&#xA;{&#xA;&#x9;protected IMemoryCache Cache;&#xA;&#xA;&#x9;public MemoryCacheService(IMemoryCache cache)&#xA;&#x9;{&#xA;&#x9;&#x9;Cache = cache;&#xA;&#x9;}&#xA;&#xA;&#x9;public void Store(string key, object content)&#xA;&#x9;{&#xA;&#x9;&#x9;Store(key, content, DefaultCacheDuration);&#xA;&#x9;}&#xA;&#xA;&#x9;public void Store(string key, object content, int duration)&#xA;&#x9;{&#xA;&#x9;&#x9;object cached;&#xA;&#x9;&#x9;if (Cache.TryGetValue(key, out cached))&#xA;&#x9;&#x9;{&#xA;&#x9;&#x9;&#x9;Cache.Remove(key);&#xA;&#x9;&#x9;}&#xA;&#xA;&#x9;&#x9;Cache.Set(key, content,&#xA;&#x9;&#x9;&#x9;new MemoryCacheEntryOptions&#xA;&#x9;&#x9;&#x9;{&#xA;&#x9;&#x9;&#x9;&#x9;AbsoluteExpiration = DateTime.Now + TimeSpan.FromSeconds(duration),&#xA;&#x9;&#x9;&#x9;&#x9;Priority = CacheItemPriority.Low&#xA;&#x9;&#x9;&#x9;});&#xA;&#x9;}&#xA;&#xA;&#xA;&#x9;private static int DefaultCacheDuration =&amp;gt; 60;&#xA;&#xA;&#x9;public T Get&amp;lt;T&amp;gt;(string key) where T : class&#xA;&#x9;{&#xA;&#x9;&#x9;object result;&#xA;&#x9;&#x9;if (Cache.TryGetValue(key, out result))&#xA;&#x9;&#x9;{&#xA;&#x9;&#x9;&#x9;return result as T;&#xA;&#x9;&#x9;}&#xA;&#x9;&#x9;return null;&#xA;&#x9;}&#xA;}&lt;/code&gt;&lt;/pre&gt;&#xA;    &lt;h2&gt;Wrap up&lt;/h2&gt;&#xA;    &lt;p&gt;And that&#39;s it. I hope you like my post, please share or/and leave a comment!&#xA;        Of course, the whole source code can be found on github: &lt;a&#xA;            href=&#34;https://github.com/marrrcin/aspnet-core-redis-html-cache&#34; target=&#34;_blank&#34;&#xA;            title=&#34;ASP.NET Core 1.0 Redis HTML Cache&#34;&gt;https://github.com/marrrcin/aspnet-core-redis-html-cache&lt;/a&gt;&#xA;        &lt;br&gt;&#xA;        &lt;strong&gt;Please comment and share if you like it!&lt;/strong&gt;&#xA;    &lt;/p&gt;&#xA;&lt;/div&gt;</description>
    </item>
  </channel>
</rss>
