MVC4 Less Bundle @import Каталог

Я пытаюсь использовать связывание MVC4, чтобы сгруппировать некоторые из моих файлов с меньшим количеством файлов, но похоже, что путь импорта, который я использую, отключен. Моя структура каталогов:

static/
    less/
        mixins.less
        admin/
            user.less

В user.less я пытаюсь импортировать mixins.less, используя это:

@import "../mixins.less";

Это работало для меня раньше, когда я использовал chirpy с dotless, но теперь я заметил, что ELMAH злится на меня, говоря следующее:

System.IO.FileNotFoundException: 
    You are importing a file ending in .less that cannot be found.
File name: '../mixins.less'

Должен ли я использовать другой @import с MVC4?

Некоторая дополнительная информация

Вот код меньшего класса и global.asax.cs, который я использую, чтобы попытаться это сделать:

LessMinify.cs

...
public class LessMinify : CssMinify
{
    public LessMinify() {}

    public override void Process(BundleContext context, BundleResponse response)
    {
        response.Content = Less.Parse(response.Content);
        base.Process(context, response);
    }
}
...

Global.asax.cs

...
DynamicFolderBundle lessFB = 
    new DynamicFolderBundle("less", new LessMinify(), "*.less");
    
BundleTable.Bundles.Add(lessFB);

Bundle AdminLess = new Bundle("~/AdminLessBundle", new LessMinify());
...
AdminLess.AddFile("~/static/less/admin/user.less");
BundleTable.Bundles.Add(AdminLess);
...

person JesseBuesking    schedule 06.03.2012    source источник
comment
Не имеет отношения к вашему вопросу, но можете ли вы поделиться тем, что вы используете для своего Less Parsing?   -  person Shane Courtrille    schedule 21.03.2012
comment
@ShaneCourtrile проверьте nuget.org/packages?q=dotless Кажется, я использую без точек 1,3   -  person JesseBuesking    schedule 27.03.2012


Ответы (10)


Я написал небольшой пост в блоге об использовании LESS CSS. С веб-оптимизацией MVC4.

В основном это сводится к использованию BundleTransformer.Less Nuget Package и изменению вашего BundleConfig.cs.

Проверено с помощью бутстрапа.

EDIT: Следует упомянуть причину, по которой я это говорю, потому что я также столкнулся с проблемой структуры каталогов @import, и эта библиотека правильно ее обрабатывает.

person Ben Cull    schedule 02.10.2012
comment
спасибо за это, я тихо сходил с ума и не находил то, что мне нужно. Не могу поверить, что за этот пост не проголосовали. Я добавил сообщение с дополнением к вашему решению. - person awrigley; 31.10.2012
comment
Я бы не назвал его простым или элегантным. BundleTransformer — это очень тяжелый пакет Nuget, если вы используете только LESS. (на самом деле более 5 пакетов nuget и требует установки IE9+ на ваших веб-серверах). Ответ Майкла Бэрда намного проще - person arserbin3; 19.09.2013
comment
Бен, я следовал инструкциям вашего сообщения в блоге, а также примерам, приведенным на странице документации BundleTransformer.Less, и я все еще получаю сообщения об ошибках, связанных с тем, что без точки не удается найти файл, на который ссылается оператор @import. Все мои файлы находятся в одном каталоге, поэтому я просто использую @import url("filename.less");. Я даже следил за этим сообщением: ask-for-more-with-less-css-part-2.aspx" rel="nofollow noreferrer">geekswithblogs.net/ToStringTheory/archive/2012/11/30/ Есть идеи, что может происходить? MVC4/.NET 4.5 - person ps2goat; 19.10.2013
comment
@ ps2goat Удар в темноте, но заменили ли вы два раздела web.config, которые он просит вас заменить при установке пакета nuget BundleTransformer? - person Ben Cull; 30.10.2013
comment
Я думаю, что это были все требования, которые не были автоматически загружены через пакет nuget. в итоге мы остановились на решении @MichaelBaird, потому что, как говорили другие, для него не требовались библиотеки IE 9+ или Visual C++ (в зависимости от вашей версии переключателя движка javascript). - person ps2goat; 30.10.2013
comment
Добавление css.Transforms.Add(cssTransformer); в BundleConfig у меня работал cs. Спасибо! - person VladN; 23.07.2014

На GitHub Gist опубликован код, который хорошо работает с @import и dotLess: https://gist.github.com/2002958

Я протестировал его с помощью Twitter Bootstrap, и он работает хорошо.

ImportedFilePathResolver.cs

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

    /// <summary>
    /// Initializes a new instance of the <see cref="ImportedFilePathResolver"/> class.
    /// </summary>
    /// <param name="currentFilePath">The path to the currently processed file.</param>
    public ImportedFilePathResolver(string 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)
    {
        filePath = filePath.Replace('\\', '/').Trim();

        if(filePath.StartsWith("~"))
        {
            filePath = VirtualPathUtility.ToAbsolute(filePath);
        }

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

        return filePath;
    }
}

LessMinify.cs

public class LessMinify : IBundleTransform
{
    /// <summary>
    /// Processes the specified bundle of LESS files.
    /// </summary>
    /// <param name="bundle">The LESS bundle.</param>
    public void Process(BundleContext context, BundleResponse bundle)
    {
        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);

        foreach(FileInfo file in bundle.Files)
        {
            SetCurrentFilePath(lessParser, file.FullName);
            string source = File.ReadAllText(file.FullName);
            content.Append(lessEngine.TransformToCss(source, file.FullName));
            content.AppendLine();

            AddFileDependencies(lessParser);
        }

        bundle.Content = content.ToString();
        bundle.ContentType = "text/css";
        //base.Process(context, bundle);
    }

    /// <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, false);
    }

    /// <summary>
    /// Adds imported files to the collection of files on which the current response is dependent.
    /// </summary>
    /// <param name="lessParser">The LESS parser.</param>
    private void AddFileDependencies(Parser lessParser)
    {
        IPathResolver pathResolver = GetPathResolver(lessParser);

        foreach(string importedFilePath in lessParser.Importer.Imports)
        {
            string fullPath = pathResolver.GetFullPath(importedFilePath);
            HttpContext.Current.Response.AddFileDependency(fullPath);
        }

        lessParser.Importer.Imports.Clear();
    }

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

        return null;
    }

    /// <summary>
    /// Informs the LESS parser about the path to the currently processed file. 
    /// This is done by using 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)
        {
            var fileReader = importer.FileReader as FileReader;

            if(fileReader == null)
            {
                importer.FileReader = fileReader = new FileReader();
            }

            var pathResolver = fileReader.PathResolver as ImportedFilePathResolver;

            if(pathResolver != null)
            {
                pathResolver.CurrentFilePath = currentFilePath;
            }
            else
            {
               fileReader.PathResolver = new ImportedFilePathResolver(currentFilePath);
            }
        }
        else
        {
            throw new InvalidOperationException("Unexpected importer type on dotless parser");
        }


    }
}
person Michael Baird    schedule 06.08.2012
comment
Получение ошибки при попытке открыть ваше решение. nuget.targets не найден. - person sheldonj; 09.08.2012
comment
Это было именно то, что я искал. Отличный пост Михаил! - person Reaction21; 24.08.2012
comment
Существует слегка улучшенная версия, которая, как утверждается, работает и для .net 4.5: github.com/dotless/ dotless/issues/148 bitbucket.org/mrcode/bundlingsandbox/changeset/ - person ATL_DEV; 29.09.2012
comment
Что-то могло измениться в .net 4.5, но приведенный выше код неправильно кэширует импорт. Чтобы убедиться, что зависимости кэша настроены правильно, вам необходимо добавить все пути импорта в коллекцию Bundle.Files, когда включена оптимизация. Мой рабочий код — gist.github.com/3924025 - person Ben Foster; 20.10.2012
comment
Посмотрите ответ Бена Калла и проголосуйте за него. Это тот, который работает в современную эпоху с пакетом nuget BundleTransform.Less. Без боли, просто работает. - person awrigley; 31.10.2012
comment
мертвые ссылки для демонстрационного проекта - person Alex; 08.04.2015
comment
В моем коде bundle.Files не возвращает FileInfo. Нужно вызвать это, чтобы получить путь к файлу file.IncludedVirtualPath.Replace("~/", HttpRuntime.AppDomainAppPath); - person Hp93; 03.05.2017

Дополнение к ответу Бена Калла:

Я знаю, что это «должно быть комментарием к сообщению Бена Калла», но оно добавляет немного лишнего, что было бы невозможно добавить в комментарий. Так что проголосуйте за меня, если хотите. Или закрой меня.

Сообщение в блоге Бена делает все это, за исключением того, что оно не определяет минимизацию.

Итак, установите пакет BundleTransformer.Less, как предлагает Бен, а затем, если вы хотите минимизировать свой css, сделайте следующее (в ~/App_Start/BundleConfig.cs):

var cssTransformer = new CssTransformer();
var jsTransformer = new JsTransformer();
var nullOrderer = new NullOrderer();

var css = new Bundle("~/bundles/css")
    .Include("~/Content/site.less");
css.Transforms.Add(cssTransformer);
css.Transforms.Add(new CssMinify());
css.Orderer = nullOrderer;

bundles.Add(css);

Добавлена ​​строка:

css.Transforms.Add(new CssMinify());

Где CssMinify находится в System.Web.Optimizations

Я так рад обойти проблему @import и полученный файл с расширением .less не найден, что мне все равно, кто меня проголосует.

Если, наоборот, вы чувствуете желание проголосовать за этот ответ, пожалуйста, отдайте свой голос Бену.

Так что.

person awrigley    schedule 31.10.2012
comment
Это работает, но кажется, что импортированный файл встраивается несколько раз (один раз для каждого импорта). Это противоречит цели всей идеи объединения, которая заключается в уменьшении размера файла... - person Clement; 11.11.2012

Обходной путь, который мне показался действительно полезным, заключался в том, чтобы установить каталог перед запуском Less.Parse внутри LessMinify.Process(). Вот как я это сделал:

public class LessTransform : IBundleTransform
    {
        private string _path;

        public LessTransform(string path)
        {
            _path = path;
        }

        public void Process(BundleContext context, BundleResponse response)
        {
            Directory.SetCurrentDirectory(_path);

            response.Content = Less.Parse(response.Content);
            response.ContentType = "text/css";
        }
    }

Затем перейдите по пути при создании объекта меньшего преобразования, например:

lessBundle.Transforms.Add(
    new LessTransform(HttpRuntime.AppDomainAppPath + "/Content/Less")
);

Надеюсь это поможет.

person Chris Peterson    schedule 02.10.2013
comment
Интересно, какого черта такая сложность в других ответах, когда достаточно простого решения. Спасибо - person Fabio Milheiro; 10.03.2014
comment
Это помогло мне, спасибо за публикацию. Это простой ответ, и он хорошо работает. - person WebDever; 03.05.2014

Проблема в том, что DynamicFolderBundle считывает все содержимое файлов и передает объединенное содержимое в LessMinify.

Таким образом, любой @imports не имеет ссылки на место, откуда был получен файл.

Чтобы решить эту проблему, мне пришлось поместить все файлы «меньше» в одно место.

Затем вы должны понять, что порядок файлов становится важным. Таким образом, я начал переименовывать файл с числом (например: «0 CONSTANTS.less», «1 MIXIN.less», что означает, что они загружаются вверху комбинированного вывода, прежде чем они перейдут в LessMinify.

если вы отлаживаете свой LessMinify и просматриваете ответ. Контент, вы увидите комбинированный результат меньше!

Надеюсь это поможет

person TheRealQuagmire    schedule 15.03.2012
comment
Это не помогло. У меня есть 0colors.less, и я использую bundle.AddDirectory для загрузки всех моих файлов less, которые находятся в одной папке. @import 0colors.less выдает ту же ошибку. - person Shane Courtrille; 23.03.2012
comment
в global.asax.cs у меня есть: DynamicFolderBundle lessFb = new DynamicFolderBundle (less, new LessMinify(), *.less); BundleTable.Bundles.Add(lessFb); затем используйте путь /static/less/admin/less (как в примере выше), чтобы добраться до относительного местоположения. - person TheRealQuagmire; 28.03.2012

Вот самая простая версия кода для обработки этого, которую я мог придумать:

public class LessTransform : IBundleTransform
{
    public void Process(BundleContext context, BundleResponse bundle)
    {
        var pathResolver = new ImportedFilePathResolver(context.HttpContext.Server);
        var lessParser = new Parser();
        var lessEngine = new LessEngine(lessParser);
        (lessParser.Importer as Importer).FileReader = new FileReader(pathResolver);

        var content = new StringBuilder(bundle.Content.Length);
        foreach (var bundleFile in bundle.Files)
        {
            pathResolver.SetCurrentDirectory(bundleFile.IncludedVirtualPath);
            content.Append(lessEngine.TransformToCss((new StreamReader(bundleFile.VirtualFile.Open())).ReadToEnd(), bundleFile.IncludedVirtualPath));
            content.AppendLine();
        }

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

public class ImportedFilePathResolver : IPathResolver
{
    private HttpServerUtilityBase server { get; set; }
    private string currentDirectory { get; set; }

    public ImportedFilePathResolver(HttpServerUtilityBase server)
    {
        this.server = server;
    }

    public void SetCurrentDirectory(string fileLocation)
    {
        currentDirectory = Path.GetDirectoryName(fileLocation);
    }

    public string GetFullPath(string filePath)
    {
        var baseDirectory = server.MapPath(currentDirectory);
        return Path.GetFullPath(Path.Combine(baseDirectory, filePath));
    }
}
person John    schedule 19.06.2014
comment
Этот код работал для меня. Во всяком случае, я внес небольшое изменение в строку 14 вашего кода: используя (var stream = new StreamReader(bundleFile.VirtualFile.Open())) { content.Append(lessEngine.TransformToCss(stream.ReadToEnd(), bundleFile.IncludedVirtualPath)) ; }; в противном случае после загрузки страницы я не мог внести изменения в файл .less, потому что он использовался другим процессом - person Mirko Lugano; 09.09.2016

Вот что я сделал:

Добавлен модуль Twitter Bootstrap Nuget.

Добавил это в мой файл _Layout.cshtml:

<link href="@System.Web.Optimization.BundleTable.Bundles.ResolveBundleUrl("~/Content/twitterbootstrap/less")" rel="stylesheet" type="text/css" />

Обратите внимание, что я переименовал свою папку "less" в twitterbootstrap только для того, чтобы продемонстрировать, что я могу

Все файлы меньшего размера перемещены во вложенную папку с именем «imports», кроме bootstrap.less и (для адаптивного дизайна) responsive.less.

~/Content/twitterbootstrap/imports

Добавлена ​​конфигурация в web.config:

<add key="TwitterBootstrapLessImportsFolder" value="imports" />

Создал два класса (небольшая модификация класса выше):

using System.Configuration;
using System.IO;
using System.Web.Optimization;
using dotless.Core;
using dotless.Core.configuration;
using dotless.Core.Input;

namespace TwitterBootstrapLessMinify
{
    public class TwitterBootstrapLessMinify : CssMinify
    {
        public static string BundlePath { get; private set; }

        public override void Process(BundleContext context, BundleResponse response)
        {
            setBasePath(context);

            var config = new DotlessConfiguration(dotless.Core.configuration.DotlessConfiguration.GetDefault());
            config.LessSource = typeof(TwitterBootstrapLessMinifyBundleFileReader);

            response.Content = Less.Parse(response.Content, config);
            base.Process(context, response);
        }

        private void setBasePath(BundleContext context)
        {
            var importsFolder = ConfigurationManager.AppSettings["TwitterBootstrapLessImportsFolder"] ?? "imports";
            var path = context.BundleVirtualPath;

            path = path.Remove(path.LastIndexOf("/") + 1);

            BundlePath = context.HttpContext.Server.MapPath(path + importsFolder + "/");
        }
    }

    public class TwitterBootstrapLessMinifyBundleFileReader : IFileReader
    {
        public IPathResolver PathResolver { get; set; }
        private string basePath;

        public TwitterBootstrapLessMinifyBundleFileReader() : this(new RelativePathResolver())
        {
        }

        public TwitterBootstrapLessMinifyBundleFileReader(IPathResolver pathResolver)
        {
            PathResolver = pathResolver;
            basePath = TwitterBootstrapLessMinify.BundlePath;
        }

        public bool DoesFileExist(string fileName)
        {
            fileName = PathResolver.GetFullPath(basePath + fileName);

            return File.Exists(fileName);
        }

        public string GetFileContents(string fileName)
        {
            fileName = PathResolver.GetFullPath(basePath + fileName);

            return File.ReadAllText(fileName);
        }
    }
}

В моей реализации IFileReader используется статический член BundlePath класса TwitterBootstrapLessMinify. Это позволяет нам ввести базовый путь для импорта. Я хотел бы использовать другой подход (предоставив экземпляр моего класса, но я не мог).

Наконец, я добавил следующие строки в Global.asax:

BundleTable.Bundles.EnableDefaultBundles();

var lessFB = new DynamicFolderBundle("less", new TwitterBootstrapLessMinify(), "*.less", false);
BundleTable.Bundles.Add(lessFB);

Это эффективно решает проблему импорта, не зная, откуда импортировать.

person PeteK68    schedule 25.05.2012

По состоянию на февраль 2013 года: отличное решение Майкла Бэрда было заменено ответом «BundleTransformer.Less Nuget Package», упомянутым в сообщении Бена Калла. Аналогичный ответ на: http://blog.cdeutsch.com/2012/08/using-less-and-twitter-bootstrap-in.html

Блог Cdeutsch и сообщение awrigley о добавлении минимизации — это хорошо, но, по-видимому, сейчас это неправильный подход.

Кто-то еще с таким же решением получил несколько ответов от автора BundleTransformer: http://geekswithblogs.net/ToStringTheory/archive/2012/11/30/who-could-ask-for-more-with-less-css-part-2.aspx. Смотрите комментарии внизу.

Таким образом, используйте BundleTransformer.MicrosoftAjax вместо встроенных встроенных минификаторов. например css.Transforms.Add (новый CssMinify()); заменено на css.Transforms.Add(new BundleTransformer.MicrosoftAjax());

person RockResolve    schedule 08.02.2013

В соответствии с приведенным ниже RockResolve, чтобы использовать минификатор MicrosoftAjax, укажите его как минимизатор CSS по умолчанию в файле web.config, а не передавайте его в качестве аргумента.

From https://bundletransformer.codeplex.com/wikipage/?title=Bundle%20Transformer%201.7.0%20Beta%201#BundleTransformerMicrosoftAjax_Chapter

Чтобы сделать MicrosoftAjaxCssMinifier CSS-минификатором по умолчанию, а MicrosoftAjaxJsMinifier — JS-минификатором по умолчанию, необходимо внести изменения в файл Web.config. В атрибуте defaultMinifier элемента \configuration\bundleTransformer\core\css должно быть установлено значение, равное MicrosoftAjaxCssMinifier, а в этом же атрибуте элемента \configuration\bundleTransformer\core\js - MicrosoftAjaxJsMinifier.

person BarryF    schedule 16.04.2013

Я столкнулся с той же проблемой, увидев то же сообщение об ошибке. Поиск решения в Интернете привел меня сюда. Моя проблема заключалась в следующем:

В меньшем файле у меня в какой-то момент был неправильный стиль, который предупреждал меня. Файл less не может быть проанализирован. Я избавился от сообщения об ошибке, удалив неправильную строку.

Я надеюсь, что это помогает кому-то.

person Memet Olsen    schedule 09.12.2013