Search code examples
c#system.web.optimizationbundletransformer

Create ThemeBuilder for dynamic themes


I'm working on a multi-tenant app that has a Theme builder based on their defined settings like theme color etc..

I used this approach http://bundletransformer-theme-builder.azurewebsites.net/ for bootstrap 3. However since we have upgraded to Bootstrap 4 it is now in .scss.

So my approach on the implementation of Bootstrap 4 to the project is to add the following:

theme/theme-variables.scss

/*
 * Application global variables.
 */


$orcid-color : #96D72F;
$primary-color: #1c223d;

@import "../bootstrap/_functions.scss";
@import "../bootstrap/_variables.scss";

theme/theme.scss

/*
 * Global application theme.
 */
@import "theme-variables";
html,
body {
    height: 100%;
}
body {
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}
// ...

main.scss

/*
 * Entry point of global application style.
 */

// Theme variables, must be included before the libraries to allow overriding defaults
@import "theme/theme-variables";

// 3rd party libraries
@import "bootstrap/bootstrap.scss";


// Theme customization
@import "theme/theme";

Now in the BundleConfig is defined this the below

public class BundleConfig
{
    public static void RegisterBundles(BundleCollection bundles)
    {
        bundles.Add(new CustomStyleBundle("~/bundles/styles")
            .Include("~/Contents/main.scss"));
    }
}

Then create the below codes..

[Route("bundles/themes/{id}"), AllowAnonymous]
public ContentResult Themes(int id)
{
    var id = 1;
    var institutionPath = string.Format("~/bundles/themes/{0}", id);

    if (BundleTable.Bundles.All(x => x.Path != institutionPath))
    {
        var themeStyles = new CustomStyleBundle(institutionPath)
            .Include("~/Contents/main.scss", new InjectContentItemTransform($"$primary-color: red;"));

        BundleTable.Bundles.Add(themeStyles);
    }

    return null;
}


public sealed class InjectContentItemTransform : IItemTransform
{
    private readonly string _content;

    public InjectContentItemTransform(string content)
    {
        _content = content;
    }

    public string Process(string includedVirtualPath, string input)
    {
        if (!_content.HasValue())
        {
            return input;
        }

        var builder = new StringBuilder();

        builder.Append(_content);

        return builder.ToString();
    }
}

Then in the main layout...

<!doctype html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

    <link rel="shortcut icon" href="~/favicon.ico">

    @Styles.Render(string.Format("~/bundles/themes/{0}", 1))

    @Styles.Render("~/bundles/styles")

    <title>@ViewBag.Title</title>
</head>
<body class="body">
    <!-- //.... -->
</body>
</html>

What is the proper way to add a variable or override a variable to apply value like color?


Solution

  • Sass unlike LESS, does not support the modification of variable values, so by using the item transformation we can only define variables.

    First, you should remove the variable definition from the theme/theme-variables.scss file:

    /*
     * Application global variables.
     */
    
    
    $orcid-color : #96D72F;
    //$primary-color: #1c223d;
    
    @import "../bootstrap/_functions.scss";
    @import "../bootstrap/_variables.scss";
    

    Your implementation of the InjectContentItemTransform class contains an error: only variable definitions will always be included in the bundle content. Here is the correct implementation:

    public sealed class InjectContentItemTransform : IItemTransform
    {
        private readonly string _content;
    
        public InjectContentItemTransform(string content)
        {
            _content = content;
        }
    
        public string Process(string includedVirtualPath, string input)
        {
            if (!_content.HasValue())
            {
                return input;
            }
    
            var builder = new StringBuilder();
            builder.AppendLine(_content);
            builder.Append(input);
    
            return builder.ToString();
        }
    }
    

    For item transformations to be applied in debug mode, you must register the custom bundle resolver:

    …
    using BundleTransformer.Core.Resolvers;
    …
    
    public class BundleConfig
    {
        public static void RegisterBundles(BundleCollection bundles)
        {
            BundleResolver.Current = new CustomBundleResolver();
            …
        }
    }
    

    Many developers who create a multi-tenant web application have to write own implementation of the System.Web.Optimization.IBundleCache interface, System.Web.Hosting.VirtualPathProvider class and own version of the BundleTransformer.SassAndScss.HttpHandlers.SassAndScssAssetHandler class with an overridden GetCacheKey method. I recommend you familiarize with source code of the SmartStore.NET project.