[英]how do I cache an output in ASP.NET Core
我有一个 API controller,场景是:我需要使用第三方数据源(为了简单起见,假设第三方作为 dll 文件提供,dll 包含Student
model 和StudentDataSource
,其中包含很多检索学生的方法),并且调用第三方数据源的成本很高,而且数据每 6 小时才更新一次。 所以我需要以某种方式缓存 output,下面是我的 api controller 中的一些操作方法:
// api controller that contain action methods below
[HttpGet]
public JsonResult GetAllStudentRecords()
{
var dataSource = new StudentDataSource();
return Json(dataSource.GetAllStudents());
}
[HttpGet("{id}")]
public JsonResult GetStudent(int id)
{
var dataSource = new StudentDataSource();
return Json(dataSource.getStudent(id));
}
那么我应该如何缓存结果,特别是对于第二个操作方法,缓存每个具有不同 id 的学生结果是愚蠢的
我的团队正在 API 控制器上实施类似的缓存策略,使用自定义 Action 过滤器属性来处理缓存逻辑。 有关操作过滤器的更多信息,请参见此处。
Action 过滤器的OnActionExecuting
方法在您的控制器方法之前运行,因此您可以检查您要查找的数据是否已经缓存并直接从这里返回,当缓存数据存在时绕过对第三方数据源的调用。 我们也使用这种方法来检查请求的类型并在更新和删除时重置缓存,但听起来您不会修改数据。
Action 过滤器的OnActionExecuted
方法在您的控制器方法逻辑之后立即运行,让您有机会在将响应对象返回给客户端之前缓存它。
您如何实现实际缓存的细节很难提供答案,但 Microsoft 为 .NET Core 中的内存缓存提供了一些选项(请参阅MemoryCache.Default 在 .NET Core 中不可用? )
正如@chris-brenberg 指出的那样,我通过 controller API 使用了带有缓存策略的解决方案,结果是这样的
在 controller class
[ServerResponseCache(false)]
[HttpGet]
[Route("cache")]
public ActionResult GetCache(string? dateFormat) {
Logger.LogInformation("Getting current datetime");
return Ok(new { date = DateTime.Now.ToString() });
}
在 ServerResponseCacheAttribute.cs 上
namespace Site.Api.Filters {
using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Primitives;
using System.Globalization;
using System.Threading.Tasks;
public class ServerResponseCacheAttribute : TypeFilterAttribute {
public ServerResponseCacheAttribute(bool byUserContext = true) : base(typeof(ServerResponseCacheAttributeImplementation)) =>
Arguments = new object[] { new ServerResponseCacheProps { ByUserContext = byUserContext } };
public ServerResponseCacheAttribute(int secondsTimeout, bool byUserContext = true) : base(typeof(ServerResponseCacheAttributeImplementation)) =>
Arguments = new object[] { new ServerResponseCacheProps { SecondsTimeout = secondsTimeout, ByUserContext = byUserContext } };
public class ServerResponseCacheProps {
public int? SecondsTimeout { get; set; }
public bool ByUserContext { get; set; }
}
public class ServerResponseCacheConfig {
public bool Disabled { get; set; }
public int SecondsTimeout { get; set; } = 60;
public string[] HeadersOnCache { get; set; } = { "Accept-Language" };
}
private class ServerResponseCacheAttributeImplementation : IAsyncActionFilter {
private string _cacheKey = default;
readonly ILogger<ServerResponseCacheAttributeImplementation> _logger;
readonly IMemoryCache _memoryCache;
readonly ServerResponseCacheConfig _config;
readonly bool _byUserContext;
public ServerResponseCacheAttributeImplementation(ILogger<ServerResponseCacheAttributeImplementation> logger,
IMemoryCache memoryCache, ServerResponseCacheProps props) {
_logger = logger;
_memoryCache = memoryCache;
_byUserContext = props.ByUserContext;
_config = new ServerResponseCacheConfig {
SecondsTimeout = props.SecondsTimeout ?? 60,
HeadersOnCache = new[] { "Accept-Language" }
};
}
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) {
if (context == null) {
throw new ArgumentNullException(nameof(context));
}
if (next == null) {
throw new ArgumentNullException(nameof(next));
}
if (_config.Disabled) {
await next();
return;
}
OnActionExecutingAsync(context);
if (context.Result == null) {
OnActionExecuted(await next());
}
}
void OnActionExecutingAsync(ActionExecutingContext context) {
SetCacheKey(context.HttpContext.Request);
// Not use a stored response to satisfy the request. Will regenerates the response for the client, and updates the stored response in its cache.
bool noCache = context.HttpContext.Request.Headers.CacheControl.Contains("no-cache");
if (noCache) {
return;
}
TryLoadResultFromCache(context);
}
void SetCacheKey(HttpRequest request) {
if (request == null) {
throw new ArgumentException(nameof(request));
}
if (!string.Equals(request.Method, "GET", StringComparison.InvariantCultureIgnoreCase)) {
return;
}
List<string> cacheKeys = new List<string>();
if (_byUserContext && request.HttpContext.User.Identity.IsAuthenticated) {
cacheKeys.Add($"{request.HttpContext.User.Identity.Name}");
}
string uri = UriHelper.BuildAbsolute(request.Scheme, request.Host, request.PathBase, request.Path, request.QueryString);
cacheKeys.Add(uri);
foreach (string headerKey in _config.HeadersOnCache) {
StringValues headerValue;
if (request.Headers.TryGetValue(headerKey, out headerValue)) {
cacheKeys.Add($"{headerKey}:{headerValue}");
}
}
_cacheKey = string.Join('_', cacheKeys).ToLower();
}
void TryLoadResultFromCache(ActionExecutingContext context) {
ResultCache resultCache;
if (_cacheKey != null && _memoryCache.TryGetValue(_cacheKey, out resultCache)) {
_logger.LogInformation("ServerResponseCache: Response loaded from cache, cacheKey: {cacheKey}, expires at: {expiration}.", _cacheKey, resultCache.Expiration);
context.Result = resultCache.Result;
SetExpiresHeader(context.HttpContext.Response, resultCache.Expiration);
}
}
/// <summary>Add expires header (the time after which the response is considered stale).</summary>
void SetExpiresHeader(HttpResponse response, DateTimeOffset expiration) {
string expireHttpDate = expiration.UtcDateTime.ToString("ddd, dd MMM yyyy HH:mm:ss", CultureInfo.InvariantCulture);
response.Headers.Add("Expires", $"{expireHttpDate} GMT");
}
void OnActionExecuted(ActionExecutedContext context) {
if (_cacheKey == null) {
return;
}
if (context.Result != null) {
DateTimeOffset expiration = SetCache(context.Result);
SetExpiresHeader(context.HttpContext.Response, expiration);
} else {
RemoveCache();
}
}
DateTimeOffset SetCache(IActionResult result) {
DateTimeOffset absoluteExpiration = DateTimeOffset.Now.AddSeconds(_config.SecondsTimeout);
ResultCache resultCache = new ResultCache {
Result = result,
Expiration = absoluteExpiration
};
_memoryCache.Set(_cacheKey, resultCache, absoluteExpiration);
_logger.LogInformation("ServerResponseCache: Response set on cache, cacheKey: {cacheKey}, until: {expiration}.", _cacheKey, absoluteExpiration);
return absoluteExpiration;
}
void RemoveCache() {
_memoryCache.Remove(_cacheKey);
_logger.LogInformation("ServerResponseCache: Response removed from cache, cacheKey: {cacheKey}.", _cacheKey);
}
}
private class ResultCache {
public IActionResult Result { get; set; }
public DateTimeOffset Expiration { get; set; }
}
}}
我希望它能帮助别人,最好的问候
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.