Asp.net-Mvc

ASP.NET 捆綁/縮小:包括動態生成的 Javascript

  • May 19, 2016

我有一個動態生成 Javascript 的網站。生成的程式碼描述了類型元數據和一些伺服器端常量,以便客戶端可以輕鬆地使用伺服器的服務——因此它是非常可記憶體的。

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

我想將此 Javascript 包含在與其他靜態 Javascript 文件(例如 jQuery 等)一起的 Javascript 包中:所以就像靜態文件一樣,我希望它在調試模式下單獨引用,並以縮小形式與其他文件捆綁在非調試模式。

如何在捆綁包中包含動態生成的 Javascript?

Darin 是對的,目前捆綁僅適用於靜態文件。但是,如果您可以添加具有最新內容的佔位符文件,則捆綁會設置文件更改通知,該通知將在佔位符文件更改時自動檢測。

此外,我們很快將轉向使用 VirtualPathProviders,這可能是一種提供動態生成內容的方式。

**更新:**支持 VPP 的 1.1-alpha1 版本現已發布

有了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));
   }
}
  1. 實現一個虛擬路徑提供者,它包裝現有的路徑並攔截所有應該傳遞動態內容的虛擬路徑。
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);
   }
}
  1. 註冊您的虛擬路徑提供程序:
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"));
}
  1. 可選:將 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 上添加了一個概念證明儲存庫。

引用自:https://stackoverflow.com/questions/12018429