【问题标题】:How to upload bundled and minified files to Windows Azure CDN如何将捆绑和缩小的文件上传到 Windows Azure CDN
【发布时间】:2012-08-16 09:25:11
【问题描述】:

我在 Microsoft.AspNet.Web.Optimization 命名空间(例如 @Styles.Render("~/content/static/css"))中使用 ASP.NET MVC 4 捆绑和缩小功能。

我想将它与 Windows Azure CDN 结合使用。

我考虑编写自定义 BundleTransform,但内容尚未优化。

我还研究了在运行时解析和上传优化的流,但这对我来说就像是 hack,我不太喜欢它:

@StylesCdn.Render(Url.AbsoluteContent(
    Styles.Url("~/content/static/css").ToString()
    ));

public static IHtmlString Render(string absolutePath)
{
    // get the version hash
    string versionHash = HttpUtility.ParseQueryString(
        new Uri(absolutePath).Query
        ).Get("v");

    // only parse and upload to CDN if version hash is different
    if (versionHash != _versionHash)
    {
        _versionHash = versionHash;

        WebClient client = new WebClient();
        Stream stream = client.OpenRead(absolutePath);

        UploadStreamToAzureCdn(stream);
    }

    var styleSheetLink = String.Format(
        "<link href=\"{0}://{1}/{2}/{3}?v={4}\" rel=\"stylesheet\" type=\"text/css\" />",
        cdnEndpointProtocol, cdnEndpointUrl, cdnContainer, cdnCssFileName, versionHash
        );

    return new HtmlString(styleSheetLink);
}

如何将捆绑和缩小的版本自动上传到我的 Windows Azure CDN?

【问题讨论】:

  • Nate Totten 做了这样的事情:github.com/ntotten/wa-cdnhelpers/wiki。不过,请访问该存储库的主页……看起来他最近推荐了其他解决方案。
  • 你能告诉我这个 _versionHash 参数在哪里吗?谢谢。
  • @BarbarosAlp _versionHash 表示添加到您的资产中的查询字符串 v。在上面的实现中,它将与之前缓存的字符串进行比较。
  • 快3年了,有没有人找到OTB解决方案?

标签: azure asp.net-mvc-4 cdn bundling-and-minification asp.net-optimization


【解决方案1】:

您可以定义origin domain as Azure's website(这可能是在原始问题之后很久才添加的)。

一旦你有了 CDN 端点,你需要允许它的查询字符串,然后你可以通过 CDN 直接引用包:

<link href="//az888888.vo.msecnd.net/Content/css-common?v=ioYVnAg-Q3qYl3Pmki-qdKwT20ESkdREhi4DsEehwCY1" rel="stylesheet"/>

我还创建了这个助手来附加 CDN 主机名:

public static IHtmlString RenderScript(string virtualPath)
{
    if (HttpContext.Current.IsDebuggingEnabled)
        return Scripts.Render(virtualPath);
    else
        return new HtmlString(String.Format(
            CultureInfo.InvariantCulture, 
            Scripts.DefaultTagFormat, 
            "//CDN_HOST" + Scripts.Url(virtualPath).ToHtmlString()));
}

【讨论】:

    【解决方案2】:

    对于@manishKungwani 在之前的评论中要求。只需设置 UseCdn 然后使用 cdnHost 重载以创建捆绑包。我用它来放入 AWS CloudFront 域 (xxx.cloudfront.net),但事后看来,它应该被更通用地命名以与任何其他 CDN 提供商一起使用。

    public class CloudFrontScriptBundle : Bundle
    {
        public CloudFrontScriptBundle(string virtualPath, string cdnHost = "")
            : base(virtualPath, null, new IBundleTransform[] { new JsMinify(), new CloudFrontBundleTransformer { CdnHost = cdnHost } })
        {
            ConcatenationToken = ";";
        }
    }
    
    public class CloudFrontStyleBundle : Bundle
    {
        public CloudFrontStyleBundle(string virtualPath, string cdnHost = "")
            : base(virtualPath, null, new IBundleTransform[] { new CssMinify(), new CloudFrontBundleTransformer { CdnHost = cdnHost } })
        {
        }
    }
    
    public class CloudFrontBundleTransformer : IBundleTransform
    {
        public string CdnHost { get; set; }
    
        static CloudFrontBundleTransformer()
        {
        }
    
        public virtual void Process(BundleContext context, BundleResponse response)
        {
            if (context.BundleCollection.UseCdn && !String.IsNullOrWhiteSpace(CdnHost))
            {
                var virtualFileName = VirtualPathUtility.GetFileName(context.BundleVirtualPath);
                var virtualDirectory = VirtualPathUtility.GetDirectory(context.BundleVirtualPath);
    
                if (!String.IsNullOrEmpty(virtualDirectory))
                    virtualDirectory = virtualDirectory.Trim('~');
    
                var uri = string.Format("//{0}{1}{2}", CdnHost, virtualDirectory, virtualFileName);
                using (var hashAlgorithm = CreateHashAlgorithm())
                {
                    var hash = HttpServerUtility.UrlTokenEncode(hashAlgorithm.ComputeHash(Encoding.Unicode.GetBytes(response.Content)));
                    context.BundleCollection.GetBundleFor(context.BundleVirtualPath).CdnPath = string.Format("{0}?v={1}", uri, hash);
                }
            }
        }
    
        private static SHA256 CreateHashAlgorithm()
        {
            if (CryptoConfig.AllowOnlyFipsAlgorithms)
            {
                return new SHA256CryptoServiceProvider();
            }
    
            return new SHA256Managed();
        }
    }
    

    【讨论】:

      【解决方案3】:

      按照 Hao 的建议,我扩展了 Bundle 和 IBundleTransform。

      将 AzureScriptBundle 或 AzureStyleBundle 添加到包中;

      bundles.Add(new AzureScriptBundle("~/bundles/modernizr.js", "cdn").Include("~/Scripts/vendor/modernizr.custom.68789.js"));
      

      结果;

      <script src="//127.0.0.1:10000/devstoreaccount1/cdn/modernizr.js?v=g-XPguHFgwIb6tGNcnvnI_VY_ljCYf2BDp_NS5X7sAo1"></script>
      

      如果未设置 CdnHost,它将使用 blob 的 Uri 而不是 CDN。

      using System;
      using System.Text;
      using System.Web;
      using System.Web.Optimization;
      using System.Security.Cryptography;
      using Microsoft.WindowsAzure;
      using Microsoft.WindowsAzure.ServiceRuntime;
      using Microsoft.WindowsAzure.StorageClient;
      
      namespace SiegeEngineWebRole.BundleExtentions
      {
          public class AzureScriptBundle : Bundle
          {
              public AzureScriptBundle(string virtualPath, string containerName, string cdnHost = "")
                  : base(virtualPath, null, new IBundleTransform[] { new JsMinify(), new AzureBlobUpload { ContainerName = containerName, CdnHost = cdnHost } })
              {
                  ConcatenationToken = ";";
              }
          }
      
          public class AzureStyleBundle : Bundle
          {
              public AzureStyleBundle(string virtualPath, string containerName, string cdnHost = "")
                  : base(virtualPath, null, new IBundleTransform[] { new CssMinify(), new AzureBlobUpload { ContainerName = containerName, CdnHost = cdnHost } })
              {
              }
          }
      
          public class AzureBlobUpload : IBundleTransform
          {
              public string ContainerName { get; set; }
              public string CdnHost { get; set; }
      
              static AzureBlobUpload()
              {
              }
      
              public virtual void Process(BundleContext context, BundleResponse response)
              {
                  var file = VirtualPathUtility.GetFileName(context.BundleVirtualPath);
      
                  if (!context.BundleCollection.UseCdn)
                  {
                      return;
                  }
                  if (string.IsNullOrWhiteSpace(ContainerName))
                  {
                      throw new Exception("ContainerName Not Set");
                  }
      
                  var conn = CloudStorageAccount.Parse(RoleEnvironment.GetConfigurationSettingValue("DataConnectionString"));
                  var blob = conn.CreateCloudBlobClient()
                      .GetContainerReference(ContainerName)
                      .GetBlobReference(file);
      
                  blob.Properties.ContentType = response.ContentType;
                  blob.UploadText(response.Content);
      
                  var uri = string.IsNullOrWhiteSpace(CdnHost) ? blob.Uri.AbsoluteUri.Replace("http:", "").Replace("https:", "") : string.Format("//{0}/{1}/{2}", CdnHost, ContainerName, file);
      
                  using (var hashAlgorithm = CreateHashAlgorithm())
                  {
                      var hash = HttpServerUtility.UrlTokenEncode(hashAlgorithm.ComputeHash(Encoding.Unicode.GetBytes(response.Content)));
                      context.BundleCollection.GetBundleFor(context.BundleVirtualPath).CdnPath = string.Format("{0}?v={1}", uri, hash);
                  }
              }
      
              private static SHA256 CreateHashAlgorithm()
              {
                  if (CryptoConfig.AllowOnlyFipsAlgorithms)
                  {
                      return new SHA256CryptoServiceProvider();
                  }
      
                  return new SHA256Managed();
              }
          }
      }
      

      【讨论】:

      • 谢谢,这是一个非常好的解决方案。我接受并对其进行了修改,以便与静态主机 DNS (Amazon CloudFront) 一起使用。
      • @ZackZ。你能发布那个课程吗?
      • 每次调用Process,是否创建一个新的Hash?如果文件没有改变怎么办,是不是意味着浏览器会在不应该的时候重新下载文件?
      • @BarbarosAlp 自从我研究捆绑以来已经有很长时间了,所以我不能 100% 确定将文件推送到 blob 存储会对捆绑新哈希产生什么影响。也许知道的人可以加入答案或对其进行测试并发表评论。
      • 对不起,你散列了缩小后的css或js的内容。因此,如果内容没有更改,则散列字符串保持不变。谢谢。
      【解决方案4】:

      所以目前没有很好的方法来做到这一点。我们设想的长期工作流程是添加构建时捆绑支持。然后,您将运行构建任务(如果您愿意,也可以运行 exe)来生成捆绑包,然后能够将它们上传到 AzureCDN。最后,您只需在 BundleCollection 上打开 UseCDN,脚本/样式助手就会自动切换到渲染到您的 AzureCDN 的链接,并适当地回退到您的本地包。

      就短期而言,我认为您正在尝试做的是在首次构建捆绑包时将捆绑包上传到 AzureCDN?

      我猜 BundleTransform 是一种方法,它有点小技巧,但你可以在你的包中最后添加一个 BundleTransform。自上次以来,BundleResponse.Content 实际上是最终的捆绑响应。届时,您可以将其上传到您的 CDN。这有意义吗?

      【讨论】:

      • 感谢您的回答。如果基础文件发生更改,构建时捆绑将不支持使用不同哈希的重新缩小。如果 CDN 支持可以在环境级别上配置,那么我会很高兴,这样只有 prod/int 环境才使用 CDN 中的包。是的,我尝试在首次构建捆绑包时将其上传到 CDN。我会尝试你的建议并添加另一个 BundleTransform 来解决这个问题。
      • @MartinBuberl 你有没有得到这个工作?这里的工作解决方案是什么?
      • 如果 Azure CDN 支持直通(与其他提供商一样),那就太好了,这样它就不会上传任何内容,而只会请求您的服务器。因此,如果您请求 my.cdn.com/myfile.jpg 并且它没有被缓存,cdn 将使用 website.com/myfile.jpg 从您的服务器获取它。使用捆绑功能非常简单且易于支持。
      • 这是在 Mvc 5 中实现的吗?
      猜你喜欢
      • 2023-03-21
      • 2013-09-11
      • 2013-05-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多