I have previously introduced tools like AspNetCoreRateLimit to everyone, so I won’t go over that today. Instead, let’s talk about the underlying principles. Feedback from experts is welcome!
Consider some common API request interfaces we often see:

Take the seven-day weather forecast API for major foreign cities as an example: non-VIP users can only use it 2000 times, while VIP users can make up to 10,000 requests per day. To access this API, you must register an account to obtain an appid and secret key.
Based on this requirement, let’s design a rate-limited weather API.
Step 1
Verify whether the login account exists. If it does not exist, throw an error indicating that the account does not exist.
[HttpPost("GetWeather")]
public IActionResult ApiLimit(WeatherInfor weatherInfor)
{
if (!_userService.IsValid(weatherInfor.Appid, weatherInfor.Appsecret))
{
throw new Exception("Incorrect account or password");
}
}
Step 2
Determine whether the account is a VIP user. If it is a VIP, there is no limit on total calls, only a daily limit. For a daily limit that resets the next day, using a cache is reasonable. We set the cache to expire at 23:59:59 each day, which is an absolute expiration time.
The specific business logic is as follows: Since the user's appid is unique, we can use it as the key and the call count as the value. If the cache does not exist, we add it; if it exists, we retrieve the call count. If the count exceeds 2000, we inform the caller that the quota is exhausted. Otherwise, we get the cached call count and increment it by one.
Cache class
public class MemoryCacheHelper
{
public static MemoryCache _cache = new MemoryCache(new MemoryCacheOptions());
/// <summary>
/// Check whether the cache item exists
/// </summary>
/// <param name="key">Cache key</param>
/// <returns></returns>
public static bool Exists(string key)
{
if (key == null)
{
return false;
}
return _cache.TryGetValue(key, out _);
}
/// <summary>
/// Get cached data
/// </summary>
/// <param name="key">Cache key</param>
/// <returns></returns>
public static object Get(string key)
{
if (key == null)
{
throw new ArgumentNullException(nameof(key));
}
if (!Exists(key))
throw new ArgumentNullException(nameof(key));
return _cache.Get(key);
}
/// <summary>
/// Add cache
/// </summary>
/// <param name="key">Cache key</param>
/// <param name="value">Cache value</param>
/// <param name="expiresSliding">Sliding expiration time (if there is an operation within the expiration time, the expiration time is extended from the current time)</param>
/// <param name="expiressAbsoulte">Absolute expiration time</param>
/// <returns></returns>
public static bool AddMemoryCache(string key, object value)
{
if (key == null)
{
throw new ArgumentNullException(nameof(key));
}
if (value == null)
{
throw new ArgumentNullException(nameof(value));
}
DateTime time = Convert.ToDateTime(DateTime.Now.AddDays(1).ToString("D").ToString()).AddSeconds(-1);
_cache.Set(key, value, time);
return Exists(key);
}
}
Business logic
public bool IsLimit(string appId)
{
if (MemoryCacheHelper.Exists(appId))
{
int value = int.Parse(MemoryCacheHelper.Get(appId).ToString());
if (value > 2000)
return false;
else
value = value + 1;
MemoryCacheHelper.AddMemoryCache(appId, value);
}
else
{
MemoryCacheHelper.AddMemoryCache(appId, 1);
}
return true;
}
Finally
Query the weather API and return the data.
[HttpPost("GetWeather")]
public IActionResult ApiLimit(WeatherInfor weatherInfor)
{
if (!_userService.IsValid(weatherInfor.Appid, weatherInfor.Appsecret))
{
throw new Exception("Incorrect account or password");
}
bool isLimit = false;
if (_userService.IsVIP(weatherInfor.Appid))
{
isLimit= _sqlServices.IsLimit(weatherInfor.Appid);
}
else
{
isLimit = _memoryCacheServices.IsLimit(weatherInfor.Appid);
}
if (isLimit)
{
// Query the weather API and return data
}
else
{
throw new Exception("Call quota exhausted");
}
return Ok("");
}
If you like my article, please follow and give me a like. I hope the .NET ecosystem gets better and better!