Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
982 views
in Technique[技术] by (71.8m points)

asp.net mvc - Returning generated CSS from an MVC5 or Web API 2 controller action

In our multi-tenant application we have a need to customize the styles used per-tenant.

We currently plan to do so using LESS and variables in the following way on the client:

  1. Download dependent LESS files from server
  2. Call web service to get configuration object
  3. Form string of valid LESS with variables defined
  4. Use less.js compiler to compile LESS based on these variables and the fixed LESS files from step 1

This approach has a number of downsides:

  • Clients can behave badly
  • Some browsers have problems with less.js
  • Compilation takes time

We would instead like to take care of this work on the server, so that roughly speaking, this happens on the server instead:

  1. Client requests to download one big compiled stylesheet - GET content/styles/{tenantName}.css
  2. Using tenantName the server fetches configuration
  3. Using a template and the appropriate variables (maybe string.Format or something more sophisticated)
  4. Server compiles LESS to CSS string
  5. Server returns CSS string with appropriate Content-Type

Here are my questions:

  1. Is this an unusual or undesirable way to achieve said result?
  2. Short of setting up architecture for server-side JavaScript, how can I compile the LESS into CSS?
  3. What must I do in the controller action or in the route configuration to make the client think that the server is returning a regular old CSS file, complete with cache control, not modified?
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

You could use BundleTransformer to compile your LESS server side.

It can depend on how you want to serve the file. If you know all the tenants then just add add a bundle url for each tenant application to the bundle config.

 var themeStyles = new CustomStyleBundle("~bundles/theme/tenant").Include("~/Content/theme.less");
 themeStyles.Builder = new ThemeBuilder();
 BundleTable.Bundles.Add(themeStyles);

If you don't and the tenants are flexible as was the case in our situation then add the following controller action for your themes.

    [Route("bundles/theme/{id}")]
    public ContentResult Theme(string id)
    {
        var tenantThemePath = string.Format("~/bundles/theme/{0}", id);

        // Check that bundle has not already been added.
        if (BundleTable.Bundles.All(x => x.Path != tenantThemePath))
        {
            var themeStyles = new CustomStyleBundle(tenantThemePath ).Include("~/Content/theme.less");

            themeStyles.Builder = new ThemeBuilder();

            BundleTable.Bundles.Add(themeStyles);
        }

        var context = new BundleContext(HttpContext, BundleTable.Bundles, institutionPath);

        var response = BundleTable.Bundles.GetBundleFor(tenantThemePath).GenerateBundleResponse(context);

        Response.Cache.SetCacheability(response.Cacheability);

        return Content(response.Content, response.ContentType);
    }

The ThemeBuilder implementation for BundleTransformer

public class ThemeBuilder : IBundleBuilder
{
    public string BuildBundleContent(Bundle bundle, BundleContext context, IEnumerable<BundleFile> files)
    {
        var lessTranslator = bundle.Transforms.OfType<StyleTransformer>()
            .Where(x => x != null)
            .Select(x => x.Translators.OfType<LessTranslator>().FirstOrDefault())
            .FirstOrDefault();

        if (lessTranslator == null)
        {
            return string.Empty;
        }

        lessTranslator.GlobalVariables = GetThemeVariables();

        return string.Empty;
    }

    private string GetThemeVariables()
    {
        // Simplified for brevity
        // This will be translated to less variables by the BundleTransformer
        // themeColour should correspond to a variable name in your less file.  
        return string.Format("themeColour={0}", themeColour);
    }

}

You will need away of getting the theme colours out we stashed those variables in HttpContext stores so that we could pull them out using an extension method in the GetThemeVariables method.

I hope this helps.

UPDATE I've expanded on my original answer and created a more reusable way of including themes.

Demo site here: http://bundletransformer-theme-builder.azurewebsites.net/

GitHub repo here: https://github.com/benembery/bundle-transformer-theme-builder


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...