Stop EPiServer clearing output cache on publish - caching

This isn't a question I've seen around, usually it's 'EPiServer isn't clearing the output cache'. I'm trying to achieve the opposite. Each time a page is published the entire cache is dropped and as the client publishes several times a day, this is frustrating.
I'm using the [ContentOutputCache] attribute and tried to implement a httpCacheVaryByCustom rule with an accompanying scheduled task in EPiServer to invalidate the cache when we decide to i.e. bundle updates together and invalidate at a predetermined time.
I've tested this rule and it works using:
public override string GetVaryByCustomString(HttpContext context, string custom)
I was under the impression that by using this type of caching rule it would stop EPiServer dumping my cache whenever something is published / media uploaded.
It doesn't though is there a way to stop this from happening?
I've had success by using the standard [OutputCache] with the same custom string rule the only problem with this is that editors will always see a cached version of the page they are editing.
The application settings I have in my web.config for EPiServer are:
<applicationSettings globalErrorHandling="Off" operationCompatibility="DynamicProperties" uiSafeHtmlTags="b,i,u,br,em,strong,p,a,img,ol,ul,li" disableVersionDeletion="false"
httpCacheability="Public" uiEditorCssPaths="~/assets/css/styles.css, ~/assets/css/editor.css" urlRebaseKind="ToRootRelative"
pageUseBrowserLanguagePreferences="false" uiShowGlobalizationUserInterface="false" subscriptionHandler="EPiServer.Personalization.SubscriptionMail,EPiServer"
uiMaxVersions="20" pageValidateTemplate="false" utilUrl="~/util/"
uiUrl="~/EPiServer/CMS/" httpCacheExpiration="01:00:00" httpCacheVaryByCustom="invalidateSiteCache" />

A custom GetVaryByCustomString function will determine when the cache is invalidated, but any request for content that is using the ContentOutputCache is checked against a master cache key Episerver.DataFactoryCache.Version. This version number is incremented any time content is published, updated etc, and the cache is invalidated if the version number is changed.
To understand what you need to do, I recommend using a decompiler (e.g. DotPeek) and looking at the ContentOutputCacheAttribute and OutputCacheHandler classes in the Episerver dll.
You will need to:
Derive a new handler from EPiServer.Web.OutputCacheHandler
Create an alternative method to ValidateOutputCache(...) that still calls OutputCacheHandler.UseOutputCache(...) but ignores the cache version number
Derive a new attribute from ContentOutputCacheAttribute
Override the method OnResultExecuting(ResultExecutingContext filterContext) using the same logic as the current method (this is where a decompiler is useful), but that adds a callback to your new validate method instead of the current one. Unfortunately we can't inject the new handler because the validate method is passed statically.
e.g.
public override void OnResultExecuting(ResultExecutingContext filterContext)
{
// Rest of method
filterContext.HttpContext.Response.Cache.AddValidationCallback(new HttpCacheValidateHandler(CustomOutputCacheHandler.CustomValidateOutputCache), (object) tuple);
}
Use the new attribute in place of [ContentOutputCache]

Related

Get notified when TaggedValue of an element changes in Enterprise Architect

I am new in creating Addins for Enterprise Architect and I have this problem:
I have a diagram with elements which have TaggedValues. I want to get notified when the value of a TaggedValue changes and see the new value.
I saw that there is this event EA_OnElementTagEdit available but I can't seem to get it triggered. I also saw that the tagged value has to be of type AddinBroadcast but I can't seem to make it work. What am I missing?
I will put below a sample of my code:
//creating tagged value
EA.TaggedValue ob3 = (EA.TaggedValue)NewElement.TaggedValues.AddNew("Responsible", "val");
ob3.Value = EEPROMBlocks.ElementAt(index).Responsible;
ob3.SetAttribute("Type", "AddinBroadcast");
ob3.Update();
//event method
public override void EA_OnElementTagEdit(EA.Repository Repository, long ObjectID, ref string TagName, ref string TagValue, ref string TagNotes)
You are not missing anything. This is simply not possible. The only way around is the OnContext... where you temporarily store the status of one element and look if a tag has changed when the context changes. I would not recommend that since it involved a lot of superfluous DB accesses.
Send a feature request (if you are an optimistic guy). Alternatively you should think of ways to get around this somehow else.

How to migrate a cached ServiceStack session to a new "version"

When we add new properties to our custom AuthUserSession based session DTO, we either need to invalidate users active sessions and force them to re-login, or migrate their sessions (either in mass, or in lazy fashion). If this is not done, expected properties will not be filled, and adds a lot more complexity to the code relying on those properties.
I dug around and looked for any events around hydration of sessions from cache, but didn't see any easy place to tie in and determine if the session should be refreshed.
Any suggestions on where to plug in such logic in the flow where it will always happen before some session object is used by a ServiceStack Service or Razor view?
For Caching providers that implement ICacheClientExtended you can access all Sessions with:
var sessionPattern = IdUtils.CreateUrn<IAuthSession>(""); //= urn:iauthsession:
var sessionKeys = Cache.GetKeysStartingWith(sessionPattern).ToList();
var allSessions = Cache.GetAll<IAuthSession>(sessionKeys);
Otherwise I've just added a custom hook to be able to filter a session (in this commit), by overriding OnSessionFilter() in your AppHost, e.g:
public override IAuthSession OnSessionFilter(IAuthSession session, string id)
{
return base.OnSessionFilter(session, id);
}
This change is available from v4.0.49 that's now available from MyGet.

How to update specific cached route?

I am running a Symfony2 app and I have a question about caching.
There is a comment on an answer here in SO that says:
you could create a command that only updates this one cached route. or
maybe consider using a kernel event listener that newly registers the
route on every request if you can afford the performance impact.
How could I update only this one cached route?
Where are cached routes stored?
The cache classes for url matching/generation can be found in app/cache/environment and are called appEnvironmentUrlGenerator.php and appEnvironmentUrlGenerator.php with "environment" being one of dev,prod, .. etc.
API reference:
http://api.symfony.com/2.3/Symfony/Component/Routing/Matcher/UrlMatcher.html
http://api.symfony.com/2.3/Symfony/Component/Routing/Generator/UrlGenerator.html
How does it work?
The router service receives a url-matcher and a url-generator when being constructed. Those are then being used inside the match() and generate() methods of the router.
https://github.com/symfony/symfony/blob/2.3/src/Symfony/Component/Routing/Router.php
For warming up the cache the RoutingCacheWarmer uses the router's warmUp() method (if it implements WarmableInterface).
Everything written by nifr is true, but doesn't really help you.
Symfony's built in router designed to support 'static' routes, so on the first cache warmup the routes will be generated and cached. If you check the mentioned cache files you will see that the routes saved in a static private variable.
There is no simple way to update a route (or change routes).
Using Symfony2 CMF
This a bit complex solution for your simple problem: http://symfony.com/doc/master/cmf/index.html
Clearing (invalidating) the cache
If you check the CacheClearCommand you will see that the implementation is quite complex. Some suggest to delete the whole cache dir, which I don't recommend, it is quite heavy and makes your site hang on until the cache is regenerated (even you can get exceptions of missing files / and logged out users if the sessions saved under the cache folder)
Calling the cache_warmer warmup has no effect if the cache already delegated.
If you just remove the two related cache file and then call the warmUp on the router would be a better solution, but also not nice one..
$cacheDir = $this->container->getParameter("kernel.cache_dir");
// Delete routing cache files
$filesystem = $this->container->get('filesystem');
$finder = new Finder();
/** #var File $file */
foreach ($finder->files()->depth('== 0')->in($cacheDir) as $file) {
if (preg_match('/UrlGenerator|UrlMatcher/', $file->getFilename()) == 1) {
$filesystem->remove($file->getRealpath());
}
}
$router = $this->container->get('router');
$router->warmUp($cacheDir);
Override the default Router class
As of 2.8 the Router class is defined with a parameter router.class
See here: https://github.com/symfony/framework-bundle/blob/v2.8.2/Resources/config/routing.xml#L63
Add something like this to your config.yml
parameters:
router.class: "My\Fancy\Router"
You can implement your own Router class, extending the original Router, and also you will need to extend the UrlMatcher and UrlGenerator classes to call the parent implementation and add your own routes to match against / generate with. This way you don't need to refresh the cache, because you can add your routes dynamically.
Note: I'm not sure if you can rely on this on long term, if you check master, the definition has changed, the parameter is not there anymore:
https://github.com/symfony/framework-bundle/blob/master/Resources/config/routing.xml#L54

How do I get Magento to serve up the cached version of the page when I have unique URL parameters?

It's a simple question with no answer in search(google/bing/stackoverflow). The answer of course could be complicated.
I've read a couple articles on FPC within Magento, and have yet to really nail down where I need to add or create code so that when certain URL parameters are sent it serves up cached version of the page and not try and re-cache with the URL parameters.
http://www.kingletas.com/2012/09/how-does-magento-full-page-cache-works.html
So for example, when you go to http://www.example.com/shoes it loads the correct cached version. however, with google analytics and any other type of 3rd party reporting, especially with unique identifiers, it will reload the page as if it wasn't cached. So http://www.example.com/shoes?utm_key=A1537BD94EF07 would create a new cached version of that page and so on.
I would like to be able to exclude certain URL parameters and not all. Mainly any parameter I am using for tracking of customers.
As far as code, I have not come up with anything, due to the fact of the complexity of FPC and not having a dev site currently setup to test on.
Any leads as to where I can add this exception would be helpful, thanks!
EDIT: I would like to add that I am working with the Enterprise Edition. And using the Redis for cache.
I developed my own extension on the fix.
In short the get parameters are used in the cache ID. So in order to bypass this, I created an extension that changed the following:
/app/code/core/Enterprise/PageCache/Model/Processor/Category.php
Two functions where changed
protected function _getQueryParams()
AND
public function getPageIdWithoutApp(Enterprise_PageCache_Model_Processor $processor)
/app/code/core/Enterprise/PageCache/Model/Processor/Default.php
One function was changed
public function getPageIdWithoutApp(Enterprise_PageCache_Model_Processor $processor)
Once changed, it no longer created the cache ID with my specified tracking parameters.
example:
public function getPageIdWithoutApp(Enterprise_PageCache_Model_Processor $processor)
{
$queryParams = $_GET;
ksort($queryParams);
/**
* unset known tracking codes
*/
unset($queryParams["trk_msg"]);
unset($queryParams["trk_contact"]);
unset($queryParams["utm_source"]);
unset($queryParams["utm_medium"]);
unset($queryParams["utm_term"]);
unset($queryParams["utm_campaign"]);
unset($queryParams["utm_content"]);
/** End Edit */
$queryParamsHash = md5(serialize($queryParams));
return $processor->getRequestId() . '_' . $queryParamsHash;
}

MVC3 with hundred of thousands layouts/templates

I have an application where each user can choose a custom layout. The layouts can be different and it's not just css styles but html as well.
I know that mvc would cache the layout, but having so many layouts I doubt it would fit in cache. So what would it be better to save templates in DB or on the disk?
FYI: DB that I'm using is MongoDB.
I would save the layouts on disk because at the moment I don't see any advantage in a database (unless you do). But one thing that is worth mentioning is that you can create a class derived from OutputCacheAttribute and have your saved result depend on the layout you're using.
Does the layout depend on user? You could use the VaryByCustom property to have it vary by user.
EDIT
Are your users allowed to change layouts dinamically? If yes, you should also have a guid associated to your users change it each time the layouts change so you return on your VaryByCustom method:
return string.Format("User-{0}-{1}", user.Id, user.LayoutUpdateGuid);
See the meaning of this? This way, when a user changes the layouts, they will see their pages updated immediately.
How to apply the VaryByCustom attribute in your situation
In your action method, you may use:
[OutputCache(Duration = 3600, VaryByCustom = "UserLayouts")]
public ActionResult Details(string param)
{
// Returning the view
}
Then, in your VaryByCustom method in your Global.asax.cs file:
protected override string VaryByCustom(string custom)
{
switch (custom)
{
case "UserLayouts":
//// Here you fetch your user details so you can return a unique
//// string for each user and "publishing cycle"
//// Also, I strongly suggest you cache this user object and expire it
//// whenever the user is changed (e.g. when the LayoutUpdateGuid is
//// changed) so you achieve maximum speed and not defeat the purpose
//// of using output cache.
return string.Format("User-{0}-{1}", user.Id, user.LayoutUpdateGuid);
break;
}
}
The missing piece
The missing piece here is that you need to store a value that I called LayoutUpdateGuid (I'm sure you'll find a better name) and change that value whenever a user changes his layouts => this will lead to a different string being returned by the VaryByCustom(string) method in the Global.asasx.cs which in turn will force your action method to run again and return the result with the updated layout.
Makes sense to you?
Note: I can't test the specific code I wrote here, but I am sure (apart from typos) it is correct.

Resources