10

我有一个动态生成 Javascript 的网站。生成的代码描述了类型元数据和一些服务器端常量,以便客户端可以轻松地使用服务器的服务——因此它是非常可缓存的。

生成的 Javascript 由 ASP.NET MVC 控制器提供服务;所以它有一个 Uri;说~/MyGeneratedJs

我想将此 Javascript 包含在与其他静态 Javascript 文件(例如 jQuery 等)一起的 Javascript 包中:所以就像静态文件一样,我希望它在调试模式下单独引用,并以缩小形式与其他文件捆绑在非调试模式。

如何在捆绑包中包含动态生成的 Javascript?

4

3 回答 3

7

有了VirtualPathProviders这个现在是可能的。将动态内容集成到捆绑过程中需要以下步骤:

  1. 编写请求/构建所需内容的逻辑。直接从 Controller 生成内容需要一些工作:

    public static class ControllerActionHelper
    {
        public static string RenderControllerActionToString(string virtualPath)
        {
            HttpContext httpContext = CreateHttpContext(virtualPath);
            HttpContextWrapper httpContextWrapper = new HttpContextWrapper(httpContext);
    
            RequestContext httpResponse = new RequestContext()
            {
                HttpContext = httpContextWrapper,
                RouteData = RouteTable.Routes.GetRouteData(httpContextWrapper)
            };
    
            // Set HttpContext.Current if RenderActionToString is called outside of a request
            if (HttpContext.Current == null)
            {
                HttpContext.Current = httpContext;
            }
    
            IControllerFactory controllerFactory = ControllerBuilder.Current.GetControllerFactory();
            IController controller = controllerFactory.CreateController(httpResponse,
                httpResponse.RouteData.GetRequiredString("controller"));
            controller.Execute(httpResponse);
    
            return httpResponse.HttpContext.Response.Output.ToString();
        }
    
        private static HttpContext CreateHttpContext(string virtualPath)
        {
            HttpRequest httpRequest = new HttpRequest(string.Empty, ToDummyAbsoluteUrl(virtualPath), string.Empty);
            HttpResponse httpResponse = new HttpResponse(new StringWriter());
    
            return new HttpContext(httpRequest, httpResponse);
        }
    
        private static string ToDummyAbsoluteUrl(string virtualPath)
        {
            return string.Format("http://dummy.net{0}", VirtualPathUtility.ToAbsolute(virtualPath));
        }
    }
    
  2. 实现一个虚拟路径提供者,它包装现有的路径并拦截所有应该传递动态内容的虚拟路径。

    public class ControllerActionVirtualPathProvider : VirtualPathProvider
    {
        public ControllerActionVirtualPathProvider(VirtualPathProvider virtualPathProvider)
        {
            // Wrap an existing virtual path provider
            VirtualPathProvider = virtualPathProvider;
        }
    
        protected VirtualPathProvider VirtualPathProvider { get; set; }
    
        public override string CombineVirtualPaths(string basePath, string relativePath)
        {
            return VirtualPathProvider.CombineVirtualPaths(basePath, relativePath);
        }
    
        public override bool DirectoryExists(string virtualDir)
        {
            return VirtualPathProvider.DirectoryExists(virtualDir);
        }
    
        public override bool FileExists(string virtualPath)
        {
            if (ControllerActionHelper.IsControllerActionRoute(virtualPath))
            {
                return true;
            }
    
            return VirtualPathProvider.FileExists(virtualPath);
        }
    
        public override CacheDependency GetCacheDependency(string virtualPath, IEnumerable virtualPathDependencies,
            DateTime utcStart)
        {
            AggregateCacheDependency aggregateCacheDependency = new AggregateCacheDependency();
    
            List<string> virtualPathDependenciesCopy = virtualPathDependencies.Cast<string>().ToList();
    
            // Create CacheDependencies for our virtual Controller Action paths
            foreach (string virtualPathDependency in virtualPathDependenciesCopy.ToList())
            {
                if (ControllerActionHelper.IsControllerActionRoute(virtualPathDependency))
                {
                    aggregateCacheDependency.Add(new ControllerActionCacheDependency(virtualPathDependency));
                    virtualPathDependenciesCopy.Remove(virtualPathDependency);
                }
            }
    
            // Aggregate them with the base cache dependency for virtual file paths
            aggregateCacheDependency.Add(VirtualPathProvider.GetCacheDependency(virtualPath, virtualPathDependenciesCopy,
                utcStart));
    
            return aggregateCacheDependency;
        }
    
        public override string GetCacheKey(string virtualPath)
        {
            return VirtualPathProvider.GetCacheKey(virtualPath);
        }
    
        public override VirtualDirectory GetDirectory(string virtualDir)
        {
            return VirtualPathProvider.GetDirectory(virtualDir);
        }
    
        public override VirtualFile GetFile(string virtualPath)
        {
            if (ControllerActionHelper.IsControllerActionRoute(virtualPath))
            {
                return new ControllerActionVirtualFile(virtualPath,
                    new MemoryStream(Encoding.Default.GetBytes(ControllerActionHelper.RenderControllerActionToString(virtualPath))));
            }
    
            return VirtualPathProvider.GetFile(virtualPath);
        }
    
        public override string GetFileHash(string virtualPath, IEnumerable virtualPathDependencies)
        {
            return VirtualPathProvider.GetFileHash(virtualPath, virtualPathDependencies);
        }
    
        public override object InitializeLifetimeService()
        {
            return VirtualPathProvider.InitializeLifetimeService();
        }
    }
    
    public class ControllerActionVirtualFile : VirtualFile
    {
        public CustomVirtualFile (string virtualPath, Stream stream)
            : base(virtualPath)
        {
            Stream = stream;
        }
    
        public Stream Stream { get; private set; }
    
        public override Stream Open()
        {
             return Stream;
        }
    }
    

    如果需要,还必须实现 CacheDependency:

    public class ControllerActionCacheDependency : CacheDependency
    {
        public ControllerActionCacheDependency(string virtualPath, int actualizationTime = 10000)
        {
            VirtualPath = virtualPath;
            LastContent = GetContentFromControllerAction();
    
            Timer = new Timer(CheckDependencyCallback, this, actualizationTime, actualizationTime);
        }
    
        private string LastContent { get; set; }
    
        private Timer Timer { get; set; }
    
        private string VirtualPath { get; set; }
    
        protected override void DependencyDispose()
        {
            if (Timer != null)
            {
                Timer.Dispose();
            }
    
            base.DependencyDispose();
        }
    
        private void CheckDependencyCallback(object sender)
        {
            if (Monitor.TryEnter(Timer))
            {
                try
                {
                    string contentFromAction = GetContentFromControllerAction();
    
                    if (contentFromAction != LastContent)
                    {
                        LastContent = contentFromAction;
                        NotifyDependencyChanged(sender, EventArgs.Empty);
                    }
                }
                finally
                {
                    Monitor.Exit(Timer);
                }
            }
        }
    
        private string GetContentFromControllerAction()
        {
            return ControllerActionHelper.RenderControllerActionToString(VirtualPath);
        }
    }
    
  3. 注册您的虚拟路径提供程序:

    public static void RegisterBundles(BundleCollection bundles)
    {
        // Set the virtual path provider
        BundleTable.VirtualPathProvider = new ControllerActionVirtualPathProvider(BundleTable.VirtualPathProvider);
    
        bundles.Add(new Bundle("~/bundle")
            .Include("~/Content/static.js")
            .Include("~/JavaScript/Route1")
            .Include("~/JavaScript/Route2"));
    }
    
  4. 可选:将 Intellisense 支持添加到您的视图中。在您的视图中使用<script>标签并让它们被自定义 ViewResult 删除:

    public class DynamicContentViewResult : ViewResult
    {
        public DynamicContentViewResult()
        {
            StripTags = false;
        }
    
        public string ContentType { get; set; }
    
        public bool StripTags { get; set; }
    
        public string TagName { get; set; }
    
        public override void ExecuteResult(ControllerContext context)
        {
            if (context == null)
            {
                throw new ArgumentNullException("context");
            }
    
            if (string.IsNullOrEmpty(ViewName))
            {
                ViewName = context.RouteData.GetRequiredString("action");
            }
    
            ViewEngineResult result = null;
    
            if (View == null)
            {
                result = FindView(context);
                View = result.View;
            }
    
            string viewResult;
    
            using (StringWriter viewContentWriter = new StringWriter())
            {
                ViewContext viewContext = new ViewContext(context, View, ViewData, TempData, viewContentWriter);
    
                View.Render(viewContext, viewContentWriter);
    
                if (result != null)
                {
                    result.ViewEngine.ReleaseView(context, View);
                }
    
                viewResult = viewContentWriter.ToString();
    
                // Strip Tags
                if (StripTags)
                {
                    string regex = string.Format("<{0}[^>]*>(.*?)</{0}>", TagName);
                    Match res = Regex.Match(viewResult, regex,
                        RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace | RegexOptions.Multiline | RegexOptions.Singleline);
    
                    if (res.Success && res.Groups.Count > 1)
                    {
                        viewResult = res.Groups[1].Value;
                    }
                    else
                    {
                        throw new InvalidProgramException(
                            string.Format("Dynamic content produced by View '{0}' expected to be wrapped in '{1}' tag.", ViewName, TagName));
                    }
                }
            }
    
            context.HttpContext.Response.ContentType = ContentType;
            context.HttpContext.Response.Output.Write(viewResult);
        }
    }
    

    使用扩展方法或向控制器添加辅助函数:

    public static DynamicContentViewResult JavaScriptView(this Controller controller, string viewName, string masterName, object model)
    {
        if (model != null)
        {
            controller.ViewData.Model = model;
        }
    
        return new DynamicContentViewResult
        {
            ViewName = viewName,
            MasterName = masterName,
            ViewData = controller.ViewData,
            TempData = controller.TempData,
            ViewEngineCollection = controller.ViewEngineCollection,
            ContentType = "text/javascript",
            TagName = "script",
            StripTags = true
        };
    }
    

其他类型的动态内容的步骤与此类似。例如,请参阅捆绑和缩小以及嵌入式资源

如果您想尝试一下,我在GitHub 上添加了一个概念证明存储库。

于 2014-12-16T16:07:25.163 回答
4

Darin 是对的,目前捆绑仅适用于静态文件。但是,如果您可以添加具有最新内容的占位符文件,则捆绑会设置文件更改通知,该通知将在占位符文件更改时自动检测。

此外,我们很快将转向使用 VirtualPathProviders,这可能是一种提供动态生成内容的方式。

更新:支持 VPP 的 1.1-alpha1 版本现已发布

于 2012-08-20T17:10:21.113 回答
3

这是不可能的。捆绑包仅适用于静态文件。

于 2012-08-18T12:40:32.327 回答