/// <summary>
        /// Initializes a new instance of the <see cref="ThrottlingSentinelAttribute"/> class.
        /// </summary>
        /// <param name="rateLimit">The allowed rate from within a given <paramref name="window"/>.</param>
        /// <param name="window">The duration of the window.</param>
        /// <param name="windowUnit">One of the enumeration values that specifies the time unit of <paramref name="window"/>.</param>
        public ThrottlingSentinelAttribute(int rateLimit, double window, TimeUnit windowUnit)
        {
            var options = new ThrottlingSentinelOptions();

            RateLimit                    = rateLimit;
            Window                       = window;
            WindowUnit                   = windowUnit;
            UseRetryAfterHeader          = options.UseRetryAfterHeader;
            RetryAfterHeader             = options.RetryAfterHeader;
            TooManyRequestsMessage       = options.TooManyRequestsMessage;
            RateLimitHeaderName          = options.RateLimitHeaderName;
            RateLimitRemainingHeaderName = options.RateLimitRemainingHeaderName;
            RateLimitResetHeaderName     = options.RateLimitResetHeaderName;
        }
        public static async Task InvokeThrottlerSentinelAsync(HttpContext context, IThrottlingCache tc, ThrottlingSentinelOptions options, Action <HttpResponseMessage, HttpResponse> transformer)
        {
            var utcNow            = DateTime.UtcNow;
            var throttlingContext = options.ContextResolver?.Invoke(context);

            if (!throttlingContext.IsNullOrWhiteSpace())
            {
                try
                {
                    await ThrottleLocker.WaitAsync();

                    if (!tc.TryGetValue(throttlingContext, out var tr))
                    {
                        tr = new ThrottleRequest(options.Quota);
                        tc.AddIfNotContainsKey(throttlingContext, tr);
                    }
                    else
                    {
                        tr.Refresh();
                        tr.Total++;
                    }

                    var window = new TimeRange(utcNow, tr.Expires);
                    var delta  = window.Duration;
                    var reset  = utcNow.Add(delta);
                    context.Response.Headers.AddOrUpdate(options.RateLimitHeaderName, tr.Quota.RateLimit.ToString(CultureInfo.InvariantCulture));
                    context.Response.Headers.AddOrUpdate(options.RateLimitRemainingHeaderName, Math.Max(tr.Quota.RateLimit - tr.Total, 0).ToString(CultureInfo.InvariantCulture));
                    context.Response.Headers.AddOrUpdate(options.RateLimitResetHeaderName, reset.ToEpochTime().ToString(CultureInfo.InvariantCulture));
                    if (tr.Total > tr.Quota.RateLimit && tr.Expires > utcNow)
                    {
                        var message = options.ResponseBroker?.Invoke(delta, reset);
                        if (message != null)
                        {
                            message.ToHttpResponse(context.Response, transformer);
                            throw new ThrottlingException((int)message.StatusCode, await message.Content.ReadAsStringAsync(), tr.Quota.RateLimit, delta, reset);
                        }
                    }

                    tc[throttlingContext] = tr;
                }
                finally
                {
                    ThrottleLocker.Release();
                }
            }
        }