/// <summary>
        ///
        /// </summary>
        /// <param name="context"></param>
        /// <param name="next"></param>
        /// <returns></returns>
        public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
        {
            IServiceProvider serviceProvider = context.HttpContext.RequestServices;
            var responseCacheOption          = serviceProvider.GetService <ResponseCacheOption>();

            //配置存在且配置关闭或者持续时长等于0,即忽略响应缓存
            if ((responseCacheOption != null && !responseCacheOption.Enabled) || Duration == 0)
            {
                goto gotoNext;
            }
            context.HttpContext.Request.EnableBuffering();
            var           _logger        = serviceProvider.GetRequiredService <ILogger <PostResponseCacheAttribute> >();
            var           _configuration = serviceProvider.GetRequiredService <IConfiguration>();
            var           _memorycache   = serviceProvider.GetService <IMemoryCache>();
            IRedisManager _redisManager  = null;

            if ((responseCacheOption != null && responseCacheOption.Cluster) || Cluster)
            {
                _redisManager = serviceProvider.GetService <IRedisManager>();
                if (_redisManager == null)
                {
                    throw new ArgumentNullException(nameof(IMemoryCache), "Post响应已启用集群模式,缓存依赖IRedisManager,请services.AddRedisManager()注入IRedisManager后再使用[PostResponseCache]");
                }
            }
            if (_memorycache == null)
            {
                throw new ArgumentNullException(nameof(IMemoryCache), "Post响应缓存依赖IMemoryCache,请services.AddMemoryCache()注入IMemoryCache后再使用[PostResponseCache]");
            }
            //标识已触发过响应缓存,防止中间件再次触发
            _memorycache.Set($"PostResponseCache_{context.HttpContext.Request.Path}", "标识已触发过响应缓存,防止中间件再次触发", new MemoryCacheEntryOptions {
                AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5)
            });

            var _requestCacheData = serviceProvider.GetService <RequestCacheData>();

            if (_requestCacheData == null)
            {
                throw new ArgumentNullException(nameof(IMemoryCache), "Post响应缓存依赖ResponseCacheData,请调用services.AddShareRequestBody()注入后再使用[PostResponseCache]");
            }

            var token = context.HttpContext.RequestAborted.Register(async() =>
            {
                await Task.CompletedTask;
                return;
            });

            var descriptor = (Microsoft.AspNetCore.Mvc.Controllers.ControllerActionDescriptor)context.ActionDescriptor;

            var attribute = (IgnorePostResponseCacheAttribute)descriptor.MethodInfo.GetCustomAttributes(typeof(IgnorePostResponseCacheAttribute), true).FirstOrDefault();

            if (attribute != null)
            {
                //记录忽略的路由,中间件跳过
                _memorycache.Set($"IgnorePostResponseCache_{context.HttpContext.Request.Path}", true, new MemoryCacheEntryOptions {
                    AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5)
                });
                goto gotoNext;
            }

            if (context.HttpContext.Request.Method.Equals("get", StringComparison.OrdinalIgnoreCase) || context.HttpContext.Request.Method.Equals("head", StringComparison.OrdinalIgnoreCase))
            {
                goto gotoNext;
            }
            else
            {
                var convertedDictionatry = context.HttpContext.Request.Query.ToDictionary(s => s.Key.ToLower(), s => s.Value);

                if (IgnoreVaryByQueryKeys == null || !IgnoreVaryByQueryKeys.Any())
                {
                    if (responseCacheOption.IgnoreVaryByQueryKeys.Any())
                    {
                        IgnoreVaryByQueryKeys = responseCacheOption.IgnoreVaryByQueryKeys.ToArray();
                    }
                }

                foreach (var item in IgnoreVaryByQueryKeys ?? new string[0])
                {
                    if (convertedDictionatry.ContainsKey(item.ToLower()))
                    {
                        convertedDictionatry.Remove(item.ToLower());
                    }
                }

                StringBuilder requestStrKey = new StringBuilder(context.HttpContext.Request.Path);
                foreach (var item in convertedDictionatry)
                {
                    requestStrKey.Append($"{item.Key}{item.Value}");
                }

                string bodyValue;
                if (_requestCacheData == null || string.IsNullOrEmpty(_requestCacheData.Body))
                {
                    bodyValue = await Common.ReadAsString(context.HttpContext);

                    _requestCacheData = new RequestCacheData {
                        Body = bodyValue
                    };
                }
                else
                {
                    bodyValue = _requestCacheData.Body;
                }
                if (!string.IsNullOrEmpty(bodyValue) && !"null".Equals(bodyValue))
                {
                    //非Get请求body有值才被缓存,其他默认不缓存,防止body读取失败导致缓存异常
                    bodyValue = Regex.Replace(bodyValue, @"\s(?=([^""]*""[^""]*"")*[^""]*$)", string.Empty);
                    bodyValue = bodyValue.Replace("\r\n", "").Replace(" : ", ":").Replace("\n  ", "").Replace("\n", "").Replace(": ", ":").Replace(", ", ",");

                    requestStrKey.Append($"body{bodyValue}");
                    ResponseCacheData cacheResponseBody = null;
                    if (Cluster)
                    {
                        cacheResponseBody = _redisManager.Get <ResponseCacheData>($"NetProPostResponse:{requestStrKey}");
                    }
                    else
                    {
                        cacheResponseBody = _memorycache.Get <ResponseCacheData>($"NetProPostResponse:{requestStrKey}");
                    }

                    if (cacheResponseBody != null && !context.HttpContext.RequestAborted.IsCancellationRequested)
                    {
                        if (!context.HttpContext.Response.HasStarted)
                        {
                            _logger.LogInformation($"触发PostResponseCacheAttribute本地缓存");
                            switch (ResponseMode)
                            {
                            case ResponseMode.Cache:
                                context.HttpContext.Response.StatusCode  = cacheResponseBody.StatusCode;
                                context.HttpContext.Response.ContentType = cacheResponseBody.ContentType;
                                await context.HttpContext.Response.WriteAsync(cacheResponseBody.Body);

                                await Task.CompletedTask;
                                return;

                            case ResponseMode.Error:
                                if (cacheResponseBody.StatusCode == 200)
                                {
                                    //TODO确定StatusCode与Headers响应的先后顺序
                                    context.HttpContext.Response.StatusCode = (int)HttpStatusCode.Conflict;
                                    context.HttpContext.Response.Headers.Add("Kestrel-ResponseMode", $"{ResponseMode}");
                                    context.HttpContext.Response.ContentType = cacheResponseBody.ContentType;
                                    await context.HttpContext.Response.WriteAsync(JsonSerializer.Serialize(new
                                    {
                                        Code = -1,
                                        Msg  = $"{Message}"
                                    }, new JsonSerializerOptions
                                    {
                                        PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
                                        Encoder = System.Text.Encodings.Web.JavaScriptEncoder.Create(System.Text.Unicode.UnicodeRanges.All)
                                    }), Encoding.UTF8);

                                    await Task.CompletedTask;
                                    return;
                                }
                                else
                                {
                                    context.HttpContext.Response.StatusCode  = cacheResponseBody.StatusCode;
                                    context.HttpContext.Response.ContentType = cacheResponseBody.ContentType;
                                    await context.HttpContext.Response.WriteAsync(cacheResponseBody.Body);

                                    await Task.CompletedTask;
                                    return;;
                                }
                            }
                        }
                        else
                        {
                            _logger.LogError($"StatusCode无法设置,因为响应已经启动,位置为:触发本地缓存开始赋值[responsecache2]");
                            await Task.CompletedTask;
                            return;
                        }
                    }
                    else if (!context.HttpContext.RequestAborted.IsCancellationRequested)
                    {
                        try
                        {
                            var actionResult = await next();

                            dynamic responseResult = (dynamic)actionResult.Result;
                            if (actionResult.Exception != null)
                            {
                                // 过滤器中await next();执行的始终是Action而不是下一个过滤器或者中间件
                                await Task.CompletedTask;
                                return;
                            }
                            if (responseResult == null)
                            {
                                await Task.CompletedTask;
                                return;
                            }

                            if (responseResult.GetType().Name == "EmptyResult")
                            {
                                await Task.CompletedTask;
                                return;
                            }

                            string body = string.Empty;
                            if (responseResult as Microsoft.AspNetCore.Mvc.ObjectResult != null)
                            {
                                body = JsonSerializer.Serialize(responseResult.Value, new JsonSerializerOptions
                                {
                                    PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
                                    Encoder = System.Text.Encodings.Web.JavaScriptEncoder.Create(System.Text.Unicode.UnicodeRanges.All)
                                });
                            }

                            if (Cluster)
                            {
                                _redisManager.Set($"NetProPostResponse:{requestStrKey}", new ResponseCacheData
                                {
                                    Body        = body,
                                    ContentType = "application/json",
                                    StatusCode  = responseResult.StatusCode ?? 200,
                                }, TimeSpan.FromSeconds(Duration));
                            }
                            else
                            {
                                _memorycache.Set($"NetProPostResponse:{requestStrKey}", new ResponseCacheData
                                {
                                    Body        = body,
                                    ContentType = "application/json",
                                    StatusCode  = responseResult.StatusCode ?? 200,
                                }, TimeSpan.FromSeconds(Duration));
                            }
                        }
                        catch (Exception ex)
                        {
                            await Task.CompletedTask;
                            return;
                        }
                        await Task.CompletedTask;
                        return;
                    }
                    else if (context.HttpContext.RequestAborted.IsCancellationRequested)
                    {
                        await Task.CompletedTask;
                        return;
                    }
                }
                else if (!context.HttpContext.RequestAborted.IsCancellationRequested)
                {
                    goto gotoNext;
                }
                else
                {
                    await Task.CompletedTask;
                    return;
                }
            }

gotoNext:
            await next();
        }
示例#2
0
        /// <summary>
        /// Post:(从头排序后+body json整体 )hash
        /// </summary>
        /// <param name="context"></param>
        /// <param name="responseCacheData">自定义对象不能ctor注入</param>
        /// <returns></returns>
        public async Task InvokeAsync(HttpContext context, ResponseCacheData responseCacheData, RequestCacheData requestCacheData)
        {
            context.Request.EnableBuffering();
            var token = context.RequestAborted.Register(async() =>
            {
                await Task.CompletedTask;
                return;
            });

            var endpoint = context.Features.Get <IEndpointFeature>()?.Endpoint;

            if (endpoint != null)
            {
                if (endpoint.Metadata
                    .Any(m => m is IgnorePostResponseCacheAttribute))
                {
                    goto gotoNext;
                }
            }

            if (context.Request.Method.Equals("get", StringComparison.OrdinalIgnoreCase) ||
                context.Request.Method.Equals("head", StringComparison.OrdinalIgnoreCase) ||
                _memorycache.TryGetValue($"PostResponseCache_{context.Request.Path}", out object _tempIgnoe) ||
                _memorycache.TryGetValue($"IgnorePostResponseCache_{context.Request.Path}", out object _temp))
            {
                goto gotoNext;
            }
            else
            {
                var convertedDictionatry = context.Request.Query.ToDictionary(s => s.Key.ToLower(), s => s.Value);

                foreach (var item in _responseCacheOption?.IgnoreVaryQuery ?? new List <string>())
                {
                    if (convertedDictionatry.ContainsKey(item.ToLower()))
                    {
                        convertedDictionatry.Remove(item.ToLower());
                    }
                }

                StringBuilder requestStrKey = new StringBuilder(context.Request.Path);
                foreach (var item in convertedDictionatry)
                {
                    requestStrKey.Append($"{item.Key}{item.Value}");
                }

                string bodyValue;
                if (requestCacheData == null || string.IsNullOrEmpty(requestCacheData.Body))
                {
                    bodyValue = await ReadAsString(context);

                    requestCacheData = new RequestCacheData {
                        Body = bodyValue
                    };
                }
                else
                {
                    bodyValue = requestCacheData.Body;
                }
                if (!string.IsNullOrEmpty(bodyValue) && !"null".Equals(bodyValue))
                {
                    //非Get请求body有值才被缓存,其他默认不缓存,防止body读取失败导致缓存异常
                    bodyValue = Regex.Replace(bodyValue, @"\s(?=([^""]*""[^""]*"")*[^""]*$)", string.Empty);
                    bodyValue = bodyValue.Replace("\r\n", "").Replace(" : ", ":").Replace("\n  ", "").Replace("\n", "").Replace(": ", ":").Replace(", ", ",");

                    requestStrKey.Append($"body{bodyValue}");

                    var cacheResponseBody = _memorycache.Get <ResponseCacheData>($"NetProPostResponse:{requestStrKey}");
                    if (cacheResponseBody != null && !context.RequestAborted.IsCancellationRequested)
                    {
                        //https://stackoverflow.com/questions/45675102/asp-net-core-middleware-cannot-set-status-code-on-exception-because-response-ha
                        if (!context.Response.HasStarted)
                        {
                            context.Response.StatusCode  = cacheResponseBody.StatusCode;
                            context.Response.ContentType = cacheResponseBody.ContentType;
                            await context.Response.WriteAsync(cacheResponseBody.Body);

                            _iLogger.LogInformation($"触发PostResponseCacheMiddleware本地缓存");
                        }
                        else
                        {
                            _iLogger.LogError($"StatusCode无法设置,因为响应已经启动,位置为:触发本地缓存开始赋值[responsecache2]");
                            await Task.CompletedTask;
                            return;
                        }
                    }
                    else if (!context.RequestAborted.IsCancellationRequested)
                    {
                        Stream originalBody = context.Response.Body;
                        try
                        {
                            using (var memStream = new MemoryStream())
                            {
                                context.Response.Body = memStream;

                                await _next(context);

                                memStream.Position = 0;
                                string responseBody = new StreamReader(memStream).ReadToEnd();
                                responseCacheData = new ResponseCacheData
                                {
                                    Body        = responseBody,
                                    ContentType = context.Response.ContentType,
                                    StatusCode  = context.Response.StatusCode
                                };
                                memStream.Position = 0;
                                await memStream.CopyToAsync(originalBody);

                                _memorycache.GetOrCreate <ResponseCacheData>($"NetProPostResponse:{requestStrKey}", s =>
                                {
                                    s.AbsoluteExpirationRelativeToNow = new TimeSpan(_responseCacheOption.Duration);
                                    return(new ResponseCacheData
                                    {
                                        Body = responseBody,
                                        ContentType = context.Response.ContentType,
                                        StatusCode = context.Response.StatusCode
                                    });
                                });
                            }
                            await Task.CompletedTask;
                            return;
                        }
                        finally
                        {
                            context.Response.Body = originalBody;
                        }
                    }
                    else if (context.RequestAborted.IsCancellationRequested)
                    {
                        await Task.CompletedTask;
                        return;
                    }
                }
                else if (!context.RequestAborted.IsCancellationRequested)
                {
                    goto gotoNext;
                }
                else
                {
                    await Task.CompletedTask;
                    return;
                }
            }

gotoNext:
            await _next(context);
        }
示例#3
0
        /// <summary>
        /// Post:(从头排序后+body json整体 )hash
        /// </summary>
        /// <param name="context"></param>
        /// <param name="responseCacheData">自定义对象不能ctor注入</param>
        /// <returns></returns>
        public async Task InvokeAsync(HttpContext context, ResponseCacheData responseCacheData, RequestCacheData requestCacheData)
        {
            context.Request.EnableBuffering();
            var token = context.RequestAborted.Register(async() =>
            {
                await Task.CompletedTask;
                return;
            });

            var endpoint = context.GetEndpoint();

            if (endpoint != null)
            {
                if (endpoint.Metadata
                    .Any(m => m is IgnorePostResponseCacheAttribute))
                {
                    goto gotoNext;
                }
            }

            if (context.Request.Method.Equals("get", StringComparison.OrdinalIgnoreCase) ||
                context.Request.Method.Equals("head", StringComparison.OrdinalIgnoreCase) ||
                _memorycache.TryGetValue($"PostResponseCache_{context.Request.Path}", out object _tempIgnoe) ||
                _memorycache.TryGetValue($"IgnorePostResponseCache_{context.Request.Path}", out object _temp))
            {
                goto gotoNext;
            }
            else
            {
                var convertedDictionatry = context.Request.Query.ToDictionary(s => s.Key.ToLower(), s => s.Value);

                foreach (var item in _responseCacheOption?.IgnoreVaryByQueryKeys ?? new List <string>())
                {
                    if (convertedDictionatry.ContainsKey(item.ToLower()))
                    {
                        convertedDictionatry.Remove(item.ToLower());
                    }
                }

                StringBuilder requestStrKey = new StringBuilder(context.Request.Path);
                foreach (var item in convertedDictionatry)
                {
                    requestStrKey.Append($"{item.Key}{item.Value}");
                }

                string bodyValue;
                if (requestCacheData == null || string.IsNullOrEmpty(requestCacheData.Body))
                {
                    bodyValue = await Common.ReadAsString(context);

                    requestCacheData = new RequestCacheData {
                        Body = bodyValue
                    };
                }
                else
                {
                    bodyValue = requestCacheData.Body;
                }
                if (!string.IsNullOrEmpty(bodyValue) && !"null".Equals(bodyValue))
                {
                    //非Get请求body有值才被缓存,其他默认不缓存,防止body读取失败导致缓存异常
                    bodyValue = Regex.Replace(bodyValue, @"\s(?=([^""]*""[^""]*"")*[^""]*$)", string.Empty);
                    bodyValue = bodyValue.Replace("\r\n", "").Replace(" : ", ":").Replace("\n  ", "").Replace("\n", "").Replace(": ", ":").Replace(", ", ",");

                    requestStrKey.Append($"body{bodyValue}");
                    ResponseCacheData cacheResponseBody = null;
                    IRedisManager     _redisManager     = null;
                    if (_responseCacheOption.Cluster)
                    {
                        _redisManager = context.RequestServices.GetService <IRedisManager>();
                        if (_redisManager == null)
                        {
                            throw new ArgumentNullException(nameof(RedisCacheOption), $"PostResponseCache组件在集群模式下未检测到注入NetPro.CsRedis组件,请检查是否遗漏{nameof(RedisCacheOption)}配置节点");
                        }
                        cacheResponseBody = _redisManager.Get <ResponseCacheData>($"NetProPostResponse:{requestStrKey}");
                    }
                    else
                    {
                        cacheResponseBody = _memorycache.Get <ResponseCacheData>($"NetProPostResponse:{requestStrKey}");
                    }

                    if (cacheResponseBody != null && !context.RequestAborted.IsCancellationRequested)
                    {
                        //https://stackoverflow.com/questions/45675102/asp-net-core-middleware-cannot-set-status-code-on-exception-because-response-ha
                        if (!context.Response.HasStarted)
                        {
                            context.Response.StatusCode  = cacheResponseBody.StatusCode;
                            context.Response.ContentType = cacheResponseBody.ContentType;
                            context.Response.Headers["response-cache"] = "true";
                            await context.Response.WriteAsync(cacheResponseBody.Body);

                            _iLogger.LogInformation($"触发PostResponseCacheMiddleware本地缓存");
                            //直接return可避免此错误 :OnStarting cannot be set because the response has already started.
                            await Task.CompletedTask;
                            return;
                        }
                        else
                        {
                            _iLogger.LogError($"StatusCode无法设置,因为响应已经启动,位置为:触发本地缓存开始赋值[responsecache2]");
                            await Task.CompletedTask;
                            return;
                        }
                    }
                    else if (!context.RequestAborted.IsCancellationRequested)
                    {
                        Stream originalBody = context.Response.Body;
                        try
                        {
                            using (var memStream = new MemoryStream())
                            {
                                context.Response.Body = memStream;

                                await _next(context);

                                memStream.Position = 0;
                                string responseBody = new StreamReader(memStream).ReadToEnd();
                                responseCacheData = new ResponseCacheData
                                {
                                    Body        = responseBody,
                                    ContentType = context.Response.ContentType,
                                    StatusCode  = context.Response.StatusCode
                                };
                                memStream.Position = 0;
                                await memStream.CopyToAsync(originalBody);

                                if (_responseCacheOption.Cluster)
                                {
                                    _redisManager.Set($"NetProPostResponse:{requestStrKey}", new ResponseCacheData
                                    {
                                        Body        = responseBody,
                                        ContentType = context.Response.ContentType,
                                        StatusCode  = context.Response.StatusCode
                                    }, TimeSpan.FromSeconds(_responseCacheOption.Duration));
                                }
                                else
                                {
                                    _memorycache.Set <ResponseCacheData>($"NetProPostResponse:{requestStrKey}", new ResponseCacheData
                                    {
                                        Body        = responseBody,
                                        ContentType = context.Response.ContentType,
                                        StatusCode  = context.Response.StatusCode
                                    }, TimeSpan.FromSeconds(_responseCacheOption.Duration));
                                }
                            }
                            await Task.CompletedTask;
                            return;
                        }
                        finally
                        {
                            context.Response.Body = originalBody;
                        }
                    }
                    else if (context.RequestAborted.IsCancellationRequested)
                    {
                        await Task.CompletedTask;
                        return;
                    }
                }
                else if (!context.RequestAborted.IsCancellationRequested)
                {
                    goto gotoNext;
                }
                else
                {
                    await Task.CompletedTask;
                    return;
                }
            }

gotoNext:
            await _next(context);
        }