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

ASP.NET Core .NET 5中创建HTML离线应用Manifest及旧代码迁移

适配方案与优化建议

一、原有AppCache代码快速适配.NET 5

针对你遇到的编译错误,对应修改点如下:

1. 响应相关API替换

  • 原response.Cache.SetMaxAge替换为直接设置Cache-Control响应头
  • 移除ContentEncoding赋值,直接在ContentType中指定charset=utf-8即可
  • response.Write替换为await response.WriteAsync,需引入命名空间using Microsoft.AspNetCore.Http

2. BundleTable替换方案

.NET 5已移除System.Web.Optimization的BundleTable,推荐用以下两种方式替代:

  • 项目安装BuildBundlerMinifierNuGet包,配置bundleconfig.json定义资源打包规则,直接使用打包后的静态资源路径
  • 用ASP.NET Core内置的asp-append-versionTagHelper特性获取带哈希戳的资源路径,可通过IWebHostEnvironment读取资源文件生成指纹

3. 适配后的完整代码示例

适配后的AppCacheResult类

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Http;
using System.Text;

namespace Store.Controllers
{
    public class AppCacheResult : IActionResult
    {
        public AppCacheResult(
            IEnumerable<string> cacheAssets,
            IEnumerable<string> networkAssets = null,
            IDictionary<string, string> fallbackAssets = null,
            string fingerprint = null)
        {
            if (cacheAssets == null)
            {
                throw new ArgumentNullException(nameof(cacheAssets));
            }

            CacheAssets = cacheAssets.ToList();

            if (!CacheAssets.Any())
            {
                throw new ArgumentException(
                    "Cached url cannot be empty.", nameof(cacheAssets));
            }

            NetworkAssets = networkAssets ?? new List<string>();
            FallbackAssets = fallbackAssets ?? new Dictionary<string, string>();
            Fingerprint = fingerprint;
        }

        protected IEnumerable<string> CacheAssets { get; private set; }
        protected IEnumerable<string> NetworkAssets { get; private set; }
        protected IDictionary<string, string> FallbackAssets { get; private set; }
        protected string Fingerprint { get; private set; }

        public async Task ExecuteResultAsync(ActionContext context)
        {
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }

            var response = context.HttpContext.Response;
            // 替换原Cache设置
            response.Headers["Cache-Control"] = "max-age=0";
            // 直接指定编码在ContentType中
            response.ContentType = "text/cache-manifest; charset=utf-8";
            // 异步写入内容
            await response.WriteAsync(GenerateContent(), Encoding.UTF8);
        }

        protected virtual string GenerateHeader()
        {
            return "CACHE MANIFEST" + Environment.NewLine;
        }

        protected virtual string GenerateFingerprint()
        {
            return string.IsNullOrWhiteSpace(Fingerprint) ?
                string.Empty :
                Environment.NewLine +
                "# " + Fingerprint +
                Environment.NewLine;
        }

        protected virtual string GenerateCache()
        {
            var result = new StringBuilder();

            result.AppendLine();
            result.AppendLine("CACHE:");
            CacheAssets.ToList().ForEach(a => result.AppendLine(a));

            return result.ToString();
        }

        protected virtual string GenerateNetwork()
        {
            var result = new StringBuilder();

            result.AppendLine();
            result.AppendLine("NETWORK:");

            var networkAssets = NetworkAssets.ToList();

            if (networkAssets.Any())
            {
                networkAssets.ForEach(a => result.AppendLine(a));
            }
            else
            {
                result.AppendLine("*");
            }

            return result.ToString();
        }

        protected virtual string GenerateFallback()
        {
            if (!FallbackAssets.Any())
            {
                return string.Empty;
            }

            var result = new StringBuilder();

            result.AppendLine();
            result.AppendLine("FALLBACK:");

            foreach (var pair in FallbackAssets)
            {
                result.AppendLine(pair.Key + " " + pair.Value);
            }

            return result.ToString();
        }

        private string GenerateContent()
        {
            var content = new StringBuilder();

            content.Append(GenerateHeader());
            content.Append(GenerateFingerprint());
            content.Append(GenerateCache());
            content.Append(GenerateNetwork());
            content.Append(GenerateFallback());

            return content.ToString();
        }
    }
}

适配后的控制器代码示例

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Hosting;
using System.IO;
using System.Security.Cryptography;

namespace Store.Controllers
{
    public class MobileOrderController : ControllerBase
    {
        private readonly IWebHostEnvironment _webHostEnv;
        public MobileOrderController(IWebHostEnvironment webHostEnv)
        {
            _webHostEnv = webHostEnv;
        }

        public async Task<IActionResult> Manifest()
        {
            // 示例:获取jquery资源路径与指纹,实际可根据bundleconfig配置读取
            var jqueryPath = "/lib/jquery/dist/jquery.min.js";
            var jqueryFile = Path.Combine(_webHostEnv.WebRootPath, "lib/jquery/dist/jquery.min.js");
            var fingerprint = Convert.ToBase64String(SHA256.Create().ComputeHash(File.ReadAllBytes(jqueryFile)));
            
            return new AppCacheResult(new[] { jqueryPath }, fingerprint: fingerprint);
        }
    }
}

二、更优的离线应用实现方案

AppCache标准已被所有主流浏览器废弃,.NET 5环境下推荐使用 Service Worker + PWA 方案实现离线能力,优势更明显:

  • 缓存规则完全自定义,比AppCache的黑白名单模式灵活度高几个量级
  • 离线数据可存储在IndexedDB中,支持存储大量订单数据
  • 配合Background Sync API,可在网络恢复后自动触发订单提交,无需用户手动操作
  • 前端可直接用Workbox库封装好的能力,无需手写复杂的Service Worker逻辑
  • 后端只需提供标准的订单提交接口即可,不用维护AppCache相关的特殊逻辑

内容的提问来源于stack exchange,提问作者Andrus

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.04 19:21:00