.NET 8中实现类甜甜圈缓存的最佳实践咨询(ASP.NET Core Razor Pages)
ASP.NET Core MVC/Razor Pages 实现「部分缓存+动态内容」最佳实践
针对你需要保留页面输出缓存、同时服务器端动态生成广告内容的场景,ASP.NET Core虽无官方原生的「甜甜圈缓存」组件,但可以通过以下几种贴近原MvcDonutCaching逻辑的方案实现:
方案1:自定义Tag Helper标记动态区域
这是最直接的替代方案,通过自定义标签标记不需要缓存的内容,页面其余部分正常使用输出缓存:
步骤1:给页面/Action启用输出缓存
在Razor Page模型类或MVC控制器Action上添加输出缓存特性:
// Razor Page示例 [OutputCache(Duration = 3600, VaryByQueryKeys = new[] { "category" })] public class IndexModel : PageModel { // 页面初始化逻辑 }
步骤2:编写动态内容Tag Helper
创建用于渲染服务器端广告的Tag Helper,每次请求都会重新生成内容:
[HtmlTargetElement("server-side-ad")] public class ServerSideAdTagHelper : TagHelper { private readonly IAdPlatformService _adService; public ServerSideAdTagHelper(IAdPlatformService adService) { _adService = adService; } public async override Task ProcessAsync(TagHelperContext context, TagHelperOutput output) { // 调用服务器到服务器的广告接口获取内容 var adHtml = await _adService.FetchAdAsync(); // 替换标签为实际广告内容,移除原标签节点 output.Content.SetHtmlContent(adHtml); output.TagName = null; } }
步骤3:在视图中使用
在需要动态广告的位置添加自定义标签,页面其余部分会被输出缓存:
<div class="page-body"> <!-- 此区域会被输出缓存 --> <h1>@Model.ArticleTitle</h1> <div>@Model.CachedContent</div> <!-- 此区域每次请求动态生成 --> <server-side-ad></server-side-ad> </div>
方案2:部分视图+缓存排除
将动态内容封装为独立部分视图,主页面启用输出缓存但不缓存该部分视图:
步骤1:创建动态部分视图(_ServerSideAd.cshtml)
@inject IAdPlatformService AdService @{ var adContent = await AdService.FetchAdAsync(); } <div class="ad-wrapper">@Html.Raw(adContent)</div>
步骤2:主页面引用部分视图
主页面启用输出缓存,直接引用动态部分视图(不包裹cache标签):
@page @model IndexModel @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers [OutputCache(Duration = 3600)] <div class="page-container"> <!-- 缓存区域 --> <div>@Model.PageContent</div> <!-- 动态区域:直接渲染不缓存的部分视图 --> @await Html.PartialAsync("_ServerSideAd") </div>
方案3:缓存占位符替换(适合复杂场景)
先缓存带占位符的页面内容,返回时替换为动态生成的广告:
步骤1:控制器中实现缓存与替换逻辑
public async Task<IActionResult> Index(string category) { var cacheKey = $"Page_Index_{category}"; var cachedPage = await _distributedCache.GetStringAsync(cacheKey); if (!string.IsNullOrEmpty(cachedPage)) { // 替换缓存中的占位符为动态广告 var adContent = await _adService.FetchAdAsync(); var finalPage = cachedPage.Replace("{{SERVER_SIDE_AD}}", adContent); return Content(finalPage, "text/html"); } // 未命中缓存,生成页面并缓存(包含占位符) var model = await GetPageModelAsync(category); var pageHtml = await RenderViewToStringAsync("Index", model); await _distributedCache.SetStringAsync(cacheKey, pageHtml, new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1) }); // 替换占位符后返回 var adContent = await _adService.FetchAdAsync(); var finalPage = pageHtml.Replace("{{SERVER_SIDE_AD}}", adContent); return Content(finalPage, "text/html"); } // 辅助方法:将视图转换为字符串 private async Task<string> RenderViewToStringAsync(string viewName, object model) { var httpContext = new DefaultHttpContext { RequestServices = HttpContext.RequestServices }; var actionContext = new ActionContext(httpContext, new RouteData(), new ControllerActionDescriptor()); using var sw = new StringWriter(); var viewResult = _viewEngine.FindView(actionContext, viewName, false); var viewContext = new ViewContext(actionContext, viewResult.View, new ViewDataDictionary(model), new TempDataDictionary(httpContext, _tempDataProvider), sw, new HtmlHelperOptions()); await viewResult.View.RenderAsync(viewContext); return sw.ToString(); }
步骤2:视图中添加占位符
<div class="ad-container">{{SERVER_SIDE_AD}}</div>
关键注意事项
- 缓存键设计:务必包含影响页面内容的参数(如分类、用户角色),避免缓存污染。
- 动态内容缓存:如果广告内容允许短时间缓存,可以给广告服务单独加二级缓存(如缓存5分钟),平衡性能与实时性。
- 服务注册:确保广告服务等依赖已正确注入到Tag Helper或控制器中。
内容的提问来源于stack exchange,提问作者Rok
相关产品推荐
相关产品推荐

