Diseños anidados Razor con secciones en cascada

80

Tengo un sitio MVC3 que usa Razor como motor de visualización. Quiero que mi sitio sea personalizable. La mayoría de las máscaras posibles son lo suficientemente similares como para derivar de un diseño maestro compartido.

Por lo tanto, estoy considerando este diseño:

Diagrama de vista planificada

Sin embargo, me gustaría ser capaz de llamar RenderSectionen la capa inferior, _Common.cshtmly tienen que rinde una sección que se define en la capa superior, Detail.cshtml. Esto no funciona: RenderSectionaparentemente solo renderiza las secciones que están definidas en la siguiente capa.

Por supuesto, puedo definir cada sección en cada skin. Por ejemplo, si _Commonnecesita llamar RenderSection("hd")a una sección definida en Detail, simplemente coloco esto en cada uno _Skiny funciona:

@section hd {
    @RenderSection("hd")
}

Esto da como resultado cierta duplicación de código (ya que ahora cada máscara debe tener la misma sección) y, en general, se siente desordenado. Todavía soy nuevo en Razor y parece que me estoy perdiendo algo obvio.

Al depurar, puedo ver la lista completa de secciones definidas en WebViewPage.SectionWritersStack. Si pudiera decirle a RenderSection que revise toda la lista antes de rendirme, encontraría la sección que necesito. Por desgracia, SectionWritersStack no es público.

Alternativamente, si pudiera acceder a la jerarquía de páginas de diseño e intentar ejecutar RenderSection en cada contexto diferente, podría ubicar la sección que necesito. Probablemente me esté perdiendo algo, pero no veo ninguna forma de hacer esto.

¿Hay alguna forma de lograr este objetivo, además del método que ya describí?

Chris Nielsen
fuente

Respuestas:

35

De hecho, esto no es posible hoy usando la API pública (aparte de usar el enfoque de redefinición de secciones). Es posible que tenga algo de suerte utilizando la reflexión privada, pero eso, por supuesto, es un enfoque frágil. Veremos cómo facilitar este escenario en la próxima versión de Razor.

Mientras tanto, aquí hay un par de publicaciones de blog que he escrito sobre el tema:

maridar
fuente
3
Gracias por la respuesta, he estado jugando con las secciones anidadas usando esta sintaxis (como arriba): @section hd {@RenderSection ("hd")} ... que realmente funciona para mí y parece que puedo replicar MasterPages anidadas existentes . Creo que entendí mal la pregunta un poco y pensé que esto no funcionaría.
Mark Redman
2
Tanto la pregunta como la respuesta ayudaron mucho y también estoy de acuerdo en que esto debería ser más fácil en la próxima versión de Razor. Y también debe habilitar la posibilidad de que las vistas parciales también puedan implementar secciones, lo que no es compatible en este momento.
yegua
1
@Shrike No creo que nada haya cambiado en esta área. Puede ingresar solicitudes de funciones en el sitio de uservoice o errores en el codeplex
marcind
1
@marcind Echa un vistazo a mi respuesta. Creo que esto es lo que pidió el OP. ¿Correcto?
Alireza Noori
1
MVC 5 está fuera. ¿Cualquier actualización? Alirzea dijo que encontró una solución, pero que no parecía coincidir con el problema de los OP, ya que no hacía referencia a secciones en absoluto.
Snekse
17
@helper ForwardSection( string section )
{
   if (IsSectionDefined(section))
   {
       DefineSection(section, () => Write(RenderSection(section)));
   }
}

¿Esto haría el trabajo?

Cachondo
fuente
¿Estás usando esto en la capa intermedia? ¿Más o menos lo mismo que esta clase de extensión ? Si es así, esto es más conveniente cuando se redeclara una sección, en lugar de resolver el problema, ¿verdad? Solo asegurándome de entender, ya que llego tarde a esta discusión.
drzaus
Para mí, esta fue la única solución que funcionó. Si una sección se representa condicionalmente en el diseño base, MVC arrojará un error de tiempo de ejecución a menos que esa sección esté definida condicionalmente (como esta) en la capa intermedia. ¡Gracias @Randy!
Michael
¿Es posible reenviar todas las secciones que están definidas actualmente?
nvirth
4

No estoy seguro de si esto es posible en MVC 3, pero en MVC 5 puedo hacerlo con éxito usando el siguiente truco:

En ~/Views/Shared/_Common.cshtmlescriba su código HTML común como:

<!DOCTYPE html>
<html lang="fa">
<head>
    <title>Skinnable - @ViewBag.Title</title>
</head>
<body>
@RenderBody()
</body>
</html>

En ~/Views/_ViewStart.cshtml:

@{
    Layout = "~/Views/Shared/_Common.cshtml";
}

Ahora todo lo que tienes que hacer es usar _Common.cshtmlcomo Layoutpara todas las máscaras. Por ejemplo, en ~/Views/Shared/Skin1.cshtml:

@{
    Layout = "~/Views/Shared/_Common.cshtml";
}

<p>Something specific to Skin1</p>

@RenderBody()

Ahora puede configurar la máscara como su diseño en el controlador o la vista según sus criterios. Por ejemplo:

    public ActionResult Index()
    {
        //....
        if (user.SelectedSkin == Skins.Skin1)
            return View("ViewName", "Skin1", model);
    }

Si ejecuta el código anterior, debería obtener una página HTML con el contenido Skin1.cshtmly_Common.cshtml

En resumen, establecerá el diseño de la página de diseño (aspecto).

Alireza Noori
fuente
Tuve problemas con este enfoque porque las secciones no serían visibles. Encontré la solución en blogs.msdn.microsoft.com/marcinon/2010/12/15/…
Spikolynn
1

No estoy seguro de si esto le ayudará, pero escribí algunos métodos de extensión para ayudar a "burbujear" las secciones desde los parciales, que también deberían funcionar para diseños anidados.

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

Declarar en diseño secundario / vista / parcial

@using (Html.Delayed()) {
    <b>show me multiple times, @Model.Whatever</b>
}

Render en cualquier padre

@Html.RenderDelayed();

Consulte el enlace de respuesta para obtener más casos de uso, como representar solo un bloque retrasado incluso si se declara en una vista repetida, representar bloques retrasados ​​específicos, etc.

drzaus
fuente