I’ve recently had the need to generate dynamic css for a themable MVC web application. I considered using a basic homegrown templating mechanism, or repurposing Razor, but then found the dotless project on the interwebs.
With the NuGet package it was relatively easy to get up & running, and I’ve abstracted the clunky regex code into an ActionResult subclass, recorded here in case it’s useful for somebody else.
public class DotLessResult : ActionResult { public IDictionary<string, string> Parameters { get; set;} public string Less { get; set; } public bool Minify { get; set; } public DotLessResult(string less, IDictionary<string, string> parameters = null, bool minify = false) { Less = less; Parameters = parameters ?? new Dictionary<string, string>(); Minify = minify; } public DotLessResult(Stream stream, IDictionary<string, string> parameters = null, bool minify = false) : this(new StreamReader(stream).ReadToEnd(), parameters, minify) { } public override void ExecuteResult(ControllerContext context) { var output = Less; foreach (var key in Parameters.Keys) { output = Regex.Replace(output, @"\s*@" + key + @":\s*\S+;", "@" + key + ":" + Parameters[key] + ";"); } var css = dotless.Core.Less.Parse(output, new DotlessConfiguration { MinifyOutput = Minify }); context.HttpContext.Response.ContentType = "text/css"; using (var writer = new StreamWriter(context.HttpContext.Response.OutputStream, Encoding.UTF8)) { writer.Write(css); writer.Flush(); } } }
This can then be used from your action method like so:
public ActionResult Styles(string id) { var stream = GetType().Assembly.GetManifestResourceStream(stylePath + id.Replace(".css", ".less")); if (stream == null) { return HttpNotFound(); } Dictionary<string, string> parameters = new Dictionary<string, string>(); parameters["backgroundcolor"] = "#1f1400"; // continue for all replaceable parameters return new DotLessResult(stream, parameters, true); }
The .less source should be stored in your web assembly as an embedded resource, with configurable parameters declared using the syntax “@: <placeholderValue;". Then it's just a case of looping through the supplied parameters dictionary and changing the declaration in the .less source. In the example code here I’m looking for a .less resource with the same name as the requested .css file, but you can obviously use your imagination.
What about caching, checking etags and handling If-Modified-Since? Have you considered these? Have you considered using IRouteHandler to make it even faster?
Hi Scooletz,
Yes, our implementation uses a basic Expires header for caching in addition to the MVC output caching. E-Tag/If-Modified-Since are a bit more difficult as the css source is conceptually spread over the .less files and records in the database.