Skip to content

Instantly share code, notes, and snippets.

@mike-ward
Last active August 29, 2015 13:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mike-ward/9100604 to your computer and use it in GitHub Desktop.
Save mike-ward/9100604 to your computer and use it in GitHub Desktop.
Simple Asset Bundling for NancyFx
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Nancy;
using Nancy.Helpers;
using Nancy.Responses;
namespace SendExplorer.utilities
{
public static class Bundle
{
private const string CssMimeType = "text/css";
private const string JsMimeType = "application/x-javascript";
private static readonly ConcurrentDictionary<int, AssetBundle> BundleCache = new ConcurrentDictionary<int, AssetBundle>();
public static Response Styles(this IResponseFormatter formatter, IEnumerable<string> files, IRootPathProvider rootPathProvider)
{
return formatter.GetBundle(files, rootPathProvider, CssMimeType);
}
public static Response Scripts(this IResponseFormatter formatter, IEnumerable<string> files, IRootPathProvider rootPathProvider)
{
return formatter.GetBundle(files, rootPathProvider, JsMimeType);
}
public static Response GetBundle(this IResponseFormatter formatter, IEnumerable<string> files,
IRootPathProvider rootPathProvider, string contentType)
{
var hash = BundleHash(files, rootPathProvider);
if (BundleCache.ContainsKey(hash) == false)
{
var text = ConcatTextFiles(files, rootPathProvider);
var bytes = Encoding.UTF8.GetBytes(text);
var lastWriteTimeUtc = DateTime.UtcNow;
var etag = string.Concat("\"", lastWriteTimeUtc.Ticks.ToString("x"), "\"");
var assetBundle = new AssetBundle {LastUpdate = lastWriteTimeUtc, ETag = etag, Bytes = bytes};
BundleCache.TryAdd(hash, assetBundle);
}
var bundle = BundleCache[hash];
return (CacheHelpers.ReturnNotModified(bundle.ETag, bundle.LastUpdate, formatter.Context))
? ResponseNotModified()
: ResponseFromBundle(bundle, contentType);
}
public static async Task<Response> StylesAsync(this IResponseFormatter formatter, IEnumerable<string> files, IRootPathProvider rootPathProvider)
{
return await formatter.GetBundleAsync(files, rootPathProvider, CssMimeType);
}
public static async Task<Response> ScriptsAsync(this IResponseFormatter formatter, IEnumerable<string> files, IRootPathProvider rootPathProvider)
{
return await formatter.GetBundleAsync(files, rootPathProvider, JsMimeType);
}
public static async Task<Response> GetBundleAsync(this IResponseFormatter formatter, IEnumerable<string> files,
IRootPathProvider rootPathProvider, string contentType)
{
var responseTask = new Task<Response>(() => GetBundle(formatter, files, rootPathProvider, contentType));
responseTask.Start();
return await responseTask;
}
private static int BundleHash(IEnumerable<string> files, IRootPathProvider rootPathProvider)
{
return files
.Select(file => FullPath(file, rootPathProvider))
.Select(file => new FileInfo(file).LastWriteTimeUtc.GetHashCode() ^ file.GetHashCode())
.Aggregate((a, b) => a ^ b);
}
private static Response ResponseNotModified()
{
return new Response
{
StatusCode = HttpStatusCode.NotModified,
ContentType = null,
Contents = Response.NoBody
};
}
private static Response ResponseFromBundle(AssetBundle assetBundle, string contentType)
{
var stream = new MemoryStream(assetBundle.Bytes);
var response = new StreamResponse(() => stream, contentType);
response.Headers["ETag"] = assetBundle.ETag;
response.Headers["Last-Modified"] = assetBundle.LastUpdate.ToString("R");
return response;
}
private static string ConcatTextFiles(IEnumerable<string> files, IRootPathProvider rootPathProvider)
{
return string.Join(Environment.NewLine, files.Select(p => ReadTextFile(p, rootPathProvider)));
}
private static string ReadTextFile(string path, IRootPathProvider rootPathProvider)
{
return File.ReadAllText(FullPath(path, rootPathProvider));
}
private static string FullPath(string path, IRootPathProvider rootPathProvider)
{
return (Path.Combine(rootPathProvider.GetRootPath(), path));
}
public class AssetBundle
{
public DateTime LastUpdate { get; set; }
public string ETag { get; set; }
public byte[] Bytes { get; set; }
}
}
}
@mike-ward
Copy link
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment