title: Localization and caching - cache invalidation when culture is changed
body: <p>Suppose I want to change the UI culture of my application. On the Index view I have some hyperlinks responsible for changing the language:</p>
  
  <pre><code>&lt;a href="@Url.Action("SetCulture", "System", new { lang = "en", returnUrl = Request.RawUrl }, null)"&gt;
      &lt;span&gt;English&lt;/span&gt;
  &lt;/a&gt;
  &lt;a ...
  </code></pre>
  
  <p>If I click on one of them, I'm redirected to the <code>SetCulture</code> action where, based on selected language, appropriate culture is created and persisted to the session (and to the cookie):</p>
  
  <pre><code>public class SystemController : Controller
  {
      public ActionResult SetCulture(string lang, string returnUrl)
      {
          SetCultureToCookie(lang);
          return Redirect(returnUrl);
      }        
  }
  </code></pre>
  
  <p>Just before we will be redirected to our desirable action (e.g. <code>/Home/Index</code>): </p>
  
  <pre><code>public class HomeController : BaseController
  {
      public ActionResult Index()
      {
          return View();
      }
      ...
  </code></pre>
  
  <p>execution flow firstly goes through <code>OnActionExecuting</code> event. Here, culture previously stored to the session (and cookie) is extracted and assigned to the current thread:</p>
  
  <pre><code>public abstract class BaseController : Controller
  {        
      protected override void Initialize(System.Web.Routing.RequestContext requestContext)
      {
          base.Initialize(requestContext);
  
          // btw: thread culture should be setup as soon as possible (OnActionExecuting is too
          // late, because error messages of attributes applied to models will not be translated)
          var culture = GetCultureFromCookie();
          Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture = culture;
      }
  }
  </code></pre>
  
  <p>After this is done, we have to finally find the appropriate view. Views hierarchy is straightforward here:</p>
  
  <pre class="lang-none prettyprint-override"><code>/Views
      /Home
          - Index.cshtml
          - Index_en.cshtml
          ...
  </code></pre>
  
  <p>Searching for the view is done by view engine, just before the view is rendered. I've written trivial custom engine, registered in <code>Application_Start</code>:</p>
  
  <pre><code>protected void Application_Start()
  {
      ViewEngines.Engines.Clear();
      ViewEngines.Engines.Add(new LocalizedRazorViewEngine());
      ...
  </code></pre>
  
  <p>Based on the current culture it looks for the appropriate view name:</p>
  
  <pre><code>public class LocalizedRazorViewEngine : RazorViewEngine
  {
      public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache)
      {
          useCache = false;   // &lt;--- NOTICE THAT
  
          var culture = Thread.CurrentThread.CurrentUICulture.Name;
          var isoLang = Thread.CurrentThread.CurrentUICulture.TwoLetterISOLanguageName;
  
          var locViewName = !string.IsNullOrEmpty(viewName) ? string.Format("{0}_{1}", viewName, culture) : viewName;
          var locMasterName = !string.IsNullOrEmpty(masterName) ? string.Format("{0}_{1}", masterName, culture) : masterName;
  
          var result = base.FindView(controllerContext, locViewName, locMasterName, useCache);
          if (result.View == null)
          {
              locViewName = !string.IsNullOrEmpty(viewName) ? string.Format("{0}_{1}", viewName, isoLang) : viewName;
              locMasterName = !string.IsNullOrEmpty(masterName) ? string.Format("{0}_{1}", masterName, isoLang) : masterName;
  
              result = base.FindView(controllerContext, locViewName, locMasterName, useCache);
              if (result.View == null)
                  result = base.FindView(controllerContext, viewName, masterName, useCache);
          }
  
          return result;
      }
      ...
  </code></pre>
  
  <p>If I choose English language to be loaded (<code>/System/SetCulture?lang=en&amp;returnUrl=%2FHome%2FIndex</code>), the method above returns <code>Index_en.cshtml</code> view.</p>
  
  <p>Everything works fine but there is one drawback - there is <strong>no caching</strong> here (I assume no caching is enabled by default, as I'm using VS2012 and created skeleton of my application using default ASP.NET MVC 4 template). As you can see, at the beginning of <code>FindView</code> method, I've set <code>useCache</code> to <code>false</code> so the view engine doesn't try to look for any cached view - it will always perform full search. If I remove this poor line and <strong>publish</strong> this application to the hosting server, localization is not working - language changes has no effect, some older view is always rendered (i.e. if I select English language for the index view, no <code>Index_en.cshtml</code> is rendered but the previous one e.g. <code>Index.cshtml</code> or <code>Index_fr.cshtml</code>).</p>
  
  <p>If you'd like to reproduce this issue in your development environment, you should compile the application in <strong>release</strong> mode and set <code>&lt;compilation debug="false" /&gt;</code> in <code>web.config</code>, since MVC will not do any view lookup caching if you are running your application in debug mode.</p>
  
  <p>I have some questions:</p>
  
  <ol>
  <li>How to properly use the cache in my case? How to invalidate it when the
  language is changed?</li>
  <li>How the caching on view engine level (<code>useCache</code>) differs from that
  one indicated by <code>[OutputCache]</code> attribute on controllers/actions? How to
  use that latter one here also in a correct manner?</li>
  </ol>
  
  <p>BTW: As you can see, I'd like to avoid such a mechanism where current language is indicated by permanent prefix in url (like e.g. mypage.com/<strong>en</strong>/Index), because I don't like it.</p>
  
  <p>Regards</p>