ASP.NET bundling/minification: including dynamically generated Javascript

前端 未结 3 745
孤城傲影
孤城傲影 2021-01-17 19:29

I have a site that dynamically generates Javascript. The generated code describes type-metadata and some server-side constants so that the clients can easily consume the se

3条回答
  •  情歌与酒
    2021-01-17 20:22

    With VirtualPathProviders this is now possible. Integration of dynamic content into the bundling process requires the following steps:

    1. Writing the logic that requests / builds the required content. Generating content from Controller directly requires a bit of work:

      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. Implement a virtual path provider that wraps the existing one and intercept all virtual paths that should deliver the dynamic content.

      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 virtualPathDependenciesCopy = virtualPathDependencies.Cast().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;
          }
      }
      

      You also have to implement CacheDependency if you need it:

      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. Register your virtual path provider:

      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. Optional: Add Intellisense support to your views. Use

提交回复
热议问题