Injection de contenu dans des sections spécifiques à partir d'une vue partielle ASP.NET MVC 3 avec Razor View Engine

324

J'ai cette section définie dans mon _Layout.cshtml

@RenderSection("Scripts", false)

Je peux facilement l'utiliser depuis une vue:

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

Ce qui me pose problème, c'est comment injecter du contenu dans cette section à partir d'une vue partielle.

Supposons que ceci soit ma page d'affichage:

@section Scripts { 

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

<div>
    poo bar poo
</div>

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

J'ai besoin d'injecter du contenu à l'intérieur de la Scriptssection à partir d' _myPartialune vue partielle.

Comment puis-je faire ceci?

tugberk
la source
17
pour tous ceux qui viendront plus tard - il y a un paquet nuget pour gérer cela: nuget.org/packages/Forloop.HtmlHelpers
Russ Cam
@RussCam, vous devez répondre à cette question. +1 le paquet nuget résout le problème exact qu'OP a.
Carrie Kendall
1
Le package @RussCam NuGet n'est pas une solution, le code du package peut l'être.
Maksim Vi.
8
@MaksimVi. eh bien, j'ai écrit le paquet nuget et je n'ai aucune intention de le retirer, donc plutôt que de répéter le code ( bitbucket.org/forloop/forloop-htmlhelpers/src ) ou le wiki ( bitbucket.org/forloop/forloop-htmlhelpers/wiki / Accueil ) ici, un lien vers celui-ci en tant que commentaire est conservé dans l'esprit de stackoverflow, IMO.
Russ Cam
Voici une autre solution qui semble très agréable: stackoverflow.com/questions/5355427/…
jkokorian

Réponses:

235

Les sections ne fonctionnent pas dans les vues partielles et c'est par conception. Vous pouvez utiliser certains assistants personnalisés pour obtenir un comportement similaire, mais honnêtement, c'est la responsabilité de la vue d'inclure les scripts nécessaires, pas celle du partiel. Je recommanderais d'utiliser la section @scripts de la vue principale pour ce faire et de ne pas vous soucier des scripts partiels.

Darin Dimitrov
la source
445
Mais que se passe-t-il si le script est très spécifique au partiel? N'est-il pas logique qu'elle soit définie dans le partiel et non dans la vue?
Jez
43
Pourquoi est-ce par conception?
Shimmy Weitzhandler
56
@Darin: Je ne suis pas d'accord. Et le principe DRY? Je n'aime pas me répéter, même si ce ne sont que des références de script.
fretje
14
@fretje, tout le monde a le droit d'exprimer son opinion sur le sujet. Je respecte la tienne. Dans ma réponse, j'ai exprimé la mienne et liée à une réponse qui vous permettrait d'accomplir cette tâche. Mais j'ai également souligné ce que je recommanderais et ferais pour cette situation.
Darin Dimitrov
33
seconder @JoshNoe et le reste - un "widget" (affichage + interaction riche) est un parfait exemple d'une vue partielle étroitement couplée au javascript associé. De par ma conception, je ne devrais pas avoir à écrire deux instructions d'inclusion à différents endroits pour obtenir toutes les fonctionnalités, car l'affichage ne sera jamais sans l'interaction qui l'accompagne et l'interaction n'apparaîtra jamais ailleurs.
drzaus
83

C'est une question assez populaire, donc je posterai ma solution.
J'ai eu le même problème et bien que ce ne soit pas idéal, je pense que cela fonctionne assez bien et ne rend pas le partiel dépendant de la vue.
Mon scénario était qu'une action était accessible par elle-même mais pouvait également être intégrée dans une vue - une carte Google.

Dans mon _layoutj'ai:

@RenderSection("body_scripts", false)

À mon indexavis, j'ai:

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

À mon clientsavis, j'ai (toute la carte et assoc. Html):

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

Ma Clients_Scriptsvue contient le javascript à afficher sur la page

De cette façon, mon script est isolé et peut être rendu dans la page si nécessaire, la body_scriptsbalise n'étant rendue que lors de la première occurrence que le moteur de visualisation du rasoir trouve.

Cela me permet de tout séparer - c'est une solution qui fonctionne assez bien pour moi, d'autres peuvent avoir des problèmes avec elle, mais elle corrige le trou "par conception".

dan richardson
la source
2
Je n'étais pas le seul à vous voter contre, mais je dirai que je n'aime pas vraiment cette solution car elle sépare toujours les scripts spécifiques à la vue de la vue elle-même.
écraser le
3
20 autres personnes et j'ai une opinion différente. Vous pouvez toujours avoir des scripts directement liés à une vue qui sont dans un fichier séparé, c'est une erreur de programmation si vous n'incluez pas votre script avec votre vue. Le fait de l'avoir dans un fichier séparé sépare l'interaction de la présentation et permet une abondance d'autres avantages d'être dans un fichier séparé.
dan richardson
1
Tu as tout à fait raison. En fait, je suis entièrement d'accord et je préfère personnellement cette méthode. Le vrai problème pour moi, c'est que mes collègues sont aux prises avec une telle séparation. C'est un problème de domaine, cependant. Je pense que cette méthode est idéale, surtout une fois que vous prenez en compte un processus de construction JavaScript. Je continuerai de travailler à éduquer mes collègues à l'utilisation de cette méthode et je la soutiendrai entièrement. Je pense cependant que votre réponse pourrait être améliorée. Mais vous n'avez pas besoin de mentionner les "20 personnes d'accord". Le fait qu'une réponse soit populaire ne signifie pas toujours qu'elle est juste. Dans ce cas, c'est vrai.
écraser le
Très vrai, et je suis toujours heureux d'accepter les commentaires constructifs et de modifier mon propre code et de répondre s'il y a une amélioration à apporter :)
dan richardson
1
Cette solution a l'avantage supplémentaire de toujours être en mesure de faire toutes les choses MVC-ish que vous attendez d'être en mesure de faire dans une vue typique, comme être capable de coder en JSON un modèle transmis et de générer des URL à l'aide de l'URL. Action. Cette approche est alors une manière élégante de configurer vos contrôleurs AngularJS - chaque vue partielle peut représenter un contrôleur distinct dans le module Angular. Si propre!
Dan
40

À partir des solutions de ce fil , j'ai trouvé la solution probablement trop compliquée suivante qui vous permet de retarder le rendu de tout html (scripts aussi) dans un bloc using.

USAGE

Créer la "section"

  1. Scénario typique: dans une vue partielle, n'incluez le bloc qu'une seule fois, quel que soit le nombre de répétitions de la vue partielle dans la page:

    @using (Html.Delayed(isOnlyOne: "some unique name for this section")) {
        <script>
            someInlineScript();
        </script>
    }
  2. Dans une vue partielle, incluez le bloc pour chaque utilisation du partiel:

    @using (Html.Delayed()) {
        <b>show me multiple times, @Model.Whatever</b>
    }
  3. Dans une vue partielle, n'incluez le bloc qu'une seule fois, quel que soit le nombre de répétitions du partiel, mais rendez-le plus tard spécifiquement par son nom 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>
    }

Rendre les "sections"

(c'est-à-dire afficher la section retardée dans une vue parent)

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


}
drzaus
la source
1
Wow, il est même compliqué pour moi de comprendre le code, mais +1 pour trouver une solution
Rameez Ahmed Sayad
@RameezAhmedSayad vous avez raison - en revenant ici même je suis confus par la façon dont je voulais dire comment l'utiliser. Mise à jour de la réponse ...
drzaus
Et pour clarifier davantage - la raison pour laquelle il y a deux "noms" est que si vous ne voulez le rendre qu'une fois qu'il a besoin de la clé unique en paramètre isOnlyOne, mais seulement si vous voulez le rendre à un emplacement spécifique par son nom, fournissez-vous l'identifiant, sinon, il est vidé Html.RenderDelayed().
drzaus
Personnellement, je ne pense pas qu'il serait nécessaire d'acheter le problème et d'utiliser cette approche, la section dans les vues partielles n'est tout simplement pas nécessaire car elle peut être éliminée et les scripts peuvent y aller sans définir de section. C'est parce que c'est rendu en externe et si vous voyez le code de la page rendue, vous remarquez simplement que le code pour la vue partielle n'y est pas visible. Donc, si c'est la question d'une meilleure organisation, etc., cela n'aura aucun effet.
transcendant
@Transcendant le "débat" a déjà été lancé dans les commentaires sur la réponse acceptée stackoverflow.com/a/7556594/1037948
drzaus
16

J'ai eu ce problème et j'ai utilisé cette technique.

C'est la meilleure solution que j'ai trouvée, qui est très flexible.

Veuillez également voter ici pour ajouter le support de la déclaration de section cumulative

iBoy
la source
9

Si vous avez un besoin légitime d'en exécuter à jspartir d'un partial, voici comment vous pouvez le faire, jQueryest requis:

<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>
Serj Sagan
la source
J'ai essayé @drzaus, il a besoin du 'SeeIfReady' ou cela ne fonctionne pas.
Cacho Santa
8

Suivant le principe discret , il n'est pas tout à fait nécessaire pour "_myPartial" d'injecter du contenu directement dans la section des scripts. Vous pouvez ajouter ces scripts de vue partielle dans un .jsfichier séparé et les référencer dans la section @scripts de la vue parent.

archil
la source
10
Que se passerait-il si la vue partielle n'était pas du tout rendue dans la page? Allons-nous toujours référencer ces fichiers .js dans le parent et le surcharger?
Murali Murugesan
5

Il y a une faille fondamentale dans notre façon de penser le Web, en particulier lors de l'utilisation de MVC. Le défaut est que JavaScript est en quelque sorte la responsabilité de la vue. Une vue est une vue, JavaScript (comportemental ou autre) est JavaScript. Dans le modèle MVVM de Silverlight et WPF, nous sommes confrontés à «voir d'abord» ou «modèle d'abord». Dans MVC, nous devons toujours essayer de raisonner du point de vue du modèle et JavaScript fait partie de ce modèle à bien des égards.

Je suggérerais d'utiliser le modèle AMD (j'aime moi-même RequireJS ). Séparez votre JavaScript en modules, définissez vos fonctionnalités et connectez-vous à votre code HTML à partir de JavaScript au lieu de compter sur une vue pour charger le JavaScript. Cela nettoiera votre code, séparera vos préoccupations et vous facilitera la vie d'un seul coup.

M. Baudin
la source
Pendant environ deux ou trois mois, j'utilise RequireJS et je ne pense pas que je développerai une autre application Web sans RequireJS.
tugberk
6
JavaScript peut également être la responsabilité de la vue.
Kelmen
1
L'utilisation du modèle AMD est une bonne idée, mais je ne suis pas d'accord avec votre affirmation selon laquelle JavaScript fait partie du modèle. C'est souvent pour définir le comportement de View, en particulier lorsqu'il est associé à quelque chose comme Knockout. Vous transférez une représentation JSON de votre modèle dans votre vue JavaScript. Personnellement, j'utilise simplement des fermetures, un "espace de noms" personnalisé sur l' windowobjet, et j'inclus des scripts de bibliothèque avant tout partiel.
écraser le
Je pense qu'il y a un malentendu ici. Lors du développement de la plupart des applications Web, nous développons en fait deux applications: une qui s'exécute sur le serveur et une qui s'exécute sur le client. Du point de vue du serveur, tout ce que vous envoyez au navigateur est la "vue". En ce sens, JavaScript fait partie de la vue. Du point de vue de l'application cliente, le HTML pur est la vue et JS est le code qui est parallèle au M et au C en termes MVC du serveur. Je pense que c'est pourquoi les gens ne sont pas d'accord ici.
TheAgent
3

Le but de l'OP est qu'il veuille définir des scripts en ligne dans sa vue partielle, ce que je suppose que ce script est spécifique uniquement à cette vue partielle, et avoir ce bloc inclus dans sa section de script.

Je comprends qu'il veut que cette vue partielle soit autonome. L'idée est similaire aux composants lors de l'utilisation d'Angular.

Ma façon serait de garder les scripts dans la vue partielle tels quels. Maintenant, le problème avec cela est lors de l'appel de la vue partielle, il peut y exécuter le script avant tous les autres scripts (qui sont généralement ajoutés au bas de la page de disposition). Dans ce cas, il suffit que le script Vue partielle attende les autres scripts. Il y a plusieurs moyens de le faire. Le plus simple, que j'ai utilisé auparavant, utilise un événement surbody .

Sur ma mise en page, j'aurais quelque chose en bas comme ceci:

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

Puis sur ma vue partielle (en bas):

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

      // .. do your thing here

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

Une autre solution consiste à utiliser une pile pour envoyer tous vos scripts et appeler chacun à la fin. Une autre solution, comme déjà mentionné, est le modèle RequireJS / AMD, qui fonctionne très bien également.

alans
la source
2

La première solution à laquelle je peux penser est d'utiliser ViewBag pour stocker les valeurs qui doivent être rendues.

Onestly je n'ai jamais essayé si ce travail d'une vue partielle, mais il devrait imo.

Iridio
la source
Je viens d'essayer; malheureusement cela ne fonctionne pas (créé un ViewBag.RenderScripts = new List<string>();en haut de la page principale, puis appelé @Html.Partial("_CreateUpdatePartial",Model,ViewData), puis mis @section Scripts {@foreach (string script in ViewBag.RenderScripts) Scripts.Render(script); }}. Dans la vue partielle, je mets @{ViewBag.RenderScripts = ViewBag.RenderScripts ?? new List<string>();ViewBag.RenderScripts.Add("~/bundles/jquery");}.
JohnLBevan
2

Vous ne devez pas utiliser des sections en vue partielle.

Inclure dans votre vue partielle. Il exécute la fonction après le chargement de jQuery. Vous pouvez modifier la clause de condition pour votre code.

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

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

Julio Spader

Julio Spader
la source
2

Vous pouvez utiliser ces méthodes d'extension : (Enregistrer en tant que 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);
                }
            }
        }
    }
}

Utilisez comme ceci:

Exemple partiel: (_MyPartial.cshtml) Mettez le html dans le if et le js dans le else.

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

Dans votre _Layout.cshtml, ou partout où vous voulez que les scripts des partiels soient rendus, mettez ce qui suit (une fois): Il rendra uniquement le javascript de tous les partiels sur la page en cours à cet emplacement.

@{ Html.RenderPartialScripts(); }

Ensuite, pour utiliser votre partiel, faites simplement ceci: il ne rendra que le html à cet emplacement.

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

Il existe un moyen d'insérer des sections dans des vues partielles, mais ce n'est pas joli. Vous devez avoir accès à deux variables à partir de la vue parent. Puisqu'une partie du but même de votre vue partielle est de créer cette section, il est logique d'exiger ces variables.

Voici à quoi ressemble l'insertion d'une section dans la vue partielle:

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

Et dans la page insérant la vue partielle ...

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

Vous pouvez également utiliser cette technique pour définir le contenu d'une section par programme dans n'importe quelle classe.

Prendre plaisir!

Pluton
la source
1
Pouvez-vous s'il vous plaît et un lien vers un projet pleinement fonctionnel?
Ehsan Zargar Ershadi
1

L'idée de Pluton d'une manière plus agréable:

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

Vues \ web.config:

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

Vue:

@PartialWithScripts("_BackendSearchForm")

Partielle (_BackendSearchForm.cshtml):

@{ RenderScriptsInBasePage(scripts()); }

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

Page de mise en page:

@RenderSection("scripts", required: false)
PaulSanS
la source
1

Cela a fonctionné pour moi, me permettant de co-localiser javascript et html pour une vue partielle dans le même fichier. Aide au processus de réflexion pour voir le HTML et la partie associée dans le même fichier de vue partielle.


Dans la vue qui utilise la vue partielle appelée "_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" } })

 }

Dans le fichier de vue partielle

@model SomeType

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

@if (region == "HTMLSection")
{


}

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

J'ai résolu ce problème complètement différent (parce que j'étais pressé et que je ne voulais pas implémenter un nouveau HtmlHelper):

J'ai enveloppé ma vue partielle dans une grande déclaration if-else:

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

Ensuite, j'ai appelé le partiel deux fois avec un ViewData personnalisé:

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

@section scripts{
    @Html.Partial("MyPartialView", Model, 
        new ViewDataDictionary { { "ShouldRenderScripts", true } })
}
Rick Love
la source
L'idée est sûrement que le consommateur de la vue partielle ne devrait pas avoir besoin de savoir qu'il doit inclure des scripts, c'est un peu le problème? Sinon, vous pouvez aussi bien dire @Html.Partial("MyPartialViewScripts")
dan richardson
Non, l'idée est de permettre aux scripts d'être définis dans le même document que le html, mais je suis d'accord que ce n'est pas idéal.
Rick Love
0

J'ai eu un problème similaire, où j'avais une page maître comme suit:

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

...

@Html.Partial("_Charts", Model)

mais la vue partielle dépendait de certains JavaScript dans la section Scripts. Je l'ai résolu en encodant la vue partielle en JSON, en la chargeant dans une variable JavaScript, puis en l'utilisant pour remplir un div, donc:

@{
    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>
John M
la source
OMI, vous auriez dû résoudre ce problème en déplaçant votre JS dans un fichier séparé.
Worthy7
0

vous pouvez choisir d'utiliser un dossier / index.cshtml comme page maître, puis ajouter des scripts de section. Ensuite, dans votre mise en page, vous avez:

@RenderSection("scripts", required: false) 

et votre index.cshtml:

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

et cela fonctionnera sur toutes vos vues partielles. Ça marche pour moi

RogerEdward
la source
0

En utilisant Mvc Core, vous pouvez créer un TagHelper bien rangé scriptscomme indiqué ci-dessous. Cela pourrait facilement être transformé en une sectionbalise où vous lui donnez également un nom (ou le nom est tiré du type dérivé). Notez que l'injection de dépendances doit être configurée pour IHttpContextAccessor.

Lors de l'ajout de scripts (par exemple dans un partiel)

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

Lors de la sortie des scripts (par exemple dans un fichier de mise en page)

<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()));
            }
        }
    }
BlackjacketMack
la source
0

J'ai rencontré un problème presque identique l'autre jour, sauf que la vue partielle était une réponse à une demande AJAX. Dans ma situation, le partiel était en fait une page entière, mais je voulais qu'il soit accessible en tant que partiel à partir d'autres pages.

Si vous souhaitez rendre des sections dans un partiel, la solution la plus propre consiste à créer une nouvelle disposition et à utiliser une variable ViewBag. Cela ne fonctionne pas avec @Html.Partial()ou le nouveau <partial></partial>, utilisez AJAX.

Vue principale (que vous souhaitez rendre en tant que partielle ailleurs):

@if(ViewBag.Partial == true) {
    Layout = "_layoutPartial";
}

<div>
    [...]
</div>    

@section Scripts {
    <script type="text/javascript">
        [...]
    </script>
}

Manette:

public IActionResult GetPartial() {

    ViewBag.Partial = true;

    //Do not return PartialView!
    return View("/path/to/view")
}

_layoutPartial.cshtml (nouveau):

@RenderSection("Scripts")
@RenderBody()

Utilisez ensuite AJAX dans votre page.

Si vous souhaitez rendre la page dans la mise en page principale (pas partielle), ne définissez pas ViewBag.Partial = true. Aucun assistant HTML n'est nécessaire.

KatoFett
la source
-1

Eh bien, je suppose que les autres affiches vous ont fourni un moyen d'inclure directement une @section dans votre partiel (en utilisant des assistants html tiers).

Mais, je pense que, si votre script est étroitement couplé à votre partiel, mettez simplement votre javascript directement dans une <script>balise en ligne dans votre partiel et terminez-le (faites juste attention à la duplication de script si vous avez l'intention d'utiliser le partiel plus d'une fois en une seule vue);

CShark
la source
1
Ce n'est généralement pas idéal car le chargement de jQuery, etc. se produira après les scripts en ligne ... mais pour le code natif, je suppose que c'est bien.
Worthy7
-3

supposons que vous ayez une vue partielle appelée _contact.cshtml, votre contact peut être un sujet légal (nom) ou physique (prénom, nom). votre vue doit prendre soin de ce qui est rendu et qui peut être obtenu avec javascript. un rendu différé et une vue intérieure JS peuvent donc être nécessaires.

la seule façon dont je pense, comment cela peut être omis, c'est quand nous créons une manière discrète de gérer ces problèmes d'interface utilisateur.

Notez également que MVC 6 aura un soi-disant composant d'affichage, même les futurs MVC avaient des choses similaires et Telerik prend également en charge une telle chose ...

user4298890
la source
1
3 ans de retard, et je ne pense pas que cela réponde même à la question? Qu'essayez-vous de dire ici? Répondre à une question 3 ans plus tard avec des caractéristiques spéculatives des technologies futures n'est pas vraiment une réponse ou particulièrement utile
dan richardson
-3

Je viens d'ajouter ce code sur ma vue partielle et j'ai résolu le problème, mais pas très propre, cela fonctionne. Vous devez vous assurer que les ID des objets que vous rendez.

<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>
luis
la source
-5

J'ai eu le problème similaire résolu avec ceci:

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

C'est une jolie façon d'injecter i guesse.

Pouria Jafari
la source