5

I have a multi-tenant application and I'm trying to determine the simplest means of controlling which CSS files are bundled based on the url of any incoming request.

I'm thinking I can have some conditional logic inside RegisterBundles() that takes the Url as a string, and bundles accordingly:

public static void RegisterBundles(BundleCollection bundles, string tenant = null) {
     if (tenant == "contoso"){
           bundles.Add(new StyleBundle("~/contoso.css") 
     }
}

But I don't know how to pass the string into RegisterBundles, nor even if it's possible, or the right solution. Any help here would be awesome.

RobVious
  • 12,685
  • 25
  • 99
  • 181

3 Answers3

7

It is not possible to do it in RegisterBundles right now. Dynamically generating the bundle content per request will prevent ASP.net from caching the minified CSS (it's cached in HttpContext.Cache).

What you can do is create one bundle per tenant in RegisterBundles then select the appropriate bundle in the view.

Example code in the view:

@Styles.Render("~/Content/" + ViewBag.TenantName)

Edit:

As you said, setting the TenantName in a ViewBag is problematic since you have to do it per view. One way to solve this is to create a static function like Styles.Render() that selects the correct bundle name based from the current tenant.

public static class TenantStyles
{
    public static IHtmlString Render(params string[] paths)
    {
        var tenantName = "test"; //get tenant name from where its currently stored
        var tenantExtension = "-" + tenantName;
        return Styles.Render(paths.Select(i => i + tenantExtension).ToArray());
    }
}

Usage

@TenantStyles.Render("~/Content/css")

The bundle names will need to be in the this format {bundle}-{tenant} like ~/Content/css-test. But you can change the format ofcourse.

LostInComputer
  • 15,188
  • 4
  • 41
  • 49
  • Bundle per tenant is a better approach anyway, due to all the stuff that goes on with bundling in release mode (caching, hashing, etc.) Wildcard support in the bundle constructors makes it trivially easy to do this if you separate tenants by folder - it's literally two lines of code. – Aaronaught Nov 10 '13 at 19:42
  • This is interesting. It seems like I would have to do this once per view... is there any way to only do it once for Layout.cshtml? – RobVious Nov 10 '13 at 21:05
  • Yes, you can create something like Styles.Render() which takes into consideration the current tenant which is retrieved from somewhere. – LostInComputer Nov 18 '13 at 15:08
1

I think you are after a solution that allows you to dynamically control the BundleCollection. As far as I know this is currently not possible. The bundles are configured during app start/configured per the application domain. A future version of ASP.NET may support this feature i,e using VirtualPathProvider. Here is some discussion.

See also this SO question.

Community
  • 1
  • 1
Spock
  • 7,009
  • 1
  • 41
  • 60
0

i'm not good in english, but if you mean you need to handle which CSS file load when you run any URL in your page, i can handle css file in a controler.

  • First, create a controller name : ResourceController

    // CREATE PATH TO CSS FOLDER, I store in webconfig     <add key="PathToStyles" value="/Content/MyTheme/" />
    private static string _pathToStyles = ConfigurationManager.AppSettings["PathToStyles"];
    
    public void Script(string resourceName)
    {
        if (!String.IsNullOrEmpty(resourceName))
        {
            var pathToResource = Server.MapPath(Path.Combine(_pathToScripts, resourceName));
    
            TransmitFileWithHttpCachePolicy(pathToResource, ContentType.JavaScript.GetEnumDescription());
        }
    }
    
    public void Style(string resourceName)
    {
        if (!String.IsNullOrEmpty(resourceName))
        {
            var pathToResource = Server.MapPath(Path.Combine(_pathToStyles, resourceName));
    
            TransmitFileWithHttpCachePolicy(pathToResource, ContentType.Css.GetEnumDescription());
        }
    }
    
    private void TransmitFileWithHttpCachePolicy(string pathToResource, string contentType)
    {
        //DO WHAT YOU WANT HERE;
        Response.ContentType = contentType;
        Response.TransmitFile(pathToResource);
    }
    
    //You can handle css or js file...
    private enum ContentType
    {
        [EnumDescription("text/css")]
        Css,
        [EnumDescription("text/javascript")]
        JavaScript
    }
    
  • In file Global.asax.cs, make sure in application start medthod, in contain the route config

    protected void Application_Start()
    {
         RouteConfig.RegisterRoutes(RouteTable.Routes);
    
    }
    
  • Go to routeConfig, add below map to this file (must be add in top of this file) :

      routes.MapRoute(
            name: "Resource",
            url: "resource/{action}/{resourceName}",
            defaults: new { controller = "Resource" }
        );
    
  • Now, create a UrlHelperExtensions class, same path with webconfig file

    public static class UrlHelperExtensions
    {
        public static string Style(this UrlHelper urlHelper, string resourceName)
        {
        return urlHelper.Content(String.Format("~/resource/style/{0}", resourceName));
        }
     }
    
  • And from now, you can define css file in your view like :

..."<"link href="@Url.Style("yourcss.css")" rel="stylesheet" type="text/css" />

  • Hope this help
Quan Truong
  • 154
  • 7