.Net Core WebAPI 利用 IActionFilter 实现请求缓存

.Net Core WebAPI 利用 IActionFilter 实现请求缓存

本文使用Redis缓存方式

  • 1 新建类
    首先新建一个缓存类 CustomActionCacheAttribute 继承 Attribute,因为需要给方法做标记。再引用并实现IActionFilter 接口
public class CustomActionCacheAttribute: Attribute, IActionFilter
{
     
	// 标记的方法执行前执行
	public void OnActionExecuting(ActionExecutingContext context)
	{
     
		
	}
    
    // 标记的方法执行完的回调
    public void OnActionExecuted(ActionExecutedContext context)
    {
     
    	
    }
}
  • 2 注入redis缓存
    在 .Net core 中,提供了一个操作所有缓存对象的接口 IDistributedCache,需要引用
    using Microsoft.Extensions.Caching.Distributed,然后在类中声明变量。
	private IDistributedCache _cache;
	因为该Filter标注在方法上,运行时解析,所以无法通过注入的方式来注入该对象
	所以需要使用扩展来获取IDistributedCache 对象
  • 3 扩展获取DI注入对象
    新建静态类CustomDIContainer
    代码如下:
	/// 
    /// 自定义依赖获取容器
    /// 
    public static class CustomDIContainer
    {
     
        private static IHttpContextAccessor _httpContextAccessor;

        /// 
        /// 配置全局HttpContext
        /// 
        /// 
        /// 
        public static IApplicationBuilder UseCustomHttpContext(this IApplicationBuilder app)
        {
     
            _httpContextAccessor = app.ApplicationServices.GetRequiredService<IHttpContextAccessor>();
            return app;
        }

        /// 
        /// 当前请求Http上下文
        /// 
        public static HttpContext Current => _httpContextAccessor.HttpContext;

        /// 
        /// 获取DI注入的组件
        /// 
        /// 
        /// 
        public static T GetSerivce<T>() => (T)Current?.RequestServices.GetService(typeof(T));
    }

使用到了IHttpContextAccessor,所以需要在StartupConfigureServices方法中进行http上下文初始化注入.

public void ConfigureServices(IServiceCollection services)
{
     
	......
	// 注入NewtonsoftJson组件
	services.AddControllers().AddNewtonsoftJson();
	// 注入缓存组件
    services.AddDistributedRedisCache(r => r.Configuration = Configuration["Redis:ConnectionString"]);
 	// 注入请求上下文
 	services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
 	......
}
  • 4 缓存扩展
    在缓存中,不单单仅是缓存一个方法,应该做到方法的拓展缓存,既根据不同的传参进行方法返回值的缓存,所以需要对方法的参数进行序列化。
    方法如下:
		/// 
        /// 序列化请求参数
        /// 
        /// 
        /// 
        public string GetParams(HttpContext context)
        {
     
            try
            {
     
                NameValueCollection form = HttpUtility.ParseQueryString(context.Request.QueryString.ToString());
                HttpRequest request = context.Request;

                string data = string.Empty;
                switch (request.Method)
                {
     
                    case "POST":
                        request.Body.Seek(0, SeekOrigin.Begin);
                        using (var reader = new StreamReader(request.Body, Encoding.UTF8))
                        {
     
                            data = reader.ReadToEndAsync().Result;
                            data = data.StringReplaceEmpty("{", "}", "\"", "\'").Replace(":", "=").Replace(",", "&");
                        }
                        break;
                    case "GET":
                        //第一步:取出所有get参数
                        IDictionary<string, string> parameters = new Dictionary<string, string>();
                        for (int f = 0; f < form.Count; f++)
                        {
     
                            string key = form.Keys[f];
                            parameters.Add(key, form[key]);
                        }

                        // 第二步:把字典按Key的字母顺序排序
                        IDictionary<string, string> sortedParams = new SortedDictionary<string, string>(parameters);
                        IEnumerator<KeyValuePair<string, string>> dem = sortedParams.GetEnumerator();

                        // 第三步:把所有参数名和参数值串在一起
                        StringBuilder query = new StringBuilder();
                        while (dem.MoveNext())
                        {
     
                            string key = dem.Current.Key;
                            if (!string.IsNullOrEmpty(key))
                                query.Append(key).Append("=").Append(dem.Current.Value).Append("&");
                        }
                        data = query.ToString().TrimEnd('&');
                        break;
                    default:
                        data = string.Empty;
                        break;
                }
                return data;
            }
            catch
            {
     
                return string.Empty;
            }
        }

并在方法内部声明存储请求参数及方法的字符串

private string _urlParams;
  • 5 拓展缓存过期时间
    每个方法返回结果都会根据不同的更新频率设置不同的缓存时间。声明属性可供头部标注。
        /// 
        /// 缓存过期时间[分钟] 默认缓存时间30分钟
        /// 
        public int ValidTimeMinutes {
      get; set; } = 30;
  • 6 完整代码
public class CustomActionCacheAttribute : Attribute, IActionFilter
    {
     
        /// 
        /// 缓存过期时间[分钟] 默认缓存时间30分钟
        /// 
        public int ValidTimeMinutes {
      get; set; } = 30;

        private string _urlParams;

        private IDistributedCache _cache;

        public void OnActionExecuting(ActionExecutingContext context)
        {
     
        	// 根据上方拓展的DI容器获取 IDistributedCache 对象
            this._cache = CustomDIContainer.GetSerivce<IDistributedCache>();
            // 获取调用参数拼装缓存的key
            _urlParams = $"{
       context.HttpContext.Request.Path}?{
       GetParams(context.HttpContext)}";
            // 读取缓存
            byte[] cacheValue = _cache.Get(_urlParams);
            // 判断是否存在缓存
            if (cacheValue == null || cacheValue.Length == 0) return;
            // 重新序列为结果
            // Deserialize 是自定义的byte[]转指定类型的拓展方法
            IActionResult result = cacheValue.Deserialize<string>().Json2Object<ObjectResult>();
            // 如果序列化成功,指定方法的 context.Result 将自动返回结果,不再执行方法体
            if (result != null)
                context.Result = result;
        }

        public void OnActionExecuted(ActionExecutedContext context)
        {
     
            var result = context.Result.ConvertJson();
            var options = new DistributedCacheEntryOptions().SetSlidingExpiration(TimeSpan.FromMinutes(ValidTimeMinutes));
            // Serialize 是自定义将String字符串转换为byte[]数组的方法
            this._cache.SetAsync(_urlParams, result.Serialize(), options);
        }

        /// 
        /// 序列化请求参数
        /// 
        /// 
        /// 
        public string GetParams(HttpContext context)
        {
     
            try
            {
     
                NameValueCollection form = HttpUtility.ParseQueryString(context.Request.QueryString.ToString());
                HttpRequest request = context.Request;

                string data = string.Empty;
                switch (request.Method)
                {
     
                    case "POST":
                        request.Body.Seek(0, SeekOrigin.Begin);
                        using (var reader = new StreamReader(request.Body, Encoding.UTF8))
                        {
     
                            data = reader.ReadToEndAsync().Result;
                            data = data.StringReplaceEmpty("{", "}", "\"", "\'").Replace(":", "=").Replace(",", "&");
                        }
                        break;
                    case "GET":
                        //第一步:取出所有get参数
                        IDictionary<string, string> parameters = new Dictionary<string, string>();
                        for (int f = 0; f < form.Count; f++)
                        {
     
                            string key = form.Keys[f];
                            parameters.Add(key, form[key]);
                        }

                        // 第二步:把字典按Key的字母顺序排序
                        IDictionary<string, string> sortedParams = new SortedDictionary<string, string>(parameters);
                        IEnumerator<KeyValuePair<string, string>> dem = sortedParams.GetEnumerator();

                        // 第三步:把所有参数名和参数值串在一起
                        StringBuilder query = new StringBuilder();
                        while (dem.MoveNext())
                        {
     
                            string key = dem.Current.Key;
                            if (!string.IsNullOrEmpty(key))
                                query.Append(key).Append("=").Append(dem.Current.Value).Append("&");
                        }
                        data = query.ToString().TrimEnd('&');
                        break;
                    default:
                        data = string.Empty;
                        break;
                }
                return data;
            }
            catch
            {
     
                return string.Empty;
            }
        }
    }
  • 7 最后注意
    在.Net Core 3需要配置读取body,在代码中方法执行前和方法执行后中多次使用了HttpContext的Body参数,所以需要在 StartupConfigure方法添加可多次读取。
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
     
	......
	app.Use(next => context =>
    {
     
        context.Request.EnableBuffering();
        return next(context);
    });
	......
}
  • 8 使用
		/// 
        /// 获取系统的组织架构
        /// 
        /// 
        [HttpGet("get_organization")]
        [CustomActionCache(ValidTimeMinutes = 60 * 24)] 
        public async Task<ExecuteResult<string>> GetOrganizationAsync()
        {
     
            // 具体的业务逻辑
            ......
        }

如果文中有错误之处,欢迎各位看官指正,谢谢。如需转载,请指明原出处。

你可能感兴趣的:(redis,c#)