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





0/5 (0投票)
创建一个 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。
但在第四部分结束时,我觉得这是一个不错的成品。


