Inyección de contenido en secciones específicas desde una vista parcial ASP.NET MVC 3 con Razor View Engine

324

Tengo esta sección definida en mi _Layout.cshtml

@RenderSection("Scripts", false)

Puedo usarlo fácilmente desde una vista:

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

Con lo que estoy luchando es cómo inyectar algo de contenido dentro de esta sección desde una vista parcial.

Asumamos que esta es mi página de vista:

@section Scripts { 

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

<div>
    poo bar poo
</div>

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

Necesito inyectar algo de contenido dentro de la Scriptssección desde _myPartialuna vista parcial.

¿Cómo puedo hacer esto?

tugberk
fuente
17
para cualquiera que venga más tarde a esto: hay un paquete nuget para manejar esto: nuget.org/packages/Forloop.HtmlHelpers
Russ Cam
@RussCam deberías responder esta pregunta. +1 el paquete nuget resuelve el problema exacto que tiene OP.
Carrie Kendall
1
El paquete @RussCam NuGet no es una solución, el código del paquete podría serlo.
Maksim Vi.
8
@MaksimVi. bueno, escribí el paquete nuget y no tengo intenciones de eliminarlo, así que en lugar de repetir el código ( bitbucket.org/forloop/forloop-htmlhelpers/src ) o el wiki ( bitbucket.org/forloop/forloop-htmlhelpers/wiki / Inicio ) aquí, un enlace a él como comentario se mantiene dentro del espíritu de stackoverflow, IMO.
Russ Cam
Aquí hay otra solución que parece muy agradable: stackoverflow.com/questions/5355427/…
jkokorian

Respuestas:

235

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 usar la sección @scripts de la vista principal para hacer eso y no hacer que los parciales se preocupen por los scripts.

Darin Dimitrov
fuente
445
Pero, ¿qué pasa si el guión es muy específico para el parcial? ¿No tiene sentido lógico que se defina en la vista parcial y no en la vista?
Jez
43
¿Por qué es por diseño?
Shimmy Weitzhandler
56
@Darin: no estoy de acuerdo. ¿Qué pasa con el principio DRY? No me gusta repetirme, aunque solo sean referencias de guiones.
fretje
14
@fretje, todos tienen derecho a expresar su opinión sobre el tema. Yo respeto el tuyo. En mi respuesta, he expresado la mía y vinculada a una respuesta que le permitiría lograr esta tarea. Pero también he destacado lo que recomendaría y haría para esta situación.
Darin Dimitrov
33
secundando a @JoshNoe y al resto: un "widget" (pantalla + interacción enriquecida) es un ejemplo perfecto de una vista parcial estrechamente acoplada a JavaScript asociado. Por diseño, no debería tener que escribir dos declaraciones de inclusión en diferentes lugares para obtener la funcionalidad completa, porque la pantalla nunca estará sin la interacción con el asistente, y la interacción nunca aparecerá en otro lugar.
drzaus
83

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

En mi _layouttengo:

@RenderSection("body_scripts", false)

En mi indexopinión tengo:

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

En mi clientsopinión, tengo (todo el mapa y la asociación html):

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

Mi Clients_Scriptsvista contiene el JavaScript que se representará en la página

De esta manera, mi secuencia de comandos está aislada y se puede representar en la página donde sea necesario, con la body_scriptsetiqueta solo representada en la primera vez que el motor de vista de afeitar la encuentra.

Eso me permite tener todo separado: es una solución que funciona bastante bien para mí, otros pueden tener problemas con ella, pero soluciona el agujero "por diseño".

dan richardson
fuente
2
No fui yo quien te rechazó, pero diré que realmente no me gusta esta solución porque todavía separa los scripts específicos de la vista de la vista misma.
aplastar
3
Otras 20 personas y yo tenemos una opinión diferente. Todavía puede tener scripts directamente relacionados con una vista que están en un archivo separado, es un error de programación si no incluye su script junto con su vista. Tenerlo en un archivo separado separa la interacción de la presentación y permite una gran cantidad de otros beneficios de estar en un archivo separado.
dan richardson
1
Tienes toda la razón. De hecho, estoy completamente de acuerdo y prefiero este método personalmente. El verdadero problema para mí es que mis colegas luchan con tanta separación. Sin embargo, ese es un problema de dominio. Creo que este método es ideal, especialmente una vez que tiene en cuenta un proceso de compilación de JavaScript. Continuaré trabajando para educar a mis colegas sobre el uso de este método y lo apoyaré por completo. Sin embargo, creo que tu respuesta podría mejorarse. Sin embargo, no fue necesario mencionar las "20 personas de acuerdo". El hecho de que una respuesta sea popular no siempre significa que sea correcta. En este caso es correcto.
aplastar
Muy cierto, y siempre estoy feliz de aceptar comentarios constructivos y alterar mi propio código y responder si hay una mejora para tener :)
dan richardson
1
Esta solución tiene el beneficio adicional de poder seguir haciendo todas las cosas MVC-ish que esperarías poder hacer en una Vista típica, como ser capaz de codificar JSON un modelo pasado y generar URL usando Url. Acción. Este enfoque es una forma elegante de configurar sus controladores AngularJS: cada vista parcial puede representar un controlador separado en el módulo Angular. ¡Muy limpio!
Dan
40

De las soluciones en este hilo , se me ocurrió la siguiente solución probablemente demasiado complicada que le permite retrasar la representación de cualquier 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")) {
        <script>
            someInlineScript();
        </script>
    }
    
  2. En una vista parcial, incluya el bloque por cada vez que se use el parcial:

    @using (Html.Delayed()) {
        <b>show me multiple times, @Model.Whatever</b>
    }
    
  3. En una vista parcial, solo incluya el bloque una vez, sin importar cuántas veces se repita el parcial, pero más tarde, hágalo específicamente por su nombre 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>
    }
    

Renderizar las "secciones"

(es decir, mostrar 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 {

    /// <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
fuente
1
Wow, incluso es complicado para mí entender el código, pero +1 por encontrar una solución
Rameez Ahmed Sayad
@RameezAhmedSayad tienes razón: al volver aquí, incluso estoy confundido por cómo quise decir cómo usarlo. Actualizando la respuesta ...
drzaus
Y para aclarar más, la razón por la que hay dos "nombres" es que si solo desea que se represente una vez que necesita la clave única en el parámetro isOnlyOne, pero solo si desea representarlo en una ubicación específica por nombre, proporcione el identificador, de lo contrario, se tira a la basura Html.RenderDelayed().
drzaus
Personalmente, no creo que sea necesario comprar el problema y usar este enfoque, la sección en vistas parciales simplemente no es necesaria ya que puede eliminarse, y los scripts pueden ir allí sin definir una sección. Es porque se representa externamente y si ve el código de la página representada, simplemente nota que el código para la vista parcial no está visible allí. Entonces, si se trata de una mejor organización, etc., eso no tendrá ningún efecto en absoluto.
Trascendente
@Transcendent el "debate" ya ha comenzado en los comentarios sobre la respuesta aceptada stackoverflow.com/a/7556594/1037948
drzaus
16

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 declaración de sección acumulativa

yo chico
fuente
9

Si tiene una necesidad legítima de ejecutar algunos jsde un partial, aquí es cómo puede hacerlo, jQueryse requiere:

<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
fuente
Intenté @drzaus, necesita 'SeeIfReady' o no funciona.
Cacho Santa
8

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

archil
fuente
10
¿Qué pasaría si la vista parcial no se muestra en la página? ¿Todavía hacemos referencia a esos archivos .js en padre y hacemos que se sobrecargue?
Murali Murugesan
5

Hay una falla fundamental en la forma en que pensamos sobre la web, especialmente cuando usamos MVC. La falla es que JavaScript es de alguna manera responsabilidad de la vista. Una vista es una vista, JavaScript (conductual o de otro tipo) 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 AMD (a mí mismo me gusta RequireJS ). Separe su JavaScript en módulos, defina su funcionalidad y conéctese a 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.

Señor baudin
fuente
Durante unos dos o tres meses más o menos, estoy usando RequireJS y no creo que pueda desarrollar otra aplicación web sin RequireJS.
tugberk
66
JavaScript también puede ser responsabilidad de Ver.
Kelmen
1
Usar el patrón AMD es una buena idea, pero no estoy de acuerdo con su afirmación de que JavaScript es parte del modelo. A menudo es para definir el comportamiento de Vista, especialmente cuando se combina con algo como Knockout. Vuelca una representación JSON de su modelo en su Vista de JavaScript. Personalmente, solo uso cierres, un "espacio de nombres" personalizado en el windowobjeto e incluyo scripts de biblioteca antes de cualquier parcial.
aplastar
Creo que hay un malentendido aquí. Al desarrollar la mayoría de las aplicaciones web, en realidad estamos desarrollando dos aplicaciones: una que se ejecuta en el servidor y otra que se ejecuta en el cliente. Desde la perspectiva del servidor, cualquier cosa que envíe al navegador es la "vista". En ese sentido, JavaScript es parte de la vista. Desde la perspectiva de la aplicación cliente, HTML puro es vista y JS es código que es paralelo a M y C en términos MVC del servidor. Creo que es por eso que la gente no está de acuerdo aquí.
TheAgent
3

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

Entiendo que quiere tener esa Vista Parcial para ser autónoma. La idea es similar a los componentes cuando se usa Angular.

Mi forma sería mantener los scripts dentro de la Vista parcial tal como están. Ahora, el problema con eso es cuando se llama a Vista parcial, puede ejecutar el script allí antes que todos los otros scripts (que generalmente se agrega al final de la página de diseño). En ese caso, solo tiene que el guión de Vista parcial espera los otros guiones. Hay varias formas de hacer esto. El más simple, que he usado antes, está usando un evento body.

En mi diseño, tendría algo en la parte inferior como este:

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

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

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

      // .. do your thing here

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

Otra solución es usar una pila para empujar 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.

alanos
fuente
2

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

En primer lugar, nunca intenté si esto funciona desde una vista parcial, pero debería hacerlo.

Iridio
fuente
Acabo de tratar; lamentablemente eso no funciona (creó un ViewBag.RenderScripts = new List<string>();en la parte superior de la página principal, luego llamó @Html.Partial("_CreateUpdatePartial",Model,ViewData), luego puso @section Scripts {@foreach (string script in ViewBag.RenderScripts) Scripts.Render(script); }}. En vista parcial pongo @{ViewBag.RenderScripts = ViewBag.RenderScripts ?? new List<string>();ViewBag.RenderScripts.Add("~/bundles/jquery");}.
JohnLBevan
2

No puede necesitar usar secciones en vista parcial.

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

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

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

Usar así:

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

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

En su _Layout.cshtml, o donde quiera que se representen los scripts de los parciales, coloque lo siguiente (una vez): representará solo 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: representará solo el html en esta ubicación.

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

Hay una manera de insertar secciones en vistas parciales, aunque no es bonita. Debe tener acceso a dos variables desde la Vista principal. Dado que 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<WebPageBase, HtmlHelper>
@{
    Model.Key.DefineSection("SectionNameGoesHere", () =>
    {
        Model.Value.ViewContext.Writer.Write("Test");
    });
}

Y en la página insertando la vista parcial ...

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

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

¡Disfrutar!

Plutón
fuente
1
¿Puedes por favor y un enlace a un proyecto en pleno funcionamiento?
Ehsan Zargar Ershadi
1

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

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

Views \ web.config:

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

Ver:

@PartialWithScripts("_BackendSearchForm")

Parcial (_BackendSearchForm.cshtml):

@{ RenderScriptsInBasePage(scripts()); }

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

Página de diseño:

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

Esto funcionó para mí, lo que me permitió ubicar conjuntamente JavaScript y HTML para una 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 la vista que usa la vista parcial llamada "_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" } })

 }

En archivo de vista parcial

@model SomeType

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

@if (region == "HTMLSection")
{


}

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

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

Envolví mi Vista parcial en una gran declaración if-else:

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

Luego, llamé a 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 } })
}
Rick Love
fuente
Seguramente, la idea es que el consumidor de la vista parcial no debería saber que tiene que incluir scripts, ¿ese es el problema? De lo contrario, también puedes decir @Html.Partial("MyPartialViewScripts")
dan richardson
No, la idea es permitir que los scripts se definan en el mismo documento que el html, pero estoy de acuerdo en que esto no es lo ideal.
Rick Love
0

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

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

...

@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í:

@{
    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
fuente
En mi opinión, debería haber resuelto esto moviendo su JS a un archivo separado.
Worthy7
0

Por supuesto, puede usar su Folder / index.cshtml como página maestra y luego agregar scripts de sección. Luego, en su diseño tiene:

@RenderSection("scripts", required: false) 

y tu index.cshtml:

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

y funcionará en todas sus vistas parciales. Funciona para mi

RogerEdward
fuente
0

Usando Mvc Core puede crear un TagHelper ordenado scriptscomo se ve a continuación. Esto podría fácilmente transformarse en una sectionetiqueta donde también se le da un nombre (o el nombre se toma del tipo derivado). Tenga en cuenta que la inyección de dependencia debe configurarse para IHttpContextAccessor.

Al agregar scripts (por ejemplo, en un parcial)

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

Al generar los scripts (por ejemplo, en un archivo de diseño)

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

Código

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

Me encontré con un problema casi idéntico el otro día, excepto que la vista parcial fue una respuesta a una solicitud de AJAX. En mi situación, el parcial era en realidad una página completa, pero quería que fuera accesible como parcial desde otras páginas.

Si desea renderizar secciones de forma parcial, la solución más limpia es crear un nuevo diseño y usar una variable ViewBag. Esto no funciona @Html.Partial()o lo nuevo <partial></partial>, usa AJAX.

Vista principal (que desea representar como parcial en otro lugar):

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

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

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

Controlador:

public IActionResult GetPartial() {

    ViewBag.Partial = true;

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

_layoutPartial.cshtml (nuevo):

@RenderSection("Scripts")
@RenderBody()

Luego usa AJAX en tu página.

Si desea renderizar la página en el diseño principal (no parcial), no la configure ViewBag.Partial = true. No se necesitan ayudantes HTML.

KatoFett
fuente
-1

Bueno, supongo que los otros carteles le han proporcionado un medio para incluir directamente una sección @ dentro de su parcial (mediante el uso de ayudantes html de terceros).

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

CShark
fuente
1
Esto no suele ser ideal porque la carga de jQuery, etc. ocurriría después de los scripts en línea ... pero para el código nativo, supongo que está bien.
Worthy7
-3

suponga que tiene una vista parcial llamada _contact.cshtml, su contacto puede ser legal (nombre) o un sujeto físico (nombre, apellido). su vista debe cuidar lo que se representa y eso se puede lograr con javascript. Por lo tanto, es posible que se necesite un renderizado retrasado y una vista interior de JS.

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

También tenga en cuenta que MVC 6 tendrá un llamado componente de vista, incluso los futuros de MVC tenían algunas cosas similares y Telerik también admite tal cosa ...

user4298890
fuente
1
¿Tres años tarde y no creo que esto responda a la pregunta? ¿Qué estás tratando de decir aquí? Responder una pregunta 3 años después con características especulativas de tecnologías futuras no es realmente una respuesta o particularmente útil
dan richardson
-3

Acabo de agregar este código en mi vista parcial y resolví el problema, aunque no está muy limpio, funciona. Debe asegurarse de que los ID de los objetos que está renderizando.

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

Tuve el problema similar resuelto con esto:

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

Supongo que es una buena manera de inyectar.

Pouria Jafari
fuente