Bri*_*rij 29 asp.net-web-api asp.net-core
我正在使用ASP.NET Core(ASP.NET 5)Web API应用程序,并且必须在实体标记的帮助下实现HTTP缓存.早些时候我使用了CacheCow,但它似乎不支持ASP.NET Core.我也没有找到任何其他相关的库或框架支持细节.
我可以编写相同的自定义代码,但在此之前我想看看是否有任何东西可用.如果某些东西已经可用,请分享,以及实施该方法的更好方法.
eri*_*zic 34
经过一段时间尝试使其与中间件一起工作后,我发现MVC动作过滤器实际上更适合这种功能.
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;
}
}
Run Code Online (Sandbox Code Playgroud)
然后在您想要作为属性的动作或控制器上使用它:
[HttpGet("data")]
[ETagFilter(200)]
public async Task<IActionResult> GetDataFromApi()
{
}
Run Code Online (Sandbox Code Playgroud)
Middleware和Filters之间的重要区别在于,您的中间件可以在MVC middlware之前和之后运行,并且只能与HttpContext一起使用.此外,一旦MVC开始将响应发送回客户端,对它进行任何更改都为时已晚.
另一方面,过滤器是MVC中间件的一部分.他们可以访问MVC上下文,在这种情况下,实现此功能更简单.更多关于过滤器及其在MVC中的管道.
基于Eric 的回答,我将使用一个可以在实体上实现的接口来支持实体标记。在过滤器中,如果操作返回具有此接口的实体,您将只添加 ETag。
这使您可以更有选择性地选择标记哪些实体,并允许您让每个实体控制其标记的生成方式。这比序列化所有内容并创建哈希要有效得多。它还消除了检查状态代码的需要。它可以安全轻松地作为全局过滤器添加,因为您通过在模型类上实现接口来“选择加入”该功能。
public interface IGenerateETag
{
string GenerateETag();
}
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)]
public class ETagFilterAttribute : Attribute, IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context)
{
}
public void OnActionExecuted(ActionExecutedContext context)
{
var request = context.HttpContext.Request;
var response = context.HttpContext.Response;
if (request.Method == "GET" &&
context.Result is ObjectResult obj &&
obj.Value is IGenerateETag entity)
{
string etag = entity.GenerateETag();
// Value should be in quotes according to the spec
if (!etag.EndsWith("\""))
etag = "\"" + etag +"\"";
string ifNoneMatch = request.Headers["If-None-Match"];
if (ifNoneMatch == etag)
{
context.Result = new StatusCodeResult(304);
}
context.HttpContext.Response.Headers.Add("ETag", etag);
}
}
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
13963 次 |
| 最近记录: |