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