【问题标题】:How can I add the result of an ASP.NET MVC controller action to a Bundle?如何将 ASP.NET MVC 控制器操作的结果添加到 Bundle?
【发布时间】:2012-11-09 17:03:56
【问题描述】:

我有一个返回 Javascript 文件的控制器操作。我可以从我的视图中引用这个文件,它工作正常。我想把它和其他 JS 文件放在一个 System.Web.Optimization.Bundle 中。

基本上我正在尝试这样做:

new Bundle().Include("~/DynamicScript/UrlDictionary");

我的 Bundle 中的其他文件渲染得很好,但是这个被忽略了。看到这种行为,我的假设是捆绑在应用程序能够通过路由基础设施解析 URL 之前得到处理,或者捆绑组件没有以允许该解析发生的方式请求文件。

如果有人可以为我确认和/或在这里为我指明一个好的方向,将不胜感激。

【问题讨论】:

    标签: asp.net asp.net-mvc asp.net-mvc-4 bundling-and-minification


    【解决方案1】:

    我认为这是非常可行的——但在我进入我的解决方案之前——请记住,捆绑包是在第一次点击时创建并被重复使用的——这意味着任何“动态”脚本仍然必须是全局的(即它不能是取决于特定的用户等)。这就是为什么它通常只允许静态 js 文件。这么说...我可以想象这样一种情况,您可能希望在您的 js 中粘贴版本号之类的变量或类似的东西(尽管在这种情况下,我个人只会使用 Ajax/JSON 来获取它)。

    我认为解决方法是从Bundle 创建一个派生类型。在其中,您将覆盖 EnumerateFiles 方法。最初,您将枚举 base.EnumerateFiles,然后包含您自己的虚拟文件。

    类似的东西(注意:未经测试的代码):

    public class VirtualMethodBundle : Bundle
    {
        private List<VirtualFile> _virtualContent = new List<VirtualFile>();
    
        public override IEnumerable<VirtualFile> EnumerateFiles(BundleContext context)
        {
            foreach(var file in base.EnumerateFiles(context))
            {
                yield return file;
            }
    
            foreach(var virtual in _virtualContent)
            {
                yield return virtual;
            }
        }
    
        public void AddCustomFile(VirtualFile file)
        {
            _virtualContent.Add(method);
        }
    }
    

    然后您将拥有一个特殊的 VirtualFile 定义类型,它会覆盖 Open/Name 方法并在那里返回您的动态内容...类似的东西。

    public class MethodBasedVirtualFile : VirtualFile
    {
        private readonly Func<string> _contentFactory;
        private readonly string _path;
    
        public MethodBasedVirtualFile(string path, Func<string> contentFactory)
        {
            _path = path;
            _contentFactory = contentFactory;
        }
    
        public override string Name { get { return _path; } }
    
        public override Stream Open()
        {
            MemoryStream stream = new MemoryStream();
            StreamWriter writer = new StreamWriter(stream);
            writer.Write(_contentFactory());
            writer.Flush();
            stream.Position = 0;
            return stream;
        }
    }
    

    那么,使用它你将拥有的一切......

    var bundle = new VirtualMethodBundle();
    bundle.Include(... real files ...);
    bundle.AddCustomFile(
        new MethodBasedVirtualFile("~/DynamicScript/UrlDictionary",
        ... the method that creates the content of that script...)
    );
    

    如果你够聪明,你可以制作一个 UrlVirtualFile,它采用 url 路径并在需要时使用 MVC 自动获取内容。

    【讨论】:

    • 您正在覆盖 EnumerateFiles() 返回类型,它是 IEnumerable 而不是 IEnumerable。代码不会编译。
    【解决方案2】:

    这是我为这个场景所做的。我定义了一个“Bundle Result”,然后在我的 Controller 方法中,按名称返回了一个 Bundle。我正在使用 ETags (If-None-Match Header) 作为客户端缓存的优化。

    例如:

    public ActionResult ReturnFooBundle()
    {
        return new BundleResult("foo", TimeSpan.FromDays(7));
    }
    

    这是 BundleResult 的实现:

    public class BundleResult
        : ActionResult
    {
        private class BundleInfo
        {
            public string BundleETag;
            public DateTime LastModified;
            public Bundle TheBundle;
            public BundleResponse Response;
        }
    
        private static Dictionary<string, BundleInfo> _bundleCache = new Dictionary<string, BundleInfo>();
    
        public string BundleName { get; private set; }
        public TimeSpan CacheExpiry { get; private set; }
    
        public BundleResult(string bundleName, TimeSpan cacheExpiry)
        {
            BundleName = bundleName;
            CacheExpiry = cacheExpiry;
        }
        public override void ExecuteResult(ControllerContext context)
        {
            context.HttpContext.Response.Clear();
            BundleInfo bundleInfo = GetBundle(context.HttpContext);
    
            string requestETag = context.HttpContext.Request.Headers["If-None-Match"];
            if (!string.IsNullOrEmpty(requestETag) && (requestETag == bundleInfo.BundleETag))
            {
                context.HttpContext.Response.StatusCode = (int)HttpStatusCode.NotModified;
                context.HttpContext.Response.StatusDescription = "Not Modified";
                return;
            }
            else
            {
                BundleResponse bundleResponse = bundleInfo.Response;
                HttpResponseBase response = context.HttpContext.Response;
                response.Write(bundleResponse.Content);
                response.ContentType = bundleResponse.ContentType;
    
                HttpCachePolicyBase cache = response.Cache;
                cache.SetCacheability(HttpCacheability.ServerAndPrivate);
                cache.SetLastModified(bundleInfo.LastModified);
                cache.SetETag(bundleInfo.BundleETag);
            }
        }
    
        private BundleInfo GetBundle(HttpContextBase context)
        {
            // lookup the BundleResponse
            BundleInfo retVal;
            lock (_bundleCache)
            {
                _bundleCache.TryGetValue(BundleName, out retVal);
            }
            if(retVal != null)
            {
    #if DEBUG
                // see if the contents have been modified.
                BundleContext bundleContext = new BundleContext(context, BundleTable.Bundles, BundleName);
                DateTime lastModified = retVal.TheBundle.EnumerateFiles(bundleContext).Select(fi => fi.LastWriteTimeUtc).Max();
                if (lastModified > retVal.LastModified)
                {
                    // regenerate the bundleInfo
                    retVal = null;
                }
    #endif
            }
            if (retVal == null)
            {
                string rawBundleName = BundleTable.Bundles.ResolveBundleUrl(BundleName);
                string hash = rawBundleName.Substring(rawBundleName.IndexOf("?v=") + 3);
                Bundle bundle = BundleTable.Bundles.GetBundleFor(BundleName);
                BundleContext bundleContext = new BundleContext(context, BundleTable.Bundles, BundleName);
                BundleResponse bundleResponse = bundle.GenerateBundleResponse(bundleContext);
                DateTime lastModified = bundle.EnumerateFiles(bundleContext).Select(fi => fi.LastWriteTimeUtc).Max();
                retVal = new BundleInfo
                {
                    BundleETag = hash,
                    Response = bundleResponse,
                    TheBundle = bundle,
                    LastModified = lastModified,
                };
                lock (_bundleCache)
                {
                    _bundleCache[BundleName] = retVal;
                }
            }
            return retVal;
        }
    }
    

    【讨论】:

    • 优秀。非常感谢。唯一的问题是,截至今天(2018 年底,MVC5),BundleFile 没有 LastWriteTimeUtc 属性。就我而言,我使用 File.GetLastWriteTimeUtc(HttpContext.Current.Server.MapPath(bundleFile.IncludedVirtualPath)) 修复了它
    【解决方案3】:

    捆绑系统仅支持物理文件,不支持应用程序路由。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-04-12
      • 2020-09-18
      • 1970-01-01
      • 2012-02-22
      • 1970-01-01
      • 1970-01-01
      • 2016-12-06
      相关资源
      最近更新 更多