VaryByParam fails if a param is a list

955 Views Asked by At

I've got this action in MVC

[OutputCache(Duration = 1200, VaryByParam = "*")]
public ActionResult FilterArea( string listType, List<int> designersID, int currPage = 1 )
{
   // Code removed
}

that fails to present the correct HTML with url like

Is this a know bug of OutputCache in .NET cause cannot recognize VaryByParam with a list param or am I missing something?

1

There are 1 best solutions below

1
On BEST ANSWER

I too had the same issue in MVC3 and I believe it's still the same case in MVC5.

Here is the setup I had.

Request

POST, Content-Type:application/json, passing in an array of string as the parameter

{ "options": ["option1", "option2"] }

Controller Method

[OutputCache(Duration = 3600, Location = OutputCacheLocation.Any, VaryByParam = "options")]
public ActionResult GetOptionValues(List<string> options)

I tried every option possible with OutputCache and it just wasn't caching for me. Binding worked fine for the actual method to work. My biggest suspicion was that OutputCache wasn't creating unique cache keys so I even pulled its code out of System.Web.MVC.OutputCache to verify. I've verified that it properly builds unique keys even when a List<string> is passed in. Something else is buggy in there but wasn't worth spending more effort.

OutputCacheAttribute.GetUniqueIdFromActionParameters(filterContext,
                OutputCacheAttribute.SplitVaryByParam(this.VaryByParam);

Workaround

I ended up creating my own OutputCache attribute following another SO post. Much easier to use and I can go enjoy the rest of the day.

Controller Method

[MyOutputCache(Duration=3600)]
public ActionResult GetOptionValues(Options options)

Custom Request class

I've inherited from List<string> so I can call the overriden .ToString() method in MyOutputcache class to give me a unique cache key string. This approach alone has resolved similar issues for others but not for me.

[DataContract(Name = "Options", Namespace = "")]
public class Options: List<string>
{
    public override string ToString()
    {
        var optionsString= new StringBuilder();
        foreach (var option in this)
        {
            optionsString.Append(option);
        }
        return optionsString.ToString();
    }
}

Custom OutputCache class

public class MyOutputCache : ActionFilterAttribute
{
    private string _cachedKey;

    public int Duration { get; set; }

    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        if (filterContext.HttpContext.Request.Url != null)
        {
            var path = filterContext.HttpContext.Request.Url.PathAndQuery;
            var attributeNames = filterContext.ActionParameters["Options"] as AttributeNames;
            if (attributeNames != null) _cachedKey = "MYOUTPUTCACHE:" + path + attributeNames;
        }
        if (filterContext.HttpContext.Cache[_cachedKey] != null)
        {
            filterContext.Result = (ActionResult) filterContext.HttpContext.Cache[_cachedKey];
        }
        else
        {
            base.OnActionExecuting(filterContext);
        }
    }

    public override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        filterContext.HttpContext.Cache.Add(_cachedKey, filterContext.Result, null,
            DateTime.Now.AddSeconds(Duration), System.Web.Caching.Cache.NoSlidingExpiration,
            System.Web.Caching.CacheItemPriority.Default, null);
        base.OnActionExecuted(filterContext);
    }
}