วิธีบังคับให้ BundleCollection ล้างแคชสคริปต์บันเดิลใน MVC4


85

... หรือฉันเรียนรู้ที่จะหยุดกังวลและเขียนโค้ดกับ API ที่ไม่มีเอกสารจาก Microsoftได้อย่างไร มีเอกสารSystem.Web.Optimizationเผยแพร่อย่างเป็นทางการจริงหรือไม่? 'เพราะฉันแน่ใจว่าหาไม่พบไม่มีเอกสาร XML และบทความในบล็อกทั้งหมดอ้างถึง RC API ซึ่งแตกต่างกันมาก อันฮู ..

ฉันกำลังเขียนโค้ดเพื่อแก้ไขการอ้างอิงจาวาสคริปต์โดยอัตโนมัติและกำลังสร้างบันเดิลทันทีจากการอ้างอิงเหล่านั้น ทุกอย่างใช้งานได้ดียกเว้นหากคุณแก้ไขสคริปต์หรือทำการเปลี่ยนแปลงอื่น ๆ ที่จะส่งผลต่อบันเดิลโดยไม่ต้องรีสตาร์ทแอปพลิเคชันการเปลี่ยนแปลงจะไม่แสดงผล ดังนั้นฉันจึงเพิ่มตัวเลือกในการปิดใช้งานแคชของการอ้างอิงเพื่อใช้ในการพัฒนา

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

// remove an existing bundle
BundleTable.Bundles.Remove(BundleTable.Bundles.GetBundleFor(bundleAlias));

// recreate it.
var bundle = new ScriptBundle(bundleAlias);

// dependencies is a collection of objects representing scripts, 
// this creates a new bundle from that list. 

foreach (var item in dependencies)
{
    bundle.Include(item.Path);
}

// add the new bundle to the collection

BundleTable.Bundles.Add(bundle);

// bundleAlias is the same alias used previously to create the bundle,
// like "~/mybundle1" 

var bundleUrl = BundleTable.Bundles.ResolveBundleUrl(bundleAlias);

// returns something like "/mybundle1?v=hzBkDmqVAC8R_Nme4OYZ5qoq5fLBIhAGguKa28lYLfQ1"

เมื่อใดก็ตามที่ฉันลบและสร้างบันเดิลใหม่โดยใช้นามแฝงเดียวกันจะไม่มีอะไรเกิดขึ้นอย่างแน่นอน: การbundleUrlส่งคืนจากResolveBundleUrlจะเหมือนกับก่อนที่ฉันลบและสร้างบันเดิล โดย "เหมือนกัน" ฉันหมายความว่าแฮชของเนื้อหาไม่เปลี่ยนแปลงเพื่อแสดงเนื้อหาใหม่ของกลุ่ม

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

ฉันสามารถเปลี่ยนนามแฝงเพื่อแก้ไขปัญหานี้ได้และนั่นก็โอเคสำหรับการพัฒนา แต่ฉันไม่ชอบแนวคิดนั้นเพราะมันหมายความว่าฉันต้องเลิกใช้นามแฝงหลังจากโหลดแต่ละหน้าหรือมี BundleCollection ที่มีขนาดใหญ่ขึ้น โหลดทุกหน้า หากคุณทิ้งสิ่งนี้ไว้ในสภาพแวดล้อมการผลิตอาจเป็นหายนะ

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

ลักษณะการทำงานดูเหมือนแปลก ... การลบบางสิ่งออกจากคอลเล็กชันควรลบออกจากแคช แต่มันไม่ได้ ต้องมีวิธีล้างแคชนี้และให้ใช้เนื้อหาปัจจุบันของBundleCollectionแคชแทนสิ่งที่แคชไว้เมื่อเข้าถึงกลุ่มนั้นครั้งแรก

มีความคิดอย่างไรที่ฉันจะทำสิ่งนี้?

มีResetAllวิธีนี้ซึ่งมีจุดประสงค์ที่ไม่ทราบสาเหตุ แต่มันก็ทำลายสิ่งต่างๆอยู่ดี


ปัญหาเดียวกันที่นี่ ฉันคิดว่าฉันสามารถแก้ปัญหาของฉันได้แล้ว ลองดูว่าเหมาะกับคุณหรือไม่ เห็นด้วยอย่างสิ้นเชิง. เอกสารสำหรับ System.Web การเพิ่มประสิทธิภาพเป็นขยะและตัวอย่างทั้งหมดที่คุณสามารถพบได้บนอินเทอร์เน็ตนั้นล้าสมัย
LeftyX

2
+1 สำหรับการอ้างอิงที่ดีที่ด้านบนรวมกับความคิดเห็นเกี่ยวกับความคาดหวังในความไว้วางใจของ MS และสำหรับการถามคำถามที่ฉันต้องการคำตอบ
Raif

คำตอบ:


33

เรารับฟังความเจ็บปวดของคุณเกี่ยวกับการจัดทำเอกสาร แต่น่าเสียดายที่คุณลักษณะนี้ยังคงเปลี่ยนแปลงค่อนข้างเร็วและการสร้างเอกสารมีความล่าช้าและอาจล้าสมัยเกือบจะในทันที บล็อกโพสต์ของ Rickเป็นข้อมูลล่าสุดและฉันได้พยายามตอบคำถามที่นี่เช่นกันเพื่อเผยแพร่ข้อมูลปัจจุบันในระหว่างนี้ ขณะนี้เราอยู่ในขั้นตอนการตั้งค่าไซต์ codeplex อย่างเป็นทางการซึ่งจะมีเอกสารที่เป็นปัจจุบันเสมอ

ตอนนี้เกี่ยวกับปัญหาเฉพาะของคุณเกี่ยวกับวิธีล้างบันเดิลในรูปแบบแคช

  1. เราจัดเก็บการตอบสนองที่รวมไว้ภายในแคช ASP.NET โดยใช้คีย์ที่สร้างขึ้นจาก URL ของบันเดิลที่ร้องขอกล่าวคือContext.Cache["System.Web.Optimization.Bundle:~/bundles/jquery"]เรายังตั้งค่าการอ้างอิงแคชกับไฟล์และไดเร็กทอรีทั้งหมดที่ใช้ในการสร้างบันเดิลนี้ ดังนั้นหากไฟล์หรือไดเร็กทอรีใด ๆ มีการเปลี่ยนแปลงรายการแคชจะถูกล้างออก

  2. เราไม่สนับสนุนการอัปเดต BundleTable / BundleCollection แบบเรียลไทม์ตามคำขอ สถานการณ์จำลองที่ได้รับการสนับสนุนอย่างสมบูรณ์คือบันเดิลได้รับการกำหนดค่าในระหว่างการเริ่มต้นแอพ (นี่คือเพื่อให้ทุกอย่างทำงานได้อย่างถูกต้องในสถานการณ์เว็บฟาร์มมิฉะนั้นคำขอบันเดิลบางส่วนจะกลายเป็น 404 หากส่งไปยังเซิร์ฟเวอร์ที่ไม่ถูกต้อง ดูตัวอย่างโค้ดของคุณฉันเดาว่าคุณกำลังพยายามแก้ไขคอลเลกชันบันเดิลแบบไดนามิกตามคำขอหนึ่ง ๆ ใช่หรือไม่? การดูแลระบบ / การกำหนดค่าชุดรวมทุกประเภทควรมาพร้อมกับการรีเซ็ตโดเมนแอปเพื่อรับประกันว่าทุกอย่างได้รับการตั้งค่าอย่างถูกต้อง

ดังนั้นหลีกเลี่ยงการแก้ไขคำจำกัดความของกลุ่มโดยไม่รีไซเคิลโดเมนแอป คุณมีอิสระที่จะแก้ไขไฟล์จริงภายในบันเดิลของคุณซึ่งควรถูกตรวจพบโดยอัตโนมัติและสร้างแฮชโค้ดใหม่สำหรับ URL บันเดิลของคุณ


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

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

แน่นอนว่าเราสามารถเพิ่มวิธีที่เทียบเท่ากับการล้างแคชอย่างชัดเจนซึ่งมีอยู่แล้วภายใน เกี่ยวกับปัญหาเว็บฟาร์มโดยพื้นฐานแล้วสมมติว่าคุณมีเว็บเซิร์ฟเวอร์ A และ B สองรายการคำขอของคุณจะไปที่ A ซึ่งเป็นผู้เพิ่มบันเดิลและส่งการตอบกลับลูกค้าของคุณจะไปดึงเนื้อหาของบันเดิล แต่โอ๊ะคำขอไปที่ เซิร์ฟเวอร์ B ที่ไม่ได้ลงทะเบียนบันเดิลและมี 404 ของคุณ
Hao Kung

1
การอัปเดตแคชนั้นขี้เกียจในครั้งแรกที่ใช้บันเดิล (โดยทั่วไปจะใช้การแสดงผลการอ้างอิงไปยังบันเดิล) การอัปเดตแคชจะถูกเพิ่มลงในแคช หากคุณมีแอป start hook ที่เทียบเท่าซึ่งคุณตั้งค่าบันเดิลของคุณบนเว็บเซิร์ฟเวอร์ทั้งหมดก่อนที่คุณจะเริ่มจัดการคำขอก็น่าจะใช้ได้
Hao Kung

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

21

ฉันมีปัญหาที่คล้ายกัน
ในชั้นเรียนของBundleConfigฉันฉันพยายามดูว่าผลของการใช้BundleTable.EnableOptimizations = trueคืออะไร

public class BundleConfig
{
    public static void RegisterBundles(BundleCollection bundles)
    {
        BundleTable.EnableOptimizations = true;

        bundles.Add(...);
    }
}

ทุกอย่างทำงานได้ดี
ในบางครั้งฉันกำลังทำการดีบักและตั้งค่าคุณสมบัติเป็นเท็จ
ฉันพยายามเข้าใจว่าเกิดอะไรขึ้นเพราะดูเหมือนว่าบันเดิลสำหรับ jquery (อันแรก) จะไม่ได้รับการแก้ไขและโหลด ( /bundles/jquery?v=)

หลังจากสบถฉันคิดว่า (?!) ฉันจัดการเรื่องต่างๆได้แล้ว พยายามเพิ่มbundles.Clear()และbundles.ResetAll()เมื่อเริ่มต้นการลงทะเบียนและสิ่งต่างๆควรเริ่มทำงานอีกครั้ง

public class BundleConfig
{
    public static void RegisterBundles(BundleCollection bundles)
    {
        bundles.Clear();
        bundles.ResetAll();

        BundleTable.EnableOptimizations = false;

        bundles.Add(...);
    }
}

ฉันรู้ว่าฉันจำเป็นต้องเรียกใช้สองวิธีนี้ก็ต่อเมื่อฉันเปลี่ยนEnableOptimizationsคุณสมบัติ

อัพเดท:

การขุดลึกลงไปฉันได้พบแล้วBundleTable.Bundles.ResolveBundleUrlและ@Scripts.Urlดูเหมือนจะมีปัญหาในการแก้ไขเส้นทางบันเดิล

เพื่อความเรียบง่ายฉันได้เพิ่มรูปภาพสองสามภาพ:

ภาพที่ 1

ฉันได้ปิดการเพิ่มประสิทธิภาพและรวมสคริปต์บางส่วน

ภาพที่ 2

มัดเดียวกันรวมอยู่ในร่างกาย

ภาพที่ 3

@Scripts.Urlให้เส้นทาง "ที่ดีที่สุด" ของกลุ่มในขณะที่@Scripts.Renderสร้างเส้นทางที่เหมาะสม สิ่งเดียวกันที่เกิดขึ้นกับ
BundleTable.Bundles.ResolveBundleUrl

ฉันใช้ Visual Studio 2010 + MVC 4 + Framework .Net 4.0


อืม ... สิ่งนี้คือฉันไม่ต้องการล้างตารางบันเดิลออกไปเพราะมันจะมีรายการอื่น ๆ มากมายจากเพจต่างๆ (สร้างจากชุดการอ้างอิงที่แตกต่างกัน) แต่เนื่องจากนี่เป็นเพียงสำหรับการทำงานในสภาพแวดล้อมการพัฒนาฉันคิดว่าฉันสามารถคัดลอกเนื้อหาจากนั้นล้างข้อมูลจากนั้นเพิ่มอีกครั้งหากจะล้างแคช ไม่มีประสิทธิภาพอย่างน่ากลัว แต่ถ้ามันใช้งานได้ดีพอสำหรับ dev
Jamie Treworgy

เห็นด้วย แต่นั่นเป็นทางเลือกเดียวที่ฉันมี ฉันใช้เวลาทั้งบ่ายเพื่อทำความเข้าใจว่าอะไรคือปัญหา
LeftyX

2
ฉันเพิ่งลอง แต่ยังไม่ล้างแคช !! ฉันล้างมันResetAllและลองตั้งค่าEnableOptimizationsเป็นเท็จทั้งเมื่อเริ่มต้นและอินไลน์เมื่อฉันต้องการรีเซ็ตแคชไม่มีอะไรเกิดขึ้น อ๊าก.
Jamie Treworgy

แน่นอนว่าจะเป็นการดีถ้าผู้พัฒนาสามารถปิดโพสต์บล็อกสั้น ๆ โดยใช้แม้แต่ซับเดียวเกี่ยวกับวิธีการในวัตถุเหล่านี้ :)
Jamie Treworgy

6
ดังนั้นเพื่ออธิบายว่าวิธีการเหล่านี้ทำอย่างไร: Scripts.Url เป็นเพียงนามแฝงสำหรับ BundleTable.Bundles.ResolveBundleUrl มันจะแก้ไข URL ที่ไม่ใช่บันเดิลด้วยดังนั้นเป็นตัวแก้ไข URL ทั่วไปที่เกิดขึ้นเพื่อทราบเกี่ยวกับบันเดิล Scripts.Render ใช้แฟล็ก EnableOptimizations เพื่อกำหนดว่าจะแสดงผลการอ้างอิงไปยังบันเดิลหรือส่วนประกอบที่ประกอบกันเป็นบันเดิล
Hao Kung

8

โดยคำนึงถึงคำแนะนำของ Hao Kung ที่จะไม่ทำเช่นนี้เนื่องจากสถานการณ์ในฟาร์มบนเว็บฉันคิดว่ามีหลายสถานการณ์ที่คุณอาจต้องการทำเช่นนี้ นี่คือวิธีแก้ปัญหา:

BundleTable.Bundles.ResetAll(); //or something more specific if neccesary
var bundle = new Bundle("~/bundles/your-bundle-virtual-path");
//add your includes here or load them in from a config file

//this is where the magic happens
var context = new BundleContext(new HttpContextWrapper(HttpContext.Current), BundleTable.Bundles, bundle.Path);
bundle.UpdateCache(context, bundle.GenerateBundleResponse(context));

BundleTable.Bundles.Add(bundle);

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

@Scripts.Render("~/bundles/your-bundle-virtual-path")

อ่านเพิ่มเติมที่นี่ซึ่งพูดถึงเล็กน้อยเกี่ยวกับการแคชและGenerateBundleResponse
Zac

4

ฉันยังพบปัญหาในการอัปเดตชุดรวมโดยไม่ต้องสร้างใหม่ สิ่งสำคัญที่ต้องทำความเข้าใจมีดังนี้

  • บันเดิลจะไม่ได้รับการอัพเดตหากเส้นทางของไฟล์เปลี่ยนไป
  • บันเดิลจะได้รับการอัพเดตหากพา ธ เสมือนของบันเดิลเปลี่ยนไป
  • บันเดิลจะได้รับการอัพเดตหากไฟล์บนดิสก์มีการเปลี่ยนแปลง

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

นี่คือรหัสที่ฉันลงเอยด้วยการแก้ปัญหาให้ฉัน:

    public static IHtmlString RenderStyleBundle(string bundlePath, string[] filePaths)
    {
        // Add a hash of the files onto the path to ensure that the filepaths have not changed.
        bundlePath = string.Format("{0}{1}", bundlePath, GetBundleHashForFiles(filePaths));

        var bundleIsRegistered = BundleTable
            .Bundles
            .GetRegisteredBundles()
            .Where(bundle => bundle.Path == bundlePath)
            .Any();

        if(!bundleIsRegistered)
        {
            var bundle = new StyleBundle(bundlePath);
            bundle.Include(filePaths);
            BundleTable.Bundles.Add(bundle);
        }

        return Styles.Render(bundlePath);
    }

    static string GetBundleHashForFiles(IEnumerable<string> filePaths)
    {
        // Create a unique hash for this set of files
        var aggregatedPaths = filePaths.Aggregate((pathString, next) => pathString + next);
        var Md5 = MD5.Create();
        var encodedPaths = Encoding.UTF8.GetBytes(aggregatedPaths);
        var hash = Md5.ComputeHash(encodedPaths);
        var bundlePath = hash.Aggregate(string.Empty, (hashString, next) => string.Format("{0}{1:x2}", hashString, next));
        return bundlePath;
    }

ฉันมักแนะนำให้หลีกเลี่ยงAggregateสำหรับสตริง, เนื่องจากความเสี่ยงของคนที่ไม่ได้คิดเกี่ยวกับการโดยธรรมชาติSchlemiel อัลกอริทึมของจิตรกร+ในการใช้ซ้ำแล้วซ้ำอีก string.Join("", filePaths)แต่เพียงแค่ทำ สิ่งนี้จะไม่มีปัญหาแม้สำหรับอินพุตที่มีขนาดใหญ่มาก
ErikE

3

คุณได้ลองหาจาก ( StyleBundleหรือScriptBundle ) โดยไม่เพิ่มการรวมในตัวสร้างของคุณแล้วทำการลบล้าง

public override IEnumerable<System.IO.FileInfo> EnumerateFiles(BundleContext context)

ฉันทำสิ่งนี้สำหรับสไตล์ชีตไดนามิกและ EnumerateFiles ถูกเรียกทุกคำขอ อาจไม่ใช่วิธีแก้ปัญหาที่ดีที่สุด แต่ใช้ได้ผล


0

ขอโทษที่ต้องรื้อฟื้นเธรดที่ตายแล้วอย่างไรก็ตามฉันพบปัญหาที่คล้ายกันกับการแคช Bundle ในไซต์ Umbraco ซึ่งฉันต้องการให้สไตล์ชีท / สคริปต์ลดขนาดโดยอัตโนมัติเมื่อผู้ใช้เปลี่ยนเวอร์ชันที่สวยงามในแบ็กเอนด์

รหัสที่ฉันมีอยู่แล้ว (ในเมธอด onSaved สำหรับสไตล์ชีต):

 BundleTable.Bundles.Add(new StyleBundle("~/bundles/styles.min.css").Include(
                           "~/css/main.css"
                        ));

และ (onApplicationStarted):

BundleTable.EnableOptimizations = true;

ไม่ว่าฉันจะพยายามอย่างไรไฟล์ "~ / bundles / styles.min.css" ก็ไม่เปลี่ยนแปลง ในส่วนหัวของหน้าของฉันตอนแรกฉันกำลังโหลดสไตล์ชีตดังนี้:

<link rel="stylesheet" href="~/bundles/styles.min.css" />

อย่างไรก็ตามฉันทำให้มันใช้งานได้โดยเปลี่ยนสิ่งนี้เป็น:

@Styles.Render("~/bundles/styles.min.css")

เมธอด Styles.Render ดึงสตริงการสืบค้นที่ท้ายชื่อไฟล์ซึ่งฉันเดาว่าเป็นคีย์แคชที่ Hao อธิบายไว้ข้างต้น

สำหรับฉันมันง่ายอย่างนั้น หวังว่านี่จะช่วยคนอื่น ๆ เช่นฉันที่ใช้ googling เป็นเวลาหลายชั่วโมงและสามารถหาโพสต์เก่า ๆ หลายปีได้!

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.