8

I have found this link:

http://giddyrobot.com/preserving-important-comments-in-mvc-4-bundles/

It shows how to do this same thing for JavaScript and I have used it to make an attempt for StyleBundles, but I'm unsure if it's doing things correctly on the backend.

Is the source code available? If not does anyone know if this seems right? All I want to keep is comments that start with /*! so that licenses for open source projects like normalize get included properly in production.

Here is what I have so far:

public static void RegisterBundles(BundleCollection bundles)
{
    // Allows us to keep /*! comments for licensing purposes
    var cssBundleSettings = new CssSettings
    {
        CommentMode = CssComment.Important
    };
}

public class ConfigurableStyleBundle : Bundle
{
    public ConfigurableStyleBundle(string virtualPath, CssSettings cssSettings) :
        this(virtualPath, cssSettings, null) { }

    public ConfigurableStyleBundle(string virtualPath, CssSettings cssSettings, string cdnPath) :
        base(virtualPath, cdnPath, new[] { new ConfigurableCSSTransform(cssSettings) })
    {
        // commented out from js concatenation token not sure if this one should have one
        //base.ConcatenationToken = ";";
    }
}

[ExcludeFromCodeCoverage]
public class ConfigurableCSSTransform : IBundleTransform
{

    private readonly CssSettings _cssSettings;

    public ConfigurableCSSTransform(CssSettings cssSettings)
    {
        _cssSettings = cssSettings;
    }

    public void Process(BundleContext context, BundleResponse response)
    {
        if (context == null)
        {
            throw new ArgumentNullException("context");
        }
        if (response == null)
        {
            throw new ArgumentNullException("response");
        }
        if (!context.EnableInstrumentation)
        {
            var minifier = new Minifier();
            var content = minifier.MinifyStyleSheet(response.Content, _cssSettings);
            if (minifier.ErrorList.Count > 0)
            {
                GenerateErrorResponse(response, minifier.ErrorList);
            }
            else
            {
                response.Content = content;
            }
        }
        response.ContentType = "text/css";
    }

    internal static void GenerateErrorResponse(BundleResponse bundle, IEnumerable<object> errors)
    {
        var content = new StringBuilder();
        content.Append("/* ");
        content.Append("CSS MinifyError").Append("\r\n");
        foreach (object current in errors)
        {
            content.Append(current.ToString()).Append("\r\n");
        }
        content.Append(" */\r\n");
        content.Append(bundle.Content);
        bundle.Content = content.ToString();
    }
}

All of this is wrapped in public class BundleConfig and gets called from Global.asax.

I'm just wondering if CssComment.Important could have negative effects and remove too much and if this seems to be doing what I want it to? When I have tested it everything seems to look correct styling wise, but it doesn't hurt to get some eyes seeing as this is probably useful for a lot of other ASP.NET devs who use open source libraries.

edhedges
  • 2,722
  • 2
  • 28
  • 61

1 Answers1

7

I don't think you've done anything incorrectly. Though I would approach it using the IBundleBuilder interface, as this will also keep regular comments out of production from prying eyes who switch user agent, like specified in How to prevent User-Agent: Eureka/1 to return source code. I show some steps on how to test against this in this related blog post.

public class ConfigurableStyleBuilder : IBundleBuilder
{
    public virtual string BuildBundleContent(Bundle bundle, BundleContext context, IEnumerable<BundleFile> files)
    {
        var content = new StringBuilder();

        foreach (var file in files)
        {
            FileInfo f = new FileInfo(HttpContext.Current.Server.MapPath(file.VirtualFile.VirtualPath));
            CssSettings settings = new CssSettings();
            settings.CommentMode = Microsoft.Ajax.Utilities.CssComment.Important;
            var minifier = new Microsoft.Ajax.Utilities.Minifier();
            string readFile = Read(f);
            string res = minifier.MinifyStyleSheet(readFile, settings);
            if (minifier.ErrorList.Count > 0)
            {
                res = PrependErrors(readFile, minifier.ErrorList);
                content.Insert(0, res);
            }
            else
            {
                content.Append(res);
            }

        }

        return content.ToString();
    }

    private string PrependErrors(string file, ICollection<ContextError> errors )
    {
        var content = new StringBuilder();
        content.Append("/* ");
        content.Append("CSS MinifyError").Append("\r\n");
        foreach (object current in errors)
        {
            content.Append(current.ToString()).Append("\r\n");
        }
        content.Append("Minify Error */\r\n");
        content.Append(file);
        return content.ToString();
    }

    private string Read(FileInfo file)
    {
        using (var r = file.OpenText())
        {
            return r.ReadToEnd();
        }
    }
}

public class BundleConfig
{
    public static void RegisterBundles(BundleCollection bundles)
    {          
        var cssBundle = new ConfigurableStyleBundle("~/Content/css");
        cssBundle.Include("~/Content/stylesheet1.css");
        cssBundle.Include("~/Content/stylesheet2.css");
        bundles.Add(cssBundle);

        //etc      
    }
}

I made a NuGet package for this (including a version for scripts) - https://www.nuget.org/packages/LicensedBundler/

Community
  • 1
  • 1
MikeSmithDev
  • 15,731
  • 4
  • 58
  • 89
  • Thank you for the answer! Just to make sure I'm understanding what you are saying is I'm doing everything correctly, but there is an exploit and your answer just adds the extra level of security to combat that exploit? – edhedges Mar 13 '14 at 16:04
  • Just updated answer a bit as it wasn't accounting for minification errors. This will also cause a bit difference in output as your version won't minify anything on error, which is the standard outcome. The one above will minify all files and if there are any errors it will stick the error notification and the offending file at the top, but leaving the other files minified. – MikeSmithDev Mar 13 '14 at 16:19
  • 1
    But yes, otherwise in testing, the end result normally looked the same. Difference in user-agent result & errors result. – MikeSmithDev Mar 13 '14 at 16:20
  • Thank you very much. I appreciate it! It seems that this isn't a popular subject and there are probably many MVC bundlers that aren't showing licenses. Not good! – edhedges Mar 13 '14 at 16:21
  • 2
    @edhedges you are right. I added this code up as a nuget package. Let me know if that helps! – MikeSmithDev Mar 13 '14 at 17:36
  • Great! I'll probably just modify my own code, but this is a great step towards helping others out. – edhedges Mar 13 '14 at 17:45
  • @edhedges yeah... first package I've put on nuget so hope I didn't hose it up lol. – MikeSmithDev Mar 13 '14 at 17:46