This project is read-only.

LESSCSS support

Topics: Customizing Orchard, Writing themes
Jul 6, 2011 at 2:02 AM

I am running Orchard 1.2 and am trying to add LESSCSS (via support to a new theme I am creating. I want to replace site.css with site.less which includes some LESSCSS directives to make it easier for me to customize my themes and styles.

I have added the dotlesscss http handler to Orchard.Web\web.config as per usual and have renamed "site.css" to "site.less". I also modified Layout.cshtml to include "site.less" instead of "site.css":


I build the project and have tried running it under Cassini and IIS7 express but am seeing some very odd behaviors:

Firstly, it appears as though Orchard overrides my request to include a link to "site.less", replacing it with a link to "site.css". To force Orchard to allow me to include a link to site.less, I had to modify the StylesheetBindingStrategy.Discover() method (line 74):

.Where(fileName =>
        // Make sure we accommodate LESSCSS "*.less" stylesheets. 
        string extension = Path.GetExtension(fileName);
        return string.Equals(extension, ".css", System.StringComparison.OrdinalIgnoreCase)
        || string.Equals(extension, ".less", System.StringComparison.OrdinalIgnoreCase);

After this change, I was able to see my site's pages linking to the .less file as expected: <link href="/OrchardLocal/Themes/MyTheme/Styles/Site.less" rel="stylesheet" type="text/css" />

However, when I view the content of ~/Themes/MyTheme/Styles/Site.less, I see the unprocessed LESSCSS source, rather than the generated CSS that I expect. This indicates to me that the HTML handler has not been kicked. I also note that the downloaded CSS is opened in notepad in a file called site.css ... not site.less as I'd kind of expect.

Is there any other behind-the-scenes magic that Orchard is performing that could be preventing me from inserting a link element to my .less file (not my .css file) and allowing the HTML handler to get kicked when the LESSCSS content is downloaded by the browser?


Jul 6, 2011 at 8:42 AM

I think you need to make this work with a custom Route and Controller.

So you should create a module, that handles /css/Site.less requests for example. And have it serve the compiled css by invoking dotless.Parse(requestedfile).

But I don't think you can get it working with the default Style builder, since it's meant to only find static content.

Jul 6, 2011 at 9:42 PM

There's a web.config in each module's content directories which removes all handlers, and basically ensures that only static content can be served from there. I think you'll need to modify that web.config, not the root one.

As for the .less extension problem.... well, thing is the way resources work is that they are actually transformed into shapes and rendered as such. This gives templates for example the power to change the script or css being included by just having a file with the same name. To do that we had to make an assumption about the file extension. The right way to do this would probably be to write a new binding strategy that looks for .less files and describes them as shapes. You can pretty much just copy the existing code to do that. Package it up into a lesscss module, and enable it.

Jul 6, 2011 at 10:28 PM

@sharpoverride & @infinitesloop: Thanks for the responses.

@infinitesloop - your initial point about the web.config story is what I arrived at around 23:30 last night when I had a brainwave just as I was falling asleep, causing me to leap out of bed to go try it out!!

As you point out, the main web.config file removes all HTML handlers and then adds just the 404 handler (System.Web.HttpNotFoundHandler). This prevents Orchard from serving static content by default. Various modules and themes throughout Orchard then selectively enable other capabilities, such as, for example, serving images, CSS files, etc. This is the case for the Theme's "Styles" folder too which has a web.config file that adds the System.Web.StaticFileHandler. If I add the dotLess handler just before the StaticFileHandler, requests for .less files from this folder are now correctly serviced through the dotLess HTML handler.

Regarding the assumption about the name of the site's CSS file: While I appreciate what you're aiming towards, it does appear that this is just another area of Orchard that requires just that bit more arcania than one would expect.

Jul 6, 2011 at 11:12 PM

I too played around with integrating LessCSS (as well as Sass & CoffeeScript).  I came to the same point that you did.  One thing that would make integrating these 3rd party tools is the ability for an orchard module to register custom HttpModules/Handlers. I created a work item here, feel free to vote on it if you think it should be a priority.

Jul 6, 2011 at 11:13 PM

Certainly. It's always a balance between flexibility, cost, risk, and complexity. This is a great use case though, so it's definitely a point on the side of improving this experience. I opened an issue to see if we can do anything:

Jul 19, 2011 at 5:46 PM

I've integrated dotless to my project based on Orchard while ago.

Here what you have to do.

1. Create you own theme or modify this what you use

2. Add reference to dotless.Core to the theme

3. Modify Styles/Web.Config like this

    <handlers accessPolicy="Script,Read">
      iis7 - for any request to a file exists on disk, return it via native http module.
      accessPolicy 'Script' is to allow for a managed 404 page.
      <add name ="LessFile" type="dotless.Core.LessCssHttpHandler,dotless.Core" path="*.less" verb="*" />
      <add name="StaticFile" path="*" verb="*" modules="StaticFileModule" preCondition="integratedMode" resourceType="File" requireAccess="Read" />

4. Put Site.css.less in Styles

5. Create this class in theme

4. Put Site.css.less in Styles

5. Create this class in theme

    public class ResourceManifest : IResourceManifestProvider
        public void BuildManifests(ResourceManifestBuilder builder)
            var manifest = builder.Add();

6. Put this code in your Layout.cshtml


7. Add this class to Theme

    public class LessStylesheetBindingStrategy : IShapeTableProvider
        private readonly IExtensionManager extensionManager;
        private readonly ShellDescriptor shellDescriptor;
        private readonly IVirtualPathProvider virtualPathProvider;
        private static readonly Regex safeName = new Regex(@"[/:?#\[\]@!&'()*+,;=\s\""<>\.\-_]+", RegexOptions.Compiled);

        public LessStylesheetBindingStrategy(IExtensionManager extensionManager, ShellDescriptor shellDescriptor, IVirtualPathProvider virtualPathProvider)
            this.extensionManager = extensionManager;
            this.shellDescriptor = shellDescriptor;
            this.virtualPathProvider = virtualPathProvider;

        public static string GetAlternateShapeNameFromFileName(string fileName)
            if (fileName == null)
                throw new ArgumentNullException("fileName");

            string shapeName;
            if (Uri.IsWellFormedUriString(fileName, UriKind.Absolute))
                var uri = new Uri(fileName);
                shapeName = uri.Authority + "$" + uri.AbsolutePath + "$" + uri.Query;
                shapeName = Path.GetFileNameWithoutExtension(fileName);

            return SafeName(shapeName);

        public void Discover(ShapeTableBuilder builder)
            var availableFeatures = this.extensionManager.AvailableFeatures();
            var activeFeatures = availableFeatures.Where(this.FeatureIsEnabled);
            var activeExtensions = Once(activeFeatures);

            var hits = activeExtensions.SelectMany(extensionDescriptor =>
                                                       var basePath = Path.Combine(extensionDescriptor.Location, extensionDescriptor.Id).Replace(Path.DirectorySeparatorChar, '/');
                                                       var virtualPath = Path.Combine(basePath, "Styles").Replace(Path.DirectorySeparatorChar, '/');
                                                       var shapes = this.virtualPathProvider.ListFiles(virtualPath)
                                                               .Where(fileName => string.Equals(Path.GetExtension(fileName), ".less", System.StringComparison.OrdinalIgnoreCase))
                                                               .Select(cssFileName => new
                                                                                                  fileName = Path.GetFileNameWithoutExtension(cssFileName),
                                                                                                  fileVirtualPath = Path.Combine(virtualPath, cssFileName).Replace(Path.DirectorySeparatorChar, '/'),
                                                                                                  shapeType = "Style__" + GetAlternateShapeNameFromFileName(cssFileName),
                                                       return shapes;

            foreach (var iter in hits)
                var hit = iter;
                var featureDescriptors = hit.extensionDescriptor.Features.Where(fd => fd.Id == hit.extensionDescriptor.Id);
                foreach (var featureDescriptor in featureDescriptors)
                            .From(new Feature { Descriptor = featureDescriptor })
                                    shapeDescriptor => displayContext =>
                                                           var shape = (dynamic)displayContext.Value;
                                                           var output = displayContext.ViewContext.Writer;
                                                           ResourceDefinition resource = shape.Resource;
                                                           string condition = shape.Condition;
                                                           Dictionary<string, string> attributes = shape.TagAttributes;
                                                           ResourceManager.WriteResource(output, resource, hit.fileVirtualPath, condition, attributes);
                                                           return null;

        private static string SafeName(string name)
            if (string.IsNullOrWhiteSpace(name))
                return string.Empty;

            return safeName.Replace(name, string.Empty).ToLowerInvariant();

        private static IEnumerable<ExtensionDescriptor> Once(IEnumerable<FeatureDescriptor> featureDescriptors)
            var once = new ConcurrentDictionary<string, object>();
            return featureDescriptors.Select(fd => fd.Extension).Where(ed => once.TryAdd(ed.Id, null)).ToList();

        private bool FeatureIsEnabled(FeatureDescriptor fd)
            return (DefaultExtensionTypes.IsTheme(fd.Extension.ExtensionType) && (fd.Id == "TheAdmin" || fd.Id == "SafeMode")) ||
                this.shellDescriptor.Features.Any(sf => sf.Name == fd.Id);

This class is the same class as Orchard's StylesheetBindingStrategy but it uses .less file extension for styles .. the same as @BitCrazed metioned.

This way you dont need to modify Orchard source files.. Just use your own theme.


Thaaaaaaaaank you guys for making Orchard so Open-Closed


BTW .... I didnt try but you can also decorate this class with [OrchardSuppressDependency("name of suppresed type")] This should replace StylesheetBindingStrategy and make it as fast as bare Orchard.