[css] MVC4 StyleBundle no resuelve las imágenes


Answers

La solución de Grinn / ThePirat funciona bien.

No me gustó que haya actualizado el método Include en el paquete, y que haya creado archivos temporales en el directorio de contenido. (terminaron siendo controlados, implementados, ¡entonces el servicio no se inició!)

Entonces, para seguir el diseño de Bundling, elegí realizar esencialmente el mismo código, pero en una implementación de IBundleTransform:

class StyleRelativePathTransform
    : IBundleTransform
{
    public StyleRelativePathTransform()
    {
    }

    public void Process(BundleContext context, BundleResponse response)
    {
        response.Content = String.Empty;

        Regex pattern = new Regex(@"url\s*\(\s*([""']?)([^:)]+)\1\s*\)", RegexOptions.IgnoreCase);
        // open each of the files
        foreach (FileInfo cssFileInfo in response.Files)
        {
            if (cssFileInfo.Exists)
            {
                // apply the RegEx to the file (to change relative paths)
                string contents = File.ReadAllText(cssFileInfo.FullName);
                MatchCollection matches = pattern.Matches(contents);
                // Ignore the file if no match 
                if (matches.Count > 0)
                {
                    string cssFilePath = cssFileInfo.DirectoryName;
                    string cssVirtualPath = context.HttpContext.RelativeFromAbsolutePath(cssFilePath);
                    foreach (Match match in matches)
                    {
                        // this is a path that is relative to the CSS file
                        string relativeToCSS = match.Groups[2].Value;
                        // combine the relative path to the cssAbsolute
                        string absoluteToUrl = Path.GetFullPath(Path.Combine(cssFilePath, relativeToCSS));

                        // make this server relative
                        string serverRelativeUrl = context.HttpContext.RelativeFromAbsolutePath(absoluteToUrl);

                        string quote = match.Groups[1].Value;
                        string replace = String.Format("url({0}{1}{0})", quote, serverRelativeUrl);
                        contents = contents.Replace(match.Groups[0].Value, replace);
                    }
                }
                // copy the result into the response.
                response.Content = String.Format("{0}\r\n{1}", response.Content, contents);
            }
        }
    }
}

Y luego envuelto esto en una Implementación de paquete:

public class StyleImagePathBundle 
    : Bundle
{
    public StyleImagePathBundle(string virtualPath)
        : base(virtualPath)
    {
        base.Transforms.Add(new StyleRelativePathTransform());
        base.Transforms.Add(new CssMinify());
    }

    public StyleImagePathBundle(string virtualPath, string cdnPath)
        : base(virtualPath, cdnPath)
    {
        base.Transforms.Add(new StyleRelativePathTransform());
        base.Transforms.Add(new CssMinify());
    }
}

Uso de la muestra:

static void RegisterBundles(BundleCollection bundles)
{
...
    bundles.Add(new StyleImagePathBundle("~/bundles/Bootstrap")
            .Include(
                "~/Content/css/bootstrap.css",
                "~/Content/css/bootstrap-responsive.css",
                "~/Content/css/jquery.fancybox.css",
                "~/Content/css/style.css",
                "~/Content/css/error.css",
                "~/Content/validation.css"
            ));

Aquí está mi método de extensión para RelativeFromAbsolutePath:

   public static string RelativeFromAbsolutePath(this HttpContextBase context, string path)
    {
        var request = context.Request;
        var applicationPath = request.PhysicalApplicationPath;
        var virtualDir = request.ApplicationPath;
        virtualDir = virtualDir == "/" ? virtualDir : (virtualDir + "/");
        return path.Replace(applicationPath, virtualDir).Replace(@"\", "/");
    }
Question

Mi pregunta es similar a esto:

Miniatura e imágenes de fondo de ASP.NET MVC 4

Excepto que quiero seguir con el paquete de MVC si puedo. Estoy teniendo un accidente cerebrovascular tratando de averiguar cuál es el patrón correcto para especificar los paquetes de estilos, de modo que funcionen los css independientes y los conjuntos de imágenes como jQuery UI.

Tengo una estructura de sitio MVC típica con /Content/css/ que contiene mi base CSS como styles.css . Dentro de esa carpeta css también tengo subcarpetas como /jquery-ui que contiene su archivo CSS más una carpeta /images . Las rutas de imagen en jQuery UI CSS son relativas a esa carpeta y no quiero meterme con ellas.

Tal como lo entiendo, cuando especifico un StyleBundle , necesito especificar una ruta virtual que no coincida con una ruta de contenido real, porque (suponiendo que ignoro las rutas hacia el Contenido), IIS intentaría resolver esa ruta como un archivo físico. . Así que estoy especificando:

bundles.Add(new StyleBundle("~/Content/styles/jquery-ui")
       .Include("~/Content/css/jquery-ui/*.css"));

representado usando:

@Styles.Render("~/Content/styles/jquery-ui")

Puedo ver la solicitud de salida a:

http://localhost/MySite/Content/styles/jquery-ui?v=nL_6HPFtzoqrts9nwrtjq0VQFYnhMjY5EopXsK8cxmg1

Esto devuelve la respuesta CSS correcta y miniaturizada. Pero luego el navegador envía una solicitud de una imagen relativamente vinculada como:

http://localhost/MySite/Content/styles/images/ui-bg_highlight-soft_100_eeeeee_1x100.png

Que es un 404 .

Entiendo que la última parte de mi URL jquery-ui es una URL sin extensión, un controlador para mi paquete, por lo que puedo ver por qué la solicitud relativa para la imagen es simplemente /styles/images/ .

Entonces mi pregunta es ¿cuál es la forma correcta de manejar esta situación?




Otra opción sería usar el módulo Reescribir URL de IIS para asignar la carpeta de imágenes del paquete virtual a la carpeta de imágenes físicas. A continuación se muestra un ejemplo de una regla de reescritura que puede usar para un paquete llamado "~ / bundles / yourpage / styles": tenga en cuenta que la expresión regular coincide con los caracteres alfanuméricos y los guiones, guiones bajos y puntos, que son comunes en los nombres de los archivos de imagen .

<rewrite>
  <rules>
    <rule name="Bundle Images">
      <match url="^bundles/yourpage/images/([a-zA-Z0-9\-_.]+)" />
      <action type="Rewrite" url="Content/css/jquery-ui/images/{R:1}" />
    </rule>
  </rules>
</rewrite>

Este enfoque crea una pequeña sobrecarga adicional, pero le permite tener más control sobre los nombres de sus paquetes, y también reduce el número de paquetes que puede tener que hacer referencia en una página. Por supuesto, si tiene que hacer referencia a múltiples archivos css de terceros que contienen referencias de ruta de imagen relativa, aún no puede evitar la creación de múltiples paquetes.




No es necesario especificar una transformación o tener rutas de subdirectorios locos. Después de mucha resolución de problemas lo aislé de esta regla "simple" (¿es un error?) ...

Si la ruta del paquete no comienza con la raíz relativa de los elementos que se incluyen, entonces la raíz de la aplicación web no se tendrá en cuenta.

Suena como un error más para mí, pero de todos modos así es como lo arreglas con la versión actual de .NET 4.51. Quizás las otras respuestas fueron necesarias en versiones anteriores de ASP.NET, no puedo decir que no tengo tiempo para probarlas todo de manera retrospectiva.

Para aclarar, aquí hay un ejemplo:

Tengo estos archivos ...

~/Content/Images/Backgrounds/Some_Background_Tile.gif
~/Content/Site.css  - references the background image relatively, i.e. background: url('Images/...')

Luego configura el paquete como ...

BundleTable.Add(new StyleBundle("~/Bundles/Styles").Include("~/Content/Site.css"));

Y renderíalo como ...

@Styles.Render("~/Bundles/Styles")

Y obtener el "comportamiento" (error), los archivos CSS tienen la raíz de la aplicación (por ejemplo, "http: // localhost: 1234 / MySite / Content / Site.css") pero la imagen CSS dentro de todo comienza "/ Content / Images / ... "o" / Images / ... "dependiendo de si agrego la transformación o no.

Incluso intentó crear la carpeta "Bundles" para ver si tenía que ver con la ruta existente o no, pero eso no cambió nada. La solución al problema es realmente el requisito de que el nombre del paquete debe comenzar con la ruta raíz.

Lo que significa que este ejemplo se soluciona registrando y renderizando la ruta del paquete como ...

BundleTable.Add(new StyleBundle("~/Content/StylesBundle").Include("~/Content/Site.css"));
...
@Styles.Render("~/Content/StylesBundle")

Así que, por supuesto, podría decir que se trata de RTFM, pero estoy bastante seguro de que yo y otros recogimos esta ruta "~ / Bundles / ..." desde la plantilla predeterminada o en algún lugar de la documentación en el sitio web MSDN o ASP.NET, o acaba de tropezar con él porque en realidad es un nombre bastante lógico para una ruta virtual y tiene sentido elegir rutas virtuales que no entren en conflicto con los directorios reales.

De todos modos, esa es la forma en que es. Microsoft no ve ningún error. No estoy de acuerdo con esto, o bien debería funcionar como se esperaba o se debería lanzar alguna excepción, o una anulación adicional para agregar la ruta del paquete que opta por incluir la raíz de la aplicación o no. No puedo imaginar por qué alguien no querría incluir la raíz de la aplicación cuando la hay (normalmente, a menos que haya instalado su sitio web con un alias DNS / raíz del sitio web predeterminado). De hecho, ese debería ser el valor predeterminado de todos modos.




Aunque la respuesta de Chris Baxter ayuda con el problema original, no funciona en mi caso cuando la aplicación está alojada en un directorio virtual . Después de investigar las opciones, terminé con la solución de bricolaje.

ProperStyleBundle clase ProperStyleBundle incluye código tomado de CssRewriteUrlTransform original para transformar adecuadamente las rutas relativas dentro del directorio virtual. También arroja si el archivo no existe e impide el reordenamiento de los archivos en el paquete (código tomado de BetterStyleBundle ).

using System;
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
using System.Web;
using System.Web.Optimization;
using System.Linq;

namespace MyNamespace
{
    public class ProperStyleBundle : StyleBundle
    {
        public override IBundleOrderer Orderer
        {
            get { return new NonOrderingBundleOrderer(); }
            set { throw new Exception( "Unable to override Non-Ordered bundler" ); }
        }

        public ProperStyleBundle( string virtualPath ) : base( virtualPath ) {}

        public ProperStyleBundle( string virtualPath, string cdnPath ) : base( virtualPath, cdnPath ) {}

        public override Bundle Include( params string[] virtualPaths )
        {
            foreach ( var virtualPath in virtualPaths ) {
                this.Include( virtualPath );
            }
            return this;
        }

        public override Bundle Include( string virtualPath, params IItemTransform[] transforms )
        {
            var realPath = System.Web.Hosting.HostingEnvironment.MapPath( virtualPath );
            if( !File.Exists( realPath ) )
            {
                throw new FileNotFoundException( "Virtual path not found: " + virtualPath );
            }
            var trans = new List<IItemTransform>( transforms ).Union( new[] { new ProperCssRewriteUrlTransform( virtualPath ) } ).ToArray();
            return base.Include( virtualPath, trans );
        }

        // This provides files in the same order as they have been added. 
        private class NonOrderingBundleOrderer : IBundleOrderer
        {
            public IEnumerable<BundleFile> OrderFiles( BundleContext context, IEnumerable<BundleFile> files )
            {
                return files;
            }
        }

        private class ProperCssRewriteUrlTransform : IItemTransform
        {
            private readonly string _basePath;

            public ProperCssRewriteUrlTransform( string basePath )
            {
                _basePath = basePath.EndsWith( "/" ) ? basePath : VirtualPathUtility.GetDirectory( basePath );
            }

            public string Process( string includedVirtualPath, string input )
            {
                if ( includedVirtualPath == null ) {
                    throw new ArgumentNullException( "includedVirtualPath" );
                }
                return ConvertUrlsToAbsolute( _basePath, input );
            }

            private static string RebaseUrlToAbsolute( string baseUrl, string url )
            {
                if ( string.IsNullOrWhiteSpace( url )
                     || string.IsNullOrWhiteSpace( baseUrl )
                     || url.StartsWith( "/", StringComparison.OrdinalIgnoreCase )
                     || url.StartsWith( "data:", StringComparison.OrdinalIgnoreCase )
                    ) {
                    return url;
                }
                if ( !baseUrl.EndsWith( "/", StringComparison.OrdinalIgnoreCase ) ) {
                    baseUrl = baseUrl + "/";
                }
                return VirtualPathUtility.ToAbsolute( baseUrl + url );
            }

            private static string ConvertUrlsToAbsolute( string baseUrl, string content )
            {
                if ( string.IsNullOrWhiteSpace( content ) ) {
                    return content;
                }
                return new Regex( "url\\(['\"]?(?<url>[^)]+?)['\"]?\\)" )
                    .Replace( content, ( match =>
                                         "url(" + RebaseUrlToAbsolute( baseUrl, match.Groups["url"].Value ) + ")" ) );
            }
        }
    }
}

Úselo como StyleBundle :

bundles.Add( new ProperStyleBundle( "~/styles/ui" )
    .Include( "~/Content/Themes/cm_default/style.css" )
    .Include( "~/Content/themes/custom-theme/jquery-ui-1.8.23.custom.css" )
    .Include( "~/Content/DataTables-1.9.4/media/css/jquery.dataTables.css" )
    .Include( "~/Content/DataTables-1.9.4/extras/TableTools/media/css/TableTools.css" ) );



A partir de v1.1.0-alpha1 (paquete de prelanzamiento), la infraestructura utiliza VirtualPathProvider para acceder a los archivos en lugar de tocar el sistema de archivos físicos.

El transformador actualizado se puede ver a continuación:

public class StyleRelativePathTransform
    : IBundleTransform
{
    public void Process(BundleContext context, BundleResponse response)
    {
        Regex pattern = new Regex(@"url\s*\(\s*([""']?)([^:)]+)\1\s*\)", RegexOptions.IgnoreCase);

        response.Content = string.Empty;

        // open each of the files
        foreach (var file in response.Files)
        {
            using (var reader = new StreamReader(file.Open()))
            {
                var contents = reader.ReadToEnd();

                // apply the RegEx to the file (to change relative paths)
                var matches = pattern.Matches(contents);

                if (matches.Count > 0)
                {
                    var directoryPath = VirtualPathUtility.GetDirectory(file.VirtualPath);

                    foreach (Match match in matches)
                    {
                        // this is a path that is relative to the CSS file
                        var imageRelativePath = match.Groups[2].Value;

                        // get the image virtual path
                        var imageVirtualPath = VirtualPathUtility.Combine(directoryPath, imageRelativePath);

                        // convert the image virtual path to absolute
                        var quote = match.Groups[1].Value;
                        var replace = String.Format("url({0}{1}{0})", quote, VirtualPathUtility.ToAbsolute(imageVirtualPath));
                        contents = contents.Replace(match.Groups[0].Value, replace);
                    }

                }
                // copy the result into the response.
                response.Content = String.Format("{0}\r\n{1}", response.Content, contents);
            }
        }
    }
}



After little investigation I concluded the followings: You have 2 options:

  1. go with transformations. Very usefull package for this: https://bundletransformer.codeplex.com/ you need following transformation for every problematic bundle:

    BundleResolver.Current = new CustomBundleResolver();
    var cssTransformer = new StyleTransformer();
    standardCssBundle.Transforms.Add(cssTransformer);
    bundles.Add(standardCssBundle);
    

Advantages: of this solution, you can name your bundle whatever you want => you can combine css files into one bundle from different directories. Disadvantages: You need to transform every problematic bundle

  1. Use the same relative root for the name of the bundle like where the css file is located. Advantages: there is no need for transformation. Disadvantages: You have limitation on combining css sheets from different directories into one bundle.



Simplemente puede agregar otro nivel de profundidad a la ruta de su paquete virtual

    //Two levels deep bundle path so that paths are maintained after minification
    bundles.Add(new StyleBundle("~/Content/css/css").Include("~/Content/bootstrap/bootstrap.css", "~/Content/site.css"));

Esta es una respuesta de muy baja tecnología y una especie de truco, pero funciona y no requiere ningún procesamiento previo. Dada la longitud y la complejidad de algunas de estas respuestas, prefiero hacerlo de esta manera.




Related