BundleTransformer.Less inject variables depending on context/request
Asked Answered
S

1

2

We would like the use the bundling mechanism of System.Web.Optimization in combination with the Less transformer.

The problem is that the same application/server serves pages for different branded websites. So depending on the 'SiteContext' the same .less files are used but different values should be used by the .less variables. So we want the (re)use the same less files but with different variables depending on the context of the request.

I tried a couple of different theories:

In all 3 cases I setup different bundles depending on the SiteContext.

1 inject an @import directive with the themed variables by using a custom VirtualPathProvider that intercepts the variables.less file.

So I have:

  • the styling file eg: header.less (imports the variables file)
  • the variables file: variables.less
  • a themed variables file: variables-theme.less (injected in variables.less via the VirtualPathProvider)

This is not working because the BundleTransformer cache sees this as the same file and doesn't know about the SiteContext. The cache key is based on the Url of the IAsset and we cannot influence this behavior.

2 Replace the variables.less import by variables-themed.less with an custom transformer that runs before the Less transformer.

Again no luck, same caching issues.

And as a side effect, the extra transformer was not called in debug because the assets are not bundled but called individually by the LessAssetHandler. This could be solved by writing your own AssetHandler that calls all required transformers.

3 create themed Asset names that are resolved by a custom VirtualPathProvider Eg. Add header-themeX.less to the bundle, this file doesn't exist but you resolve this file to header.less and use method 2 to set the correct variables file import. (replace the import of the variables.less to the themed version).

Once again no luck. I think this could solve the caching issue if it wasn't for the Bundle.Include(string virtualPath) that does a File.Exists(path) internally. It doesn't pass via the CustomVirtualPathProvider.


Am I looking to deep to solve this? All ideas are welcome, I can imagine that this will become a problem to more and more people as the System.Web.Optimization library gets more popular...

Keep in mind that:

  • we have a lot of .less/css files
  • we will have 5 or so themes
  • we like to keep things working in visual studio (that is why header.less has a ref. to variables.less)

Thanks for any feedback.

Streetlight answered 5/6, 2013 at 17:10 Comment(0)
B
2

Michael!

You use the Microsoft ASP.NET Web Optimization Framework and the Bundle Transformer in multi-tenant environment, so you need to replace some components of the System.Web.Optimization and create own versions of the debugging HTTP-handlers (see «Problem: LESS file imports are added to BundleResponse.Files collection» discussion). As far as I know, Murat Cakir solve all these problems in the SmartStore.NET project.

In the Bundle Transformer there are 2 ways to inject of LESS-variables:

  1. Look a properties GlobalVariables and ModifyVariables of LESS-translator:

    using System.Collections.Generic;
    using System.Web.Optimization;
    
    using BundleTransformer.Core.Builders;
    using BundleTransformer.Core.Orderers;
    using BundleTransformer.Core.Transformers;
    using BundleTransformer.Core.Translators;
    using BundleTransformer.Less.Translators;
    
    public class BundleConfig
    {
        public static void RegisterBundles(BundleCollection bundles)
        {
            var nullBuilder = new NullBuilder();
            var nullOrderer = new NullOrderer();
    
            var lessTranslator = new LessTranslator
            {
                GlobalVariables = "my-variable='Hurrah!'",
                ModifyVariables = "font-family-base='Comic Sans MS';body-bg=lime;font-size-h1=50px"
            };
            var cssTransformer = new CssTransformer(new List<ITranslator>{ lessTranslator });
    
            var commonStylesBundle = new Bundle("~/Bundles/BootstrapStyles");
            commonStylesBundle.Include(
               "~/Content/less/bootstrap-3.1.1/bootstrap.less");
            commonStylesBundle.Builder = nullBuilder;
            commonStylesBundle.Transforms.Add(cssTransformer);
            commonStylesBundle.Orderer = nullOrderer;
    
            bundles.Add(commonStylesBundle);
        }
    }
    
  2. Create a custom item transformation:

    using System.Text;
    using System.Web.Optimization;
    
    public sealed class InjectContentItemTransform : IItemTransform
    {
        private readonly string _beforeContent;
        private readonly string _afterContent;
    
        public InjectContentItemTransform(string beforeContent, string afterContent)
        {
            _beforeContent = beforeContent ?? string.Empty;
            _afterContent = afterContent ?? string.Empty;
        }
    
        public string Process(string includedVirtualPath, string input)
        {
            if (_beforeContent.Length == 0 && _afterContent.Length == 0)
            {
                return input;
            }
    
            var contentBuilder = new StringBuilder();
            if (_beforeContent.Length > 0)
            {
                contentBuilder.AppendLine(_beforeContent);
            }
            contentBuilder.AppendLine(input);
            if (_afterContent.Length > 0)
            {
                contentBuilder.AppendLine(_afterContent);
            }
    
            return contentBuilder.ToString();
        }
    }
    

And register this transformation as follows:

    using System.Web.Optimization;

    using BundleTransformer.Core.Orderers;
    using BundleTransformer.Core.Bundles;

    public class BundleConfig
    {
        public static void RegisterBundles(BundleCollection bundles)
        {
            var nullOrderer = new NullOrderer();

            const string beforeLessCodeToInject = @"@my-variable: 'Hurrah!';";
            const string afterLessCodeToInject = @"@font-family-base: 'Comic Sans MS';
@body-bg: lime;
@font-size-h1: 50px;";

            var commonStylesBundle = new CustomStyleBundle("~/Bundles/BootstrapStyles");
            commonStylesBundle.Include(
               "~/Content/less/bootstrap-3.1.1/bootstrap.less",
               new InjectContentItemTransform(beforeLessCodeToInject, afterLessCodeToInject));
            commonStylesBundle.Orderer = nullOrderer;

            bundles.Add(commonStylesBundle);
        }
    }

Both ways have disadvantage: the injection of LESS-variables does not work in debug mode.

Barter answered 29/4, 2014 at 11:42 Comment(7)
Is there anyway to overcome the issue and have the content injected even in debug mode? Maybe overriding LessAssetHandler httphandler?Deposition
Hernan! I am working on a solution to this problem. Waiting for release the Bundle Transformer 1.9.0.Barter
Hernan! In Bundle Transformer 1.9.0 Beta 1 implemented this functionality - bundletransformer.codeplex.com/releases/view/123398. In order to the debugging HTTP-handlers can use a transformations from bundles need to add in the RegisterBundles method of App_Start/BundleConfig.cs file the following code: BundleResolver.Current = new CustomBundleResolver();Barter
Thanks for your answer! I still have a problem. Once the file is injected with the less variables, is then cached, and if the variables change (for instance at the database), although the new variables are injected, I get the cached (old) version of the file. I tried overriding the cache key with a hash of the variables, but doesn't work. Where do I have to override the GetCacheKey so the new version of the file gets sent to the browser? Is this correct approach?Deposition
Try to disable server-side cache of the debugging HTTP-handler. In the disableServerCache attribute of \configuration\bundleTransformer\core\assetHandler configuration element set a value equal to true.Barter
When working with the database it is better to write your own VirtualPathProvider, how did developers of the SmartStore.NET project. Transformations in this situation will not help.Barter
disabling the ServerCache worked like a charm! Transformations are working fine. I tried with a VirtualPathProvider, but didn't manage to accomplish it. I used the solution you provided on this answer. I'll give it a second look.Deposition

© 2022 - 2024 — McMap. All rights reserved.