【问题标题】:ASP.NET bundling/minification: including dynamically generated JavascriptASP.NET 捆绑/缩小:包括动态生成的 Javascript
【发布时间】:2012-08-18 12:09:36
【问题描述】:

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

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

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

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

【问题讨论】:

    标签: asp.net-mvc .net-4.5 bundling-and-minification asp.net-optimization


    【解决方案1】:

    有了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 支持。在您的 View 中使用 &lt;script&gt; 标签,并让它们被自定义 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
          };
      }
      

    其他类型的动态内容的步骤类似。例如,请参阅Bundling and Minification and Embedded Resources

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

    【讨论】:

      【解决方案2】:

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

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

      更新:现已推出支持 VPP 的 1.1-alpha1 版本

      【讨论】:

      • 听起来不错!您是否有推荐关注此类发展的博客或新闻网站?
      • 我们刚刚公开了我们的 codeplex 站点,代码还没有,但这可能是您长期想要的:aspnetoptimization.codeplex.com
      • 不太明白你所说的占位符文件是什么意思。如果我在磁盘上有一个与我的动态 mvc 路由具有相同路径的文件,它将被返回并且我的操作将永远不会被执行。我错过了什么?
      • 还有关于 VirtualPathProviders 的任何更新变化吗?在 Codeplex 网站上看不到任何代码。
      • VPP 更改将在 1.1-alpha1 版本中进行,该版本很快就会发布
      【解决方案3】:

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

      【讨论】:

      • 如有必要,我可以添加占位符文件 - 我只是希望内容自动与服务器端代码同步。
      猜你喜欢
      • 2012-12-31
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-12-12
      • 2015-07-05
      • 2017-11-20
      • 1970-01-01
      相关资源
      最近更新 更多