I have an ASP.NET app which needs to have multiple layouts for the views. There's a normal web layout, and a "static" layout for a self-contained page with no dependencies (to be used as a display template in a doc management system).

It's easy enough to have a view switch its layout.

So the all-in-one-page layout needs to include all of its CSS in a <style> tag inside the page. I found a way to find out what files are in a bundle and wrote a method like so:

@MyCode.IncludeCSS("~/StaticLayout/css")

It reads the CSS files in the bundle and emits their content into the page inside a <style> tag.

But I'd like to minify the CSS.

I haven't been able to find any documentation for System.Web.Optimization.CssMinify. You need to call the Process method, which needs a BundleContext. So... find one, create one? MSDN isn't much use:

public BundleContext(
    HttpContextBase context,
    BundleCollection collection,
    string bundleVirtualPath
)

Parameters
    context
        Type: System.Web.HttpContextBase
        The context.

Well, that's definitely useful. I had no way of knowing that context (of type context) was the context until some bulging-brained colossus of the intellect in Redmond took some time out from his busy nap to push a definite article in front of it with his fat little paws before passing out in a puddle of warm drool.

Nevertheless, I still don't know what it is or where you get one from, and neither does Google. HttpContext.Current is not derived from it and has no properties (that I can find) which are.

This page has some good information about normal bundling, but it assumes that the framework will be doing the bundling and serving the bundle content in the conventional way, as a separately requested resource.

Has anybody done this?

有帮助吗?

解决方案

Ed Plunkett,

If you are using this as a HTML helper the HttpContextBase can be grabbed from the ViewContext.HttpContext property. This can be created such as.

public static IHtmlString BundleCss(this HtmlHelper html, string outputPath, params string[] cssFilePaths)
{
    var collection = new BundleCollection();
    foreach (var cssFilePath in cssFilePaths)
        collection.Add(new Bundle(cssFilePath));

    var bundler = new BundleContext(html.ViewContext.HttpContext, collection, outputPath);
    //... ommitted code
    System.Web.Optimization.CssMinify minify = new CssMinify();
    minify.Process(bundler, response);
    //... TODO: Grab the response content and return
}

Yes this is a basic example using a HTML helper. Let me know if this does't answer your question.

EDIT: After re-reading the question I am clarifying my response. So the above helps find the HttpContextBase property from the question. However I think the question is how you can actually read the content of the files, minify them and out put them into a <style> tag on the page.

I took a stab at writing my own interpretation of your requirement and came up with the following set of classes

My CssOutputBundler class:

public class CssOutputBundler
{
    static readonly Dictionary<string, string> cachedOutput = new Dictionary<string, string>();
    readonly string tempFileOutputPath;
    readonly HtmlHelper helper;
    readonly IList<string> virtualFilePaths;

    public CssOutputBundler(HtmlHelper helper, string tempFileOutputPath)
    {
        if (helper == null)
            throw new ArgumentNullException("helper null");
        if (string.IsNullOrWhiteSpace(tempFileOutputPath))
            this.tempFileOutputPath = tempFileOutputPath;
        this.helper = helper;
        this.virtualFilePaths = new List<string>();
        this.tempFileOutputPath = tempFileOutputPath;
    }

    public CssOutputBundler Add(string cssFilePath)
    {
        if (!this.virtualFilePaths.Contains(cssFilePath))
            this.virtualFilePaths.Add(cssFilePath);
        return this;
    }

    public IHtmlString Minify()
    {
        if (helper == null)
            throw new ArgumentNullException("helper null");

        string cache_string = string.Join(",", this.virtualFilePaths);
        if(cachedOutput.ContainsKey(cache_string))
            return formatResponse(File.ReadAllText(cachedOutput[cache_string]));
        var bundle = new StyleBundle(this.tempFileOutputPath).Include(this.virtualFilePaths.ToArray());
        var collection = new BundleCollection();
        collection.Add(bundle);
        var context = new BundleContext(helper.ViewContext.HttpContext, collection, "");
        var response = bundle.GenerateBundleResponse(context);
        System.Web.Optimization.CssMinify minify = new CssMinify();
        minify.Process(context, response);
        string serverPath = helper.ViewContext.HttpContext.Server.MapPath(this.tempFileOutputPath);
        string outputPath = Guid.NewGuid().ToString() + ".css";
        while(File.Exists(Path.Combine(serverPath, outputPath)))
            outputPath = Guid.NewGuid().ToString() + ".css";

        File.WriteAllText(outputPath, response.Content);
        cachedOutput[cache_string] = outputPath;
        return formatResponse(response.Content);
    }

    IHtmlString formatResponse(string responseContent)
    {
        StringBuilder responseBuilder = new StringBuilder();
        responseBuilder.AppendLine("<style type=\"text/css\">");
        responseBuilder.Append(responseContent);
        responseBuilder.AppendLine("</style>");
        return helper.Raw(responseBuilder.ToString());
    }
}

Ok the above class looks complex but all it does is creates an internal list of paths for the bundler to to bundle and minify. When the Minify method is called it formats the virtual paths into a string which is used as a cache key. This is how we determine if we have already bundled and minified these scripts.

If we have we read from the temporary bundled script that is stored on the HDD.

Note: I used hard drive storage as opposed to in memory storage. This can be easily changed by stuffing the full content into a static dictionary or other cacheing medium.

If this file set has not been bundled, minified etc then we proceed to bundle and minify the set. Finally we store the file onto the HDD and return the response. So this is pretty easy.

Now to implement the class I just wrote a quick HTML Helper as the Minification class supports chaining for Add files methods can be implemented nicely.

The HTML Helper:

public static CssOutputBundler BundleCss(this HtmlHelper helper, string outputVirtualPath)
{
    return new CssOutputBundler(helper, outputVirtualPath);
}

Ok basic implementation for the bundler. Now this helper takes the string outputVirtualPath this is the virtual PATH to store the temporary files. If you do decide to eliminate saving to HDD and use a cache system you could remove this parameter from the helper and the class.

Finally this can be used in your view as...

@Html.BundleCss("~/Content/").Add("~/Content/Site.css").Add("~/Content/themes/base/jquery-ui.css").Minify();

Where the ~/Content/ is the virtual path on the HDD to store the minified files. Then we add two files "~/Content/Site.css" and ~/Content/themes/base/jquery-ui.css. Finally call the minify method which returns the html string. Such as...

<style type="text/css">
.ui-helper-hidden{display:none}.ui-helper-hidden-accessible{position:absolute!important;clip:rect(1px 1px 1px 1px);clip:rect(1px,1px,1px,1px)} ....more css
</style>

Cheers.

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top