I am using Redis for asp.net MVC output cache. Some of my views take a fair bit of processing, currently I have an overnight process that generates the required data for the views and puts it in Redis cache so the views can render much quicker, however the data is only in the cache for the purpose of the initial render of the view and then the view is cached by output cache config.
It would be MUCH better if I could just render the view and put that directly into the cache from the overnight console program. How would I do this? I gather I would need to insert to Redis with the same key that ASP.NET MVC would give and call whatever internal render method that asp.net MVC uses?
I don't need instructions for inserting to Redis, rather what is the render method I need to call and how are the key names constructed for asp.net MVC OutputCache.
I am using asp.net MVC 5, however, bonus kudos if you can also answer for Core to futureproof the answer!
Please no suggestions of generating static files, that's not what I want, Thanks.
How are the key names constructed for asp.net mvc outputcache?
This part is easy to answer if you consult the source code for OutputCacheAttribute. The keys depend on the settings (e.g. the keys will have more data in them if you have set VaryByParam). You can determine the keys by checking how the attribute populates uniqueID
for you use case. Notice that the keys are concatenated and then hashed (since they could get very long) and then base64-encoded. Here is the relevant code:
internal string GetChildActionUniqueId(ActionExecutingContext filterContext)
{
StringBuilder uniqueIdBuilder = new StringBuilder();
// Start with a prefix, presuming that we share the cache with other users
uniqueIdBuilder.Append(CacheKeyPrefix);
// Unique ID of the action description
uniqueIdBuilder.Append(filterContext.ActionDescriptor.UniqueId);
// Unique ID from the VaryByCustom settings, if any
uniqueIdBuilder.Append(DescriptorUtil.CreateUniqueId(VaryByCustom));
if (!String.IsNullOrEmpty(VaryByCustom))
{
string varyByCustomResult = filterContext.HttpContext.ApplicationInstance.GetVaryByCustomString(HttpContext.Current, VaryByCustom);
uniqueIdBuilder.Append(varyByCustomResult);
}
// Unique ID from the VaryByParam settings, if any
uniqueIdBuilder.Append(GetUniqueIdFromActionParameters(filterContext, SplitVaryByParam(VaryByParam)));
// The key is typically too long to be useful, so we use a cryptographic hash
// as the actual key (better randomization and key distribution, so small vary
// values will generate dramtically different keys).
using (SHA256Cng sha = new SHA256Cng())
{
return Convert.ToBase64String(sha.ComputeHash(Encoding.UTF8.GetBytes(uniqueIdBuilder.ToString())));
}
}
You'll notice later the uniqueID is used as a key into the internal cache:
ChildActionCacheInternal.Add(uniqueId, capturedText, DateTimeOffset.UtcNow.AddSeconds(Duration));
What is the render method I need to call?
Short answer: ExecuteResult.
Long answer: Holy crap, you are asking a lot here. Essentially you wish to instantiate some objects within the console process and call methods which will faithfully recreate the output that would have been created if you called it from within the AppDomain where the web site usually runs.
Web applications often rely on initialization and state that is created when the application starts up (e.g. setting up the composition root/IoC, or setting up Automapper, that sort of thing), so you'd have to run the initialization of your web site. A specific view may rely on contextual information such as the URL, cookies, and querystring parameters; it may rely on configuration; it may call internal services, which also rely on configuration, as well as the AppDomain account being set up a certain way; it may need to use things like client certificates which may be set up in the service account's personal store, etc.
Here is the general procedure of what the console app would have to do:
Application_Start
.HttpContext
.ActionResult
object that results from the action method and call its ExecuteResult method.I don't think this is a feasible approach, but I'd like to hear back from you if you succeed at it.
Your console application should simply fire HTTP requests at your application to populate the cache in a manner consistent with actual end user usage. This is how everyone else does it.
If you wish to replace the cached page before it has expired, you can invalidate the cache by restarting the app pool, or by using a dependency.
If you are worried about your response time statistics, change the manner in which you measure them so that you exclude any time window where this refresh is occuring.
If you are worried about impacts to a Google crawl, you can modify the host load schedule and set it to 0 during your reset window.
If you insist that you don't want to exercise the site to create the cache, I suggest you make the views lighter weight, and look at caching at lower layers in your application.
For example, if the reason your views take so long to render is that they must run complicated queries with a lot of joins, consider implementing a database cache in the form of a denormalized table. You can run SQL Agent jobs to populate the denormalized table on a nightly basis, thus refreshing your cache. This way the view can be lightweight and you won't have to cache it on the web server.
For another example, if your web application calls RESTful services that take a long time to run, consider implementing cache-control headers in your service, and modify your REST client to honor them, so that repeated requests for the same representation won't actually require a service call. See Caching your REST API.