Rate Limiting in ASP.NET MVC

Rate limiting, also known as request throttling, is an important mechanism for controlling how often web resources are accessed from a client. Implementing it on your web application or service will allow you to utilize its resources better and protect it from a high volume of malicious requests. In this article we will demonstrate how to implement and setup request throttling in an ASP.NET MVC application.

ASP.NET MVC applications handle each request through action methods that are defined in a controller class. These action methods are the entry point for each request and most of the processing is done there.

The ASP.NET MVC framework provides the ActionFilterAttribute base class that can be used to implement an attribute, applied to a controller action, which modifies the way in which the action is executed. With its help we created a custom action filter, which checks whether a certain action has been executed more than the allowed number of times, within a given period, and if so, return the 429 Rate Limit Exceeded HTTP error along with some details about it.

Below is the complete code of the custom action filter class we have implemented. It uses the runtime cache to store counts for each request made by a certain client to a certain action and method, and if the configured number of requests is exceeded before that cache entry expires, a 429 HTTP error will be returned, instead of proceeding with the action.

using System;
using System.Web;
using System.Web.Caching;
using System.Web.Mvc;

namespace MyMvcProject.Filters
{
    public enum TimeUnit
    {
        Minute = 60,
        Hour = 3600,
        Day = 86400
    }

    [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
    public class ThrottleAttribute : ActionFilterAttribute
    {
        public TimeUnit TimeUnit { get; set; }
        public int Count { get; set; }

        public override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            var seconds = Convert.ToInt32(TimeUnit);

            var key = string.Join(
                "-",
                seconds,
                filterContext.HttpContext.Request.HttpMethod,
                filterContext.ActionDescriptor.ControllerDescriptor.ControllerName,
                filterContext.ActionDescriptor.ActionName,
                filterContext.HttpContext.Request.UserHostAddress
            );

            // increment the cache value
            var cnt = 1;
            if (HttpRuntime.Cache[key] != null) {
                cnt = (int)HttpRuntime.Cache[key] + 1;
            }
            HttpRuntime.Cache.Insert(
                key,
                cnt,
                null,
                DateTime.UtcNow.AddSeconds(seconds),
                Cache.NoSlidingExpiration,
                CacheItemPriority.Low,
                null
            );

            if (cnt > Count)
            {
                filterContext.Result = new ContentResult
                {
                    Content = "You are allowed to make only " + Count + " requests per " + TimeUnit.ToString().ToLower()
                };
                filterContext.HttpContext.Response.StatusCode = 429;
            }
        }
    }
}

Here is an example how to use the attribute in a controller class. In it we add different rate limits for the same action - 5 requests within a minute, 20 requests for an hour and 100 requests for a day.

        [Throttle(TimeUnit = TimeUnit.Minute, Count = 5)]
        [Throttle(TimeUnit = TimeUnit.Hour, Count = 20)]
        [Throttle(TimeUnit = TimeUnit.Day, Count = 100)]
        [RequireHttps, HttpPost, ValidateAntiForgeryToken, ActionName("login")]
        public ActionResult Login(LoginViewModel model, string returnUrl)
        {
            // login code here ...
        }

Adding request throttling this way is a quick and easy way to make your web applications more secure, stable and harder to overload.