You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 09:51:21