css ~/ - MVC4 StyleBundle no resuelve imágenes




8 Answers

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 el file name para ese paquete específico (es decir, /bundle puede 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

De acuerdo con el comentario a continuación de @Hao Kung, alternativamente, esto puede lograrse ahora aplicando una CssRewriteUrlTransformation ( Cambie las referencias de las URL relativas a los archivos CSS cuando se incluye ).

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

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

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?




Mejor aún (IMHO) implementar un paquete personalizado que corrige las rutas de 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 lugar de...

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

Lo que hace es (cuando no está en el modo de depuración) busca url(<something>) y lo reemplaza con url(<absolute\path\to\something>) . Lo escribí hace unos 10 segundos, así que podría necesitar un poco de ajuste. He tenido en cuenta las URL completas y las DataURIs de 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 ).




Descubrí que CssRewriteUrlTransform no se ejecuta si está haciendo referencia a un archivo *.min.css y tiene el archivo *.min.css asociado en la misma carpeta.

Para solucionar este problema, elimine el archivo *.min.css o *.min.css 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.




Tal vez estoy sesgado, pero me gusta mi solución, ya que no hace ninguna transformación, regex, 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 Implentation of IItemTransform encapsulado el CssRewriteUrlTransform y usé VirtualPathUtility para arreglar 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?




Aquí hay una transformación de paquete que reemplazará las URL de css con las URL relacionadas con ese archivo css. Solo agrégalo a tu 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);
    }

}



La solución de Grinn es genial.

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

Por lo tanto, modifiqué ligeramente el método de Include para resolver las rutas para cada coincidencia de expresiones regulares, permitiendo las rutas relativas y también, opcionalmente, incrustar las imágenes en el css.

También cambié el IF DEBUG para verificar BundleTable.EnableOptimizations lugar 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.




Tuve este problema con paquetes que tenían una ruta incorrecta a las imágenes y CssRewriteUrlTransform no resolvía las rutas principales relativas .. correctamente (también había un problema 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;
            }
        );
    }
}

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

/// <summary>
/// Based on: http://.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 reemplazar String.StartsWith(char) con String.StartsWith(string) .




CssRewriteUrlTransformarreglado mi problema
Si su código aún no carga imágenes después de usarlo CssRewriteUrlTransform, cambie el 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.




Related

css jquery-ui asp.net-mvc-4 bundle asp.net-optimization