การฉีดเนื้อหาออกเป็นส่วนที่เฉพาะเจาะจงจากมุมมองบางส่วน ASP.NET MVC 3 พร้อมกับ Razor View Engine


324

ฉันมีส่วนนี้ที่กำหนดไว้ในของฉัน _Layout.cshtml

@RenderSection("Scripts", false)

ฉันสามารถใช้งานได้ง่ายจากมุมมอง:

@section Scripts { 
    @*Stuff comes here*@
}

สิ่งที่ฉันกำลังดิ้นรนคือทำอย่างไรให้เนื้อหาบางส่วนถูกแทรกเข้าไปในส่วนนี้จากมุมมองบางส่วน

สมมติว่านี่เป็นหน้ามุมมองของฉัน:

@section Scripts { 

    <script>
        //code comes here
    </script>
}

<div>
    poo bar poo
</div>

<div>
  @Html.Partial("_myPartial")
</div>

ฉันต้องการฉีดเนื้อหาภายในScriptsส่วนจาก_myPartialมุมมองบางส่วน

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


17
สำหรับผู้ที่มาที่นี่ในภายหลัง - มีแพ็คเกจสำหรับการจัดการสิ่งนี้: nuget.org/packages/Forloop.HtmlHelpers
Russ Cam

@RussCam คุณควรตอบคำถามนี้ +1 แพ็คเกจ nuget แก้ปัญหา OP ที่แน่นอนกำลังเกิดขึ้น
Carrie Kendall

1
@RussCam NuGet แพ็คเกจไม่ใช่โซลูชันรหัสของแพ็คเกจอาจเป็น
Maksim Vi

8
@MaksimVi ดีที่ผมเขียนแพคเกจ nuget และมีความตั้งใจที่จะพามันลงดังนั้นแทนที่จะทำซ้ำรหัส (ไม่bitbucket.org/forloop/forloop-htmlhelpers/src ) หรือวิกิพีเดีย ( bitbucket.org/forloop/forloop-htmlhelpers/wiki / Home ) ที่นี่ลิงค์ไปยังความคิดเห็นจะถูกเก็บไว้ในจิตวิญญาณของ stackoverflow, IMO
Russ Cam

นี่เป็นอีกวิธีการหนึ่งที่ดูดีมาก: stackoverflow.com/questions/5355427/…
jkokorian

คำตอบ:


235

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


445
แต่ถ้าสคริปต์มีความเฉพาะเจาะจงกับบางส่วน มันไม่สมเหตุสมผลหรือไม่ที่มันจะถูกนิยามในบางส่วนและไม่ใช่มุมมอง?
Jez

43
ทำไมมันถึงออกแบบ
Shimmy Weitzhandler

56
@Darin: ฉันไม่เห็นด้วย หลักการของ DRY คืออะไร ฉันไม่ชอบพูดซ้ำแม้ว่ามันจะเป็นเพียงการอ้างอิงสคริปต์
fretje

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

33
@JoshNoe ที่สองและส่วนที่เหลือ - "วิดเจ็ต" (การแสดงผล + การโต้ตอบที่สมบูรณ์) เป็นตัวอย่างที่สมบูรณ์แบบของมุมมองบางส่วนควบคู่ไปกับจาวาสคริปต์ที่เกี่ยวข้องอย่างแน่นหนา จากการออกแบบฉันไม่ควรเขียนข้อความสองข้อความในสถานที่ต่างกันเพื่อให้ได้ฟังก์ชั่นเต็มรูปแบบเพราะหน้าจอจะไม่เกิดขึ้นหากไม่มีการโต้ตอบของผู้ดูแลและการโต้ตอบจะไม่ปรากฏที่อื่น
drzaus

83

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

ในฉัน_layoutฉันมี:

@RenderSection("body_scripts", false)

ในindexมุมมองของฉันฉันมี:

@Html.Partial("Clients")
@section body_scripts
{
    @Html.Partial("Clients_Scripts")
}

ในclientsมุมมองของฉันฉันมี (แผนที่และ assoc. html ทั้งหมด):

@section body_scripts
{
    @Html.Partial("Clients_Scripts")
}

ของฉัน Clients_Scriptsมุมมองมีจาวาสคริปต์ที่จะแสดงผลบนหน้า

วิธีนี้สคริปต์ของฉันถูกแยกออกและสามารถแสดงผลในหน้าเว็บตามที่ต้องการด้วย body_scriptsแท็กเท่านั้นที่ถูกสร้างการแสดงผลในเหตุการณ์แรกที่เอ็นจิ้นมุมมองมีดโกนพบ

นั่นทำให้ฉันมีทุกอย่างแยกจากกัน - มันเป็นทางออกที่ทำงานได้ค่อนข้างดีสำหรับฉันคนอื่น ๆ อาจมีปัญหากับมัน แต่มันจะแก้ไขรู "โดยการออกแบบ"


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

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

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

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

1
โซลูชันนี้มีประโยชน์เพิ่มเติมของการยังคงสามารถทำสิ่ง MVC-ish ทั้งหมดที่คุณคาดว่าจะสามารถทำได้ในมุมมองทั่วไปเช่นความสามารถในการเข้ารหัส JSON ที่ส่งผ่านใน Model และสร้าง URL โดยใช้ Url หนังบู๊. วิธีการนี้เป็นวิธีที่สวยงามในการตั้งค่าตัวควบคุม AngularJS ของคุณ - แต่ละมุมมองบางส่วนสามารถแสดงตัวควบคุมแยกต่างหากในโมดูล Angular สะอาดมาก!
Dan

40

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

การใช้งาน

สร้าง "ส่วน"

  1. สถานการณ์โดยทั่วไป:ในมุมมองบางส่วนให้รวมบล็อกหนึ่งครั้งเท่านั้นไม่ว่าจะมีการแสดงมุมมองบางส่วนซ้ำหลายครั้งในหน้า:

    @using (Html.Delayed(isOnlyOne: "some unique name for this section")) {
        <script>
            someInlineScript();
        </script>
    }
  2. ในมุมมองบางส่วนให้รวมบล็อกสำหรับทุกครั้งที่มีการใช้บางส่วน:

    @using (Html.Delayed()) {
        <b>show me multiple times, @Model.Whatever</b>
    }
  3. ในมุมมองบางส่วนให้รวมบล็อกเพียงครั้งเดียวไม่ว่าจะซ้ำส่วนกี่ครั้ง แต่ในภายหลังให้แสดงเฉพาะด้วยชื่อwhen-i-call-you:

    @using (Html.Delayed("when-i-call-you", isOnlyOne: "different unique name")) {
        <b>show me once by name</b>
        <span>@Model.First().Value</span>
    }

แสดงผล "ส่วน"

(เช่นแสดงส่วนที่ล่าช้าในมุมมองพาเรนต์)

@Html.RenderDelayed(); // writes unnamed sections (#1 and #2, excluding #3)
@Html.RenderDelayed("when-i-call-you", false); // writes the specified block, and ignore the `isOnlyOne` setting so we can dump it again
@Html.RenderDelayed("when-i-call-you"); // render the specified block by name
@Html.RenderDelayed("when-i-call-you"); // since it was "popped" in the last call, won't render anything due to `isOnlyOne` provided in `Html.Delayed`

รหัส

public static class HtmlRenderExtensions {

    /// <summary>
    /// Delegate script/resource/etc injection until the end of the page
    /// <para>@via https://stackoverflow.com/a/14127332/1037948 and http://jadnb.wordpress.com/2011/02/16/rendering-scripts-from-partial-views-at-the-end-in-mvc/ </para>
    /// </summary>
    private class DelayedInjectionBlock : IDisposable {
        /// <summary>
        /// Unique internal storage key
        /// </summary>
        private const string CACHE_KEY = "DCCF8C78-2E36-4567-B0CF-FE052ACCE309"; // "DelayedInjectionBlocks";

        /// <summary>
        /// Internal storage identifier for remembering unique/isOnlyOne items
        /// </summary>
        private const string UNIQUE_IDENTIFIER_KEY = CACHE_KEY;

        /// <summary>
        /// What to use as internal storage identifier if no identifier provided (since we can't use null as key)
        /// </summary>
        private const string EMPTY_IDENTIFIER = "";

        /// <summary>
        /// Retrieve a context-aware list of cached output delegates from the given helper; uses the helper's context rather than singleton HttpContext.Current.Items
        /// </summary>
        /// <param name="helper">the helper from which we use the context</param>
        /// <param name="identifier">optional unique sub-identifier for a given injection block</param>
        /// <returns>list of delayed-execution callbacks to render internal content</returns>
        public static Queue<string> GetQueue(HtmlHelper helper, string identifier = null) {
            return _GetOrSet(helper, new Queue<string>(), identifier ?? EMPTY_IDENTIFIER);
        }

        /// <summary>
        /// Retrieve a context-aware list of cached output delegates from the given helper; uses the helper's context rather than singleton HttpContext.Current.Items
        /// </summary>
        /// <param name="helper">the helper from which we use the context</param>
        /// <param name="defaultValue">the default value to return if the cached item isn't found or isn't the expected type; can also be used to set with an arbitrary value</param>
        /// <param name="identifier">optional unique sub-identifier for a given injection block</param>
        /// <returns>list of delayed-execution callbacks to render internal content</returns>
        private static T _GetOrSet<T>(HtmlHelper helper, T defaultValue, string identifier = EMPTY_IDENTIFIER) where T : class {
            var storage = GetStorage(helper);

            // return the stored item, or set it if it does not exist
            return (T) (storage.ContainsKey(identifier) ? storage[identifier] : (storage[identifier] = defaultValue));
        }

        /// <summary>
        /// Get the storage, but if it doesn't exist or isn't the expected type, then create a new "bucket"
        /// </summary>
        /// <param name="helper"></param>
        /// <returns></returns>
        public static Dictionary<string, object> GetStorage(HtmlHelper helper) {
            var storage = helper.ViewContext.HttpContext.Items[CACHE_KEY] as Dictionary<string, object>;
            if (storage == null) helper.ViewContext.HttpContext.Items[CACHE_KEY] = (storage = new Dictionary<string, object>());
            return storage;
        }


        private readonly HtmlHelper helper;
        private readonly string identifier;
        private readonly string isOnlyOne;

        /// <summary>
        /// Create a new using block from the given helper (used for trapping appropriate context)
        /// </summary>
        /// <param name="helper">the helper from which we use the context</param>
        /// <param name="identifier">optional unique identifier to specify one or many injection blocks</param>
        /// <param name="isOnlyOne">extra identifier used to ensure that this item is only added once; if provided, content should only appear once in the page (i.e. only the first block called for this identifier is used)</param>
        public DelayedInjectionBlock(HtmlHelper helper, string identifier = null, string isOnlyOne = null) {
            this.helper = helper;

            // start a new writing context
            ((WebViewPage)this.helper.ViewDataContainer).OutputStack.Push(new StringWriter());

            this.identifier = identifier ?? EMPTY_IDENTIFIER;
            this.isOnlyOne = isOnlyOne;
        }

        /// <summary>
        /// Append the internal content to the context's cached list of output delegates
        /// </summary>
        public void Dispose() {
            // render the internal content of the injection block helper
            // make sure to pop from the stack rather than just render from the Writer
            // so it will remove it from regular rendering
            var content = ((WebViewPage)this.helper.ViewDataContainer).OutputStack;
            var renderedContent = content.Count == 0 ? string.Empty : content.Pop().ToString();
            // if we only want one, remove the existing
            var queue = GetQueue(this.helper, this.identifier);

            // get the index of the existing item from the alternate storage
            var existingIdentifiers = _GetOrSet(this.helper, new Dictionary<string, int>(), UNIQUE_IDENTIFIER_KEY);

            // only save the result if this isn't meant to be unique, or
            // if it's supposed to be unique and we haven't encountered this identifier before
            if( null == this.isOnlyOne || !existingIdentifiers.ContainsKey(this.isOnlyOne) ) {
                // remove the new writing context we created for this block
                // and save the output to the queue for later
                queue.Enqueue(renderedContent);

                // only remember this if supposed to
                if(null != this.isOnlyOne) existingIdentifiers[this.isOnlyOne] = queue.Count; // save the index, so we could remove it directly (if we want to use the last instance of the block rather than the first)
            }
        }
    }


    /// <summary>
    /// <para>Start a delayed-execution block of output -- this will be rendered/printed on the next call to <see cref="RenderDelayed"/>.</para>
    /// <para>
    /// <example>
    /// Print once in "default block" (usually rendered at end via <code>@Html.RenderDelayed()</code>).  Code:
    /// <code>
    /// @using (Html.Delayed()) {
    ///     <b>show at later</b>
    ///     <span>@Model.Name</span>
    ///     etc
    /// }
    /// </code>
    /// </example>
    /// </para>
    /// <para>
    /// <example>
    /// Print once (i.e. if within a looped partial), using identified block via <code>@Html.RenderDelayed("one-time")</code>.  Code:
    /// <code>
    /// @using (Html.Delayed("one-time", isOnlyOne: "one-time")) {
    ///     <b>show me once</b>
    ///     <span>@Model.First().Value</span>
    /// }
    /// </code>
    /// </example>
    /// </para>
    /// </summary>
    /// <param name="helper">the helper from which we use the context</param>
    /// <param name="injectionBlockId">optional unique identifier to specify one or many injection blocks</param>
    /// <param name="isOnlyOne">extra identifier used to ensure that this item is only added once; if provided, content should only appear once in the page (i.e. only the first block called for this identifier is used)</param>
    /// <returns>using block to wrap delayed output</returns>
    public static IDisposable Delayed(this HtmlHelper helper, string injectionBlockId = null, string isOnlyOne = null) {
        return new DelayedInjectionBlock(helper, injectionBlockId, isOnlyOne);
    }

    /// <summary>
    /// Render all queued output blocks injected via <see cref="Delayed"/>.
    /// <para>
    /// <example>
    /// Print all delayed blocks using default identifier (i.e. not provided)
    /// <code>
    /// @using (Html.Delayed()) {
    ///     <b>show me later</b>
    ///     <span>@Model.Name</span>
    ///     etc
    /// }
    /// </code>
    /// -- then later --
    /// <code>
    /// @using (Html.Delayed()) {
    ///     <b>more for later</b>
    ///     etc
    /// }
    /// </code>
    /// -- then later --
    /// <code>
    /// @Html.RenderDelayed() // will print both delayed blocks
    /// </code>
    /// </example>
    /// </para>
    /// <para>
    /// <example>
    /// Allow multiple repetitions of rendered blocks, using same <code>@Html.Delayed()...</code> as before.  Code:
    /// <code>
    /// @Html.RenderDelayed(removeAfterRendering: false); /* will print */
    /// @Html.RenderDelayed() /* will print again because not removed before */
    /// </code>
    /// </example>
    /// </para>

    /// </summary>
    /// <param name="helper">the helper from which we use the context</param>
    /// <param name="injectionBlockId">optional unique identifier to specify one or many injection blocks</param>
    /// <param name="removeAfterRendering">only render this once</param>
    /// <returns>rendered output content</returns>
    public static MvcHtmlString RenderDelayed(this HtmlHelper helper, string injectionBlockId = null, bool removeAfterRendering = true) {
        var stack = DelayedInjectionBlock.GetQueue(helper, injectionBlockId);

        if( removeAfterRendering ) {
            var sb = new StringBuilder(
#if DEBUG
                string.Format("<!-- delayed-block: {0} -->", injectionBlockId)
#endif
                );
            // .count faster than .any
            while (stack.Count > 0) {
                sb.AppendLine(stack.Dequeue());
            }
            return MvcHtmlString.Create(sb.ToString());
        } 

        return MvcHtmlString.Create(
#if DEBUG
                string.Format("<!-- delayed-block: {0} -->", injectionBlockId) + 
#endif
            string.Join(Environment.NewLine, stack));
    }


}

1
ว้าวมันซับซ้อนสำหรับฉันที่จะเข้าใจโค้ด แต่ +1 สำหรับการหาคำตอบ
Rameez Ahmed Sayad

@RameezAhmedSayad คุณถูกต้อง - กลับมาที่นี่อีกครั้งแม้ฉันจะสับสนกับวิธีที่ฉันตั้งใจจะพูดถึงวิธีการใช้งาน กำลังอัปเดตคำตอบ ...
drzaus

และเพื่อชี้แจงเพิ่มเติม - เหตุผลที่มีสอง "ชื่อ" คือถ้าคุณต้องการให้เรนเดอร์เมื่อมันต้องการคีย์เฉพาะในพารามิเตอร์isOnlyOneแต่ถ้าคุณต้องการที่จะเรนเดอร์ที่ตำแหน่งเฉพาะตามชื่อคุณให้ตัวระบุ Html.RenderDelayed()มิฉะนั้นจะได้รับการทิ้งที่
drzaus

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

@ แปลว่า "การอภิปราย" ได้เริ่มขึ้นแล้วในความคิดเห็นเกี่ยวกับคำตอบที่ยอมรับstackoverflow.com/a/7556594/1037948
drzaus

16

ฉันมีปัญหานี้และใช้นี้เทคนิค

มันเป็นทางออกที่ดีที่สุดที่ฉันพบซึ่งยืดหยุ่นดีมาก

นอกจากนี้โปรดลงคะแนน ที่นี่เพื่อเพิ่มการสนับสนุนสำหรับการประกาศส่วนสะสม


9

หากคุณมีความต้องการที่ถูกต้องในการเรียกใช้jsจาก a partialนี่เป็นวิธีที่คุณสามารถทำได้jQuery:

<script type="text/javascript">        
    function scriptToExecute()
    {
        //The script you want to execute when page is ready.           
    }

    function runWhenReady()
    {
        if (window.$)
            scriptToExecute();                                   
        else
            setTimeout(runWhenReady, 100);
    }
    runWhenReady();
</script>

ฉันลอง @drzaus มันต้องการ 'SeeIfReady' ไม่เช่นนั้น
Cacho Santa

8

การปฏิบัติตามหลักการที่ไม่เป็นการรบกวนจึงไม่จำเป็นสำหรับ "_myPartial" ในการแทรกเนื้อหาลงในสคริปต์โดยตรง คุณสามารถเพิ่มสคริปต์มุมมองบางส่วนเหล่านั้นลงใน.jsไฟล์แยกต่างหากและอ้างอิงลงในส่วน @scripts จากมุมมองหลัก


10
จะเกิดอะไรขึ้นหากมุมมองบางส่วนไม่แสดงผลทั้งหมดในหน้า เรายังคงอ้างอิงไฟล์. js เหล่านั้นในพาเรนต์และทำให้เกินพิกัดหรือไม่
Murali Murugesan

5

มีข้อบกพร่องพื้นฐานในวิธีที่เราคิดเกี่ยวกับเว็บโดยเฉพาะเมื่อใช้ MVC ข้อบกพร่องคือ JavaScript เป็นความรับผิดชอบของมุมมอง มุมมองคือมุมมอง JavaScript (พฤติกรรมหรืออย่างอื่น) เป็น JavaScript ในรูปแบบ MVVM ของ Silverlight และ WPF เราต้องเผชิญกับ "มุมมองแรก" หรือ "รูปแบบแรก" ใน MVC เราควรพยายามหาเหตุผลจากจุดยืนของโมเดลและ JavaScript เป็นส่วนหนึ่งของโมเดลนี้ในหลาย ๆ ด้าน

ฉันอยากจะแนะนำให้ใช้รูปแบบAMD (ฉันชอบRequireJS ) แยก JavaScript ของคุณออกเป็นโมดูลกำหนดหน้าที่การใช้งานและเชื่อมต่อกับ html ของคุณจาก JavaScript แทนที่จะใช้มุมมองเพื่อโหลด JavaScript สิ่งนี้จะทำความสะอาดรหัสของคุณแยกความกังวลของคุณและทำให้ชีวิตง่ายขึ้นในคราวเดียว


เช่นสองหรือสามเดือนหรือมากกว่านั้นฉันกำลังใช้ RequireJS และฉันไม่คิดว่าฉันจะพัฒนาเว็บแอปพลิเคชันอื่นโดยไม่ต้อง RequireJS
tugberk

6
จาวาสคริปต์สามารถเป็นความรับผิดชอบดูได้เช่นกัน
Kelmen

1
การใช้รูปแบบ AMD เป็นความคิดที่ดี แต่ฉันไม่เห็นด้วยกับการยืนยันของคุณว่า JavaScript เป็นส่วนหนึ่งของแบบจำลอง มันมักจะกำหนดพฤติกรรมของมุมมองโดยเฉพาะอย่างยิ่งเมื่อรวมกับสิ่งที่น่าพิศวง คุณดัมพ์การแสดง JSON ของโมเดลของคุณลงในมุมมอง JavaScript ของคุณ โดยส่วนตัวแล้วฉันเพิ่งใช้ closures, "namespace" ที่กำหนดเองบนwindowวัตถุและรวมถึงสคริปต์ไลบรารีก่อนส่วนใด ๆ
บดขยี้

ฉันคิดว่ามีความเข้าใจผิดที่นี่ เมื่อพัฒนาแอพพลิเคชั่นบนเว็บส่วนใหญ่เรากำลังพัฒนาสองแอพพลิเคชั่น: อันที่ทำงานบนเซิร์ฟเวอร์และอีกอันที่ทำงานบนไคลเอนต์ จากมุมมองของเซิร์ฟเวอร์ทุกสิ่งที่คุณส่งลงเบราว์เซอร์คือ "มุมมอง" ในแง่ที่ว่า JavaScript เป็นส่วนหนึ่งของมุมมอง จากมุมมองของแอปไคลเอ็นต์ HTML ล้วนคือมุมมองและ JS คือรหัสที่เปรียบเทียบ M และ C ในเงื่อนไข MVC ของเซิร์ฟเวอร์ ฉันคิดว่านี่เป็นสาเหตุที่ผู้คนไม่เห็นด้วยที่นี่
TheAgent

3

เป้าหมายของ OP คือเขาต้องการกำหนดสคริปต์แบบอินไลน์ในมุมมองบางส่วนของเขาซึ่งฉันคิดว่าสคริปต์นี้มีความเฉพาะเจาะจงกับมุมมองบางส่วนนั้นและมีบล็อกนั้นรวมอยู่ในส่วนสคริปต์ของเขา

ฉันเข้าใจว่าเขาต้องการให้มุมมองบางส่วนนั้นมีอยู่ในตัวเอง แนวคิดนี้คล้ายคลึงกับส่วนประกอบเมื่อใช้ Angular

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

ในเลย์เอาต์ของฉันฉันจะมีบางอย่างที่ด้านล่างดังนี้:

// global scripts
<script src="js/jquery.min.js"></script>
// view scripts
@RenderSection("scripts", false)
// then finally trigger partial view scripts
<script>
  (function(){
    document.querySelector('body').dispatchEvent(new Event('scriptsLoaded'));
  })();
</script>

จากนั้นในมุมมองบางส่วนของฉัน (ที่ด้านล่าง):

<script>
  (function(){
    document.querySelector('body').addEventListener('scriptsLoaded', function() {

      // .. do your thing here

    });
  })();
</script>

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


2

ทางออกแรกที่ฉันคิดได้คือใช้ ViewBag เพื่อเก็บค่าที่ต้องแสดงผล

ฉันไม่เคยลองเลยถ้างานนี้มาจากมุมมองบางส่วน แต่มันควรจะเป็นเช่นนั้น


เพิ่งลอง เศร้าที่ไม่ได้ทำงาน (สร้างViewBag.RenderScripts = new List<string>();ที่ด้านบนของหน้าหลักจากนั้นก็เรียก@Html.Partial("_CreateUpdatePartial",Model,ViewData)แล้วใส่@section Scripts {@foreach (string script in ViewBag.RenderScripts) Scripts.Render(script); }}ในมุมมองบางส่วนผมใส่. @{ViewBag.RenderScripts = ViewBag.RenderScripts ?? new List<string>();ViewBag.RenderScripts.Add("~/bundles/jquery");}.
JohnLBevan

2

คุณไม่จำเป็นต้องใช้ส่วนในมุมมองบางส่วน

รวมไว้ในมุมมองบางส่วนของคุณ มันรันฟังก์ชั่นหลังจากโหลด jQuery คุณสามารถแก้ไขข้อเงื่อนไขสำหรับรหัสของคุณ

<script type="text/javascript">    
var time = setInterval(function () {
    if (window.jQuery != undefined) {
        window.clearInterval(time);

        //Begin
        $(document).ready(function () {
           //....
        });
        //End
    };
}, 10); </script>

Julio Spader


2

คุณสามารถใช้วิธีการขยายเหล่านี้ : (บันทึกเป็น PartialWithScript.cs)

namespace System.Web.Mvc.Html
{
    public static class PartialWithScript
    {
        public static void RenderPartialWithScript(this HtmlHelper htmlHelper, string partialViewName)
        {
            if (htmlHelper.ViewBag.ScriptPartials == null)
            {
                htmlHelper.ViewBag.ScriptPartials = new List<string>();
            }

            if (!htmlHelper.ViewBag.ScriptPartials.Contains(partialViewName))
            {
                htmlHelper.ViewBag.ScriptPartials.Add(partialViewName);
            }

            htmlHelper.ViewBag.ScriptPartialHtml = true;
            htmlHelper.RenderPartial(partialViewName);
        }

        public static void RenderPartialScripts(this HtmlHelper htmlHelper)
        {
            if (htmlHelper.ViewBag.ScriptPartials != null)
            {
                htmlHelper.ViewBag.ScriptPartialHtml = false;
                foreach (string partial in htmlHelper.ViewBag.ScriptPartials)
                {
                    htmlHelper.RenderPartial(partial);
                }
            }
        }
    }
}

ใช้แบบนี้:

ตัวอย่างบางส่วน: (_MyPartial.cshtml) ใส่ html ลงใน if และ js ในส่วนอื่น

@if (ViewBag.ScriptPartialHtml ?? true)
    <p>I has htmls</p>
}
else {
    <script type="text/javascript">
        alert('I has javascripts');
    </script>
}

ใน _Layout.cshtml ของคุณหรือที่ใดก็ตามที่คุณต้องการให้สคริปต์จาก partials เปิดให้แสดงต่อไปนี้ (หนึ่งครั้ง): มันจะแสดงเฉพาะจาวาสคริปต์ของ partials ทั้งหมดในหน้าปัจจุบันที่ตำแหน่งนี้

@{ Html.RenderPartialScripts(); }

จากนั้นให้ใช้บางส่วนของคุณเพียงแค่ทำสิ่งนี้: มันจะแสดงเฉพาะ html ที่ตำแหน่งนี้

@{Html.RenderPartialWithScript("~/Views/MyController/_MyPartial.cshtml");}

1

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

นี่คือสิ่งที่ดูเหมือนว่าจะแทรกส่วนในมุมมองบางส่วน:

@model KeyValuePair<WebPageBase, HtmlHelper>
@{
    Model.Key.DefineSection("SectionNameGoesHere", () =>
    {
        Model.Value.ViewContext.Writer.Write("Test");
    });
}

และในหน้าแทรกมุมมองบางส่วน ...

@Html.Partial(new KeyValuePair<WebPageBase, HtmlHelper>(this, Html))

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

สนุก!


1
คุณช่วยกรุณาและลิงค์ไปยังโครงการที่ทำงานได้อย่างสมบูรณ์
Ehsan Zargar Ershadi

1

ความคิดของพลูโตในแบบที่ดีกว่า:

CustomWebViewPage.cs:

    public abstract class CustomWebViewPage<TModel> : WebViewPage<TModel> {

    public IHtmlString PartialWithScripts(string partialViewName, object model) {
        return Html.Partial(partialViewName: partialViewName, model: model, viewData: new ViewDataDictionary { ["view"] = this, ["html"] = Html });
    }

    public void RenderScriptsInBasePage(HelperResult scripts) {
        var parentView = ViewBag.view as WebPageBase;
        var parentHtml = ViewBag.html as HtmlHelper;
        parentView.DefineSection("scripts", () => {
            parentHtml.ViewContext.Writer.Write(scripts.ToHtmlString());
        });
    }
}

ชม \ web.config:

<pages pageBaseType="Web.Helpers.CustomWebViewPage">

ดู:

@PartialWithScripts("_BackendSearchForm")

บางส่วน (_BackendSearchForm.cshtml):

@{ RenderScriptsInBasePage(scripts()); }

@helper scripts() {
<script>
    //code will be rendered in a "scripts" section of the Layout page
</script>
}

หน้าเลย์เอาต์:

@RenderSection("scripts", required: false)

1

สิ่งนี้ใช้ได้สำหรับฉันทำให้ฉันสามารถค้นหา javascript และ html ร่วมเพื่อดูบางส่วนในไฟล์เดียวกัน ช่วยในกระบวนการคิดเพื่อดู html และส่วนที่เกี่ยวข้องในไฟล์มุมมองบางส่วนที่เหมือนกัน


ในมุมมองที่ใช้มุมมองบางส่วนที่เรียกว่า "_MyPartialView.cshtml"

<div>
    @Html.Partial("_MyPartialView",< model for partial view>,
            new ViewDataDictionary { { "Region", "HTMLSection" } } })
</div>

@section scripts{

    @Html.Partial("_MyPartialView",<model for partial view>, 
                  new ViewDataDictionary { { "Region", "ScriptSection" } })

 }

ในไฟล์มุมมองบางส่วน

@model SomeType

@{
    var region = ViewData["Region"] as string;
}

@if (region == "HTMLSection")
{


}

@if (region == "ScriptSection")
{
        <script type="text/javascript">
    </script">
}

0

ฉันแก้ไขเส้นทางที่แตกต่างอย่างสิ้นเชิง (เพราะฉันกำลังรีบและไม่ต้องการใช้ HtmlHelper ใหม่):

ฉันปิดมุมมองบางส่วนไว้ในคำสั่ง if-else ขนาดใหญ่:

@if ((bool)ViewData["ShouldRenderScripts"] == true){
// Scripts
}else{
// Html
}

จากนั้นฉันเรียก Partial สองครั้งด้วย ViewData ที่กำหนดเอง:

@Html.Partial("MyPartialView", Model, 
    new ViewDataDictionary { { "ShouldRenderScripts", false } })

@section scripts{
    @Html.Partial("MyPartialView", Model, 
        new ViewDataDictionary { { "ShouldRenderScripts", true } })
}

แน่นอนว่าความคิดทั้งหมดคือผู้บริโภคในมุมมองบางส่วนไม่จำเป็นต้องรู้ว่าจะต้องมีสคริปต์ซึ่งเป็นประเด็นปัญหาใช่หรือไม่ มิฉะนั้นคุณอาจจะพูดแค่นั้นก็ได้ @Html.Partial("MyPartialViewScripts")
แดนริชาร์ดสันเมื่อ

ไม่ความคิดคืออนุญาตให้สคริปต์ถูกกำหนดในเอกสารเดียวกับ html แต่ฉันเห็นด้วยว่ามันไม่เหมาะ
Rick Love

0

ฉันมีปัญหาที่คล้ายกันซึ่งฉันมีหน้าต้นแบบดังต่อไปนี้:

@section Scripts {
<script>
    $(document).ready(function () {
        ...
    });
</script>
}

...

@Html.Partial("_Charts", Model)

แต่มุมมองบางส่วนขึ้นอยู่กับ JavaScript บางส่วนในส่วนของสคริปต์ ฉันแก้ไขมันโดยการเข้ารหัสมุมมองบางส่วนเป็น JSON, โหลดลงในตัวแปร JavaScript แล้วใช้สิ่งนี้เพื่อเติม div ดังนั้น:

@{
    var partial = Html.Raw(Json.Encode(new { html = Html.Partial("_Charts", Model).ToString() }));
}

@section Scripts {
<script>
    $(document).ready(function () {
        ...
        var partial = @partial;
        $('#partial').html(partial.html);
    });
</script>
}

<div id="partial"></div>

IMO คุณควรแก้ไขปัญหานี้ด้วยการย้าย JS ของคุณไปเป็นไฟล์แยก
7

0

คุณสามารถใช้โฟลเดอร์ / index.cshtml ของคุณเป็นมาสเตอร์เพจจากนั้นเพิ่มสคริปต์ส่วน จากนั้นในรูปแบบของคุณคุณมี:

@RenderSection("scripts", required: false) 

และ index.cshtml ของคุณ:

@section scripts{
     @Scripts.Render("~/Scripts/file.js")
}

และมันจะทำงานในบางส่วนของภาพรวมของคุณ มันใช้งานได้สำหรับฉัน


0

การใช้ Mvc Core คุณสามารถสร้าง TagHelper ที่เป็นระเบียบscriptsได้ดังที่เห็นด้านล่าง สิ่งนี้สามารถ morphed เป็นsectionแท็กที่คุณให้ชื่อได้อย่างง่ายดาย (หรือชื่อมาจากประเภทที่ได้รับ) IHttpContextAccessorโปรดทราบว่าความต้องการพึ่งพาการฉีดจะต้องมีการติดตั้งสำหรับ

เมื่อเพิ่มสคริปต์ (เช่นในบางส่วน)

<scripts>
    <script type="text/javascript">
        //anything here
    </script>
</scripts>

เมื่อแสดงผลสคริปต์ (เช่นในไฟล์เลย์เอาต์)

<scripts render="true"></scripts>

รหัส

public class ScriptsTagHelper : TagHelper
    {
        private static readonly object ITEMSKEY = new Object();

        private IDictionary<object, object> _items => _httpContextAccessor?.HttpContext?.Items;

        private IHttpContextAccessor _httpContextAccessor;

        public ScriptsTagHelper(IHttpContextAccessor httpContextAccessor)
        {
            _httpContextAccessor = httpContextAccessor;
        }

        public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
        {
            var attribute = (TagHelperAttribute)null;
            context.AllAttributes.TryGetAttribute("render",out attribute);

            var render = false;

            if(attribute != null)
            {
                render = Convert.ToBoolean(attribute.Value.ToString());
            }

            if (render)
            {
                if (_items.ContainsKey(ITEMSKEY))
                {
                    var scripts = _items[ITEMSKEY] as List<HtmlString>;

                    var content = String.Concat(scripts);

                    output.Content.SetHtmlContent(content);
                }
            }
            else
            {
                List<HtmlString> list = null;

                if (!_items.ContainsKey(ITEMSKEY))
                {
                    list = new List<HtmlString>();
                    _items[ITEMSKEY] = list;
                }

                list = _items[ITEMSKEY] as List<HtmlString>;

                var content = await output.GetChildContentAsync();

                list.Add(new HtmlString(content.GetContent()));
            }
        }
    }

-1

ฉันเดาว่าผู้โพสต์คนอื่น ๆ ได้จัดเตรียมวิธีการให้คุณรวม @section ไว้ในส่วนของคุณโดยตรง (โดยใช้ผู้ช่วยเหลือ HTML ของบุคคลที่สาม)

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


1
ซึ่งมักจะไม่เหมาะเพราะการโหลด jQuery ฯลฯ จะเกิดขึ้นหลังจากสคริปต์แบบอินไลน์ ... แต่สำหรับรหัสเนทีฟฉันคิดว่ามันใช้ได้
คุ้มค่า 7

-3

สมมติว่าคุณมีมุมมองบางส่วนที่เรียกว่า _contact.cshtml ผู้ติดต่อของคุณอาจเป็นชื่อทางกฎหมาย (ชื่อ) หรือชื่อเรื่องทางกายภาพ (ชื่อ, นามสกุล) มุมมองของคุณควรดูแลเกี่ยวกับสิ่งที่แสดงผลและสามารถทำได้ด้วยจาวาสคริปต์ ดังนั้นการเรนเดอร์ที่ล่าช้าและ JS ในมุมมองอาจจำเป็น

วิธีเดียวที่ฉันคิดว่ามันจะถูกจัดการได้อย่างไรคือเมื่อเราสร้างวิธีจัดการกับ UI ที่ไม่สร้างความรำคาญ

โปรดทราบด้วยว่า MVC 6 จะมีมุมมองที่เรียกว่า Component แม้กระทั่ง MVC Futures ก็มีสิ่งที่คล้ายกันและ Telerik ก็สนับสนุนสิ่งนี้ ...


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

-3

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

<script>
    $(document).ready(function () {
        $("#Profile_ProfileID").selectmenu({ icons: { button: 'ui-icon-circle-arrow-s' } });
        $("#TitleID_FK").selectmenu({ icons: { button: 'ui-icon-circle-arrow-s' } });
        $("#CityID_FK").selectmenu({ icons: { button: 'ui-icon-circle-arrow-s' } });
        $("#GenderID_FK").selectmenu({ icons: { button: 'ui-icon-circle-arrow-s' } });
        $("#PackageID_FK").selectmenu({ icons: { button: 'ui-icon-circle-arrow-s' } });
    });
</script>

-5

ฉันมีปัญหาที่คล้ายกันแก้ไขได้ด้วยสิ่งนี้:

@section ***{
@RenderSection("****", required: false)
}

นั่นเป็นวิธีที่ดีในการฉีดฉันเดา

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