Programmatically clearing cache from OutputCacheAttribute? - asp.net-mvc-3

In our code we have an MVC controller action that is decorated with the OutputCacheAttribute. Is there any way in some other action to clear the cache for the first action?

It depends. If it is a child action the cache is stored in the MemoryCache and the only way to clear it is undocumented and involves busting the whole memory cache:
OutputCacheAttribute.ChildActionCache = new MemoryCache("NewDefault");
The drawback of course is that this removes all cached child actions and not just the cached output of this child action. If it is a normal action then you could use the Response.RemoveOutputCacheItem method by passing it the url of the action that was cached. You might also find the following article interesting.
Caching in ASP.NET MVC 3 has still a very long way to go. Hopefully they are improving many things in ASP.NET MVC 4 and simplifying it.

Yes, it is possible
I have found the answer in this book.
See 14.3 Output Cache In ASP.NET MVC
page 372 - Deterministically removing items from cache
When we decorate an action with the OutputCacheAttribute , OutputCache
stores its result into ASP.NET Cache and automatically recovers it
when it must serve a subse- quent analogous request. If we knew which
cache key the page belongs to, we could easily remove it.
Unfortunately, this isn’t easily possible, and even if it were, we
aren’t supposed to know it because it resides in the internal logic
of the caching infrastruc- ture and might change without notice in
future releases. What we can do, though, is leverage the cache
dependencies mechanism to achieve a similar result. This feature is
similar to the change monitors we’re going to talk about in section
14.4. Leveraging cache dependencies consists of tying one cache entry to another to automatically remove the first one when the latter is invalidated.
And a piece of code
public class DependencyOutputCacheAttribute : OutputCacheAttribute
{
public string ParameterName { get; set; }
public string BasePrefix { get; set; }
public override void OnResultExecuting(ResultExecutingContext filterContext)
{
base.OnResultExecuting( filterContext );
string key = string.IsNullOrEmpty( BasePrefix )
? filterContext.RouteData.Values["action"] + "_" + filterContext.RouteData.Values["controller"]
: BasePrefix;
key = AddToCache( filterContext, key, ParameterName);
}
private string AddToCache(ResultExecutingContext filterContext, string key, string parameter)
{
if ( !string.IsNullOrEmpty( parameter ) && filterContext.RouteData.Values[parameter] != null) {
key += "/" + filterContext.RouteData.Values[parameter];
filterContext.HttpContext.Cache.AddBig( key, key );
filterContext.HttpContext.Response.AddCacheItemDependency( key );
}
return key;
}
}
And Remove cache dependency attribute
public class RemoveCachedAttribute : ActionFilterAttribute
{
public string ParameterName { get; set; }
public string BasePrefix { get; set; }
public override void OnResultExecuting( ResultExecutingContext filterContext)
{
base.OnResultExecuting(filterContext);
string key = string.IsNullOrEmpty(BasePrefix) ?
filterContext.RouteData.Values["action"].ToString() + "_" +
filterContext.RouteData.Values["controller"].ToString() : BasePrefix;
if (!string.IsNullOrEmpty(ParameterName))
key += filterContext.RouteData.Values[ParameterName];
filterContext.HttpContext.Cache.Remove(key);
}
}
and finally use it
[DependencyCache( BasePrefix = "Story", ParameterName = "id" )]
public virtual ActionResult ViewStory(int id){
//load story here
}
[HttpPost, RemoveCache( BasePrefix = "Story", ParameterName = "id" )]
public virtual ActionResult DeleteStory(int id){
//submit updated story version
}
[HttpPost, RemoveCache( BasePrefix = "Story", ParameterName = "id" )]
public virtual ActionResult EditStory(Story txt){
//submit updated story version
}
where
public class Story {
int id {get;set;} //case is important
string storyContent{get;set;}
}

Related

WebAPI2 Model Binding not working with HTTP PUT

I'm following Scott Allen's MVC4 course on PluralSight (I'm using MVC5 and WebAPI2 but they should be the same) and I am trying to pass an object via HTTP PUT. The model binder should bind it, but I am getting NULL for the parameter.
public HttpResponseMessage PutObjective(int id, [FromBody] Objective objective)
{
if (ModelState.IsValid && id == objective.ObjectiveID)
{
//todo: update - look up id, replace text
return Request.CreateResponse(HttpStatusCode.OK, objective);
}
else
{
return Request.CreateResponse(HttpStatusCode.BadRequest);
}
}
and in my front-end javascript I am doing the following (I'm creating an object for testing, so ignore 'objective' passed in):
var updateObjective = function (objective) {
var myobj = { "ObjectiveID": "3", "ObjectiveDescription": "test" };
return $.ajax(objectiveApiUrl + "/" + objective.ObjectiveID, {
type: "PUT",
data: myobj
});
}
My class looks like this:
public class Objective
{
public int ObjectiveID { get; private set; }
public string ObjectiveDescription { get; set; }
public Objective (int Id, string Desc)
{
this.ObjectiveID = Id;
this.ObjectiveDescription = Desc;
}
}
Any thoughts on why 'objective' in the backend is always 'null' ?
I've done what Scott Allen is doing, even tried adding in [FromBody] but no luck. $.ajax should have the correct content type by default I understand, so no need to set it.
I had Fiddler2 but I'm unsure as to what I am looking at to be honest. I can see my object as JSON being sent to the backend.
Well, if you're familiar with Model Binding you'll have seen the issue in my Objective class:
public int ObjectiveID { get; private set; }
with a private set, no instance can be created of the Objective class. To make it work, the 'private' access specifier needs to be removed.
What needs to happen really is that Objective becomes ObjectiveViewModel, and we convert what comes back to an Objective domain object (which may have more properties than we need for this screen). This can have a private set.

Sharing model objects between controller actions in MVC3

I have two actions in a controller. One that displays a form for file upload and another one that displays the results of the upload.
I have created a POCO called FileInfo i.e
public class FileInfo
{
public string Name { get; set; }
public int Length { get; set; }
public string FileType { get; set; }
public string ErrorMessage { get; set; }
}
When I submit the form, the Upload action creates and populates the FileInfo object and then redirects to the second action called results. I want to be able to use the same file info object in the results action.
I am able to get around this using TemPData[], but it is limited since it only holds object data for a single request. I presume there must be a better way to share abjects between controller actions.Any help is appreciated!
// Upload Action
List<FileInfo> fileInfo= new List<FileInfo>();
//populate the fileInfo object using fi.Add()
if ((status.ToString() == "OK"))
{
TempData["Info"] = fileInfo;
return RedirectToAction("Results");
}
else
{
return RedirectToAction("Index");
}
//Results action.
public ActionResult Results()
{
List<FileInfo> fi = TempData["Info"] as List<FileInfo>;
if (fi != null)
{
return View(fi);
}
else
{
return View("Index");
}
}
If you need something to stick around longer then one subsequent request, you will have to put it in Session or in persistent storage (e.g. database).

How shall I approach search result caching for this MVC3 / RavenDB app?

In my simple MVC3 application users can perform searches against my data, which is held in a RavenDB database at RavenHQ. I know that RavenDB caches proactively, but I'd like to avoid the http call to RavenHQ in the first place by caching searches. It's likely that each user will perform the same search more than once, and it's also likely that different users will perform the same searches. The data won't change more often than weekly.
The search params are properties of a search object. I've tried without success using output caching on the action (below), varying on the search object. It may be that I need to vary by each property of the search object individually, but that seems unsatisfactory as I may in the future add more properties.
How would you approach this?
Output caching on the action varying by search.AccName, etc.
No caching in web app, rely on RavenDB caching.
Use HttpRuntime.Cache (but if so, how)?
Some other strategy.
Excuse code formatting, had some problems with that.
public class AccItemSearch
{
public string Location { get; set; }
public string AccName { get; set; }
public int? MinPrice { get; set; }
public int? MaxPrice { get; set; }
}
public class AccItemSearchResults
{
public IEnumerable<AccItem> AccItems { get; set; }
public AccItemSearch Search { get; set; }
}
public PartialViewResult Accommodation(AccItemSearch search)
{
var accItems = new List<AccItem>();
using (IDocumentSession session = MvcApplication.Store.OpenSession())
{
// fill accItems collection by querying the RavenDB database
}
return PartialView(new AccItemSearchResults
{
AccItems = accItems.ToList(), Search = search
});
}
RavenDB has builtin support for that, see:
http://ayende.com/blog/25601/ravendb-aggressive-caching-mode
Since you are executing this action method asynchronously, I would try using the OutputCache attribute:
[OutputCache(VaryByParam = "*", Duration = 1800)]
public PartialViewResult Accommodation(AccItemSearch search)
{
var accItems = new List<AccItem>();
using (IDocumentSession session = MvcApplication.Store.OpenSession())
{
// fill accItems collection by querying the RavenDB database
}
return PartialView(new AccItemSearchResults
{
AccItems = accItems.ToList(), Search = search
});
}
The VaryByParam should tell it to cache separately based on the properties in your search viewmodel argument.

ASP.NET MVC3 Get Last Modified time of any part of view?

I want to get the last modified time of any part of a view prior to it rendering. This includes layout pages, partial views etc.
I want to set a proper time for
Response.Cache.SetLastModified(viewLastWriteUtcTime);
to properly handle http caching. Currently I have this working for the view itself however if there are any changes in the layout pages, or child partial views those are not picked up by
var viewLastWriteUtcTime = System.IO.File.GetLastWriteTime(
Server.MapPath(
(ViewEngines.Engines.FindView(ControllerContext, ViewBag.HttpMethod, null)
.View as BuildManagerCompiledView)
.ViewPath)).ToUniversalTime();
Is there any way I can get the overall last modified time?
I don't want to respond with 304 Not Modified after deployments that modified a related part of the view as users would get inconsistent behavior.
I'm not going to guarantee that this is the most effective way to do it, but I've tested it and it works. You might need to adjust the GetRequestKey() logic and you may want to choose an alternate temporary storage location depending on your scenario. I didn't implement any caching for file times since that seemed like something you wouldn't be interested in. It wouldn't be hard to add if it was ok to have the times be a small amount off and you wanted to avoid the file access overhead on every request.
First, extend RazorViewEngine with a view engine that tracks the greatest last modified time for all the views rendered during this request. We do this by storing the latest time in the session keyed by session id and request timestamp. You could just as easily do this with any other view engine.
public class CacheFriendlyRazorViewEngine : RazorViewEngine
{
protected override IView CreateView(ControllerContext controllerContext, string viewPath, string masterPath)
{
UpdateLatestTime(controllerContext, GetLastModifiedForPath(controllerContext, viewPath));
var pathToMaster = masterPath;
if (string.IsNullOrEmpty(pathToMaster))
{
pathToMaster = "~/Views/Shared/_Layout.cshtml"; // TODO: derive from _ViewStart.cshtml
}
UpdateLatestTime(controllerContext, GetLastModifiedForPath(controllerContext, pathToMaster));
return base.CreateView(controllerContext, viewPath, masterPath);
}
protected override IView CreatePartialView(ControllerContext controllerContext, string partialPath)
{
UpdateLatestTime(controllerContext, GetLastModifiedForPath(controllerContext, partialPath));
return base.CreatePartialView(controllerContext, partialPath);
}
private DateTime GetLastModifiedForPath(ControllerContext controllerContext, string path)
{
return System.IO.File.GetLastWriteTime(controllerContext.HttpContext.Server.MapPath(path)).ToUniversalTime();
}
public static void ClearLatestTime(ControllerContext controllerContext)
{
var key = GetRequestKey(controllerContext.HttpContext);
controllerContext.HttpContext.Session.Remove(key);
}
public static DateTime GetLatestTime(ControllerContext controllerContext, bool clear = false)
{
var key = GetRequestKey(controllerContext.HttpContext);
var timestamp = GetLatestTime(controllerContext, key);
if (clear)
{
ClearLatestTime(controllerContext);
}
return timestamp;
}
private static DateTime GetLatestTime(ControllerContext controllerContext, string key)
{
return controllerContext.HttpContext.Session[key] as DateTime? ?? DateTime.MinValue;
}
private void UpdateLatestTime(ControllerContext controllerContext, DateTime timestamp)
{
var key = GetRequestKey(controllerContext.HttpContext);
var currentTimeStamp = GetLatestTime(controllerContext, key);
if (timestamp > currentTimeStamp)
{
controllerContext.HttpContext.Session[key] = timestamp;
}
}
private static string GetRequestKey(HttpContextBase context)
{
return string.Format("{0}-{1}", context.Session.SessionID, context.Timestamp);
}
}
Next, replace the existing engine(s) with your new one in global.asax.cs
protected void Application_Start()
{
System.Web.Mvc.ViewEngines.Engines.Clear();
System.Web.Mvc.ViewEngines.Engines.Add(new ViewEngines.CacheFriendlyRazorViewEngine());
...
}
Finally, in some global filter or on a per-controller basis add an OnResultExecuted. Note, I believe OnResultExecuted in the controller runs after the response has been sent, so I think you must use a filter. My testing indicates this to be true.
Also, note that I am clearing the value out of the session after it is used to keep from polluting the session with the timestamps. You might want to keep it in the Cache and set a short expiration on it so you don't have to explicitly clean things out or if your session isn't kept in memory to avoid the transaction costs of storing it in the session.
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class UpdateLastModifiedFromViewsAttribute : ActionFilterAttribute
{
public override void OnResultExecuted(ResultExecutedContext filterContext)
{
var cache = filterContext.HttpContext.Response.Cache;
cache.SetLastModified(CacheFriendlyRazorViewEngine.GetLatestTime(filterContext.Controller.ControllerContext, true));
}
}
Finally, apply the filter to the controller you want to use it on or as a global filter:
[UpdateLastModifiedFromViews]
public class HomeController : Controller
{
...
}

How to use CheckBox in View _CreateOrEdit.cshtml for an integer or character database field

MVC 3, EntityFramework 4.1, Database First, Razor customization:
I have an old database that sometimes uses Int16 or Char types for a field that must appear as a CheckBox in the MVC _CreateOrEdit.cshtml View. If it is an Int, 1=true and 0=false. If it is a Char, "Y"=true and "N"=false. This is too much for the Entity Framework to convert automatically. For the Details View, I can use:
#Html.CheckBox("SampleChkInt", Model.SampleChkInt==1?true:false)
But this won't work in place of EditorFor in the _CreateOrEdit.cshtml View.
How to do this? I was thinking of a custom HtmlHelper, but the examples I've found don't show me how to tell EntityFramework to update the database properly. There are still other such customizations that I might like to do, where the MVC View does not match the database cleanly enough for EntityFramework to do an update. Answering this question would be a good example. I am working on a sample project, using the following automatically generated (so I can't make changes to it) model class:
namespace AaWeb.Models
{
using System;
using System.Collections.Generic;
public partial class Sample
{
public int SampleId { get; set; }
public Nullable<bool> SampleChkBit { get; set; }
public Nullable<short> SampleChkInt { get; set; }
public Nullable<System.DateTime> SampleDate { get; set; }
public string SampleHtml { get; set; }
public Nullable<int> SampleInt { get; set; }
public Nullable<short> SampleYesNo { get; set; }
public string Title { get; set; }
public byte[] ConcurrencyToken { get; set; }
}
}
I figured it out. Do not need a model binder or Html Helper extension:
In _CreateOrEdit.cshtml, I made up a new name SampleChkIntBool for the checkbox, and set it according to the value of the model SampleChkInt:
#Html.CheckBox("SampleChkIntBool", Model == null ? false : ( Model.SampleChkInt == 1 ? true : false ), new { #value = "true" })
Then, in the [HttpPost] Create and Edit methods of the Sample.Controller, I use Request["SampleChkIntBool"] to get the value of SampleChkIntBool and use it to set the model SampleChkInt before saving:
string value = Request["SampleChkIntBool"];
// #Html.CheckBox always generates a hidden field of same name and value false after checkbox,
// so that something is always returned, even if the checkbox is not checked.
// Because of this, the returned string is "true,false" if checked, and I only look at the first value.
if (value.Substring(0, 4) == "true") { sample.SampleChkInt = 1; } else { sample.SampleChkInt = 0; }
I believe a custom model binder would be in order here to handle the various mappings to your model.
ASP.NET MVC Model Binder for Generic Type
etc
etc
Here is the way to go from checkbox to database, without the special code in the controller:
// The following statement added to the Application_Start method of Global.asax.cs is what makes this class apply to a specific entity:
// ModelBinders.Binders.Add(typeof(AaWeb.Models.Sample), new AaWeb.Models.SampleBinder());
// There are two ways to do this, choose one:
// 1. Declare a class that extends IModelBinder, and supply all values of the entity (a big bother).
// 2. Declare a class extending DefaultModelBinder, and check for and supply only the exceptions (much better).
// This must supply all values of the entity:
//public class SampleBinder : IModelBinder
//{
// public object BindModel(ControllerContext cc, ModelBindingContext mbc)
// {
// Sample samp = new Sample();
// samp.SampleId = System.Convert.ToInt32(cc.HttpContext.Request.Form["SampleId"]);
// // Continue to specify all of the rest of the values of the Sample entity from the form, as done in the above statement.
// // ...
// return samp;
// }
//}
// This must check the property names and supply appropriate values from the FormCollection.
// The base.BindProperty must be executed at the end, to make sure everything not specified is take care of.
public class SampleBinder : DefaultModelBinder
{
protected override void BindProperty( ControllerContext cc, ModelBindingContext mbc, System.ComponentModel.PropertyDescriptor pd)
{
if (pd.Name == "SampleChkInt")
{
// This converts the "true" or "false" of a checkbox to an integer 1 or 0 for the database.
pd.SetValue(mbc.Model, (Nullable<Int16>)(cc.HttpContext.Request.Form["SampleChkIntBool"].Substring(0, 4) == "true" ? 1 : 0));
// To do the same in the reverse direction, from database to view, use pd.GetValue(Sample object).
return;
}
// Need the following to get all of the values not specified in this BindProperty method:
base.BindProperty(cc, mbc, pd);
}
}

Resources