Injizieren von Inhalten in bestimmte Abschnitte aus einer Teilansicht ASP.NET MVC 3 mit Razor View Engine


324

Ich habe diesen Abschnitt in meinem definiert _Layout.cshtml

@RenderSection("Scripts", false)

Ich kann es leicht aus einer Sicht verwenden:

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

Ich habe Probleme damit, wie einige Inhalte aus einer Teilansicht in diesen Abschnitt eingefügt werden können.

Nehmen wir an, dies ist meine Ansichtsseite:

@section Scripts { 

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

<div>
    poo bar poo
</div>

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

Ich muss einige Inhalte Scriptsaus der _myPartialTeilansicht in den Abschnitt einfügen.

Wie kann ich das machen?


17
Für alle, die später dazu kommen - es gibt ein Nuget-Paket, um dies zu handhaben
Russ Cam

@RussCam sollten Sie diese Frage beantworten. +1 Das Nuget-Paket löst das genaue Problem, das OP hat.
Carrie Kendall

1
@RussCam NuGet-Paket ist keine Lösung, Code des Pakets könnte sein.
Maksim Vi.

8
@MaksimVi. Nun, ich habe das Nuget-Paket geschrieben und habe nicht die Absicht, es zu entfernen, anstatt den Code ( bitbucket.org/forloop/forloop-htmlhelpers/src ) oder das Wiki ( bitbucket.org/forloop/forloop-htmlhelpers/wiki ) zu wiederholen / Home ) Hier wird ein Link als Kommentar im Sinne von Stackoverflow, IMO, gehalten.
Russ Cam

Hier ist eine andere Lösung, die sehr schön erscheint: stackoverflow.com/questions/5355427/…
jkokorian

Antworten:


235

Abschnitte funktionieren nicht in Teilansichten und das ist beabsichtigt. Sie können einige benutzerdefinierte Helfer verwenden , um ein ähnliches Verhalten zu erzielen, aber ehrlich gesagt liegt es in der Verantwortung der Ansicht, die erforderlichen Skripte einzuschließen, nicht in der Verantwortung des Teils. Ich würde empfehlen, den Abschnitt @scripts der Hauptansicht zu verwenden, um dies zu tun, und die Partials müssen sich nicht um Skripte kümmern.


445
Aber was ist, wenn das Skript sehr spezifisch für den Teil ist? Ist es nicht logisch, dass es im Teil und nicht in der Ansicht definiert wird?
Jez

43
Warum ist es beabsichtigt?
Shimmy Weitzhandler

56
@Darin: Ich bin anderer Meinung. Was ist mit dem DRY-Prinzip? Ich wiederhole mich nicht gerne, auch wenn es nur Skriptreferenzen sind.
Fretje

14
@fretje, jeder hat das Recht, seine Meinung zu diesem Thema zu äußern. Ich respektiere deine. In meiner Antwort habe ich meine ausgedrückt und mit einer Antwort verknüpft, mit der Sie diese Aufgabe erfüllen können. Ich habe aber auch hervorgehoben, was ich für diese Situation empfehlen und tun würde.
Darin Dimitrov

33
Seconding @JoshNoe und der Rest - ein "Widget" (Anzeige + reichhaltige Interaktion) ist ein perfektes Beispiel für eine Teilansicht, die eng mit dem zugehörigen Javascript verbunden ist. Von Natur aus sollte ich nicht zwei Include-Anweisungen an verschiedenen Stellen schreiben müssen, um die volle Funktionalität zu erhalten, da die Anzeige niemals ohne die damit verbundene Interaktion sein wird und die Interaktion niemals anderswo erscheinen wird.
Drzaus

83

Dies ist eine sehr beliebte Frage, daher werde ich meine Lösung veröffentlichen.
Ich hatte das gleiche Problem und obwohl es nicht ideal ist, denke ich, dass es tatsächlich ziemlich gut funktioniert und das Teil nicht von der Ansicht abhängig macht.
Mein Szenario war, dass eine Aktion von selbst zugänglich war, aber auch in eine Ansicht eingebettet werden konnte - eine Google Map.

In meinem habe _layoutich:

@RenderSection("body_scripts", false)

Meiner indexAnsicht nach habe ich:

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

Aus meiner clientsSicht habe ich (alle Karte und Assoc. HTML):

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

Meine Clients_ScriptsAnsicht enthält das Javascript, das auf der Seite gerendert werden soll

Auf diese Weise wird mein Skript isoliert und kann bei Bedarf auf der Seite gerendert werden, wobei das body_scriptsTag nur beim ersten Auftreten gerendert wird, wenn die Razor View Engine es findet.

Dadurch kann ich alles trennen - es ist eine Lösung, die für mich recht gut funktioniert, andere haben möglicherweise Probleme damit, aber sie flicken das "by design" -Loch.


2
Ich war nicht derjenige, der Sie abgelehnt hat, aber ich werde sagen, dass mir diese Lösung nicht wirklich gefällt, da sie immer noch die ansichtsspezifischen Skripte von der Ansicht selbst trennt.
Crush

3
20 andere Leute und ich haben eine andere Meinung. Sie können weiterhin Skripte haben, die in direktem Zusammenhang mit einer Ansicht stehen und sich in einer separaten Datei befinden. Dies ist ein Programmierfehler, wenn Sie Ihr Skript nicht zusammen mit Ihrer Ansicht einfügen. Wenn es in einer separaten Datei gespeichert wird, wird die Interaktion von der Präsentation getrennt, und es werden zahlreiche andere Vorteile daraus gezogen, dass es sich in einer separaten Datei befindet.
Dan Richardson

1
Du hast vollkommen recht. Ich stimme dem vollkommen zu und bevorzuge diese Methode persönlich. Das eigentliche Problem für mich ist, dass meine Kollegen mit so viel Trennung zu kämpfen haben. Das ist jedoch ein Domain-Problem. Ich denke, diese Methode ist ideal, insbesondere wenn Sie einen JavaScript-Erstellungsprozess berücksichtigen. Ich werde weiterhin daran arbeiten, meine Kollegen in die Anwendung dieser Methode einzuweisen und sie voll und ganz zu unterstützen. Ich denke jedoch, dass Ihre Antwort verbessert werden könnte. Sie mussten jedoch nicht erwähnen, dass "20 Personen zustimmen". Nur weil eine Antwort beliebt ist, heißt das nicht immer, dass sie richtig ist. In diesem Fall ist es richtig.
Crush

Sehr wahr, und ich freue mich immer über konstruktives Feedback und ändere meinen eigenen Code und antworte, wenn es eine Verbesserung gibt :)
dan richardson

1
Diese Lösung hat den zusätzlichen Vorteil, dass Sie weiterhin alle MVC-ähnlichen Aufgaben ausführen können, die Sie in einer typischen Ansicht erwarten würden, z. B. die JSON-Codierung eines übergebenen Modells und das Generieren von URLs mithilfe der URL. Aktion. Dieser Ansatz ist dann eine elegante Möglichkeit, Ihre AngularJS-Controller einzurichten. Jede Teilansicht kann einen separaten Controller im Angular-Modul darstellen. So sauber!
Dan

40

Aus den Lösungen in diesem Thread habe ich die folgende wahrscheinlich überkomplizierte Lösung gefunden, mit der Sie das Rendern von HTML (auch Skripten) innerhalb eines using-Blocks verzögern können.

VERWENDUNGSZWECK

Erstellen Sie den "Abschnitt"

  1. Typisches Szenario: Fügen Sie in einer Teilansicht den Block nur einmal ein, unabhängig davon, wie oft die Teilansicht auf der Seite wiederholt wird:

    @using (Html.Delayed(isOnlyOne: "some unique name for this section")) {
        <script>
            someInlineScript();
        </script>
    }
  2. Fügen Sie in einer Teilansicht den Block für jedes Mal ein, wenn der Teil verwendet wird:

    @using (Html.Delayed()) {
        <b>show me multiple times, @Model.Whatever</b>
    }
  3. Fügen Sie in einer Teilansicht den Block nur einmal ein, unabhängig davon, wie oft der Teil wiederholt wird, und rendern Sie ihn später spezifisch nach Namen 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>
    }

Rendern Sie die "Abschnitte"

(dh den verzögerten Abschnitt in einer übergeordneten Ansicht anzeigen)

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

CODE

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
Wow, es ist sogar kompliziert für mich, den Code zu verstehen, aber +1 für die Lösung
Rameez Ahmed Sayad

@RameezAhmedSayad du hast recht - ich komme hierher zurück, auch wenn ich verwirrt bin, wie ich sagen wollte, wie man es benutzt. Aktualisierung der Antwort ...
drzaus

Und um es weiter zu verdeutlichen: Der Grund dafür, dass es zwei "Namen" gibt, ist, dass Sie den Bezeichner angeben, wenn Sie ihn isOnlyOnenur dann rendern möchten, wenn er den eindeutigen Schlüssel für den Parameter benötigt , aber nur, wenn Sie ihn an einem bestimmten Ort nach Namen rendern möchten. sonst wird es abgeladen Html.RenderDelayed().
Drzaus

Ich persönlich glaube nicht, dass es notwendig wäre, den Ärger zu kaufen und diesen Ansatz zu verwenden. Der Abschnitt in Teilansichten wird einfach nicht benötigt, da er beseitigt werden kann, und die Skripte können dorthin gehen, ohne einen Abschnitt zu definieren. Dies liegt daran, dass das extern gerendert wird und wenn Sie den Code für die gerenderte Seite sehen, bemerken Sie einfach, dass der Code für die Teilansicht dort nicht sichtbar ist. Wenn es also um eine bessere Organisation usw. geht, hat dies keinerlei Auswirkungen.
Transzendent

@Transcendent die "Debatte" wurde bereits in den Kommentaren zur akzeptierten Antwort stackoverflow.com/a/7556594/1037948
drzaus

16

Ich hatte dieses Problem und benutzte diese Technik.

Es ist die beste Lösung, die ich gefunden habe und die sehr flexibel ist.

Auch stimmen Sie bitte hier Unterstützung für kumulativen Abschnitt Erklärung hinzufügen


9

Wenn Sie ein legitimes Bedürfnis haben, einige jsvon a auszuführen partial, jQueryist Folgendes erforderlich:

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

Ich habe @drzaus ausprobiert, es braucht das 'SeeIfReady' oder es funktioniert nicht.
Cacho Santa

8

Nach dem unauffälligen Prinzip ist es für "_myPartial" nicht unbedingt erforderlich, Inhalte direkt in den Skriptbereich einzufügen. Sie können diese Skripts für die Teilansicht in eine separate .jsDatei einfügen und sie in der übergeordneten Ansicht im Abschnitt @scripts referenzieren.


10
Was würde passieren, wenn die Teilansicht auf der Seite überhaupt nicht gerendert wird? Verweisen wir immer noch auf diese .js-Dateien im übergeordneten Element und überladen sie?
Murali Murugesan

5

Es gibt einen grundlegenden Fehler in der Art und Weise, wie wir über Web denken, insbesondere bei der Verwendung von MVC. Der Fehler ist, dass JavaScript irgendwie in der Verantwortung der Ansicht liegt. Eine Ansicht ist eine Ansicht, JavaScript (verhaltensbezogen oder anderweitig) ist JavaScript. In Silverlight und dem MVVM-Muster von WPF sehen wir uns mit "Ansicht zuerst" oder "Modell zuerst" konfrontiert. In MVC sollten wir immer versuchen, vom Standpunkt des Modells aus zu argumentieren, und JavaScript ist in vielerlei Hinsicht ein Teil dieses Modells.

Ich würde vorschlagen, das AMD- Muster zu verwenden (ich selbst mag RequireJS ). Trennen Sie Ihr JavaScript in Module, definieren Sie Ihre Funktionalität und schließen Sie sich über JavaScript an Ihr HTML an, anstatt sich beim Laden des JavaScript auf eine Ansicht zu verlassen. Dies wird Ihren Code bereinigen, Ihre Bedenken trennen und das Leben auf einen Schlag erleichtern.


Etwa zwei oder drei Monate lang verwende ich RequireJS und ich glaube nicht, dass ich jemals eine andere Webanwendung ohne RequireJS entwickeln werde.
Tugberk

6
JavaScript kann auch in der Verantwortung von View liegen.
Kelmen

1
Die Verwendung des AMD-Musters ist eine gute Idee, aber ich stimme Ihrer Behauptung nicht zu, dass JavaScript Teil des Modells ist. Oft wird das Ansichtsverhalten definiert, insbesondere wenn es mit etwas wie Knockout gekoppelt ist. Sie speichern eine JSON-Darstellung Ihres Modells in Ihrer JavaScript-Ansicht. Persönlich verwende ich nur Closures, einen benutzerdefinierten "Namespace" für das windowObjekt, und füge Bibliotheksskripte vor Partials ein.
Crush

Ich denke, hier gibt es ein Missverständnis. Bei der Entwicklung der meisten Webanwendungen entwickeln wir tatsächlich zwei Anwendungen: eine, die auf dem Server ausgeführt wird, und eine, die auf dem Client ausgeführt wird. Aus Sicht des Servers ist alles, was Sie an den Browser senden, die "Ansicht". In diesem Sinne ist JavaScript Teil der Ansicht. Aus der Sicht der Client-App ist reines HTML Ansicht und JS ist Code, der in den MVC-Begriffen des Servers Parallelen zu M und C aufweist. Ich denke, deshalb sind sich die Leute hier nicht einig.
TheAgent

3

Das Ziel des OP ist, dass er Inline-Skripte in seiner Teilansicht definieren möchte. Ich gehe davon aus, dass dieses Skript nur für diese Teilansicht spezifisch ist und diesen Block in seinen Skriptabschnitt aufgenommen hat.

Ich verstehe, dass er diese Teilansicht haben möchte, um in sich geschlossen zu sein. Die Idee ähnelt den Komponenten bei der Verwendung von Angular.

Mein Weg wäre, die Skripte so wie sie sind in der Teilansicht zu belassen. Das Problem dabei ist nun, dass beim Aufrufen der Teilansicht das dortige Skript möglicherweise vor allen anderen Skripten ausgeführt wird (die normalerweise am Ende der Layoutseite hinzugefügt werden). In diesem Fall muss das Partial View-Skript nur auf die anderen Skripte warten. Es gibt verschiedene Möglichkeiten, dies zu tun. Das einfachste, das ich zuvor verwendet habe, ist die Verwendung eines Ereignisses für body.

In meinem Layout hätte ich unten etwas wie dieses:

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

Dann in meiner Teilansicht (unten):

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

      // .. do your thing here

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

Eine andere Lösung besteht darin, einen Stapel zu verwenden, um alle Ihre Skripte zu pushen und jedes am Ende aufzurufen. Eine andere Lösung ist, wie bereits erwähnt, das RequireJS / AMD-Muster, das auch sehr gut funktioniert.


2

Die erste Lösung, die ich mir vorstellen kann, ist die Verwendung von ViewBag zum Speichern der Werte, die gerendert werden müssen.

Nur habe ich nie versucht, ob diese Arbeit aus einer Teilansicht, aber es sollte imo.


Gerade versucht; Leider funktioniert das nicht (erstellt ein ViewBag.RenderScripts = new List<string>();oben auf der Hauptseite, dann aufgerufen @Html.Partial("_CreateUpdatePartial",Model,ViewData), dann setzen @section Scripts {@foreach (string script in ViewBag.RenderScripts) Scripts.Render(script); }}. In Teilansicht habe ich gesetzt @{ViewBag.RenderScripts = ViewBag.RenderScripts ?? new List<string>();ViewBag.RenderScripts.Add("~/bundles/jquery");}.
JohnLBevan

2

Sie müssen keine Abschnitte in der Teilansicht verwenden.

In Ihre Teilansicht aufnehmen. Es führt die Funktion aus, nachdem jQuery geladen wurde. Sie können die Bedingungsklausel für Ihren Code ändern.

<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

Sie können diese Erweiterungsmethoden verwenden : (Als PartialWithScript.cs speichern)

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

Verwenden Sie wie folgt:

Beispiel teilweise: (_MyPartial.cshtml) Fügen Sie das HTML in das if und das js in das else ein.

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

Fügen Sie in Ihrer _Layout.cshtml oder wo immer Sie möchten, dass die Skripte aus den Partials gerendert werden, Folgendes (einmal) ein: Es wird nur das Javascript aller Partials auf der aktuellen Seite an dieser Stelle gerendert.

@{ Html.RenderPartialScripts(); }

Um Ihr Teil zu verwenden, gehen Sie einfach wie folgt vor: Es wird nur das HTML an dieser Stelle gerendert.

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

1

Es gibt eine Möglichkeit, Abschnitte in Teilansichten einzufügen, obwohl dies nicht schön ist. Sie müssen über die übergeordnete Ansicht auf zwei Variablen zugreifen können. Da ein Teil des Zwecks Ihrer Teilansicht darin besteht, diesen Abschnitt zu erstellen, ist es sinnvoll, diese Variablen zu benötigen.

So sieht es aus, wenn Sie einen Abschnitt in die Teilansicht einfügen:

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

Und auf der Seite die Teilansicht einfügen ...

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

Mit dieser Technik können Sie auch den Inhalt eines Abschnitts programmgesteuert in einer beliebigen Klasse definieren.

Genießen!


1
Können Sie bitte und einen Link zu einem voll funktionsfähigen Projekt?
Ehsan Zargar Ershadi

1

Plutos Idee auf eine schönere Art:

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

Ansichten \ web.config:

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

Aussicht:

@PartialWithScripts("_BackendSearchForm")

Teilweise (_BackendSearchForm.cshtml):

@{ RenderScriptsInBasePage(scripts()); }

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

Layoutseite:

@RenderSection("scripts", required: false)

1

Dies funktionierte für mich und ermöglichte es mir, Javascript und HTML für die Teilansicht in derselben Datei zusammen zu finden. Hilft beim Gedankenprozess, HTML und verwandte Teile in derselben Teilansichtsdatei anzuzeigen.


In der Ansicht, die die Teilansicht "_MyPartialView.cshtml" verwendet

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

 }

In der Teilansichtsdatei

@model SomeType

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

@if (region == "HTMLSection")
{


}

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

0

Ich habe dies auf eine völlig andere Weise gelöst (weil ich es eilig hatte und keinen neuen HtmlHelper implementieren wollte):

Ich habe meine Teilansicht in eine große if-else-Anweisung eingeschlossen:

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

Dann habe ich das Partial zweimal mit benutzerdefinierten ViewData aufgerufen:

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

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

Sicherlich ist die ganze Idee, dass der Konsument der Teilansicht nicht wissen muss, dass sie Skripte enthalten muss, das ist irgendwie das Problem? Andernfalls können Sie auch einfach sagen @Html.Partial("MyPartialViewScripts")
Dan Richardson

Nein, die Idee ist, die Skripte im selben Dokument wie das HTML definieren zu lassen, aber ich stimme zu, dass dies nicht ideal ist.
Rick Love

0

Ich hatte ein ähnliches Problem, bei dem ich eine Masterseite wie folgt hatte:

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

...

@Html.Partial("_Charts", Model)

Die Teilansicht hing jedoch von JavaScript im Abschnitt "Skripte" ab. Ich habe es gelöst, indem ich die Teilansicht als JSON codiert, in eine JavaScript-Variable geladen und diese dann zum Auffüllen eines Div verwendet habe.

@{
    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 sollten Sie dies gelöst haben, indem Sie Ihre JS in eine separate Datei verschieben.
Worthy7

0

Sie können Ihren Ordner / index.cshtml als Masterseite verwenden und dann Abschnittsskripte hinzufügen. Dann haben Sie in Ihrem Layout:

@RenderSection("scripts", required: false) 

und Ihre index.cshtml:

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

und es wird über alle Ihre Teilansichten funktionieren. Es funktioniert für mich


0

Mit Mvc Core können Sie einen ordentlichen TagHelper erstellen scripts(siehe unten). Dies könnte leicht in ein sectionTag umgewandelt werden, in dem Sie ihm auch einen Namen geben (oder der Name wird vom abgeleiteten Typ übernommen). Beachten Sie, dass die Abhängigkeitsinjektion für eingerichtet werden muss IHttpContextAccessor.

Beim Hinzufügen von Skripten (zB teilweise)

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

Bei der Ausgabe der Skripte (zB in einer Layoutdatei)

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

Code

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

Nun, ich denke, die anderen Poster haben Ihnen die Möglichkeit gegeben, einen @ -Abschnitt direkt in Ihren Teil aufzunehmen (mithilfe von HTML-Helfern von Drittanbietern).

Ich gehe jedoch davon aus, dass Sie, wenn Ihr Skript eng mit Ihrem Teil verknüpft ist, Ihr Javascript direkt in ein Inline- <script>Tag innerhalb Ihres Teils einfügen und damit fertig sind (achten Sie nur auf die Duplizierung des Skripts, wenn Sie das Teil mehr als einmal verwenden möchten in einer einzigen Ansicht);


1
Dies ist normalerweise nicht ideal, da das Laden von jQuery usw. nach den Inline-Skripten erfolgen würde ... aber für nativen Code ist das in Ordnung.
Worthy7

-3

Angenommen, Sie haben eine Teilansicht mit dem Namen _contact.cshtml. Ihr Kontakt kann ein legaler (Name) oder ein physischer Betreff (Vorname, Nachname) sein. Ihre Ansicht sollte sich darum kümmern, was gerendert wird und was mit Javascript erreicht werden kann. Daher sind möglicherweise verzögertes Rendern und JS-Innenansicht erforderlich.

Der einzige Weg, wie ich denke, wie es weggelassen werden kann, ist, wenn wir eine unauffällige Art des Umgangs mit solchen UI-Problemen schaffen.

Beachten Sie auch, dass MVC 6 eine sogenannte View-Komponente haben wird, sogar MVC-Futures hatten ähnliche Dinge und Telerik unterstützt auch so etwas ...


1
3 Jahre zu spät, und ich glaube nicht, dass dies die Frage überhaupt beantwortet? Was versuchst du hier zu sagen? Die Beantwortung einer Frage 3 Jahre später mit spekulativen Merkmalen zukünftiger Technologien ist keine wirkliche Antwort oder besonders hilfreich
dan richardson

-3

Ich habe diesen Code gerade zu meiner Teilansicht hinzugefügt und das Problem gelöst, obwohl es nicht sehr sauber ist, funktioniert es. Sie müssen sicherstellen, dass die IDs der Objekte, die Sie rendern, vorhanden sind.

<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

Ich hatte das ähnliche Problem damit gelöst:

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

Das ist eine hübsche Art zu spritzen, denke ich.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.