<p>We're experimenting with various ways to throttle user actions in a <strong>given time period</strong>:</p>

<ul>
<li>Limit question/answer posts</li>
<li>Limit edits</li>
<li>Limit feed retrievals</li>
</ul>

<p>For the time being, we're using the Cache to simply insert a record of user activity - if that record exists if/when the user does the same activity, we throttle.</p>

<p>Using the Cache automatically gives us stale data cleaning and sliding activity windows of users, but how it will scale could be a problem.</p>

<p>What are some other ways of ensuring that requests/user actions can be effectively throttled (emphasis on stability)?</p>

[{'answer_id': 291390, 'author': 'Rob Kraft', 'author_id': 37749, 'author_profile': 'https://Stackoverflow.com/users/37749', 'pm_score': 4, 'selected': False, 'text': '<p>We use the technique borrowed from this URL <a href="http://www.codeproject.com/KB/aspnet/10ASPNetPerformance.aspx" rel="noreferrer">http://www.codeproject.com/KB/aspnet/10ASPNetPerformance.aspx</a>, not for throttling, but for a poor man\'s Denial Of Service (D.O.S).  This is also cache-based, and may be similar to what you are doing.  Are you throttling to prevent D.O.S. attacks?  Routers can certainly be used to reduce D.O.S; do you think a router could handle the throttling you need?</p>\n'}, {'answer_id': 584689, 'author': 'notandy', 'author_id': 4221, 'author_profile': 'https://Stackoverflow.com/users/4221', 'pm_score': 6, 'selected': False, 'text': '<p>Microsoft has a new extension for IIS 7 called Dynamic IP Restrictions Extension for IIS 7.0 - Beta. </p>\n\n<blockquote>\n  <p>"The Dynamic IP Restrictions for IIS 7.0 is a module that provides protection against denial of service and brute force attacks on web server and web sites. Such protection is provided by temporarily blocking IP addresses of the HTTP clients who make unusually high number of concurrent requests or who make large number of requests over small period of time."\n  <a href="http://learn.iis.net/page.aspx/548/using-dynamic-ip-restrictions/" rel="noreferrer">http://learn.iis.net/page.aspx/548/using-dynamic-ip-restrictions/</a></p>\n</blockquote>\n\n<p>Example:</p>\n\n<p>If you set the criteria to block after <code>X requests in Y milliseconds</code> or <code>X concurrent connections in Y milliseconds</code> the IP address will be blocked for <code>Y milliseconds</code> then requests will be permitted again.</p>\n'}, {'answer_id': 1318059, 'author': 'Jarrod Dixon', 'author_id': 3, 'author_profile': 'https://Stackoverflow.com/users/3', 'pm_score': 9, 'selected': True, 'text': '<p>Here\'s a generic version of what we\'ve been using on Stack Overflow for the past year:</p>\n\n<pre><code>/// &lt;summary&gt;\n/// Decorates any MVC route that needs to have client requests limited by time.\n/// &lt;/summary&gt;\n/// &lt;remarks&gt;\n/// Uses the current System.Web.Caching.Cache to store each client request to the decorated route.\n/// &lt;/remarks&gt;\n[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]\npublic class ThrottleAttribute : ActionFilterAttribute\n{\n    /// &lt;summary&gt;\n    /// A unique name for this Throttle.\n    /// &lt;/summary&gt;\n    /// &lt;remarks&gt;\n    /// We\'ll be inserting a Cache record based on this name and client IP, e.g. "Name-192.168.0.1"\n    /// &lt;/remarks&gt;\n    public string Name { get; set; }\n\n    /// &lt;summary&gt;\n    /// The number of seconds clients must wait before executing this decorated route again.\n    /// &lt;/summary&gt;\n    public int Seconds { get; set; }\n\n    /// &lt;summary&gt;\n    /// A text message that will be sent to the client upon throttling.  You can include the token {n} to\n    /// show this.Seconds in the message, e.g. "Wait {n} seconds before trying again".\n    /// &lt;/summary&gt;\n    public string Message { get; set; }\n\n    public override void OnActionExecuting(ActionExecutingContext c)\n    {\n        var key = string.Concat(Name, "-", c.HttpContext.Request.UserHostAddress);\n        var allowExecute = false;\n\n        if (HttpRuntime.Cache[key] == null)\n        {\n            HttpRuntime.Cache.Add(key,\n                true, // is this the smallest data we can have?\n                null, // no dependencies\n                DateTime.Now.AddSeconds(Seconds), // absolute expiration\n                Cache.NoSlidingExpiration,\n                CacheItemPriority.Low,\n                null); // no callback\n\n            allowExecute = true;\n        }\n\n        if (!allowExecute)\n        {\n            if (String.IsNullOrEmpty(Message))\n                Message = "You may only perform this action every {n} seconds.";\n\n            c.Result = new ContentResult { Content = Message.Replace("{n}", Seconds.ToString()) };\n            // see 409 - http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html\n            c.HttpContext.Response.StatusCode = (int)HttpStatusCode.Conflict;\n        }\n    }\n}\n</code></pre>\n\n<p>Sample usage:</p>\n\n<pre><code>[Throttle(Name="TestThrottle", Message = "You must wait {n} seconds before accessing this url again.", Seconds = 5)]\npublic ActionResult TestThrottle()\n{\n    return Content("TestThrottle executed");\n}\n</code></pre>\n\n<p>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.</p>\n\n<p>Feel free to give feedback on this method; when we make Stack Overflow better, you get your <a href="https://stackoverflow.blog/2009/05/31/the-stack-overflow-trilogy/">Ewok fix</a> even faster :)</p>\n'}, {'answer_id': 71646995, 'author': 'JsAndDotNet', 'author_id': 852806, 'author_profile': 'https://Stackoverflow.com/users/852806', 'pm_score': 2, 'selected': False, 'text': '<p>It took me some time to work out an equivalent for .NET 5+ (formerly .NET Core), so here\'s a starting point.</p>\n<p>The old way of caching has gone and been replaced by <code>Microsoft.Extensions.Caching.Memory</code> with <a href="https://learn.microsoft.com/en-us/aspnet/core/performance/caching/memory?view=aspnetcore-6.0" rel="nofollow noreferrer">IMemoryCache</a>.</p>\n<p>I separated it out a bit more, so here\'s what you need...</p>\n<p><strong>The Cache Management Class</strong></p>\n<p>I\'ve added the whole thing here, so you can see the using statements.</p>\n<pre><code>using Microsoft.Extensions.Caching.Memory;\nusing Microsoft.Extensions.Primitives;\nusing System;\nusing System.Threading;\n\nnamespace MyWebApplication\n{\n    public interface IThrottleCache\n    {\n        bool AddToCache(string key, int expriryTimeInSeconds);\n\n        bool AddToCache&lt;T&gt;(string key, T value, int expriryTimeInSeconds);\n\n        T GetFromCache&lt;T&gt;(string key);\n\n        bool IsInCache(string key);\n    }\n\n    /// &lt;summary&gt;\n    /// A caching class, based on the docs\n    /// https://learn.microsoft.com/en-us/aspnet/core/performance/caching/memory?view=aspnetcore-6.0\n    /// Uses the recommended library &quot;Microsoft.Extensions.Caching.Memory&quot;\n    /// &lt;/summary&gt;\n    public class ThrottleCache : IThrottleCache\n    {\n        private IMemoryCache _memoryCache;\n\n        public ThrottleCache(IMemoryCache memoryCache)\n        {\n            _memoryCache = memoryCache;\n        }\n\n\n        public bool AddToCache(string key, int expriryTimeInSeconds)\n        {\n            bool isSuccess = false; // Only a success if a new value gets added.\n\n            if (!IsInCache(key))\n            {\n                var cancellationTokenSource = new CancellationTokenSource(\n                                                     TimeSpan.FromSeconds(expriryTimeInSeconds));\n\n                var cacheEntryOptions = new MemoryCacheEntryOptions()\n                    .SetSize(1)\n                    .AddExpirationToken(\n                        new CancellationChangeToken(cancellationTokenSource.Token));\n\n                _memoryCache.Set(key, DateTime.Now, cacheEntryOptions);\n\n                isSuccess = true;\n            }\n\n            return isSuccess;\n        }\n\n\n        public bool AddToCache&lt;T&gt;(string key, T value, int expriryTimeInSeconds)\n        {\n            bool isSuccess = false;\n\n            if (!IsInCache(key))\n            {\n                var cancellationTokenSource = new CancellationTokenSource(\n                                                     TimeSpan.FromSeconds(expriryTimeInSeconds));\n\n                var cacheEntryOptions = new MemoryCacheEntryOptions()\n                    .SetAbsoluteExpiration(DateTimeOffset.Now.AddSeconds(expriryTimeInSeconds))\n                    .SetSize(1)\n                    .AddExpirationToken(\n                        new CancellationChangeToken(cancellationTokenSource.Token));\n\n                _memoryCache.Set&lt;T&gt;(key, value, cacheEntryOptions);\n\n                isSuccess = true;\n            }\n\n            return isSuccess;\n        }\n\n\n        public T GetFromCache&lt;T&gt;(string key)\n        {\n            return _memoryCache.Get&lt;T&gt;(key);\n        }\n\n\n        public bool IsInCache(string key)\n        {\n            var item = _memoryCache.Get(key);\n\n            return item != null;\n        }\n\n\n    }\n}\n</code></pre>\n<p><strong>The attribute itself</strong></p>\n<pre><code>using Microsoft.AspNetCore.Mvc;\nusing Microsoft.AspNetCore.Mvc.Filters;\nusing System;\nusing System.Net;\n\nnamespace MyWebApplication\n{\n    /// &lt;summary&gt;\n    /// Decorates any MVC route that needs to have client requests limited by time.\n    /// Based on how they throttle at stack overflow (updated for .NET5+)\n    /// https://stackoverflow.com/questions/33969/best-way-to-implement-request-throttling-in-asp-net-mvc/1318059#1318059\n    /// &lt;/summary&gt;\n    /// &lt;remarks&gt;\n    /// Uses the current System.Web.Caching.Cache to store each client request to the decorated route.\n    /// &lt;/remarks&gt;\n    [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]\n    public class ThrottleByIPAddressAttribute : ActionFilterAttribute\n    {\n        /// &lt;summary&gt;\n        /// The caching class (which will be instantiated as a singleton)\n        /// &lt;/summary&gt;\n        private IThrottleCache _throttleCache;\n\n\n        /// &lt;summary&gt;\n        /// A unique name for this Throttle.\n        /// &lt;/summary&gt;\n        /// &lt;remarks&gt;\n        /// We\'ll be inserting a Cache record based on this name and client IP, e.g. &quot;Name-192.168.0.1&quot;\n        /// &lt;/remarks&gt;\n        public string Name { get; set; }\n\n        /// &lt;summary&gt;\n        /// The number of seconds clients must wait before executing this decorated route again.\n        /// &lt;/summary&gt;\n        public int Seconds { get; set; }\n\n        /// &lt;summary&gt;\n        /// A text message that will be sent to the client upon throttling.  You can include the token {n} to\n        /// show this.Seconds in the message, e.g. &quot;Wait {n} seconds before trying again&quot;.\n        /// &lt;/summary&gt;\n        public string Message { get; set; } = &quot;You may only perform this action every {n} seconds.&quot;;\n\n        public override void OnActionExecuting(ActionExecutingContext c)\n        {\n            if(_throttleCache == null)\n            {\n                var cache = c.HttpContext.RequestServices.GetService(typeof(IThrottleCache));\n                _throttleCache = (IThrottleCache)cache;\n            }\n            \n            var key = string.Concat(Name, &quot;-&quot;, c.HttpContext.Request.HttpContext.Connection.RemoteIpAddress);\n\n            var allowExecute = _throttleCache.AddToCache(key, Seconds);\n\n\n            if (!allowExecute)\n            {\n                if (String.IsNullOrEmpty(Message))\n                    Message = &quot;You may only perform this action every {n} seconds.&quot;;\n\n                c.Result = new ContentResult { Content = Message.Replace(&quot;{n}&quot;, Seconds.ToString()) };\n                // see 409 - http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html\n                c.HttpContext.Response.StatusCode = (int)HttpStatusCode.Conflict;\n            }\n        }\n    }\n}\n</code></pre>\n<p><strong>Startup.cs or Program.cs - Register the services with DI</strong></p>\n<p>This example uses Startup.cs/ConfigureServices - Put the code somewhere after <code>AddControllersWithViews</code>).</p>\n<p>For a project created in .NET6+ I think you\'d add the equivalent between <code>builder.Services.AddRazorPages();</code> and <code>var app = builder.Build();</code> in program.cs. <code>services</code> would be <code>builder.Services</code>.</p>\n<p>If you don\'t get the placement of this code right, the cache will be empty every time you check it.</p>\n<pre><code>// The cache for throttling must be a singleton and requires IMemoryCache to be set up.\n// Place it after AddControllersWithViews or AddRazorPages as they build a cache themselves\n\n// Need this for IThrottleCache to work.\nservices.AddMemoryCache(_ =&gt; new MemoryCacheOptions\n{\n    SizeLimit = 1024, /* TODO: CHECK THIS IS THIS THE RIGHT SIZE FOR YOU! */\n    CompactionPercentage = .3,\n    ExpirationScanFrequency = TimeSpan.FromSeconds(30),\n});\nservices.AddSingleton&lt;IThrottleCache, ThrottleCache&gt;();\n</code></pre>\n<p><strong>Example Usage</strong></p>\n<pre><code>[HttpGet, Route(&quot;GetTest&quot;)]\n[ThrottleByIPAddress(Name = &quot;MyControllerGetTest&quot;, Seconds = 5)]\npublic async Task&lt;ActionResult&lt;string&gt;&gt; GetTest()\n{\n    return &quot;Hello world&quot;;\n}\n</code></pre>\n<p>To help understand caching in .NET 5+, I\'ve also made a <a href="https://gist.github.com/JsAndDotNet/594019f4053a498aef3e0fcd59f5c68d" rel="nofollow noreferrer">caching console demo</a>.</p>\n'}, {'answer_id': 72008712, 'author': 'prem', 'author_id': 3085520, 'author_profile': 'https://Stackoverflow.com/users/3085520', 'pm_score': 1, 'selected': False, 'text': '<p>Since the highly voted answers to this question are too old, I am sharing the latest solution which worked for me.</p>\n<p>I tried using the Dynamic IP restrictions as given in an <a href="https://stackoverflow.com/a/584689/3085520">answer</a> on this page but when I tried to use that extension, I found that this extension has been discontinued by Microsoft and on the <a href="https://www.iis.net/downloads/microsoft/dynamic-ip-restrictions" rel="nofollow noreferrer">download page</a> they have clearly written the below message.</p>\n<pre><code>Microsoft has discontinued the Dynamic IP Restrictions extension and this download is no longer available.\n</code></pre>\n<p>So I researched further and found that the Dynamic IP Restrictions is now by default included in IIS 8.0 and above. The below information is fetched from the Microsoft Dynamic IP Restrictions page.</p>\n<p>In IIS 8.0, Microsoft has expanded the built-in functionality to include several new features:</p>\n<ul>\n<li>Dynamic IP address filtering, which allows administrators to\nconfigure their server to block access for IP addresses that exceed\nthe specified number of requests.</li>\n<li>The IP address filtering features now allow administrators to specify\nthe behavior when IIS blocks an IP address, so requests from\nmalicious clients can be aborted by the server instead of returning\nHTTP 403.6 responses to the client.</li>\n<li>IP filtering now feature a proxy mode, which allows IP addresses to\nbe blocked not only by the client IP that is seen by IIS but also by\nthe values that are received in the x-forwarded-for HTTP header</li>\n</ul>\n<p>For step by step instructions to implement Dynamic IP Restrictions, please visit the below link:</p>\n<p><a href="https://learn.microsoft.com/en-us/iis/get-started/whats-new-in-iis-8/iis-80-dynamic-ip-address-restrictions" rel="nofollow noreferrer">https://learn.microsoft.com/en-us/iis/get-started/whats-new-in-iis-8/iis-80-dynamic-ip-address-restrictions</a></p>\n<p>I hope it helps someone stuck in a similar problem.</p>\n'}]
['https://Stackoverflow.com/questions/33969', 'https://Stackoverflow.com', 'https://Stackoverflow.com/users/3/']