Asp.net mvc 4 如何使用ASP.NETMVC4在发布模式下捆绑更少的文件?

Asp.net mvc 4 如何使用ASP.NETMVC4在发布模式下捆绑更少的文件?,asp.net-mvc-4,less,bundle,Asp.net Mvc 4,Less,Bundle,我尝试在我的web项目中使用更少的文件,并将MVC4绑定功能调用到dotLess库中,将更少的文件转换为CSS,然后缩小结果并将其提供给浏览器 我在标题“LESS,CoffeeScript,SCSS,Sass捆绑”下找到了一个例子。。这给了我一个LessTransform类,看起来像这样: public class LessTransform : IBundleTransform { public void Process(BundleContext context, BundleRes

我尝试在我的web项目中使用更少的文件,并将MVC4绑定功能调用到dotLess库中,将更少的文件转换为CSS,然后缩小结果并将其提供给浏览器

我在标题“LESS,CoffeeScript,SCSS,Sass捆绑”下找到了一个例子。。这给了我一个LessTransform类,看起来像这样:

public class LessTransform : IBundleTransform
{
    public void Process(BundleContext context, BundleResponse response)
    {
        response.Content = dotless.Core.Less.Parse(response.Content);
        response.ContentType = "text/css";
    }
}
我的BundleConfig课程中的这一行:

最后,我在my_Layout.cshtml的

如果站点处于调试模式,则会呈现到浏览器:

<link href="/Content/less/test.less" rel="stylesheet"/>
将应用.less文件中的规则,并按照该链接显示less已正确转换为CSS

但是,如果我将站点置于发布模式,则会呈现以下内容:

<link href="/Content/less?v=lEs-HID6XUz3s2qkJ35Lvnwwq677wTaIiry6fuX8gz01" rel="stylesheet"/>
不应用.less文件中的规则,因为以下链接会导致来自IIS的404错误


所以看起来捆绑销售出现了一些问题。如何在发布模式下使其工作,或者如何找出到底出了什么问题?

看起来这是可行的-我更改了处理方法以迭代文件集合:

public void Process(BundleContext context, BundleResponse response)
{
    var builder = new StringBuilder();
    foreach (var fileInfo in response.Files)
    {
        using (var reader = fileInfo.OpenText())
        {
            builder.Append(dotless.Core.Less.Parse(reader.ReadToEnd()));
        }
    }

    response.Content = builder.ToString();
    response.ContentType = "text/css";
}
如果您的less文件中有任何@import语句,则会出现这种情况。不过,在这种情况下,您必须做更多的工作,例如:

编辑于2019年12月8日这不再是一个可接受的答案,因为多年来ASP.NET中出现了突破性的变化。下面还有其他答案修改了此代码或提供了其他答案来帮助您解决此问题

似乎无点引擎需要知道当前处理的捆绑文件的路径才能解析@import路径。如果运行上面的流程代码,则当正在解析的.Less文件导入了其他Less文件时,dotless.Core.Less.Parse的结果是空字符串

Ben Foster在这里的回答将通过首先读取导入的文件来解决这一问题:

按如下方式更改LessTransform文件:

public class LessTransform : IBundleTransform
{
    public void Process(BundleContext context, BundleResponse bundle)
    {
        if (context == null)
        {
            throw new ArgumentNullException("context");
        }

        if (bundle == null)
        {
            throw new ArgumentNullException("bundle");
        }

        context.HttpContext.Response.Cache.SetLastModifiedFromFileDependencies();

        var lessParser = new Parser();
        ILessEngine lessEngine = CreateLessEngine(lessParser);

        var content = new StringBuilder(bundle.Content.Length);

        var bundleFiles = new List<FileInfo>();

        foreach (var bundleFile in bundle.Files)
        {
            bundleFiles.Add(bundleFile);

            SetCurrentFilePath(lessParser, bundleFile.FullName);
            string source = File.ReadAllText(bundleFile.FullName);
            content.Append(lessEngine.TransformToCss(source, bundleFile.FullName));
            content.AppendLine();

            bundleFiles.AddRange(GetFileDependencies(lessParser));
        }

        if (BundleTable.EnableOptimizations)
        {
            // include imports in bundle files to register cache dependencies
            bundle.Files = bundleFiles.Distinct();
        }

        bundle.ContentType = "text/css";
        bundle.Content = content.ToString();
    }

    /// <summary>
    /// Creates an instance of LESS engine.
    /// </summary>
    /// <param name="lessParser">The LESS parser.</param>
    private ILessEngine CreateLessEngine(Parser lessParser)
    {
        var logger = new AspNetTraceLogger(LogLevel.Debug, new Http());
        return new LessEngine(lessParser, logger, true, false);
    }

    /// <summary>
    /// Gets the file dependencies (@imports) of the LESS file being parsed.
    /// </summary>
    /// <param name="lessParser">The LESS parser.</param>
    /// <returns>An array of file references to the dependent file references.</returns>
    private IEnumerable<FileInfo> GetFileDependencies(Parser lessParser)
    {
        IPathResolver pathResolver = GetPathResolver(lessParser);

        foreach (var importPath in lessParser.Importer.Imports)
        {
            yield return new FileInfo(pathResolver.GetFullPath(importPath));
        }

        lessParser.Importer.Imports.Clear();
    }

    /// <summary>
    /// Returns an <see cref="IPathResolver"/> instance used by the specified LESS lessParser.
    /// </summary>
    /// <param name="lessParser">The LESS parser.</param>
    private IPathResolver GetPathResolver(Parser lessParser)
    {
        var importer = lessParser.Importer as Importer;
        var fileReader = importer.FileReader as FileReader;

        return fileReader.PathResolver;
    }

    /// <summary>
    /// Informs the LESS parser about the path to the currently processed file. 
    /// This is done by using a custom <see cref="IPathResolver"/> implementation.
    /// </summary>
    /// <param name="lessParser">The LESS parser.</param>
    /// <param name="currentFilePath">The path to the currently processed file.</param>
    private void SetCurrentFilePath(Parser lessParser, string currentFilePath)
    {
        var importer = lessParser.Importer as Importer;

        if (importer == null)
            throw new InvalidOperationException("Unexpected dotless importer type.");

        var fileReader = importer.FileReader as FileReader;

        if (fileReader == null || !(fileReader.PathResolver is ImportedFilePathResolver))
        {
            fileReader = new FileReader(new ImportedFilePathResolver(currentFilePath));
            importer.FileReader = fileReader;
        }
    }
}

public class ImportedFilePathResolver : IPathResolver
{
    private string currentFileDirectory;
    private string currentFilePath;

    public ImportedFilePathResolver(string currentFilePath)
    {
        if (string.IsNullOrEmpty(currentFilePath))
        {
            throw new ArgumentNullException("currentFilePath");
        }

        CurrentFilePath = currentFilePath;
    }

    /// <summary>
    /// Gets or sets the path to the currently processed file.
    /// </summary>
    public string CurrentFilePath
    {
        get { return currentFilePath; }
        set
        {
            currentFilePath = value;
            currentFileDirectory = Path.GetDirectoryName(value);
        }
    }

    /// <summary>
    /// Returns the absolute path for the specified improted file path.
    /// </summary>
    /// <param name="filePath">The imported file path.</param>
    public string GetFullPath(string filePath)
    {
        if (filePath.StartsWith("~"))
        {
            filePath = VirtualPathUtility.ToAbsolute(filePath);
        }

        if (filePath.StartsWith("/"))
        {
            filePath = HostingEnvironment.MapPath(filePath);
        }
        else if (!Path.IsPathRooted(filePath))
        {
            filePath = Path.GetFullPath(Path.Combine(currentFileDirectory, filePath));
        }

        return filePath;
    }
}
作为对的补充,我创建了一个LessBundle类,它是StyleBundle类的等价性较低的类

LessBundle.cs代码为:

用法类似于StyleBundle类,指定较少的文件而不是CSS文件

将以下内容添加到BundleConfig.RegisterBundlesBundleCollection方法中:

使现代化 这种方法在优化关闭时可以很好地工作,但在优化打开时,我遇到了CSS资源路径的一些小问题。经过一个小时的研究,我发现我

如果您确实想要我上面描述的LessBundle功能,请查看


可以找到NuGet包。

接受的答案不适用于最近对ASP.NET的更改,因此不再正确

我已修复了公认答案中的来源:

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Web.Hosting;
using System.Web.Optimization;
using dotless.Core;
using dotless.Core.Abstractions;
using dotless.Core.Importers;
using dotless.Core.Input;
using dotless.Core.Loggers;
using dotless.Core.Parser;

namespace Web.App_Start.Bundles
{
    public class LessTransform : IBundleTransform
    {
        public void Process(BundleContext context, BundleResponse bundle)
        {
            if (context == null)
            {
                throw new ArgumentNullException("context");
            }

            if (bundle == null)
            {
                throw new ArgumentNullException("bundle");
            }

            context.HttpContext.Response.Cache.SetLastModifiedFromFileDependencies();

            var lessParser = new Parser();
            ILessEngine lessEngine = CreateLessEngine(lessParser);

            var content = new StringBuilder(bundle.Content.Length);

            var bundleFiles = new List<BundleFile>();

            foreach (var bundleFile in bundle.Files)
            {
                bundleFiles.Add(bundleFile);

                var name = context.HttpContext.Server.MapPath(bundleFile.VirtualFile.VirtualPath);
                SetCurrentFilePath(lessParser, name);
                using (var stream = bundleFile.VirtualFile.Open())
                using (var reader = new StreamReader(stream))
                {
                    string source = reader.ReadToEnd();
                    content.Append(lessEngine.TransformToCss(source, name));
                    content.AppendLine();
                }

                bundleFiles.AddRange(GetFileDependencies(lessParser));
            }

            if (BundleTable.EnableOptimizations)
            {
                // include imports in bundle files to register cache dependencies
                bundle.Files = bundleFiles.Distinct();
            }

            bundle.ContentType = "text/css";
            bundle.Content = content.ToString();
        }

        /// <summary>
        /// Creates an instance of LESS engine.
        /// </summary>
        /// <param name="lessParser">The LESS parser.</param>
        private ILessEngine CreateLessEngine(Parser lessParser)
        {
            var logger = new AspNetTraceLogger(LogLevel.Debug, new Http());
            return new LessEngine(lessParser, logger, true, false);
        }

        /// <summary>
        /// Gets the file dependencies (@imports) of the LESS file being parsed.
        /// </summary>
        /// <param name="lessParser">The LESS parser.</param>
        /// <returns>An array of file references to the dependent file references.</returns>
        private IEnumerable<BundleFile> GetFileDependencies(Parser lessParser)
        {
            IPathResolver pathResolver = GetPathResolver(lessParser);

            foreach (var importPath in lessParser.Importer.Imports)
            {
                yield return
                    new BundleFile(pathResolver.GetFullPath(importPath),
                        HostingEnvironment.VirtualPathProvider.GetFile(importPath));
            }

            lessParser.Importer.Imports.Clear();
        }

        /// <summary>
        /// Returns an <see cref="IPathResolver"/> instance used by the specified LESS lessParser.
        /// </summary>
        /// <param name="lessParser">The LESS parser.</param>
        private IPathResolver GetPathResolver(Parser lessParser)
        {
            var importer = lessParser.Importer as Importer;
            var fileReader = importer.FileReader as FileReader;

            return fileReader.PathResolver;
        }

        /// <summary>
        /// Informs the LESS parser about the path to the currently processed file. 
        /// This is done by using a custom <see cref="IPathResolver"/> implementation.
        /// </summary>
        /// <param name="lessParser">The LESS parser.</param>
        /// <param name="currentFilePath">The path to the currently processed file.</param>
        private void SetCurrentFilePath(Parser lessParser, string currentFilePath)
        {
            var importer = lessParser.Importer as Importer;

            if (importer == null)
                throw new InvalidOperationException("Unexpected dotless importer type.");

            var fileReader = importer.FileReader as FileReader;

            if (fileReader == null || !(fileReader.PathResolver is ImportedFilePathResolver))
            {
                fileReader = new FileReader(new ImportedFilePathResolver(currentFilePath));
                importer.FileReader = fileReader;
            }
        }
    }
}

请注意此代码的一个已知问题是,LESS@imports必须使用其完整路径,即您必须使用@import~/Areas/Admin/Css/global.LESS;而不是@import global.less

已经有了一些很好的答案,下面是我在尝试添加涉及较少文件的MVC捆绑包时为自己找到的一个非常简单的解决方案

创建less文件(例如test.less)后,右键单击该文件,然后在“Web编译器选项”下选择“编译文件”

这将从较小的css文件生成结果css文件,以及它的缩小版本。test.css和test.min.css

在bundle上,只需参考生成的css文件

在您的视图中,引用该捆绑包:

@Styles.Render("~/bundles/myLess-styles")

它应该可以正常工作。

您是在同一台机器上以发布模式运行,还是发布到另一个盒子?@davidlive我认为这是因为在调试模式下,没有文件的缩编或串联,每个单独的文件都作为单独的文件输出,以便更容易调试。只有在发布模式下,才会进行缩小和捆绑。我只是在使用IIS Express运行它,只是在web.config中更改了该项。@如果我在pc上的其他地方发布,然后在IIS 7中设置一个合适的网站,则行为与此相同。也许这解决了您的问题@Jasen Thank,这确实停止了403错误-但在发布模式下仍然无法工作,我只是得到了一个404作为样式表链接;“System.Collections.Generic.List”不包含“Distinct”的定义,并且找不到接受“System.Collections.Generic.List”类型的第一个参数的扩展方法“Distinct”。是否缺少using指令或程序集引用?@Fadi您需要使用System.Linq添加;到代码的顶部。Distinct是在namespace.bundle.Files不是列表中找到的扩展方法;这是一张单子。此代码对我无效。此代码无效。如何验证此答案?此代码是6年前编写的。你必须向下滚动到其他有问题的答案
由于添加了新版本的ASP.NETA以使其能够与较新版本的ASP.NETA一起工作,因此请注意以下几点:1。您必须从接受答案中的代码复制ImportedFilePathResolver。这门课就在最下面。2.@import代码在.less文件中。它对我不起作用。当我将EnableOptimizations设置为false时,一切都正常。当我将此设置为true时,更少的文件不再工作。什么是BundleCacheTransform?@FrenkyB,对不起,我没有意识到我把BundleCacheTransform放在那里了,这是我们为一个特定项目创建的东西,不应该影响结果。我只是想添加一个Web编译器,它绝对是我问题的解决方案。它就像一个符咒,非常容易使用。不需要像dotLess那样额外安装。Web编译器似乎只支持VS 2015和17-u-。。从3年前开始,我就一直告诉我的团队升级VS。我们仍然使用VS10。链接包显然不支持小于1.4.x或更高版本
using System.Web.Optimization;

namespace MyProject
{
    public class LessBundle : Bundle
    {
        public LessBundle(string virtualPath) : base(virtualPath, new IBundleTransform[] {new LessTransform(), new CssMinify()})
        {

        }

        public LessBundle(string virtualPath, string cdnPath)
            : base(virtualPath, cdnPath, new IBundleTransform[] { new LessTransform(), new CssMinify() })
        {

        }
    }
}
bundles.Add(new LessBundle("~/Content/less").Include(
                 "~/Content/MyStyles.less"));
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Web.Hosting;
using System.Web.Optimization;
using dotless.Core;
using dotless.Core.Abstractions;
using dotless.Core.Importers;
using dotless.Core.Input;
using dotless.Core.Loggers;
using dotless.Core.Parser;

namespace Web.App_Start.Bundles
{
    public class LessTransform : IBundleTransform
    {
        public void Process(BundleContext context, BundleResponse bundle)
        {
            if (context == null)
            {
                throw new ArgumentNullException("context");
            }

            if (bundle == null)
            {
                throw new ArgumentNullException("bundle");
            }

            context.HttpContext.Response.Cache.SetLastModifiedFromFileDependencies();

            var lessParser = new Parser();
            ILessEngine lessEngine = CreateLessEngine(lessParser);

            var content = new StringBuilder(bundle.Content.Length);

            var bundleFiles = new List<BundleFile>();

            foreach (var bundleFile in bundle.Files)
            {
                bundleFiles.Add(bundleFile);

                var name = context.HttpContext.Server.MapPath(bundleFile.VirtualFile.VirtualPath);
                SetCurrentFilePath(lessParser, name);
                using (var stream = bundleFile.VirtualFile.Open())
                using (var reader = new StreamReader(stream))
                {
                    string source = reader.ReadToEnd();
                    content.Append(lessEngine.TransformToCss(source, name));
                    content.AppendLine();
                }

                bundleFiles.AddRange(GetFileDependencies(lessParser));
            }

            if (BundleTable.EnableOptimizations)
            {
                // include imports in bundle files to register cache dependencies
                bundle.Files = bundleFiles.Distinct();
            }

            bundle.ContentType = "text/css";
            bundle.Content = content.ToString();
        }

        /// <summary>
        /// Creates an instance of LESS engine.
        /// </summary>
        /// <param name="lessParser">The LESS parser.</param>
        private ILessEngine CreateLessEngine(Parser lessParser)
        {
            var logger = new AspNetTraceLogger(LogLevel.Debug, new Http());
            return new LessEngine(lessParser, logger, true, false);
        }

        /// <summary>
        /// Gets the file dependencies (@imports) of the LESS file being parsed.
        /// </summary>
        /// <param name="lessParser">The LESS parser.</param>
        /// <returns>An array of file references to the dependent file references.</returns>
        private IEnumerable<BundleFile> GetFileDependencies(Parser lessParser)
        {
            IPathResolver pathResolver = GetPathResolver(lessParser);

            foreach (var importPath in lessParser.Importer.Imports)
            {
                yield return
                    new BundleFile(pathResolver.GetFullPath(importPath),
                        HostingEnvironment.VirtualPathProvider.GetFile(importPath));
            }

            lessParser.Importer.Imports.Clear();
        }

        /// <summary>
        /// Returns an <see cref="IPathResolver"/> instance used by the specified LESS lessParser.
        /// </summary>
        /// <param name="lessParser">The LESS parser.</param>
        private IPathResolver GetPathResolver(Parser lessParser)
        {
            var importer = lessParser.Importer as Importer;
            var fileReader = importer.FileReader as FileReader;

            return fileReader.PathResolver;
        }

        /// <summary>
        /// Informs the LESS parser about the path to the currently processed file. 
        /// This is done by using a custom <see cref="IPathResolver"/> implementation.
        /// </summary>
        /// <param name="lessParser">The LESS parser.</param>
        /// <param name="currentFilePath">The path to the currently processed file.</param>
        private void SetCurrentFilePath(Parser lessParser, string currentFilePath)
        {
            var importer = lessParser.Importer as Importer;

            if (importer == null)
                throw new InvalidOperationException("Unexpected dotless importer type.");

            var fileReader = importer.FileReader as FileReader;

            if (fileReader == null || !(fileReader.PathResolver is ImportedFilePathResolver))
            {
                fileReader = new FileReader(new ImportedFilePathResolver(currentFilePath));
                importer.FileReader = fileReader;
            }
        }
    }
}
style = new StyleBundle("~/bundles/myLess-styles")
    .Include("~/Content/css/test.css", new CssRewriteUrlTransform());

bundles.Add(style);
@Styles.Render("~/bundles/myLess-styles")