MVC4 StyleBundle ไม่แก้ไขภาพ


293

คำถามของฉันคล้ายกับสิ่งนี้:

ASP.NET MVC 4 Minification & ภาพพื้นหลัง

ยกเว้นว่าฉันต้องการที่จะยึดติดกับการรวมกลุ่มของ MVC ถ้าฉันสามารถทำได้ ฉันประสบปัญหาสมองล้มเหลวในการพยายามคิดออกว่ารูปแบบที่ถูกต้องคืออะไรสำหรับการระบุบันเดิลสไตล์เช่นชุด css แบบสแตนด์อโลนและชุดรูปภาพเช่น jQuery UI

ฉันมีโครงสร้างเว็บไซต์ MVC ทั่วไปที่มี/Content/css/ที่มี CSS styles.cssฐานของฉันเช่น ภายในโฟลเดอร์ css นั้นฉันยังมีโฟลเดอร์ย่อยเช่น/jquery-uiที่มีไฟล์ CSS และ/imagesโฟลเดอร์ เส้นทางรูปภาพใน jQuery UI CSS นั้นสัมพันธ์กับโฟลเดอร์นั้นและฉันไม่ต้องการยุ่งกับมัน

ตามที่ฉันเข้าใจเมื่อฉันระบุStyleBundleฉันจำเป็นต้องระบุเส้นทางเสมือนซึ่งไม่ตรงกับเส้นทางเนื้อหาจริงเนื่องจาก (สมมติว่าฉันเพิกเฉยเส้นทางไปยังเนื้อหา) IIS จะพยายามแก้ไขเส้นทางนั้นเป็นไฟล์ทางกายภาพ ดังนั้นฉันจึงระบุ:

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

แสดงผลโดยใช้:

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

ฉันเห็นคำขอไปที่:

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

นี่เป็นการส่งคืนการตอบสนอง CSS ที่ถูกต้องและลดขนาดลง แต่เบราว์เซอร์ก็ส่งคำขอรูปภาพที่เชื่อมโยงกันเป็น:

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

404ซึ่งเป็น

ผมเข้าใจว่าส่วนสุดท้ายของ URL ของฉันjquery-uiเป็น URL extensionless, /styles/images/จัดการสำหรับมัดของฉันดังนั้นฉันสามารถดูว่าทำไมการร้องขอญาติภาพที่เป็นเพียง

ดังนั้นคำถามของฉันคือวิธีที่ถูกต้องในการจัดการสถานการณ์นี้คืออะไร?


9
หลังจากท้อแท้ซ้ำแล้วซ้ำอีกกับส่วน Bundling และ Minification ใหม่ฉันย้ายไปที่Casseteแม่มดตอนนี้ฟรีและทำงานได้ดีขึ้น!
balexandre

3
ขอบคุณสำหรับลิงค์ Cassette ดูดีและฉันจะตรวจสอบอย่างแน่นอน แต่ฉันต้องการที่จะยึดติดกับวิธีการที่ให้ไว้ถ้าเป็นไปได้แน่นอนว่าต้องเป็นไปได้โดยไม่ต้องยุ่งกับเส้นทางภาพในไฟล์ CSS ของบุคคลที่สามทุกครั้งที่มีการเปิดตัวเวอร์ชันใหม่ สำหรับตอนนี้ฉันยังคง ScriptBundles ของฉัน (ซึ่งใช้งานได้ดี) แต่เปลี่ยนกลับเป็นลิงก์ CSS ธรรมดาจนกว่าฉันจะได้รับการแก้ไข ไชโย
Tom W Hall

การเพิ่มข้อผิดพลาดที่น่าจะเป็นไปได้สำหรับเหตุผล SEO: ไม่พบตัวควบคุมสำหรับเส้นทาง '/bundles/images/blah.jpg' หรือไม่ได้ติดตั้ง IController
Luke Puplett

คำตอบ:


361

ตามหัวข้อนี้ในการรวมกลุ่ม MVC4 css และการอ้างอิงรูปภาพหากคุณกำหนดบันเดิลของคุณเป็น:

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

โดยที่คุณกำหนดบันเดิลบนพา ธ เดียวกันกับไฟล์ต้นฉบับที่สร้างบันเดิลพา ธ อิมเมจแบบสัมพัทธ์จะยังคงใช้งานได้ ส่วนสุดท้ายของเส้นทางของบันเดิลนั้นเป็นจริงfile nameสำหรับบันเดิลนั้นโดยเฉพาะ (เช่น/bundleสามารถเป็นชื่อใดก็ได้ที่คุณต้องการ)

สิ่งนี้จะใช้งานได้หากคุณรวม CSS ไว้ด้วยกันจากโฟลเดอร์เดียวกัน (ซึ่งฉันคิดว่าเหมาะสมกับมุมมองของการรวมกลุ่ม)

ปรับปรุง

ตามความคิดเห็นด้านล่างโดย @Hao Kung หรืออาจทำได้โดยใช้CssRewriteUrlTransformation( เปลี่ยนการอ้างอิง URL สัมพัทธ์กับไฟล์ CSS เมื่อรวม )

หมายเหตุ: ฉันยังไม่ได้ยืนยันความคิดเห็นเกี่ยวกับปัญหาเกี่ยวกับการเขียนเส้นทางที่แน่นอนภายในไดเรกทอรีเสมือนดังนั้นสิ่งนี้อาจไม่ได้ผลสำหรับทุกคน (?)

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

1
ตำนาน! ใช่ว่าทำงานได้อย่างสมบูรณ์ ฉันมี CSS ในระดับที่แตกต่างกัน แต่พวกเขาแต่ละคนมีโฟลเดอร์รูปภาพของตัวเองเช่นไซต์หลักของฉัน CSS อยู่ในโฟลเดอร์ CSS รูทจากนั้น jquery-ui อยู่ในนั้นพร้อมโฟลเดอร์รูปภาพของตัวเองดังนั้นฉันจึงระบุ 2 กลุ่มสำหรับหนึ่ง CSS พื้นฐานและอีกอันสำหรับ jQuery UI - ซึ่งอาจจะไม่เหมาะสมที่สุดในแง่ของการร้องขอ แต่อายุการใช้งานสั้น ไชโย!
Tom W Hall

3
ใช่ขออภัยจนกระทั่งการรวมกลุ่มมีการสนับสนุนการเขียน url ที่ฝังอยู่ภายใน css อีกครั้งคุณต้องมีไดเรกทอรีเสมือนของ css bundle ให้ตรงกับไฟล์ css ก่อนที่จะทำการ bundling นี่คือเหตุผลที่กลุ่มแม่แบบเริ่มต้นไม่มี URL เช่น ~ / bundles / themes และดูเหมือนโครงสร้างไดเรกทอรี: ~ / content / theemes / base / css
Hao Kung

27
ตอนนี้ได้รับการสนับสนุนผ่าน ItemTransforms, .Include ("~ / Content / css / jquery-ui / *. css", CssRewriteUrlTransform () ใหม่); ใน 1.1Beta1 ควรแก้ไขปัญหานี้
Hao Kung

2
ได้รับการแก้ไขใน Microsoft ASP.NET Web Optimization Framework 1.1.3 หรือไม่ ฉันไม่พบข้อมูลใด ๆ เกี่ยวกับสิ่งที่เปลี่ยนแปลงในนี้
Andrus

13
ใหม่ CssRewriteUrlTransform () นั้นใช้ได้ถ้าคุณมีเว็บไซต์ใน IIS แต่ถ้าเป็นแอปพลิเคชันหรือแอปพลิเคชันย่อยสิ่งนี้จะไม่ทำงานและคุณต้องหันไปกำหนดบันเดิลของคุณในตำแหน่งเดียวกับ CSS
avidenic

34

วิธีการแก้ปัญหาของ Grinn / ThePirat ทำงานได้ดี

ฉันไม่ชอบที่มันจะรวมวิธีการในบันเดิลและสร้างไฟล์ชั่วคราวในไดเรกทอรีเนื้อหา (พวกเขาลงเอยด้วยการเช็คอินปรับใช้แล้วบริการจะไม่เริ่มต้น!)

ดังนั้นเพื่อติดตามการออกแบบของ Bundling ฉันเลือกที่จะดำเนินการโดยใช้รหัสเดียวกันเป็นหลัก แต่ในการใช้งาน 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);
            }
        }
    }
}

จากนั้นพันสิ่งนี้ไว้ใน Bundle Implemetation

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

ตัวอย่างการใช้งาน:

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

นี่คือวิธีการขยายของฉันสำหรับ 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(@"\", "/");
    }

ดูเหมือนว่าฉันจะทำความสะอาดเช่นกัน ขอบคุณ ฉันลงคะแนนให้คุณทั้งสามคนเพราะดูเหมือนจะเป็นความพยายามของทีม :)
Josh Mouch

รหัสตามที่คุณมีตอนนี้ไม่ทำงานสำหรับฉัน ฉันกำลังพยายามแก้ไข แต่คิดว่าฉันจะแจ้งให้คุณทราบ ไม่มีคอนเท็กซ์บริบท. HttpContext.RelativeFromAbsolutePath นอกจากนี้หากเส้นทาง URL เริ่มต้นด้วย "/" (ทำให้สมบูรณ์) พา ธ ของคุณจะรวมตรรกะเข้าด้วยกัน
Josh Mouch

2
@AcidPAT ใช้งานได้ดี ลอจิกล้มเหลวหาก url มีการสอบถาม (บางไลบรารีของบุคคลที่สามเพิ่มไว้เช่น FontAwesome สำหรับการอ้างอิง. woff) เป็นการแก้ไขที่ง่าย หนึ่งสามารถปรับ Regex หรือแก้ไขก่อนที่จะเรียกrelativeToCSS Path.GetFullPath()
sergiopereira

2
@ChrisMarisic รหัสของคุณดูเหมือนจะไม่ทำงาน - response.Files เป็นอาร์เรย์ของ BundleFiles วัตถุเหล่านี้ไม่มีคุณสมบัติเช่น "Exists", "DirectoryName" ฯลฯ
Nick Coad

2
@ChrisMarisic อาจจะมีเนมสเปซที่ฉันควรจะนำเข้าที่ให้วิธีการขยายสำหรับชั้นเรียน BundleFile?
Nick Coad

20

ยังดีกว่า (IMHO) ใช้ Bundle แบบกำหนดเองที่แก้ไขเส้นทางของรูปภาพ ฉันเขียนหนึ่งแอพของฉัน

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

}

หากต้องการใช้ให้ทำ:

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

...แทน...

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

สิ่งที่มันไม่เป็น (เมื่อไม่อยู่ในโหมดการแก้ปัญหา) จะมองหาและแทนที่มันด้วยurl(<something>) url(<absolute\path\to\something>)ฉันเขียนสิ่งนี้เมื่อประมาณ 10 วินาทีที่แล้วดังนั้นมันอาจต้องมีการปรับแต่งเล็กน้อย ฉันได้พิจารณา URL ที่ผ่านการรับรองโดยสมบูรณ์และ base64 DataURIs ด้วยการทำให้แน่ใจว่าไม่มีโคลอน (:) ในพา ธ URL ในสภาพแวดล้อมของเราภาพปกติจะอยู่ในโฟลเดอร์เดียวกับไฟล์ css แต่ฉันได้ทดสอบกับทั้งพาเรนต์โฟลเดอร์ ( url(../someFile.png)) และโฟลเดอร์ย่อย ( url(someFolder/someFile.png)


นี่เป็นทางออกที่ดี ฉันปรับเปลี่ยน Regex ของคุณเล็กน้อยเพื่อให้สามารถทำงานกับไฟล์ LESS ได้ แต่แนวคิดดั้งเดิมคือสิ่งที่ฉันต้องการ ขอบคุณ
Tim Coulter

คุณอาจทำให้การเริ่มต้น regex นอกวงเช่นกัน อาจเป็นคุณสมบัติแบบอ่านอย่างเดียวคงที่
Miha Markic

12

ไม่จำเป็นต้องระบุการแปลงสภาพหรือมีเส้นทางย่อยไดเรกทอรีบ้า หลังจากการแก้ไขปัญหามากฉันแยกมันเป็นกฎ "ง่าย" นี้ (มันเป็นข้อบกพร่องหรือไม่) ...

หากพา ธ บันเดิลของคุณไม่เริ่มต้นด้วยรูทสัมพัทธ์ของไอเท็มที่ถูกรวมไว้รูทเว็บแอ็พพลิเคชันจะไม่ถูกนำมาพิจารณา

ฟังดูมีข้อบกพร่องมากกว่าสำหรับฉันแต่ทว่านั่นเป็นวิธีที่คุณจะแก้ไขด้วยเวอร์ชัน. NET 4.51 ปัจจุบัน บางทีคำตอบอื่น ๆ นั้นจำเป็นสำหรับการสร้าง ASP.NET แบบเก่าไม่สามารถพูดได้ว่าไม่มีเวลาในการทดสอบย้อนหลังทั้งหมด

เพื่อชี้แจงนี่คือตัวอย่าง:

ฉันมีไฟล์เหล่านี้ ...

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

จากนั้นตั้งค่าบันเดิลเช่น ...

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

และทำให้เหมือน ...

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

และรับ "พฤติกรรม" (ข้อผิดพลาด) ไฟล์ CSS เองมีรูทแอปพลิเคชัน (เช่น "http: // localhost: 1234 / MySite / Content / Site.css") แต่ภาพ CSS ภายในเริ่มต้นทั้งหมด "/ เนื้อหา / รูปภาพ / ... "หรือ" / Images / ... "ขึ้นอยู่กับว่าฉันจะเพิ่มการแปลงหรือไม่

แม้แต่พยายามสร้างโฟลเดอร์ "การรวมกลุ่ม" เพื่อดูว่ามันจะทำอย่างไรกับเส้นทางที่มีอยู่หรือไม่ แต่นั่นก็ไม่ได้เปลี่ยนแปลงอะไรเลย การแก้ปัญหาคือความต้องการที่ชื่อของบันเดิลต้องเริ่มต้นด้วยรูทพา ธ

ความหมายตัวอย่างนี้ได้รับการแก้ไขโดยการลงทะเบียนและการแสดงผลเส้นทางมัดเช่น ..

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

ดังนั้นคุณสามารถพูดได้ว่านี่คือ RTFM แต่ฉันค่อนข้างแน่ใจว่าฉันและคนอื่นเลือกเส้นทาง "~ / Bundles / ... " นี้จากแม่แบบเริ่มต้นหรือที่อื่นในเอกสารที่เว็บไซต์ MSDN หรือ ASP.NET หรือ เพิ่งสะดุดกับมันเพราะจริงๆแล้วมันเป็นชื่อที่ค่อนข้างสมเหตุสมผลสำหรับเส้นทางเสมือนและเหมาะสมที่จะเลือกเส้นทางเสมือนดังกล่าวซึ่งไม่ขัดแย้งกับไดเรกทอรีจริง

อย่างไรก็ตามนั่นคือวิธีที่มันเป็น Microsoft ไม่เห็นข้อผิดพลาด ฉันไม่เห็นด้วยกับสิ่งนี้ไม่ว่าจะควรทำงานตามที่คาดไว้หรือมีข้อยกเว้นบางอย่างหรือมีการแทนที่เพิ่มเติมเพื่อเพิ่มเส้นทางของบันเดิลซึ่งเลือกที่จะรวมแอปพลิเคชันรูทหรือไม่ ฉันไม่สามารถจินตนาการได้ว่าทำไมทุกคนไม่ต้องการให้รูทแอปพลิเคชันรวมอยู่เมื่อมี (โดยปกติแล้วหากคุณไม่ได้ติดตั้งเว็บไซต์ของคุณด้วย DNS alias / รูทเว็บไซต์เริ่มต้น) ดังนั้นที่จริงแล้วควรเป็นค่าเริ่มต้นอยู่ดี


ดูเหมือนว่า "ทางออก" ที่ง่ายที่สุดสำหรับฉัน คนอื่น ๆ อาจมีผลข้างเคียงเช่นเดียวกับภาพ: ข้อมูล
Fabrice

@MohamedEmaish มันทำงานได้คุณอาจจะมีบางอย่างผิดปกติ เรียนรู้วิธีติดตามคำขอเช่นใช้ Fiddler Tool เพื่อดูว่าเบราว์เซอร์ใดถูกร้องขอ เป้าหมายไม่ใช่การเขียนโค้ดที่ยากลำบากสำหรับเส้นทางที่สัมพันธ์กันทั้งหมดเพื่อให้เว็บไซต์ของคุณสามารถติดตั้งในตำแหน่งต่าง ๆ (เส้นทางของรูท) บนเซิร์ฟเวอร์เดียวกันหรือผลิตภัณฑ์ของคุณสามารถเปลี่ยน URL เริ่มต้นได้โดยไม่ต้องเขียนเว็บไซต์จำนวนมาก (จุดที่มีและตัวแปรรูทของแอ็พพลิเคชัน)
Tony Wall

ไปกับตัวเลือกนี้และมันใช้งานได้ดี ต้องทำให้แน่ใจว่าแต่ละบันเดิลมีรายการจากโฟลเดอร์เดียว (ไม่สามารถรวมรายการจากโฟลเดอร์หรือโฟลเดอร์ย่อยอื่น ๆ ) ซึ่งน่ารำคาญเล็กน้อย แต่ตราบใดที่มันใช้งานได้ฉันมีความสุข! ขอบคุณสำหรับการโพสต์
hvaughan3

1
ขอบคุณ ถอนหายใจ บางวันฉันต้องการใช้เวลาในการเขียนโค้ดมากกว่าการเรียกดูสแต็ก
Bruce Pierson

ฉันมีปัญหาที่คล้ายกันที่กำหนดเอง jQuery-UI ซึ่งมีโฟลเดอร์ซ้อนกัน ทันทีที่ฉันปรับระดับสิ่งต่างๆตามที่กล่าวมามันก็ใช้งานได้ มันไม่ชอบโฟลเดอร์ซ้อนกัน
Andrei Bazanov

11

ฉันพบว่า CssRewriteUrlTransform ไม่สามารถทำงานได้หากคุณอ้างอิง*.cssไฟล์และคุณมี*.min.cssไฟล์ที่เกี่ยวข้องในโฟลเดอร์เดียวกัน

ในการแก้ไขปัญหานี้ให้ลบ*.min.cssไฟล์หรืออ้างอิงโดยตรงในบันเดิลของคุณ:

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

หลังจากนั้นคุณจะทำเช่นนั้น URL ของคุณจะถูกแปลงอย่างถูกต้องและรูปภาพของคุณควรได้รับการแก้ไขอย่างถูกต้อง


1
ขอบคุณ! หลังจากสองวันของการค้นหาออนไลน์นี่เป็นการกล่าวถึงครั้งแรกที่ฉันได้เห็นทุกที่ของ CssRewriteUrlTransform ทำงานกับไฟล์ * .css แต่ไม่ใช่ไฟล์ * .min.css ที่เกี่ยวข้องซึ่งดึงมาเมื่อคุณไม่ได้ทำงานในการดีบัก สิ่งแวดล้อม แน่นอนดูเหมือนว่าเป็นข้อบกพร่องสำหรับฉัน จะต้องตรวจสอบประเภทสภาพแวดล้อมด้วยตนเองเพื่อกำหนดบันเดิลด้วยเวอร์ชันที่ไม่ได้แก้ไขสำหรับการดีบัก แต่อย่างน้อยฉันก็มีวิธีแก้ไขปัญหาทันที!
Sean

1
นี่เป็นการแก้ไขปัญหาสำหรับฉัน ดูเหมือนว่านี่จะเป็นข้อผิดพลาดอย่างแน่นอน มันไม่มีเหตุผลว่าควรละเว้น CssRewriteUrlTransform หากพบไฟล์. min.css ที่มีอยู่แล้ว
user1751825

10

บางทีฉันลำเอียง แต่ฉันชอบวิธีการแก้ปัญหาของฉันเพราะมันไม่ได้ทำการแปลงใด ๆ ฯลฯ ของ regex และมีรหัสน้อยที่สุด :)

สิ่งนี้ใช้ได้กับไซต์ที่โฮสต์เป็นไดเรกทอรีเสมือนในเว็บไซต์ IIS และเป็นเว็บไซต์รูทบน IIS

ดังนั้นฉันจึงสร้าง Implentation ของIItemTransformencapsulated CssRewriteUrlTransformและใช้VirtualPathUtilityเพื่อแก้ไขเส้นทางและเรียกรหัสที่มีอยู่:

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

ดูเหมือนว่าจะทำงานได้ดีสำหรับฉัน


1
นี่คือห้องสวีทที่สมบูรณ์แบบสำหรับฉัน ทางออกที่ดีเยี่ยม คะแนนของฉันคือ +1
imdadhusen

1
นี่คือคำตอบที่ถูกต้อง คลาส CssUrlTransformWrapper จัดทำโดยเฟรมเวิร์กแก้ปัญหายกเว้นว่าจะไม่ทำงานเฉพาะเมื่อแอปพลิเคชันไม่ได้อยู่ที่รูทเว็บไซต์ เสื้อคลุมนี้ย่อที่อยู่ที่บกพร่อง
Nine Tails

7

ถึงแม้ว่าคำตอบที่คริสแบ็กซ์เตอร์จะช่วยให้มีปัญหาเดิมก็ไม่ได้ทำงานในกรณีของฉันเมื่อสมัครเป็นเจ้าภาพในไดเรกทอรีเสมือน หลังจากตรวจสอบตัวเลือกต่าง ๆ ฉันก็ใช้วิธี DIY

ProperStyleBundleชั้นเรียนรวมถึงรหัสที่ยืมมาจากต้นฉบับCssRewriteUrlTransformเพื่อเปลี่ยนเส้นทางที่สัมพันธ์กันภายในไดเรกทอรีเสมือน นอกจากนี้ยังโยนหากไฟล์ไม่มีอยู่และป้องกันการเรียงลำดับไฟล์ในบันเดิล (รหัสที่นำมาจาก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 ) + ")" ) );
            }
        }
    }
}

ใช้มันเหมือน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
วิธีแก้ปัญหาที่ดี แต่ก็ยังล้มเหลว (เช่น CssRewriteUrlTransform) หากคุณมี URI ข้อมูลใน CSS ของคุณ (เช่น "data: image / png; base64, ... ") คุณไม่ควรเปลี่ยน url ที่ขึ้นต้นด้วย "data:" ใน RebaseUrlToAbsolute ()
miles82

1
@ miles82 แน่นอน! ขอบคุณที่ชี้นำสิ่งนี้ ฉันเปลี่ยน RebaseUrlToAbsolute () แล้ว
nrodic

6

ตั้งแต่ v1.1.0-alpha1 (แพกเกจรุ่นก่อนวางจำหน่าย) เฟรมเวิร์กใช้VirtualPathProviderเพื่อเข้าถึงไฟล์แทนการสัมผัสกับระบบไฟล์ฟิสิคัล

หม้อแปลงที่อัพเดตสามารถดูได้จากด้านล่าง:

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

ที่จริงแล้วจะทำอย่างไรถ้าแทนที่ URL ที่เกี่ยวข้องใน CSS ด้วย URL ที่แน่นอน
Fabrice

6

นี่คือการแปลง Bundle ที่จะแทนที่ URL css ด้วย url ที่สัมพันธ์กับไฟล์ css นั้น เพียงเพิ่มลงในชุดรวมของคุณและควรแก้ไขปัญหา

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

}

วิธีการใช้งาน? มันแสดงให้ฉันเห็นข้อยกเว้น:cannot convert type from BundleFile to FileInfo
Stiger

@Stiger change css.FullName.Replace (เป็น css.VirtualFile.VirtualPath.Replace (
lkurylo

ฉันอาจจะใช้ผิดนี้ แต่ไม่ว่าจะเขียน URL ทั้งหมดในการทำซ้ำทุกครั้งและปล่อยให้พวกเขาเทียบกับไฟล์ CSS ล่าสุดที่เห็น?
Andyrooger

4

ตัวเลือกอื่นจะใช้โมดูล IIS URL Rewrite เพื่อแมปโฟลเดอร์รูปภาพบันเดิลเสมือนกับโฟลเดอร์อิมเมจฟิสิคัล ด้านล่างนี้เป็นตัวอย่างของกฎการเขียนซ้ำจากที่คุณสามารถใช้สำหรับบันเดิลที่เรียกว่า "~ / บันเดิล / yourpage / สไตล์" - สังเกตการจับคู่ regex กับตัวอักษรและตัวเลขรวมถึงเครื่องหมายขีดกลางขีดล่างและจุดซึ่งเป็นเรื่องธรรมดาในชื่อไฟล์รูปภาพ .

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

วิธีนี้สร้างค่าใช้จ่ายเพิ่มเล็กน้อย แต่ช่วยให้คุณสามารถควบคุมชื่อบันเดิลของคุณได้มากขึ้นและยังลดจำนวนของบันเดิลที่คุณอาจต้องอ้างอิงในหน้าเดียว แน่นอนถ้าคุณต้องอ้างอิงไฟล์ css ของบุคคลที่สามหลายรายการที่มีการอ้างอิงเส้นทางภาพสัมพัทธ์คุณยังคงไม่สามารถสร้างกลุ่มหลาย ๆ กลุ่มได้


4

ทางออกของ Grinn นั้นยอดเยี่ยม

อย่างไรก็ตามมันไม่ทำงานสำหรับฉันเมื่อมีการอ้างอิงแบบสัมพันธ์กับโฟลเดอร์แม่ใน url กล่าวคือurl('../../images/car.png')

ดังนั้นฉันจึงเปลี่ยนIncludeวิธีการเล็กน้อยเพื่อแก้ไขเส้นทางสำหรับการจับคู่ regex แต่ละครั้งช่วยให้เส้นทางสัมพัทธ์และยังสามารถเลือกที่จะฝังรูปภาพใน css

ฉันยังมีการเปลี่ยนแปลงหาก DEBUG เพื่อตรวจสอบแทนBundleTable.EnableOptimizationsHttpContext.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;
    }

หวังว่าจะช่วยด้วยความนับถือ


2

คุณสามารถเพิ่มความลึกอีกระดับในพา ธ บันเดิลเสมือนของคุณ

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

นี่คือคำตอบที่ใช้เทคโนโลยีขั้นต่ำสุดยอดและเป็นแฮ็ค แต่ก็ใช้งานได้และไม่ต้องการการประมวลผลล่วงหน้า ด้วยความยาวและความซับซ้อนของคำตอบบางข้อฉันชอบที่จะทำแบบนี้


สิ่งนี้ไม่ได้ช่วยเมื่อคุณมีเว็บแอปเป็นแอปพลิเคชันเสมือนใน IIS ฉันหมายความว่ามันสามารถทำงานได้ แต่คุณต้องตั้งชื่อแอปเสมือนของ IIS ในรหัสของคุณซึ่งไม่ใช่สิ่งที่คุณต้องการใช่ไหม
psulek

ฉันมีปัญหาเดียวกันเมื่อแอพเป็นแอปพลิเคชันเสมือนจริงใน IIS คำตอบนี้ช่วยฉัน
BILL

2

ฉันมีปัญหากับการรวมกลุ่มที่มีเส้นทางที่ไม่ถูกต้องไปยังรูปภาพและCssRewriteUrlTransformไม่แก้ไขเส้นทางพาเรนต์ที่สัมพันธ์กัน..อย่างถูกต้อง (มีปัญหากับทรัพยากรภายนอกเช่น webfonts) นั่นเป็นเหตุผลที่ฉันเขียนการแปลงที่กำหนดเองนี้ (ดูเหมือนจะทำทุกอย่างที่ถูกต้อง):

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

แก้ไข: ฉันไม่ทราบ แต่ฉันใช้วิธีการขยายที่กำหนดเองในรหัส ซอร์สโค้ดของเหล่านั้นคือ:

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

แน่นอนมันควรจะเป็นไปได้ที่จะแทนที่ด้วยString.StartsWith(char)String.StartsWith(string)


ฉันไม่มี String.Count () เกินพิกัดที่ยอมรับสตริง ( m.Groups[2].Value.Count("..")ใช้งานไม่ได้) และValue.StartsWith('/')ไม่ทำงานอย่างใดอย่างหนึ่งเนื่องจาก StartsWith คาดว่าจะใช้สตริงแทนอักขระ
jao

@ จ้าวของฉันไม่ดีฉันรวมวิธีการขยายของฉันเองในรหัสโดยไม่ทราบ
jahu

1
@jao เพิ่มซอร์สโค้ดของวิธีการขยายเหล่านั้นในคำตอบ
jahu

1

หลังจากการสอบสวนเล็กน้อยฉันได้ข้อสรุปดังต่อไปนี้: คุณมี 2 ตัวเลือก:

  1. ไปกับการเปลี่ยนแปลง แพ็คเกจที่มีประโยชน์มากสำหรับสิ่งนี้: https://bundletransformer.codeplex.com/ คุณต้องการการแปลงต่อไปนี้สำหรับบันเดิลที่มีปัญหาทุกชุด:

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

ข้อดี: สำหรับโซลูชันนี้คุณสามารถตั้งชื่อบันเดิลของคุณตามที่คุณต้องการ => คุณสามารถรวมไฟล์ css เป็นหนึ่งบันเดิลจากไดเรกทอรีที่แตกต่างกัน ข้อเสีย: คุณต้องเปลี่ยนชุดข้อมูลที่มีปัญหาทุกชุด

  1. ใช้รูทสัมพันธ์เดียวกันสำหรับชื่อของบันเดิลเช่นเดียวกับที่ตั้งไฟล์ css ข้อดี: ไม่จำเป็นต้องมีการเปลี่ยนแปลง ข้อเสีย: คุณมีข้อ จำกัด ในการรวม css ชีตจากไดเร็กตอรี่ที่แตกต่างกันในบันเดิลเดียว

0

CssRewriteUrlTransformแก้ไขปัญหาของฉัน
หากรหัสของคุณยังคงไม่โหลดภาพหลังจากใช้งานCssRewriteUrlTransformให้เปลี่ยนชื่อไฟล์ css ของคุณจาก:

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

ถึง:

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

Someway. (จุด) ไม่รู้จักใน url


0

เพียงจำไว้ว่าต้องแก้ไขการรวม CSS หลายรายการในชุดข้อมูลเช่น:

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

คุณไม่สามารถเพิ่มnew CssRewriteUrlTransform()ไปยังจุดสิ้นสุดอย่างที่คุณสามารถทำได้ด้วยไฟล์ CSS ไฟล์เดียวเนื่องจากวิธีนี้ไม่รองรับดังนั้นคุณต้องใช้Includeหลายครั้ง :

bundles.Add(new StyleBundle("~/Content/styles/jquery-ui")
    .Include("~/Content/css/path1/somestyle1.css", new CssRewriteUrlTransform())
    .Include("~/Content/css/path2/somestyle2.css", new CssRewriteUrlTransform()));
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.