css MVC4 StyleBundle no resuelve imágenes



7 Answers

La solución de Grinn / ThePirat funciona bien.

No me gustó que haya hecho una nueva versión del método Include en el paquete y que haya creado archivos temporales en el directorio de contenido. (terminaron siendo registrados, desplegados, ¡entonces el servicio no se iniciaría!)

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(@"\", "/");
    }
css jquery-ui asp.net-mvc-4 bundle asp.net-optimization

Mi pregunta es similar a esta:

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

Excepto que quiero seguir con el propio paquete de MVC si puedo. Estoy teniendo una falla cerebral al tratar de averiguar cuál es el patrón correcto para especificar paquetes de estilo, como el css independiente y conjuntos de imágenes como jQuery UI.

Tengo una estructura de sitio MVC típica con /Content/css/ que contiene mi CSS base 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 el jQuery UI CSS son relativas a esa carpeta y no quiero meterme con ellas.

Como lo entiendo, cuando especifico un StyleBundle necesito especificar una ruta virtual que no coincida con una ruta de contenido real, porque (asumiendo que estoy ignorando las rutas hacia el Contenido), IIS intentará 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"));

renderizado utilizando:

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

Puedo ver la solicitud saliendo a:

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

Esto está devolviendo la respuesta CSS correcta y minificada. 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, así que puedo ver por qué la solicitud relativa de la imagen es simplemente /styles/images/ .

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




No es necesario especificar una transformación o tener rutas de subdirectorio locas. Después de muchos problemas, lo aislé a esta regla "simple" (¿es un error?) ...

Si la ruta de su 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 las versiones anteriores de ASP.NET, no puedo decir que no haya tiempo para probar retrospectivamente todo eso.

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 hazlo como ...

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

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

Incluso intenté crear la carpeta "Paquetes" 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 raíz de la ruta.

El significado de este ejemplo se corrige al registrar y representar 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 esto es RTFM, pero estoy bastante seguro de que otros y yo recogimos esta ruta "~ / Bundles / ..." de la plantilla predeterminada o en algún lugar de la documentación en el sitio web de MSDN o ASP.NET, o Simplemente lo encontré porque en realidad es un nombre bastante lógico para una ruta virtual y tiene sentido elegir esas rutas virtuales que no entren en conflicto con directorios reales.

De todos modos, así es como es. Microsoft no ve ningún error. No estoy de acuerdo con esto, debería funcionar como se esperaba o debería lanzarse 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é nadie querría que se incluyera la raíz de la aplicación cuando había una (normalmente, a menos que haya instalado su sitio web con un alias DNS / raíz de sitio web predeterminada). Así que en realidad eso debería ser el 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 para transformar adecuadamente las rutas relativas dentro del directorio virtual. También se lanza si el archivo no existe e impide que se BetterStyleBundle a BetterStyleBundle 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 ) + ")" ) );
            }
        }
    }
}

Úsalo 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), el marco utiliza el VirtualPathProvider para acceder a los archivos en lugar de tocar el sistema de archivos físico.

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



Otra opción sería utilizar el módulo IIS URL Rewrite 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 podría usar para un paquete llamado "~ / bundles / yourpage / styles"; tenga en cuenta las coincidencias de expresiones regulares en caracteres alfanuméricos, así como guiones, guiones bajos y puntos, que son comunes en los nombres de 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 la cantidad de paquetes a los que debe hacer referencia en una página. Por supuesto, si tiene que hacer referencia a varios archivos css de terceros que contienen referencias de rutas de imagen relativas, aún no puede crear múltiples paquetes.




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 súper baja en tecnología y una especie de pirateo, 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.




Después de una pequeña investigación, concluí lo siguiente: Tienes 2 opciones:

  1. Ir con las transformaciones. Paquete muy útil para esto: https://bundletransformer.codeplex.com/ necesita la siguiente transformación para cada paquete problemático:

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

Ventajas: de esta solución, puede nombrar su paquete como desee => puede combinar archivos css en un paquete de directorios diferentes. Desventajas: Necesitas transformar cada paquete problemático.

  1. Utilice la misma raíz relativa para el nombre del paquete, como donde se encuentra el archivo css. Ventajas: no hay necesidad de transformación. Desventajas: tiene una limitación en la combinación de hojas css de diferentes directorios en un solo paquete.



Related