I'm using the ASP.NET MVC 4 bundling and minifying features in the Microsoft.AspNet.Web.Optimization
namespace (e.g. @Styles.Render("~/content/static/css")
).
I'd like to use it in combination with a Windows Azure CDN.
I looked into writing a custom BundleTransform
but the content is not optimized there yet.
I also looked into parsing and uploading the optimized stream on runtime but that feels like a hack to me and I don't really like it:
@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); }
How can I upload the bundled and minified versions automatically to my Windows Azure CDN?
Bundling and minification are two techniques you can use in ASP.NET to improve page load performance for your web application. Bundling combines multiple files into a single file. Minification performs a variety of different code optimizations to scripts and CSS, which results in smaller payloads.
Bundling and minification are two techniques you can use in ASP.NET 4.5 to improve request load time. Bundling and minification improves load time by reducing the number of requests to the server and reducing the size of requested assets (such as CSS and JavaScript.)
Sign in to the Azure portal. From the left menu, select Storage accounts, then select the name of your storage account. Select Containers, then select the thumbnails container. Select Upload to open the Upload blob pane.
Azure CDN is best for delivering static content like Videos, Images and PDFs whereas Azure Front Door is for delivering sites, services and APIs. Azure CDN is cost-effective whereas Azure Front Door charges per ruleset. Azure CDN does all the functionality similar to Azure Front Door.
Following Hao's advice I Extended Bundle and IBundleTransform.
Adding AzureScriptBundle or AzureStyleBundle to bundles;
bundles.Add(new AzureScriptBundle("~/bundles/modernizr.js", "cdn").Include("~/Scripts/vendor/modernizr.custom.68789.js"));
Results in;
<script src="//127.0.0.1:10000/devstoreaccount1/cdn/modernizr.js?v=g-XPguHFgwIb6tGNcnvnI_VY_ljCYf2BDp_NS5X7sAo1"></script>
If CdnHost isn't set it will use the Uri of the blob instead of the CDN.
Class
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(); } } }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With