Here's a generic version of what we've been using on Stack Overflow for the past year:
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class ThrottleAttribute : ActionFilterAttribute
{
public string Name { get; set; }
public int Seconds { get; set; }
public string Message { get; set; }
public override void OnActionExecuting(ActionExecutingContext c)
{
var key = string.Concat(Name, "-", c.HttpContext.Request.UserHostAddress);
var allowExecute = false;
if (HttpRuntime.Cache[key] == null)
{
HttpRuntime.Cache.Add(key,
true,
null,
DateTime.Now.AddSeconds(Seconds),
Cache.NoSlidingExpiration,
CacheItemPriority.Low,
null);
allowExecute = true;
}
if (!allowExecute)
{
if (String.IsNullOrEmpty(Message))
Message = "You may only perform this action every {n} seconds.";
c.Result = new ContentResult { Content = Message.Replace("{n}", Seconds.ToString()) };
c.HttpContext.Response.StatusCode = (int)HttpStatusCode.Conflict;
}
}
}
Sample usage:
[Throttle(Name="TestThrottle", Message = "You must wait {n} seconds before accessing this url again.", Seconds = 5)]
public ActionResult TestThrottle()
{
return Content("TestThrottle executed");
}
The ASP.NET Cache works like a champ here - by using it, you get automatic clean-up of your throttle entries. And with our growing traffic, we're not seeing that this is an issue on the server.
Feel free to give feedback on this method; when we make Stack Overflow better, you get your Ewok fix even faster :)