Jak używać ASP.Net MVC 4 do pakietu mniej plików w trybie Release?

Staram się mieć mniej plików w moim projekcie internetowym, i mieć połączenie funkcji wiązania MVC 4 z biblioteką bez kropek, aby przekształcić mniej w CSS, a następnie zminimalizować wynik i dać go do przeglądarki.

Znalazłem przykład na ASP.NET site (pod nagłówkiem LESS, CoffeeScript, SCSS, Sass Bundling.). To dało mi LessTransform klasę, która wygląda tak:

public class LessTransform : IBundleTransform
{
    public void Process(BundleContext context, BundleResponse response)
    {
        response.Content = dotless.Core.Less.Parse(response.Content);
        response.ContentType = "text/css";
    }
}

I ten wiersz w mojej klasie BundleConfig:

bundles.Add(new Bundle(
    "~/Content/lessTest", 
    new LessTransform(), 
    new CssMinify()).Include("~/Content/less/test.less"));

Wreszcie mam następujące linia w moim _Layout.cshtml, w <head>:

@Styles.Render("~/Content/lessTest")

Jeśli mam stronę w trybie debugowania, jest to renderowane do przeglądarki:

<link href="/Content/less/test.less" rel="stylesheet"/>

Zasady w .pliki less są stosowane, a po tym linku pokazuje, że LESS został poprawnie przekształcony w CSS.

Jednakże, jeśli włączę stronę w tryb release, zostanie to renderowane:

<link href="/Content/less?v=lEs-HID6XUz3s2qkJ35Lvnwwq677wTaIiry6fuX8gz01" rel="stylesheet"/>

Zasady w .pliki less są stosowane , a nie, ponieważ po kliknięciu linku pojawia się błąd 404 z IIS.

Więc to wygląda na to, że coś jest nie tak z pakietem. Jak uruchomić ten tryb w trybie release lub jak dowiedzieć się, co dokładnie jest nie tak?

Author: Graham Clark, 2013-03-06

5 answers

Wygląda na to, że silnik bez kropek musi znać ścieżkę aktualnie przetwarzanego pliku bundle, aby rozwiązać ścieżki @import. Jeśli uruchomisz kod procesu, który masz powyżej, wynik bez kropki.Rdzeń.Mniej.Parse () jest pustym łańcuchem, gdymniej przetwarzanych plików ma zaimportowane inne mniej plików.

ODPOWIEDŹ Bena Fostera naprawi to, najpierw odczytując importowane pliki:

Import plików i bez kropek

Zmień swój plik LessTransform jako follows:

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;
    }
}
 13
Author: Emma Middlebrook,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-03-07 13:15:10

Jako uzupełnienie zaakceptowanej odpowiedzi , stworzyłem klasę Lessbundle, która jest mniej równoprawną klasą StyleBundle.

LessBundle.kod cs to:

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() })
        {

        }
    }
}

Użycie jest podobne do klasy StyleBundle, określając plik LESS zamiast pliku CSS.

Dodaj następujące do swojego BundleConfig.RegisterBundles (BundleCollection) metoda:

bundles.Add(new LessBundle("~/Content/less").Include(
                 "~/Content/MyStyles.less"));

Update

Ta metoda działa dobrze przy wyłączonej optymalizacji, ale napotkałem kilka drobnych problemów (ze ścieżkami zasobów CSS) po włączeniu optymalizacji. Po godzinie badań nad tym zagadnieniem odkryłem, że odkryłem na nowo koło...

Jeśli chcesz mieć funkcjonalność LessBundle, którą opisałem powyżej, sprawdź System.Www.Optymalizacja.Mniej .

Pakiet NuGet można znaleźć tutaj .

 15
Author: David Kirkland,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:00:21

Zaakceptowana odpowiedź nie działa przy ostatnich zmianach w ASP.NET, więc nie jest już poprawny.

Naprawiłem źródło w zaakceptowanej odpowiedzi:

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;
            }
        }
    }
}

Należy zauważyć, że jeden znany problem z tym kodem jest taki, że mniej @imports musi używać swoich pełnych ścieżek, tzn. musisz użyć @import "~/Areas/Admin/Css/global.less"; zamiast @import "global.less";.

 3
Author: Ian Newson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-04-21 10:21:02

Wygląda na to, że to działa-zmieniłem metodę Process na iterację nad zbiorem plików:

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";
}

To się psuje, jeśli są jakieś @import instrukcje w Twoich plikach less, w tym przypadku musisz wykonać trochę więcej pracy, jak to: https://gist.github.com/chrisortman/2002958

 2
Author: Graham Clark,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-03-07 13:15:01

Już kilka świetnych odpowiedzi, oto bardzo proste rozwiązanie, które znalazłem dla siebie, próbując dodać pakiety MVC, które dotyczą less plików.

Po utworzeniu pliku less (na przykład test.less), Kliknij go prawym przyciskiem myszy i pod Web Compiler (pobierz go tutaj) wybierz opcję Compile File.

Generuje wynikowy plik css z twojego less, a także jego zminifikowaną wersję. (test.css i test.min.css).

W Twoim pakiecie, po prostu zapoznaj się z wygenerowanym css plik

style = new StyleBundle("~/bundles/myLess-styles")
    .Include("~/Content/css/test.css", new CssRewriteUrlTransform());

bundles.Add(style);

A według Ciebie, odwołaj się do tego pakietu:

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

To powinno działać dobrze.

 1
Author: chiapa,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-06-24 16:23:06