Wim Coenen
Wim Coenen

Reputation: 66723

Choose OutputCache policy based on query string

I have an ASP.NET MVC application where certain resources are addressed like this:

/controller/action/id?revision=123

The revision parameter is optional:

My first attempt was to do something like this:

[OutputCache(Duration=10,Location=OutputCacheLocation.Server)]
public Action(string id)
{
    long lastRevision = GetLastRevision(id);
    return RedirectToAction("Action", 
        new { Id = id, revision = lastRevision }); 
}

[OutputCache(Duration=int.MaxValue,Location=OutputCacheLocation.Server)]
public Action(string id, long revision)
{
   // ... 
}

Unfortunately, the ASP.NET MVC routing doesn't seem to like method overloads. It expects to have a single Action method with an optional parameter instead (i.e. long? revision), but then I can't specify different caching policies for both cases.

How can I chose a different caching policy based on the presence of the query string here?

Upvotes: 2

Views: 786

Answers (2)

Wim Coenen
Wim Coenen

Reputation: 66723

It turns out that I had already solved this problem without realizing it by making use of 302 redirects: apparently 302 responses are not cached even if you have an OutputCache attribute on your controller method!

Therefore both cases can be handled by a single controller method with the [OutputCache(...)] attribute specifying what to do for 200 responses.

Though this now begs the question of what to do if you do want to cache a 302...

Upvotes: 0

Darin Dimitrov
Darin Dimitrov

Reputation: 1038730

You could write a custom method selector:

public class RevisionMethodSelectorAttribute : ActionMethodSelectorAttribute
{
    public override bool IsValidForRequest(ControllerContext controllerContext, MethodInfo methodInfo)
    {
        var revision = controllerContext.Controller.ValueProvider.GetValue("revision");
        var hasRevisionParam = methodInfo.GetParameters().Any(p => string.Equals("revision", p.Name, StringComparison.OrdinalIgnoreCase));
        if (revision != null && !string.IsNullOrEmpty(revision.AttemptedValue) && hasRevisionParam)
        {
            return true;
        }

        if ((revision == null || string.IsNullOrEmpty(revision.AttemptedValue)) && !hasRevisionParam)
        {
            return true;
        }

        return false;
    }
}

and then decorate the 2 actions with it:

[RevisionMethodSelector]
public ActionResult MyAction(string id)
{
    long lastRevision = GetLastRevision(id);
    return RedirectToAction("MyAction", new { id = id, revision = lastRevision }); 
}

[RevisionMethodSelector]
[OutputCache(Duration = int.MaxValue, Location = OutputCacheLocation.Server, VaryByParam = "revision")]
public ActionResult MyAction(string id, long revision)
{
    ...
}

The first action is not cached. It will be picked up if there's no revision parameter in the request and it will simply redirect to the second action. The second action is cached for a very long time, this cache is made to vary according to the revision parameter value (which you didn't have) and will be picked by the custom method selector if a revision parameter is present in the request.

Upvotes: 2

Related Questions