65.9K
CodeProject 正在变化。 阅读更多。
Home

BootBrander:一个 Bootstrap .less 生成器 UI (第 4 部分 / 生成 custom-bootstrap.css)

2015年3月3日

CPOL

4分钟阅读

viewsIcon

16841

downloadIcon

162

创建一个 MVC 站点,允许用户输入来更改 bootstrap 变量并生成自定义品牌的 bootstrap.css。

引言

我们来到了 Bootstrap 颜色选择器 UI 系列的第四部分。在本系列中,我们将为用户提供一种下载他们创建的 CSS 文件的方式。

我们将利用一些本身也很有用的技术。首先,我们将使用 Dotless 库来转换我们的 less 文件。

如果您已经关注了之前的系列,您需要适应我们现在切换到 .NET C# 的事实。

文章索引

DotlessClientOnly nuget

我已经安装了 `DotlessClientOnly` nuget 包。这很重要!不要安装完整包,因为它会在每次请求时转换您的 `.less` 文件,从而阻止我们的 UI 解析它。

一个 FileContentResult 操作

在 Home 控制器中,我们将添加一个名为 `GetCss` 的操作,并将其类型设置为 `FileContentResult`。Home 控制器位于“Controllers/HomeController.cs”路径下。

        public FileContentResult GetCss()
        {
            var contentType = "text/css";

            var content = "body { background-color : #fff; }";
            var bytes = Encoding.UTF8.GetBytes(content);

            var result = new FileContentResult(bytes, contentType);

            return result;
        }

如果我们运行站点并访问 `Home/GetCss` 路径,我们应该会看到以下输出:

body { background-color : #fff; }

解析我们的 bootstrap.less 文件

首先,我们将忽略用户通过 UI 创建的变量。如果它能按原样解析默认的 `bootstrap.less` 文件,我们就会很高兴。

所以,让我们利用 `Dotless` 类。

        public FileContentResult GetCss()
        {
            //Set the content type
            var contentType = "text/css";
            //Get the bootstrap.less contents
            var bootstrapLessContent = System.IO.File.ReadAllText(
                    HostingEnvironment.MapPath("~/Content/bootstrap/bootstrap.less")
                );
            
            //Create a Dotless Configuration
            var config = new DotlessConfiguration();
 
            config.MinifyOutput = false;
            config.ImportAllFilesAsLess = true;
            config.CacheEnabled = false;

            //Parse the .less file
            var content = Less.Parse(
                bootstrapLessContent
                , config);

            var bytes = Encoding.UTF8.GetBytes(content);
            var result = new FileContentResult(bytes, contentType);

            return result;
        }

测试

如果您正确解决了所有引用,这应该可以编译。但如果我们运行,它会抛出一个异常。

这发生的原因是 `bootstrap.less` 文件中的其他文件的导入设置时没有路径。因此 `Dotless` 类不知道如何找到它们。

IFileReader

幸运的是,dotless 库为我们提供了一个解决方案。我们可以决定它实际如何找到文件。这可以通过实现 `IFileReader` 接口来完成。这很好,因为我们以后可以利用它来替换我们自己的变量。

但首先,我们将尝试只输出标准的 `.css`。

我们需要创建一个类。我在子文件夹 classes 中完成了这项工作,并将其命名为 `VirtualFileReader`。该类应该如下所示:

   internal sealed class VirtualFileReader: IFileReader
    {
        public byte[] GetBinaryFileContents(string fileName)
        {
            fileName = GetFullPath(fileName);
            return File.ReadAllBytes(fileName);
        }

        public string GetFileContents(string fileName)
        {
            fileName = GetFullPath(fileName);
            return File.ReadAllText(fileName);
        }

        public bool DoesFileExist(string fileName)
        {
            fileName = GetFullPath(fileName);
            return File.Exists(fileName);
        }

        private static string GetFullPath(string path)
        {
            return HostingEnvironment.MapPath("~/Content/bootstrap/" + path);
        }

        public bool UseCacheDependencies
        {
            get { return true; }
        }
    }

接下来,我们需要 `DotlessConfiguration` 来使用这个类。所以在我们的 `GetCss` 操作中,将此行添加到 `config` 中。

config.LessSource = typeof(VirtualFileReader);

测试

运行站点并访问路径

/home/getcss

我们现在应该会得到一个编译后的 CSS 文件。

/*! normalize.css v3.0.2 | MIT License | git.io/normalize */

html {
  font-family: sans-serif;
  -ms-text-size-adjust: 100%;
  -webkit-text-size-adjust: 100%;
}
body {
  margin: 0;
}
article,
aside,
details,
figcaption,
figure,
footer,
header,
hgroup,
main,
menu,
nav,
...... and so on...

保存我们的变量

在本节中,我们将回到 JavaScript,将我们的变量发送到服务器。但首先,我们将在 Home 控制器中再创建一个操作,将它们存储在 `Session` 变量中。

        [HttpPost]
        public EmptyResult SendVariables()
        {
            Stream req = Request.InputStream;
            req.Seek(0, System.IO.SeekOrigin.Begin);
            string variables = new StreamReader(req).ReadToEnd();
            
            HttpContext.Session["variables.less"] = variables;
            
            return new EmptyResult();
        }

现在,我们需要回到 JavaScript。基本上,我们将创建一个函数来重写 `viewModel` 中的 `variables.less` 文件。

所以,在我们的 `main.js` 中,我们将创建一个 `sendVariables` 函数,它将遍历类别、它们的变量并重写 `variables.less` 文件。

但首先,我们将添加一些其他东西。在 `_Layout.cshtml` 文件中,在主菜单栏中添加一个 ActionLink。如下所示:

<div class="navbar-collapse collapse">
    <ul class="nav navbar-nav">
        <li>@Html.ActionLink("Home", "Index", "Home")</li>
        <li>@Html.ActionLink("Elements", "Elements", "Home")</li>
        <li>@Html.ActionLink("Contact", "Contact", "Home")</li>
        <li><a href="#" 
        data-bind="click: sendAndReturnVariablesFile">Get the css</a></li>
    </ul>
    @Html.Partial("_LoginPartial")
</div>

我们希望用户将此文件作为下载。这不可能通过 Ajax 实现,并且将他们发送到一个空白页也是不可接受的。所以我们将设置一个隐藏的 iframe。

<iframe id="file-download" class="hidden"></iframe>

这可以放在页面的任何位置。但在 `body` 结束标签之前的最底部似乎是最好的位置。

我们刚刚创建了 `sendAndReturnVariables` 绑定,所以我们需要将其添加到我们的 `viewModel` 中。

        /**
         * Recreates variables.less and sends it to the server
         * @function sendVariablesFile
         */
        sendAndReturnVariablesFile: function () {
            var model = ko.toJS(viewModel),
                lessFileString = "",
                i, j, prop;

            //Recreate the variables.less content
            for (prop in model.categories) {
                var category = model.categories[prop];
                lessFileString += "//== " + prop + '\n';

                for (i = 0, j = category.variables.length; i < j; i++) {
                    var varName = category.variables[i];
                    lessFileString += varName + ":" + model.variables[varName].value + ";\n";
                }
            }
            //Send it to the server
            $.ajax({
                url: "/Home/SendVariables",
                type: "POST",
                data: lessFileString,
                success: function () {
                    //Set the iframe src to the css
                    $("#file-download").attr("src", "/Home/GetCss");
                }
            });

现在,我们需要在 `homeController` 的 `GetCss` 操作中添加一行。

            var bytes = Encoding.UTF8.GetBytes(content);
            var result = new FileContentResult(bytes, contentType);

            result.FileDownloadName = "bootstrap-custom.css";

            return result;

替换我们的变量

所以现在,我们需要执行最后一步。还记得 `VirtualFileReader` 吗?它有一个 `GetFileContents` 函数,我们将从 `Session` 返回我们保存的变量。

        public string GetFileContents(string fileName)
        {
            if (fileName == "variables.less")
            {
                var variables = HttpContext.Current.Session["variables.less"];

                return (string)variables;
            }
            else
            {
                fileName = GetFullPath(fileName);
                return  File.ReadAllText(fileName);
            }
        }

结束 (暂时)

我写文章已经有一段时间了,但我后悔了。我非常喜欢创建这个系列,并学到了足够多的知识用于我的实际产品。我希望那里有人能欣赏我的工作。

我可能会写更多关于这个例子的文章。我可以想到一些我们可以做的其他事情。用户可以创建一个账户,让他们可以将多个版本存储在数据库中。

然后我们可以将整个东西存储在 Azure 中。或者可能将 `.css` 文件发布到 Azure CDN。

但在第四部分结束时,我觉得这是一个不错的成品。

© . All rights reserved.