MVC4 StyleBundle no resuelve imágenes


293

Mi pregunta es similar a esta:

ASP.NET MVC 4 Minificación e imágenes de fondo

Excepto que quiero seguir con el paquete de MVC si puedo. Tengo un colapso cerebral al tratar de descubrir cuál es el patrón correcto para especificar paquetes de estilos tales como css independientes y conjuntos de imágenes como jQuery UI.

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

Según tengo entendido, cuando especifico un StyleBundleNecesito especificar una ruta virtual que no coincide con una ruta de contenido real, porque (suponiendo que estoy ignorando las rutas al Contenido) IIS intentaría resolver esa ruta como un archivo físico. Entonces estoy especificando:

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

renderizado usando:

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

Puedo ver la solicitud yendo 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-uies una URL sin extensión, un controlador para mi paquete, por lo que puedo ver por qué la solicitud relativa de la imagen es sencilla /styles/images/.

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


99
Después de haber estado frustrado una y otra vez con la nueva parte Bundling and Minification, me mudé a Cassete, ¡la bruja ahora es gratis y funciona mucho mejor!
balexandre

3
Gracias por el enlace, Cassette se ve bien y definitivamente lo comprobaré. Pero quiero seguir con el enfoque proporcionado si es posible, seguramente esto debe ser posible sin alterar las rutas de imágenes en archivos CSS de terceros cada vez que se lanza una nueva versión. por ahora he mantenido mis ScriptBundles (que funcionan bien) pero volví a enlaces CSS simples hasta que obtuve una resolución. Salud.
Tom W Hall

Agregando el error probable por razones de SEO: El controlador para la ruta '/bundles/images/blah.jpg' no se encontró o no implementa IController.
Luke Puplett

Respuestas:


361

De acuerdo con este hilo en el paquete MVC4 css y las referencias de imagen , si define su paquete como:

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

Cuando defina el paquete en la misma ruta que los archivos de origen que formaron el paquete, las rutas de imagen relativas seguirán funcionando. La última parte de la ruta del paquete es realmente file namepara ese paquete específico (es decir, /bundlepuede ser cualquier nombre que desee).

Esto solo funcionará si está agrupando CSS desde la misma carpeta (lo que creo que tiene sentido desde una perspectiva de agrupación).

Actualizar

Según el comentario a continuación de @Hao Kung, alternativamente, esto ahora se puede lograr aplicando un CssRewriteUrlTransformation( Cambiar referencias de URL relativas a archivos CSS cuando se incluye ).

NOTA: No he confirmado comentarios sobre problemas con la reescritura de rutas absolutas dentro de un directorio virtual, por lo que esto puede no funcionar para todos (?).

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

1
¡Leyenda! Sí, eso funciona perfectamente. Tengo CSS en diferentes niveles, pero cada uno tiene sus propias carpetas de imágenes, por ejemplo, mi sitio principal CSS está en la carpeta raíz de CSS y luego jquery-ui está dentro de eso con su propia carpeta de imágenes, así que solo especifico 2 paquetes, uno para mi CSS base y uno para jQuery UI, que tal vez no sea súper óptimo en términos de solicitudes, pero la vida es corta. ¡Salud!
Tom W Hall

3
Sí, desafortunadamente, hasta que la agrupación sea compatible con la reescritura de URL incrustadas dentro del propio CSS, necesita el directorio virtual del paquete CSS para que coincida con los archivos CSS antes de la agrupación. Esta es la razón por la cual los paquetes de plantillas predeterminados no tienen URL como ~ / bundles / themes, y en cambio se ven como la estructura del directorio: ~ / content / theemes / base / css
Hao Kung

27
Esto ahora es compatible a través de ItemTransforms, .Include ("~ / Content / css / jquery-ui / *. Css", new CssRewriteUrlTransform ())); en el 1.1Beta1 debería solucionar este problema
Hao Kung

2
¿Se soluciona esto en Microsoft ASP.NET Web Optimization Framework 1.1.3? ¿He encontrado alguna información sobre lo que ha cambiado en esto?
Andrus

13
new CssRewriteUrlTransform () está bien si tiene un sitio web en IIS. pero si se trata de una aplicación o sub-aplicación, esto no funcionará, y debe recurrir a definir su paquete en la misma ubicación que CSS.
avidenic

34

La solución Grinn / ThePirat funciona bien.

No me gustó que haya renovado el método Incluir en el paquete y que haya creado archivos temporales en el directorio de contenido. (terminaron siendo registrados, desplegados, ¡entonces el servicio no comenzó!)

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 envolvió 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 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(@"\", "/");
    }

Esto me parece más limpio también. Gracias. Los votaré a los tres porque parecía ser un esfuerzo de equipo. :)
Josh Mouch

El código como lo tienes ahora no me funciona. Estoy tratando de arreglarlo, pero pensé que te lo haría saber. El método context.HttpContext.RelativeFromAbsolutePath no existe. Además, si la ruta url comienza con un "/" (haciéndolo absoluto), la lógica de combinación de ruta está desactivada.
Josh Mouch

2
@AcidPAT gran trabajo. La lógica fallaba si la url tenía una cadena de consulta (algunas bibliotecas de terceros la agregan, como FontAwesome por su referencia .woff). Sin embargo, es una solución fácil. Uno puede ajustar la expresión regular o corregir relativeToCSSantes de llamar Path.GetFullPath().
sergiopereira

2
@ChrisMarisic parece que su código no funciona: respuesta.Files es una matriz de BundleFiles, estos objetos no tienen propiedades como "Existe", "DirectoryName", etc.
Nick Coad

2
@ChrisMarisic ¿hay quizás un espacio de nombres que debería importar que proporcione métodos de extensión para la clase BundleFile?
Nick Coad

20

Mejor aún (en mi humilde opinión) implementar un paquete personalizado que corrige las rutas de la imagen. Escribí uno para mi aplicación.

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

...

public class StyleImagePathBundle : Bundle
{
    public StyleImagePathBundle(string virtualPath)
        : base(virtualPath, new IBundleTransform[1]
      {
        (IBundleTransform) new CssMinify()
      })
    {
    }

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

    public new Bundle Include(params string[] virtualPaths)
    {
        if (HttpContext.Current.IsDebuggingEnabled)
        {
            // Debugging. Bundling will not occur so act normal and no one gets hurt.
            base.Include(virtualPaths.ToArray());
            return this;
        }

        // In production mode so CSS will be bundled. Correct image paths.
        var bundlePaths = new List<string>();
        var svr = HttpContext.Current.Server;
        foreach (var path in virtualPaths)
        {
            var pattern = new Regex(@"url\s*\(\s*([""']?)([^:)]+)\1\s*\)", RegexOptions.IgnoreCase);
            var contents = IO.File.ReadAllText(svr.MapPath(path));
            if(!pattern.IsMatch(contents))
            {
                bundlePaths.Add(path);
                continue;
            }


            var bundlePath = (IO.Path.GetDirectoryName(path) ?? string.Empty).Replace(@"\", "/") + "/";
            var bundleUrlPath = VirtualPathUtility.ToAbsolute(bundlePath);
            var bundleFilePath = String.Format("{0}{1}.bundle{2}",
                                               bundlePath,
                                               IO.Path.GetFileNameWithoutExtension(path),
                                               IO.Path.GetExtension(path));
            contents = pattern.Replace(contents, "url($1" + bundleUrlPath + "$2$1)");
            IO.File.WriteAllText(svr.MapPath(bundleFilePath), contents);
            bundlePaths.Add(bundleFilePath);
        }
        base.Include(bundlePaths.ToArray());
        return this;
    }

}

Para usarlo, haz:

bundles.Add(new StyleImagePathBundle("~/bundles/css").Include(
  "~/This/Is/Some/Folder/Path/layout.css"));

...en vez de...

bundles.Add(new StyleBundle("~/bundles/css").Include(
  "~/This/Is/Some/Folder/Path/layout.css"));

Lo que hace es (cuando no está en modo de depuración) lo busca url(<something>)y lo reemplaza por url(<absolute\path\to\something>). Escribí la cosa hace unos 10 segundos, por lo que podría necesitar un pequeño ajuste. He tenido en cuenta las URL totalmente calificadas y los DataURI base64 asegurándome de que no haya dos puntos (:) en la ruta de la URL. En nuestro entorno, las imágenes normalmente residen en la misma carpeta que sus archivos CSS, pero lo he probado con las carpetas principales ( url(../someFile.png)) y las carpetas secundarias ( url(someFolder/someFile.png).


Esta es una gran solución. Modifiqué un poco tu Regex para que también funcionara con MENOS archivos, pero el concepto original era exactamente lo que necesitaba. Gracias.
Tim Coulter

También puede poner la inicialización de expresiones regulares fuera del ciclo. Quizás como una propiedad de solo lectura estática.
Miha Markic

12

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

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

A mí me parece más un error, 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 tenga 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 configure 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 CSS dentro de todo comienza "/ Contenido / Imágenes / ... "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 raíz de la ruta.

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")

Entonces, por supuesto, podría decir que esto es RTFM, pero estoy bastante seguro de que yo y otros hemos elegido esta ruta "~ / Bundles / ..." de la plantilla predeterminada o en algún lugar de la documentación en el sitio web MSDN o ASP.NET, o simplemente me topé con él porque en realidad es un nombre bastante lógico para una ruta virtual y tiene sentido elegir tales rutas virtuales que no entren en conflicto con directorios reales.

De todos modos, así son las cosas. Microsoft no ve ningún error. No estoy de acuerdo con esto, o 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 que se incluyera la raíz de la aplicación cuando hubiera una (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.


Me parece la "solución" más simple. Los otros pueden tener efectos secundarios, como con la imagen: datos.
Fabrice

@MohamedEmaish funciona, probablemente hay algo mal. Aprenda a rastrear las solicitudes, por ejemplo, use la herramienta Fiddler para ver qué URL solicita el navegador. El objetivo no es codificar la ruta relativa completa para que su sitio web pueda instalarse en diferentes ubicaciones (rutas raíz) en el mismo servidor o su producto pueda cambiar la URL predeterminada sin tener que volver a escribir una gran parte del sitio web (el punto de tener una variable raíz de aplicación).
Tony Wall

Fui con esta opción y funcionó muy bien. Tenía que asegurarme de que cada paquete solo tuviera elementos de una sola carpeta (no puede incluir elementos de otras carpetas o subcarpetas), lo cual es un poco molesto, pero mientras funcione, ¡estoy feliz! Gracias por la publicacion.
hvaughan3

1
Gracias. Suspiro. Algún día me gustaría pasar más tiempo escribiendo código que navegando por Stack.
Bruce Pierson

Tuve un problema similar con un jquery-ui personalizado que tenía carpetas anidadas. tan pronto como nivelé las cosas como arriba, funcionó. No le gustan las carpetas anidadas.
Andrei Bazanov

11

Descubrí que CssRewriteUrlTransform no se ejecuta si hace referencia a un *.cssarchivo y tiene el *.min.cssarchivo asociado en la misma carpeta.

Para solucionar esto, elimine el *.min.cssarchivo o haga referencia a él directamente en su paquete:

bundles.Add(new Bundle("~/bundles/bootstrap")
    .Include("~/Libs/bootstrap3/css/bootstrap.min.css", new CssRewriteUrlTransform()));

Después de eso, sus URL se transformarán correctamente y sus imágenes se resolverán correctamente.


1
¡Gracias! Después de dos días de búsqueda en línea, esta es la primera mención que he visto en cualquier lugar de CssRewriteUrlTransform trabajando con archivos * .css, pero no con el archivo * .min.css asociado que se extrae cuando no se está ejecutando en una depuración ambiente. Definitivamente me parece un error. Tendré que verificar manualmente el tipo de entorno para definir un paquete con la versión no minificada para la depuración, ¡pero al menos ahora tengo una solución!
Sean

1
Esto solucionó el problema para mí. Esto ciertamente parece un error. No tiene sentido que deba ignorar CssRewriteUrlTransform si encuentra un archivo .min.css preexistente.
user1751825

10

Tal vez soy parcial, pero me gusta mucho mi solución, ya que no hace ninguna transformación, expresiones regulares, etc. y tiene la menor cantidad de código :)

Esto funciona para un sitio alojado como un Directorio virtual en un sitio web de IIS y como un sitio web raíz en IIS

Así que creé una implementación de IItemTransformencapsulado CssRewriteUrlTransformy solía VirtualPathUtilityarreglar la ruta y llamar al código existente:

/// <summary>
/// Is a wrapper class over CssRewriteUrlTransform to fix url's in css files for sites on IIS within Virutal Directories
/// and sites at the Root level
/// </summary>
public class CssUrlTransformWrapper : IItemTransform
{
    private readonly CssRewriteUrlTransform _cssRewriteUrlTransform;

    public CssUrlTransformWrapper()
    {
        _cssRewriteUrlTransform = new CssRewriteUrlTransform();
    }

    public string Process(string includedVirtualPath, string input)
    {
        return _cssRewriteUrlTransform.Process("~" + VirtualPathUtility.ToAbsolute(includedVirtualPath), input);
    }
}


//App_Start.cs
public static void Start()
{
      BundleTable.Bundles.Add(new StyleBundle("~/bundles/fontawesome")
                         .Include("~/content/font-awesome.css", new CssUrlTransformWrapper()));
}

¿Parece funcionar bien para mi?


1
Esto es perfectamente suite para mí. Excelente solución. mi voto es +1
imdadhusen

1
Esta es la respuesta correcta. La clase CssUrlTransformWrapper proporcionada por el marco aborda el problema, excepto que no funciona solo cuando la aplicación no está en la raíz del sitio web. Este contenedor aborda sucintamente esa deficiencia.
Nueve colas el

7

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.

ProperStyleBundleLa clase incluye código prestado del original CssRewriteUrlTransformpara transformar adecuadamente las rutas relativas dentro del directorio virtual. También arroja si el archivo no existe y evita la reordenación 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" ) );

2
Buena solución, pero aún falla (al igual que CssRewriteUrlTransform) si tiene un URI de datos en su CSS (por ejemplo, "datos: imagen / png; base64, ..."). No debe cambiar las URL que comienzan con "datos:" en RebaseUrlToAbsolute ().
miles82

1
@ miles82 ¡Por supuesto! Gracias por señalar esto. He cambiado RebaseUrlToAbsolute ().
nrodic

6

A partir de v1.1.0-alpha1 (paquete de lanzamiento previo), el marco utiliza el VirtualPathProviderpara 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);
            }
        }
    }
}

En realidad, qué hace esto si reemplaza las URL relativas en CSS por absolutas.
Fabrice

6

Aquí hay una Transformación de paquete que reemplazará las URL de CSS con URL relativas a ese archivo CSS. Simplemente agréguelo a su paquete y debería solucionar el problema.

public class CssUrlTransform: IBundleTransform
{
    public void Process(BundleContext context, BundleResponse response) {
        Regex exp = new Regex(@"url\([^\)]+\)", RegexOptions.IgnoreCase | RegexOptions.Singleline);
        foreach (FileInfo css in response.Files) {
            string cssAppRelativePath = css.FullName.Replace(context.HttpContext.Request.PhysicalApplicationPath, context.HttpContext.Request.ApplicationPath).Replace(Path.DirectorySeparatorChar, '/');
            string cssDir = cssAppRelativePath.Substring(0, cssAppRelativePath.LastIndexOf('/'));
            response.Content = exp.Replace(response.Content, m => TransformUrl(m, cssDir));
        }
    }


    private string TransformUrl(Match match, string cssDir) {
        string url = match.Value.Substring(4, match.Length - 5).Trim('\'', '"');

        if (url.StartsWith("http://") || url.StartsWith("data:image")) return match.Value;

        if (!url.StartsWith("/"))
            url = string.Format("{0}/{1}", cssDir, url);

        return string.Format("url({0})", url);
    }

}

¿Cómo usarlo ?, me muestra una excepción:cannot convert type from BundleFile to FileInfo
Stiger

@Stiger cambie css.FullName.Replace (a css.VirtualFile.VirtualPath.Replace (
lkurylo

Podría estar usando esto mal, pero ¿ese foreach reescribe todas las URL en cada iteración y las deja en relación con el último archivo css que vio?
Andyrooger

4

Otra opción sería utilizar el módulo de reescritura de URL de IIS para asignar la carpeta de imagen de paquete virtual a la carpeta de imagen física. A continuación se muestra un ejemplo de una regla de reescritura que podría usar para un paquete llamado "~ / bundles / yourpage / styles". .

<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 un poco de sobrecarga adicional, pero le permite tener más control sobre los nombres de sus paquetes, y también reduce la cantidad 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 relativas, todavía no puede crear múltiples paquetes.


4

La solución de Grinn es genial.

Sin embargo, no funciona para mí cuando hay referencias relativas a la carpeta principal en la url. es decirurl('../../images/car.png')

Entonces, cambié ligeramente el Includemétodo para resolver las rutas para cada coincidencia de expresiones regulares, permitiendo rutas relativas y también incrustar opcionalmente las imágenes en el CSS.

También cambié IF DEBUG para verificar en BundleTable.EnableOptimizationslugar de HttpContext.Current.IsDebuggingEnabled.

    public new Bundle Include(params string[] virtualPaths)
    {
        if (!BundleTable.EnableOptimizations)
        {
            // Debugging. Bundling will not occur so act normal and no one gets hurt. 
            base.Include(virtualPaths.ToArray());
            return this;
        }
        var bundlePaths = new List<string>();
        var server = HttpContext.Current.Server;
        var pattern = new Regex(@"url\s*\(\s*([""']?)([^:)]+)\1\s*\)", RegexOptions.IgnoreCase);
        foreach (var path in virtualPaths)
        {
            var contents = File.ReadAllText(server.MapPath(path));
            var matches = pattern.Matches(contents);
            // Ignore the file if no matches
            if (matches.Count == 0)
            {
                bundlePaths.Add(path);
                continue;
            }
            var bundlePath = (System.IO.Path.GetDirectoryName(path) ?? string.Empty).Replace(@"\", "/") + "/";
            var bundleUrlPath = VirtualPathUtility.ToAbsolute(bundlePath);
            var bundleFilePath = string.Format("{0}{1}.bundle{2}",
                                               bundlePath,
                                               System.IO.Path.GetFileNameWithoutExtension(path),
                                               System.IO.Path.GetExtension(path));
            // Transform the url (works with relative path to parent folder "../")
            contents = pattern.Replace(contents, m =>
            {
                var relativeUrl = m.Groups[2].Value;
                var urlReplace = GetUrlReplace(bundleUrlPath, relativeUrl, server);
                return string.Format("url({0}{1}{0})", m.Groups[1].Value, urlReplace);
            });
            File.WriteAllText(server.MapPath(bundleFilePath), contents);
            bundlePaths.Add(bundleFilePath);
        }
        base.Include(bundlePaths.ToArray());
        return this;
    }


    private string GetUrlReplace(string bundleUrlPath, string relativeUrl, HttpServerUtility server)
    {
        // Return the absolute uri
        Uri baseUri = new Uri("http://dummy.org");
        var absoluteUrl = new Uri(new Uri(baseUri, bundleUrlPath), relativeUrl).AbsolutePath;
        var localPath = server.MapPath(absoluteUrl);
        if (IsEmbedEnabled && File.Exists(localPath))
        {
            var fi = new FileInfo(localPath);
            if (fi.Length < 0x4000)
            {
                // Embed the image in uri
                string contentType = GetContentType(fi.Extension);
                if (null != contentType)
                {
                    var base64 = Convert.ToBase64String(File.ReadAllBytes(localPath));
                    // Return the serialized image
                    return string.Format("data:{0};base64,{1}", contentType, base64);
                }
            }
        }
        // Return the absolute uri 
        return absoluteUrl;
    }

Espero que ayude, saludos.


2

Simplemente puede agregar otro nivel de profundidad a su ruta de 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 requerirá ningún procesamiento previo. Dada la extensión y complejidad de algunas de estas respuestas, prefiero hacerlo de esta manera.


Esto no ayuda cuando tiene su aplicación web como aplicación virtual en IIS. Quiero decir que puede funcionar, pero debes nombrar tu aplicación virtual IIS como en tu código, que no es lo que quieres, ¿verdad?
psulek

Tengo el mismo problema cuando la aplicación es una aplicación virtual en IIS. Esta respuesta me ayuda.
BILL

2

Tuve este problema con los paquetes que tienen rutas incorrectas a las imágenes y CssRewriteUrlTransformno resuelven las rutas principales relativas ..correctamente (también hubo problemas con recursos externos como webfonts). Es por eso que escribí esta transformación personalizada (parece hacer todo lo anterior correctamente):

public class CssRewriteUrlTransform2 : IItemTransform
{
    public string Process(string includedVirtualPath, string input)
    {
        var pathParts = includedVirtualPath.Replace("~/", "/").Split('/');
        pathParts = pathParts.Take(pathParts.Count() - 1).ToArray();
        return Regex.Replace
        (
            input,
            @"(url\(['""]?)((?:\/??\.\.)*)(.*?)(['""]?\))",
            m => 
            {
                // Somehow assigning this to a variable is faster than directly returning the output
                var output =
                (
                    // Check if it's an aboslute url or base64
                    m.Groups[3].Value.IndexOf(':') == -1 ?
                    (
                        m.Groups[1].Value +
                        (
                            (
                                (
                                    m.Groups[2].Value.Length > 0 ||
                                    !m.Groups[3].Value.StartsWith('/')
                                )
                            ) ?
                            string.Join("/", pathParts.Take(pathParts.Count() - m.Groups[2].Value.Count(".."))) :
                            ""
                        ) +
                        (!m.Groups[3].Value.StartsWith('/') ? "/" + m.Groups[3].Value : m.Groups[3].Value) +
                        m.Groups[4].Value
                    ) :
                    m.Groups[0].Value
                );
                return output;
            }
        );
    }
}

Editar: no me di cuenta, pero usé algunos métodos de extensión personalizados en el código. El código fuente de esos es:

/// <summary>
/// Based on: http://stackoverflow.com/a/11773674
/// </summary>
public static int Count(this string source, string substring)
{
    int count = 0, n = 0;

    while ((n = source.IndexOf(substring, n, StringComparison.InvariantCulture)) != -1)
    {
        n += substring.Length;
        ++count;
    }
    return count;
}

public static bool StartsWith(this string source, char value)
{
    if (source.Length == 0)
    {
        return false;
    }
    return source[0] == value;
}

Por supuesto, debería ser posible reemplazarlo String.StartsWith(char)con String.StartsWith(string).


No tengo una sobrecarga String.Count () que acepta una cadena ( m.Groups[2].Value.Count("..")no funciona) Y Value.StartsWith('/')tampoco funciona porque StartsWith espera una cadena en lugar de un char.
jao

@jao my bad Incluí mis propios métodos de extensión en el código sin darme cuenta.
Jahu

1
@jao agregó el código fuente de esos métodos de extensión a la respuesta.
Jahu

1

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

  1. ir con 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 desde diferentes directorios. Desventajas: necesita transformar cada paquete problemático

  1. Use 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 limitaciones para combinar hojas de CSS de diferentes directorios en un solo paquete.

0

CssRewriteUrlTransformarreglaron mi problema
Si su código aún no carga imágenes después de usarlo CssRewriteUrlTransform, cambie su nombre de archivo css de:

.Include("~/Content/jquery/jquery-ui-1.10.3.custom.css", new CssRewriteUrlTransform())

A:

.Include("~/Content/jquery/jquery-ui.css", new CssRewriteUrlTransform())

De alguna manera. (Puntos) no se reconocen en url.


0

Solo recuerde arreglar múltiples inclusiones CSS en un paquete como:

bundles.Add(new StyleBundle("~/Content/styles/jquery-ui")
    .Include("~/Content/css/path1/somestyle1.css", "~/Content/css/path2/somestyle2.css"));

No puede simplemente agregar new CssRewriteUrlTransform()al final como puede hacerlo con un archivo CSS ya que el método no lo admite, por lo que debe usar Includevarias veces :

bundles.Add(new StyleBundle("~/Content/styles/jquery-ui")
    .Include("~/Content/css/path1/somestyle1.css", new CssRewriteUrlTransform())
    .Include("~/Content/css/path2/somestyle2.css", new CssRewriteUrlTransform()));
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.