9

我有一个网站可以动态生成Javascript。生成的代码描述了类型元数据和一些服务器端常量,以便客户端可以轻松使用服务器的服务 - 因此它非常容易缓存。ASP.NET捆绑/缩小:包括动态生成的Javascript

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

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

如何在一个包中包含动态生成的Javascript?

回答

4

达林是对的,目前捆绑只能在静态文件上工作。但是,如果您可以添加带有最新内容的占位符文件,则捆绑不会设置文件更改通知,它会在占位符文件更改时自动检测。

此外,我们将要移动使用VirtualPathProviders很快,这可能是服务于动态生成的内容的方式。

更新: 1.1-α1释放出来,现在它有VPP

+0

听起来不错!你有一个博客或一些新闻网站,你会推荐这些发展? – 2012-08-20 23:44:58

+0

我们只是将我们的codeplex网站公开,代码还没有,但这可能是您长期想要的:http://aspnetoptimization.codeplex.com – 2012-08-29 18:42:04

+1

并不真正了解您的占位符文件是什么意思。如果我的磁盘上有一个与我的动态mvc路径相同的文件,它将被返回并且我的动作永远不会被执行。我错过了什么? – 2012-10-13 00:03:15

3

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

+0

如果需要添加占位符文件,我很满意 - 我只是希望内容自动与服务器端代码同步。 – 2012-08-18 16:16:12

5

支持与VirtualPathProviders这成为可能。将动态内容集成到捆绑过程中需要以下步骤:

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

    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. 可选:你的意见添加智能感知支持。使用您的视图中<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 
        }; 
    } 
    

的步骤similiar对于其他类型的动态内容。例如,请参见Bundling and Minification and Embedded Resources

如果你想尝试一下,我添加了一个概念证明库到GitHub