. NET Core tutorial -- add a server cache to the API

Keywords: C# Redis JSON

In the past, the basic way to write cache to API interface was to write code as follows:

// redis key 
var bookRedisKey = ConstRedisKey.RecommendationBooks.CopyOne(bookId);
// Get cached data         
var cacheBookIds = _redisService.ReadCache<List<string>>(bookRedisKey);
if (cacheBookIds != null)
{
    // return
}
else
{
   // Perform additional logic to get data and write to the cache
}

And then I scattered the code everywhere.

One day, it suddenly occurred to me that the basic cache time here is almost the same, and it's all for the Web API,

Supporting caching directly in the API layer is not the end of the matter.

So, what do we do here.

In the. NET Core Web API, there are two ideas: Middleware or ActionFilter

Students who don't know can read the following documents:

Chapter IV MVC (4.3) filter of ASP.NET Core Chinese document

ASP.NET Core Chinese document Chapter 3 principle (2) middleware

Based on the fact that I only support caching for some interfaces, I can use ActionFilter directly

Didn't say, directly on the code

using System;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Newtonsoft.Json.Linq;

namespace XXXAPI.Filters
{
    public class DefaultCacheFilterAttribute : ActionFilterAttribute
    {
        // This time is used to rewrite the subclass and realize the cache at different time levels
        protected TimeSpan _expireTime;
     
        // redis read and write classes, not read
        private readonly RedisService _redisService;

        public DefaultCacheFilterAttribute(RedisService redisService)
        {
            _redisService = redisService;

        }

        public override void OnActionExecuting(ActionExecutingContext context)
        {
            if (context.HttpContext.Request.Query.ContainsKey("refresh"))
            {
                return;
            }
            KeyConfig redisKey = GetRequestRedisKey(context.HttpContext);
            var redisCache = _redisService.ReadCache<JToken>(redisKey);
            if (redisCache != null)
            {
                context.Result = new ObjectResult(redisCache);
            }
            return;
        }

        public override void OnActionExecuted(ActionExecutedContext context)
        {
            KeyConfig redisKey = GetRequestRedisKey(context.HttpContext);
            var objResult = (ObjectResult)context.Result;
            if (objResult == null)
            {
                return;
            }
            var jToken = JToken.FromObject(objResult.Value);
            _redisService.WriteCache(redisKey, jToken);
        }

        private KeyConfig GetRequestRedisKey(HttpContext httpContext)
        {
            var requestPath = httpContext.Request.Path.Value;
            if (!string.IsNullOrEmpty(httpContext.Request.QueryString.Value))
            {
                requestPath = requestPath + httpContext.Request.QueryString.Value;
            }
            if (httpContext.Request.Query.ContainsKey("refresh"))
            {
                if (httpContext.Request.Query.Count == 1)
                {
                    requestPath = requestPath.Replace("?refresh=true", "");
                }
                else
                {
                    requestPath = requestPath.Replace("refresh=true", "");
                }
            }
            // Here is a redis key class
            var redisKey = ConstRedisKey.HTTPRequest.CopyOne(requestPath);
            if (_expireTime != default(TimeSpan))
            {
                redisKey.ExpireTime = _expireTime;
            }
            return redisKey;
        }
    }

    public static class ConstRedisKey
    {
        public readonly static KeyConfig HTTPRequest = new KeyConfig()
        {
            Key = "lemon_req_",
            ExpireTime = new TimeSpan(TimeSpan.TicksPerMinute * 30),
            DBName = 5
        };
    }

    public class KeyConfig
    {
        public string Key { get; set; }

        public TimeSpan ExpireTime { get; set; }

        public int DBName { get; set; }


        public KeyConfig CopyOne(string state)
        {
            var one = new KeyConfig();
            one.DBName = this.DBName;
            one.Key = !string.IsNullOrEmpty(this.Key) ? this.Key + state : state;
            one.ExpireTime = this.ExpireTime;
            return one;
        }

    }
}

Then the Action method of Controller can be annotated directly

Such as:

        [HttpGet("v1/xxx/latest")]
        [ServiceFilter(typeof(DefaultCacheFilterAttribute))]
        public IActionResult GetLatestList([FromQuery] int page = 0, [FromQuery]int pageSize = 30)
        {
            return Ok(new
            {
                data = _service.LoadLatest(page, pageSize),
                code = 0
            });
        }

Done...

Oh, remember to inject DefaultCacheFilterAttribute in Startup.cs

I don't need to write about this injection

The drawback is that we don't know how to support custom cache time directly on annotations,

Make do with it first

End, bye

Posted by mlefebvre on Sat, 16 Nov 2019 11:04:25 -0800