Inyectar contenido en secciones específicas de una vista parcial ASP.NET MVC 3 con Razor View Engine

Tengo esta sección definida en mi _Layout.cshtml

 @RenderSection("Scripts", false) 

Puedo usarlo fácilmente desde una vista:

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

Lo que estoy luchando es cómo obtener contenido inyectado dentro de esta sección desde una vista parcial.

Supongamos que esta es mi página de vista:

 @section Scripts {  //code comes here  } 
poo bar poo
@Html.Partial("_myPartial")

Necesito inyectar un poco de contenido dentro de la sección Scripts desde _myPartial partial view.

¿Cómo puedo hacer esto?

    Las secciones no funcionan en vistas parciales y eso es por diseño. Puede usar algunos ayudantes personalizados para lograr un comportamiento similar, pero, honestamente, es responsabilidad de la vista incluir los scripts necesarios, no la responsabilidad parcial. Recomendaría utilizar la sección @scripts de la vista principal para hacer eso y no hacer que los parciales se preocupen por las secuencias de comandos.

    Esta es una pregunta bastante popular, así que publicaré mi solución.
    Tuve el mismo problema y aunque no es ideal, creo que realmente funciona bastante bien y no hace que el parcial dependa de la vista.
    Mi hipótesis era que una acción era accesible por sí misma, pero también podría integrarse en una vista: un mapa de Google.

    En mi _layout tengo:

     @RenderSection("body_scripts", false) 

    En mi vista de index tengo:

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

    En mi vista de clients tengo (todo el mapa y assoc. Html):

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

    La vista My Clients_Scripts contiene el javascript que se representará en la página

    De esta forma, mi secuencia de comandos está aislada y puede mostrarse en la página donde se requiera, con la etiqueta body_scripts solo representada en la primera aparición que el motor de visualización de la máquina de afeitar la encuentre.

    Eso me permite separar todo: es una solución que funciona bastante bien para mí, otros pueden tener problemas, pero repara el agujero “por diseño”.

    A partir de las soluciones de este hilo , se me ocurrió la siguiente solución probablemente demasiado complicada que permite retrasar la renderización de html (también scripts) dentro de un bloque de uso.

    USO

    Crea la “sección”

    1. Escenario típico: en una vista parcial, solo incluya el bloque una vez, sin importar cuántas veces se repita la vista parcial en la página:

       @using (Html.Delayed(isOnlyOne: "some unique name for this section")) {  } 
    2. En una vista parcial, incluya el bloque para cada vez que se use el parcial:

       @using (Html.Delayed()) { show me multiple times, @Model.Whatever } 
    3. En una vista parcial, solo incluya el bloque una vez, sin importar cuántas veces se repita el parcial, pero más tarde lo renderice específicamente por su nombre when-i-call-you :

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

    Renderice las “secciones”

    (es decir, muestra la sección retrasada en una vista principal)

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

    CÓDIGO

     public static class HtmlRenderExtensions { ///  /// Delegate script/resource/etc injection until the end of the page /// @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/  ///  private class DelayedInjectionBlock : IDisposable { ///  /// Unique internal storage key ///  private const string CACHE_KEY = "DCCF8C78-2E36-4567-B0CF-FE052ACCE309"; // "DelayedInjectionBlocks"; ///  /// Internal storage identifier for remembering unique/isOnlyOne items ///  private const string UNIQUE_IDENTIFIER_KEY = CACHE_KEY; ///  /// What to use as internal storage identifier if no identifier provided (since we can't use null as key) ///  private const string EMPTY_IDENTIFIER = ""; ///  /// Retrieve a context-aware list of cached output delegates from the given helper; uses the helper's context rather than singleton HttpContext.Current.Items ///  /// the helper from which we use the context /// optional unique sub-identifier for a given injection block /// list of delayed-execution callbacks to render internal content public static Queue GetQueue(HtmlHelper helper, string identifier = null) { return _GetOrSet(helper, new Queue(), identifier ?? EMPTY_IDENTIFIER); } ///  /// Retrieve a context-aware list of cached output delegates from the given helper; uses the helper's context rather than singleton HttpContext.Current.Items ///  /// the helper from which we use the context /// 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 /// optional unique sub-identifier for a given injection block /// list of delayed-execution callbacks to render internal content private static T _GetOrSet(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)); } ///  /// Get the storage, but if it doesn't exist or isn't the expected type, then create a new "bucket" ///  ///  ///  public static Dictionary GetStorage(HtmlHelper helper) { var storage = helper.ViewContext.HttpContext.Items[CACHE_KEY] as Dictionary; if (storage == null) helper.ViewContext.HttpContext.Items[CACHE_KEY] = (storage = new Dictionary()); return storage; } private readonly HtmlHelper helper; private readonly string identifier; private readonly string isOnlyOne; ///  /// Create a new using block from the given helper (used for trapping appropriate context) ///  /// the helper from which we use the context /// optional unique identifier to specify one or many injection blocks /// extra identifier used to ensure that this item is only added once; if provided, content should only appear once in the page (ie only the first block called for this identifier is used) 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; } ///  /// Append the internal content to the context's cached list of output delegates ///  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(), 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) } } } ///  /// Start a delayed-execution block of output -- this will be rendered/printed on the next call to . ///  ///  /// Print once in "default block" (usually rendered at end via @Html.RenderDelayed()). Code: ///  /// @using (Html.Delayed()) { /// show at later /// @Model.Name /// etc /// } ///  ///  ///  ///  ///  /// Print once (ie if within a looped partial), using identified block via @Html.RenderDelayed("one-time"). Code: ///  /// @using (Html.Delayed("one-time", isOnlyOne: "one-time")) { /// show me once /// @Model.First().Value /// } ///  ///  ///  ///  /// the helper from which we use the context /// optional unique identifier to specify one or many injection blocks /// extra identifier used to ensure that this item is only added once; if provided, content should only appear once in the page (ie only the first block called for this identifier is used) /// using block to wrap delayed output public static IDisposable Delayed(this HtmlHelper helper, string injectionBlockId = null, string isOnlyOne = null) { return new DelayedInjectionBlock(helper, injectionBlockId, isOnlyOne); } ///  /// Render all queued output blocks injected via . ///  ///  /// Print all delayed blocks using default identifier (ie not provided) ///  /// @using (Html.Delayed()) { /// show me later /// @Model.Name /// etc /// } ///  /// -- then later -- ///  /// @using (Html.Delayed()) { /// more for later /// etc /// } ///  /// -- then later -- ///  /// @Html.RenderDelayed() // will print both delayed blocks ///  ///  ///  ///  ///  /// Allow multiple repetitions of rendered blocks, using same @Html.Delayed()... as before. Code: ///  /// @Html.RenderDelayed(removeAfterRendering: false); /* will print */ /// @Html.RenderDelayed() /* will print again because not removed before */ ///  ///  ///  ///  /// the helper from which we use the context /// optional unique identifier to specify one or many injection blocks /// only render this once /// rendered output content 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("", 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("", injectionBlockId) + #endif string.Join(Environment.NewLine, stack)); } } 

    Tuve este problema y usé esta técnica.

    Es la mejor solución que encontré, que es muy flexible.

    También vote aquí para agregar soporte para la statement de sección acumulativa

    Siguiendo el principio discreto , no es necesario que “_myPartial” inserte contenido directamente en la sección de scripts. Puede agregar esos scripts de vista parcial en un archivo .js separado y hacer referencia a ellos en la sección @scripts desde la vista principal.

    Si tiene una necesidad legítima de ejecutar algunos js de un partial , he aquí cómo puede hacerlo, se requiere jQuery :

      

    Hay un error fundamental en la forma en que pensamos sobre la web, especialmente cuando se usa MVC. El error es que JavaScript es de alguna manera la responsabilidad de la vista. Una vista es una vista, JavaScript (de comportamiento u otro) es JavaScript. En el patrón MVVM de Silverlight y WPF nos enfrentamos con “ver primero” o “modelo primero”. En MVC siempre debemos tratar de razonar desde el punto de vista del modelo y JavaScript es parte de este modelo de muchas maneras.

    Sugeriría usar el patrón de AMD (yo mismo me gusta RequireJS ). Separe su JavaScript en los módulos, defina su funcionalidad y enganche su html desde JavaScript en lugar de confiar en una vista para cargar el JavaScript. Esto limpiará su código, separará sus preocupaciones y facilitará la vida de una sola vez.

    La primera solución que puedo pensar es usar ViewBag para almacenar los valores que se deben representar.

    Primero, nunca lo intenté si esto funciona desde una vista parcial, pero debería ser imo.

    Hay una forma de insertar secciones en vistas parciales, aunque no es bonita. Debe tener acceso a dos variables desde la vista principal. Como parte del propósito de su vista parcial es crear esa sección, tiene sentido requerir estas variables.

    Esto es lo que parece insertar una sección en la vista parcial:

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

    Y en la página que inserta la vista parcial …

     @Html.Partial(new KeyValuePair(this, Html)) 

    También puede usar esta técnica para definir los contenidos de una sección mediante progtwigción en cualquier clase.

    ¡Disfrutar!

    Puede utilizar estos métodos de extensión : (Guardar como 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(); } 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); } } } } } 

    Use esto:

    Ejemplo parcial: (_MyPartial.cshtml) Coloque el html en if y el js en else.

     @if (ViewBag.ScriptPartialHtml ?? true) 

    I has htmls

    } else { }

    En su _Layout.cshtml, o donde quiera que se procesen los scripts de los parciales, ponga lo siguiente (una vez): solo renderizará el javascript de todos los parciales en la página actual en esta ubicación.

     @{ Html.RenderPartialScripts(); } 

    Luego, para usar su parcial, simplemente haga esto: renderizará solo el html en esta ubicación.

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

    Esto funcionó para mí y me permitió ubicar javascript y html para la vista parcial en el mismo archivo. Ayuda con el proceso de pensamiento para ver html y partes relacionadas en el mismo archivo de vista parcial.


    En Vista, que usa Vista parcial llamada “_MyPartialView.cshtml”

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

    En el archivo de vista parcial

     @model SomeType @{ var region = ViewData["Region"] as string; } @if (region == "HTMLSection") { } @if (region == "ScriptSection") {  } 

    No puede necesitar usar secciones en vista parcial.

    Incluir en su Vista parcial. Ejecuta la función después de que jQuery cargó. Puede alterar la cláusula de condición para su código.

      

    Julio Spader

    Tuve un problema similar, donde tuve una página maestra de la siguiente manera:

     @section Scripts {  } ... @Html.Partial("_Charts", Model) 

    pero la vista parcial dependía de algunos JavaScript en la sección Scripts. Lo resolví codificando la vista parcial como JSON, cargándola en una variable de JavaScript y luego usándola para llenar un div, así que:

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

    De forma personalizada, puede usar una carpeta Folder / index.cshtml como página maestra y luego agregar scripts de sección. Entonces, en tu diseño tienes:

     @RenderSection("scripts", required: false) 

    y su index.cshtml:

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

    y funcionará sobre todas tus vistas parciales. Funciona para mí

    La idea de Plutón de una manera más agradable:

    CustomWebViewPage.cs:

      public abstract class CustomWebViewPage : WebViewPage { 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()); }); } } 

    Views \ web.config:

      

    Ver:

     @PartialWithScripts("_BackendSearchForm") 

    Parcial (_BackendSearchForm.cshtml):

     @{ RenderScriptsInBasePage(scripts()); } @helper scripts() {  } 

    Página de diseño:

     @RenderSection("scripts", required: false) 

    Usando Mvc Core puedes crear scripts TagHelper ordenados como se ve a continuación. Esto podría transformarse fácilmente en una etiqueta de section donde también se le da un nombre (o el nombre se toma del tipo derivado). Tenga en cuenta que la dependency injection debe configurarse para IHttpContextAccessor .

    Al agregar scripts (por ejemplo, en un parcial)

        

    Al enviar los guiones (por ejemplo, en un archivo de diseño)

      

    Código

     public class ScriptsTagHelper : TagHelper { private static readonly object ITEMSKEY = new Object(); private IDictionary _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; var content = String.Concat(scripts); output.Content.SetHtmlContent(content); } } else { List list = null; if (!_items.ContainsKey(ITEMSKEY)) { list = new List(); _items[ITEMSKEY] = list; } list = _items[ITEMSKEY] as List; var content = await output.GetChildContentAsync(); list.Add(new HtmlString(content.GetContent())); } } } 

    Acabo de agregar este código en mi vista parcial y resolví el problema, aunque no muy limpio, funciona. Debes asegurarte de los Id. De los objetos que estás renderizando.

    $ (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: {botón: ‘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’}});});

    El objective del OP es que quiera definir scripts en línea en su Vista parcial, que asumo que este script es específico solo para esa Vista parcial, y tiene ese bloque incluido en su sección de scripts.

    Entiendo que quiere que esa Vista parcial sea independiente. La idea es similar a los componentes cuando se usa Angular.

    Mi camino sería mantener las secuencias de comandos dentro de la Vista parcial como está. Ahora el problema con esto es cuando se llama a Vista parcial, puede ejecutar el script allí antes que todos los demás scripts (que generalmente se agrega al final de la página de diseño). En ese caso, solo tiene la secuencia de comandos de Vista parcial a la espera de las otras secuencias de comandos. Hay varias formas de hacer esto. El más simple, que he usado antes, es usar un evento en el body .

    En mi diseño, tendría algo en la parte inferior de esta manera:

     // global scripts  // view scripts @RenderSection("scripts", false) // then finally trigger partial view scripts  

    Luego en mi Vista parcial (en la parte inferior):

      

    Otra solución es usar una stack para enviar todos sus scripts y llamar a cada uno al final. Otra solución, como ya se mencionó, es el patrón RequireJS / AMD, que también funciona muy bien.

    Resolví esta una ruta completamente diferente (porque tenía prisa y no quería implementar un nuevo HtmlHelper):

    Envolví mi Vista parcial en una gran statement if-else:

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

    Luego, llamé al Parcial dos veces con un ViewData personalizado:

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

    Bueno, supongo que los otros carteles te proporcionaron un medio para incluir directamente una @sección dentro de tu parcial (usando ayudantes html de terceros).

    Pero creo que, si su script está estrechamente relacionado con su parcial, simplemente coloque su javascript directamente dentro de una etiqueta línea dentro de su parcial y termine con esto (solo tenga cuidado con la duplicación de scripts si tiene la intención de usar el parcial más de una vez en una sola vista);

    Supongamos que tiene una vista parcial llamada _contact.cshtml, su contacto puede ser un tema legal (nombre) o físico (nombre, apellido). su punto de vista debería ocuparse de lo que se renderiza y de lo que se puede lograr con javascript. por lo que puede ser necesario un renderizado diferido y JS inside view.

    la única forma en que pienso, cómo se puede omitir, es cuando creamos una forma discreta de manejar tales problemas de UI.

    También tenga en cuenta que MVC 6 tendrá un llamado Componente de Vista, incluso los futuros de MVC tienen algo similar y Telerik también es compatible con tal cosa …

    I had the similar problem solved it with this:

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

    Thats a pretty way to inject i guesse.