Implement HTTP Cache (ETag) in ASP.NET Core Web API

后端 未结 6 1535
悲&欢浪女
悲&欢浪女 2020-12-13 18:44

I am working on ASP.NET Core (ASP.NET 5) Web API application and have to implement HTTP Caching with the help of Entity Tags. Earlier I used CacheCow for the same but it see

6条回答
  •  感动是毒
    2020-12-13 19:00

    After a while trying to make it work with middleware I figured out that MVC action filters are actually better suited for this functionality.

    public class ETagFilter : Attribute, IActionFilter
    {
        private readonly int[] _statusCodes;
    
        public ETagFilter(params int[] statusCodes)
        {
            _statusCodes = statusCodes;
            if (statusCodes.Length == 0) _statusCodes = new[] { 200 };
        }
    
        public void OnActionExecuting(ActionExecutingContext context)
        {
        }
    
        public void OnActionExecuted(ActionExecutedContext context)
        {
            if (context.HttpContext.Request.Method == "GET")
            {
                if (_statusCodes.Contains(context.HttpContext.Response.StatusCode))
                {
                    //I just serialize the result to JSON, could do something less costly
                    var content = JsonConvert.SerializeObject(context.Result);
    
                    var etag = ETagGenerator.GetETag(context.HttpContext.Request.Path.ToString(), Encoding.UTF8.GetBytes(content));
    
                    if (context.HttpContext.Request.Headers.Keys.Contains("If-None-Match") && context.HttpContext.Request.Headers["If-None-Match"].ToString() == etag)
                    {
                        context.Result = new StatusCodeResult(304);
                    }
                    context.HttpContext.Response.Headers.Add("ETag", new[] { etag });
                }
            }
        }        
    }
    
    // Helper class that generates the etag from a key (route) and content (response)
    public static class ETagGenerator
    {
        public static string GetETag(string key, byte[] contentBytes)
        {
            var keyBytes = Encoding.UTF8.GetBytes(key);
            var combinedBytes = Combine(keyBytes, contentBytes);
    
            return GenerateETag(combinedBytes);
        }
    
        private static string GenerateETag(byte[] data)
        {
            using (var md5 = MD5.Create())
            {
                var hash = md5.ComputeHash(data);
                string hex = BitConverter.ToString(hash);
                return hex.Replace("-", "");
            }            
        }
    
        private static byte[] Combine(byte[] a, byte[] b)
        {
            byte[] c = new byte[a.Length + b.Length];
            Buffer.BlockCopy(a, 0, c, 0, a.Length);
            Buffer.BlockCopy(b, 0, c, a.Length, b.Length);
            return c;
        }
    }
    

    And then use it on the actions or controllers you want as an attribute:

    [HttpGet("data")]
    [ETagFilter(200)]
    public async Task GetDataFromApi()
    {
    }
    

    The important distinction between Middleware and Filters is that your middleware can run before and after MVC middlware and can only work with HttpContext. Also once MVC starts sending the response back to the client it's too late to make any changes to it.

    Filters on the other hand are a part of MVC middleware. They have access to the MVC context, with which in this case it's simpler to implement this functionality. More on Filters and their pipeline in MVC.

提交回复
热议问题