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

Posted 言00FFCC

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了.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
    代码如下:
	/// <summary>
    /// 自定义依赖获取容器
    /// </summary>
    public static class CustomDIContainer
    {
        private static IHttpContextAccessor _httpContextAccessor;

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

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

        /// <summary>
        /// 获取DI注入的组件
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        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 缓存扩展
    在缓存中,不单单仅是缓存一个方法,应该做到方法的拓展缓存,既根据不同的传参进行方法返回值的缓存,所以需要对方法的参数进行序列化。
    方法如下:
		/// <summary>
        /// 序列化请求参数
        /// </summary>
        /// <param name="context"></param>
        /// <returns></returns>
        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 拓展缓存过期时间
    每个方法返回结果都会根据不同的更新频率设置不同的缓存时间。声明属性可供头部标注。
        /// <summary>
        /// 缓存过期时间[分钟] 默认缓存时间30分钟
        /// </summary>
        public int ValidTimeMinutes { get; set; } = 30;
  • 6 完整代码
public class CustomActionCacheAttribute : Attribute, IActionFilter
    {
        /// <summary>
        /// 缓存过期时间[分钟] 默认缓存时间30分钟
        /// </summary>
        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);
        }

        /// <summary>
        /// 序列化请求参数
        /// </summary>
        /// <param name="context"></param>
        /// <returns></returns>
        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 使用
		/// <summary>
        /// 获取系统的组织架构
        /// </summary>
        /// <returns></returns>
        [HttpGet("get_organization")]
        [CustomActionCache(ValidTimeMinutes = 60 * 24)] 
        public async Task<ExecuteResult<string>> GetOrganizationAsync()
        {
            // 具体的业务逻辑
            ......
        }

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

以上是关于.Net Core WebAPI 利用 IActionFilter 实现请求缓存的主要内容,如果未能解决你的问题,请参考以下文章

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

ASP.NET Core中为指定类添加WebApi服务功能

dotnet core webapi调用.net webapi2

.Net Core Mvc/WebApi 返回结果封装

.net core webapi发布

选择 webApi 模板时如何将 ASP.Net 身份添加到 Asp.Net Core?