13

I am using dotnetless (http://www.dotlesscss.org/) for asp.net web forms applications, and it works great. I like using variables for colors, font-size etc. But so far as I can see variable values are static.

Is there any way using dotnetless to initialize these variables values from a database depending on userid?

Basically I want to convert this web application into a theme based website, so each user can select there own color, font, font-size etc.

Any direction will be greatly appreciated.

Kevin
  • 874
  • 3
  • 16
  • 34
daljit
  • 1,256
  • 1
  • 11
  • 30

5 Answers5

6

It is definitely possible, but unfortunately you can't query your database from LESS itself, so you basically need to write the LESS file for the user with the variable values needed, and then load it.

You can find an example from another answer here: https://stackoverflow.com/a/11628325/2330244

Community
  • 1
  • 1
Jesús Carrera
  • 11,275
  • 4
  • 63
  • 55
  • So it means should implement this IFileReader for my variables file. Is this filereader is like handler and will dotnetless wait for my process. Sorry I just do not understand how it will fit into dotnetless process on server side. Please will you explain me a bit. Will this solution work for each user. Thanks for give me this direction. – daljit Jun 04 '13 at 05:11
  • Basically each user will have a custom .less file with different variables. That file is written in a separate process, probably when the user is creating/editing its theme. Makes sense? – Jesús Carrera Jun 04 '13 at 09:17
  • So this will be maintenance problem. I was thinking more like that there is no way we can intercept the dotnetless handler and initialize the variables for each user, without any file etc. I really do appreciate your help. Thanks a lot. – daljit Jun 04 '13 at 12:26
  • Yeah that'd be the only way I can think of. If you don't want to use any file etc. you can always load the variables in inline CSS – Jesús Carrera Jun 04 '13 at 16:18
  • @JesúsCarrera - You can do just about anything you want with .Less, if you are willing to make a custom function/plugin. I show how to do this in my answer. – Peter Jun 07 '13 at 19:28
5

I think that if that is the path you want to take you should create a IHttpHandler to replace the dotless LessCssHttpHandler. This handler would perform basically the same actions as the LessCssHttpHandler but would insert the database variables into the less prior to compiling into css.

You can look at the Bundle Transformer LESS project which does less translation using dotless. It also has a IHttpHandler that you could base yours off of.

As others have stated this might not be the best course of action

Edit: Basic starting point for the Handler

public class LessHandler : IHttpHandler
{
    public void ProcessRequest(HttpContext context)
    {
        var request = context.Request;
        var response = context.Response;
        var user = context.User;

        string assetUrl = request.Url.LocalPath;
        string assetPath = context.Server.MapPath(assetUrl);

        //load less file into the data.
        var data = "";
        using (var file = new StreamReader(assetPath))
        {
            data = file.ReadToEnd();
        }

        DotlessConfiguration lessEngineConfig = DotlessConfiguration.GetDefault();
        lessEngineConfig.MapPathsToWeb = false;
        lessEngineConfig.CacheEnabled = false;
        lessEngineConfig.DisableUrlRewriting = false;
        lessEngineConfig.Web = false;
        lessEngineConfig.MinifyOutput = true;
        lessEngineConfig.LessSource = typeof(VirtualFileReader);
        var lessEngineFactory = new EngineFactory(lessEngineConfig);
        ILessEngine lessEngine = lessEngineFactory.GetEngine();

        string vars = "";
        //TODO set default for vars
        if (user != null)
        {
            //TODO get vars for user
        }

        var content = lessEngine.TransformToCss(string.Format("{0}{1}", vars, data), null);

        // Output text content of asset
        response.ContentType = "text/css";
        response.Write(content);
        response.End();
    }

    public bool IsReusable
    {
        get { return true; }
    }
}
Shawn C.
  • 6,446
  • 3
  • 34
  • 38
5

Why not create a custom .LESS plugin with functions to perform the needed lookup's?

The code below was tested as shown. I don't actually look up data in a DB, but all necessary information should be available to do so. I verified that when running in Windows authentication mode on the website I was able to retrieve the current user through HttpContext.Current.User.Identity.Name.

To use the function below you would type something like the below in your less file:

--lookup using custom function (hit db)
@brand_color:getCustomColor(usersThemeAttribute);

--then use the variable like normal
color: @brand_color;

Code

[DisplayName("UserProfilePlugin")]
public class UserProfilePlugin : IFunctionPlugin
{
    public Dictionary<string, Type> GetFunctions()
    {
        return new Dictionary<string, Type> 
        {
            { "getCustomColor", typeof(GetCustomColorFunction) }
        };
    }
}

public class GetCustomColorFunction : Function
{
    protected override Node Evaluate(Env env)
    {
        Guard.ExpectNumArguments(1, Arguments.Count(), this, Location);
        Guard.ExpectNode<Keyword>(Arguments[0], this, Arguments[0].Location);
        //the idea is that you would have many colors in a theme, this would be the name for a given color like 'bgColor', or 'foreColor'
        var colorAttrName = Arguments[0] as Keyword;

        //Lookup username
        // string username = HttpContext.Current.User.Identity.Name;

        //perform some kind of DB lookup using the username, get user theme info
        // UserProfile up = new UserProfile();
        // System.Drawing.Color c = up.GetColorByAttribute(colorAttrName.Value);
        //return the appropriate color using RGB/etc...
        // return new Color(c.R, c.G, c.B);
        return new Color(129, 129, 129);
    }
}

To register the plugin add this to web.config:

<dotless cache="false" >
    <plugin name="UserProfilePlugin" assembly="Your.Assebly.Name" />
</dotless>

Consider disabling caching for dotless, so that changes users make take immediate effect.

Link: https://github.com/dotless/dotless/wiki/FunctionPlugins

Peter
  • 9,643
  • 6
  • 61
  • 108
  • Hi Peter, using this method i am able to initialized one variable at a time, but is there any way I am able to initialized all the variables at once. I did try with something like new TextNode("@lightBlue:197.460233326px;"); and using in in less file like follows - myCustomColor; and using it as follows -color: @lightBlue;, but it did not work. If it works it will give me two advantages, for all the variables I have to hit database only once and for each stylesheet, i will call this method on top by just one functions and all the variables will be initialzed. Thanks – daljit Jun 08 '13 at 23:00
  • @daljit - There may be a way to do that, but I'm not sure. However, if your main concern is multiple database calls then some simple caching should do the trick. – Peter Jun 09 '13 at 02:10
  • yes, but also I can just add custom function at the top of the each stylesheet and all the variables will be initialized from there. – daljit Jun 10 '13 at 08:09
  • @daljit why you didnt marked peter's answer as answer? what solution you end up with ? – Usama Khalil Apr 05 '17 at 06:09
2

The easiest solution is probably to inline your LESS code in your web pages: (as explained here)

<html>
  <head>
    <style type="text/less">
      // DB and user-dependent code here
    </style>
  </head>
  ...
</html>

This way, you can customize your LESS stylesheets just like you would customize your webpages.

If you want to use dotless-compiled CSS, you can pass your customized stylesheets to the dotless parser:

Less.Parse(".foo { font-size: <user-dependent-value>;}")

Then, you need to include the compiled CSS output directly in your webpage:

<html>
  <head>
    <style type="text/css">
      // result of Less.Parse(...)
    </style>
  </head>
  ...
</html>
Community
  • 1
  • 1
Régis B.
  • 10,092
  • 6
  • 54
  • 90
1

It depends on how many themes you're going to have. If you have a limited number, it would be best (for both performance and complexity) to map the user to one of your pre-compiled .less themes.

But if you're allowing each user to customize the theme so that there are many (or unlimited number) of them, it would be best to construct, compile, optimize the .less (or CSS) file when the theme is changed and save the result. On each page load, you reference that theme file until the user chooses a new theme. Best case, it stays cached on the user's system for a long time.

Unless it is a small amount of customized LESS/CSS, I would avoid the inline CSS route. It will add to every page's file size/bandwidth, therefore load more slowly on mobile. Instead, I would use the recent ASP.NET Web.Optimization bundle to help you construct, compile, and optimize the LESS -> CSS upon theme change and cache the result.

So on page load, most pages would reference their existing, custom page theme saved in the database. Upon theme change, you would go through the full less -> css process.

Dan Sorensen
  • 11,403
  • 19
  • 67
  • 100
  • Hi Dan, thanks for the reply. We have the scope for unlimited number of users and also themes and we are allowing each user to customize the theme. Do you mean save the results on the actual file or it will work with database as well. Thanks – daljit Jun 07 '13 at 08:03
  • Yes, I would cache/save the compiled stylesheet. To get up and running quick, you might save that task until it is needed; using on the fly style processing for now. But eventually, I would save the cached stylesheet to a NoSQL database, or a table in your db dedicated to stylesheets. (those would be the easiest to manage), or you could save to a file. (easiest to serve, harder to manage) – Dan Sorensen Jun 07 '13 at 14:58