Diseño en ASP.NET Core

Por Steve Smith y Dave Brock

Las páginas y las vistas a menudo comparten elementos visuales y elementos mediante programación. En este artículo se explica cómo:

  • Usar diseños comunes.
  • Compartir directivas.
  • Ejecutar código común antes de representar páginas o vistas.

En este documento se analizan los diseños para los dos enfoques distintos para ASP.NET Core MVC: Razor Pages y controladores con vistas. Para este tema, las diferencias son mínimas:

  • Razor Pages está en la carpeta Páginas.
  • Los controladores con vistas usan una carpeta Vistas para las vistas.

Qué es un diseño

La mayoría de las aplicaciones web tienen un diseño común que ofrece al usuario una experiencia coherente mientras navegan por sus páginas. El diseño suele incluir elementos comunes en la interfaz de usuario, como el encabezado, los elementos de navegación o de menú y el pie de página de la aplicación.

Page Layout example

Las estructuras HTML comunes, como scripts y hojas de estilo, también se usan con frecuencia en muchas páginas dentro de una aplicación. Todos estos elementos compartidos se pueden definir en un archivo de diseño, al que se puede hacer referencia por cualquier vista que se use en la aplicación. Los diseños reducen el código duplicado en las vistas.

Por convención, el diseño predeterminado para una aplicación ASP.NET Core se denomina _Layout.cshtml. Los archivos de diseño para los nuevos proyectos de ASP.NET Core creados con las plantillas son:

  • Razor Pages: Pages/Shared/_Layout.cshtml

    Pages folder in Solution Explorer

  • Controlador con vistas: Views/Shared/_Layout.cshtml

    Views folder in Solution Explorer

Este diseño define una plantilla de nivel superior para las vistas en la aplicación. Las aplicaciones no requieren un diseño. Las aplicaciones pueden definir más de un diseño, con otras vistas que especifiquen otros diseños.

Este código muestra el archivo de diseño para un proyecto creado mediante plantilla con un controlador y vistas:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>@ViewData["Title"] - WebApplication1</title>

    <environment include="Development">
        <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
        <link rel="stylesheet" href="~/css/site.css" />
    </environment>
    <environment exclude="Development">
        <link rel="stylesheet" href="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.7/css/bootstrap.min.css"
              asp-fallback-href="~/lib/bootstrap/dist/css/bootstrap.min.css"
              asp-fallback-test-class="sr-only" asp-fallback-test-property="position" asp-fallback-test-value="absolute" />
        <link rel="stylesheet" href="~/css/site.min.css" asp-append-version="true" />
    </environment>
</head>
<body>
    <nav class="navbar navbar-inverse navbar-fixed-top">
        <div class="container">
            <div class="navbar-header">
                <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
                    <span class="sr-only">Toggle navigation</span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                </button>
                <a asp-page="/Index" class="navbar-brand">WebApplication1</a>
            </div>
            <div class="navbar-collapse collapse">
                <ul class="nav navbar-nav">
                    <li><a asp-page="/Index">Home</a></li>
                    <li><a asp-page="/About">About</a></li>
                    <li><a asp-page="/Contact">Contact</a></li>
                </ul>
            </div>
        </div>
    </nav>

    <partial name="_CookieConsentPartial" />

    <div class="container body-content">
        @RenderBody()
        <hr />
        <footer>
            <p>&copy; 2018 - WebApplication1</p>
        </footer>
    </div>

    <environment include="Development">
        <script src="~/lib/jquery/dist/jquery.js"></script>
        <script src="~/lib/bootstrap/dist/js/bootstrap.js"></script>
        <script src="~/js/site.js" asp-append-version="true"></script>
    </environment>
    <environment exclude="Development">
        <script src="https://ajax.aspnetcdn.com/ajax/jquery/jquery-3.3.1.min.js"
                asp-fallback-src="~/lib/jquery/dist/jquery.min.js"
                asp-fallback-test="window.jQuery"
                crossorigin="anonymous"
                integrity="sha384-tsQFqpEReu7ZLhBV2VZlAu7zcOV+rXbYlF2cqB8txI/8aZajjp4Bqd+V6D5IgvKT">
        </script>
        <script src="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.7/bootstrap.min.js"
                asp-fallback-src="~/lib/bootstrap/dist/js/bootstrap.min.js"
                asp-fallback-test="window.jQuery && window.jQuery.fn && window.jQuery.fn.modal"
                crossorigin="anonymous"
                integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa">
        </script>
        <script src="~/js/site.min.js" asp-append-version="true"></script>
    </environment>

    @RenderSection("Scripts", required: false)
</body>
</html>

Especificar un diseño

Las vistas de Razor tienen una propiedad Layout. Las vistas individuales especifican un diseño al configurar esta propiedad:

@{
    Layout = "_Layout";
}

El diseño especificado puede usar una ruta de acceso completa (ejemplo: /Pages/Shared/_Layout.cshtml o /Views/Shared/_Layout.cshtml) o un nombre parcial (ejemplo: _Layout). Cuando se proporciona un nombre parcial, el motor de vista de Razor busca el archivo de diseño mediante su proceso de detección estándar. Primero se busca la carpeta donde existe el método de controlador (o controlador), seguida de la carpeta Shared. Este proceso de detección es idéntico al que se usa para detectar vistas parciales.

De forma predeterminada, todos los diseños deben llamar a RenderBody. Cada vez que se realiza la llamada a RenderBody, se representa el contenido de la vista.

Secciones

Opcionalmente, un diseño puede hacer referencia a una o varias secciones mediante una llamada a RenderSection. Las secciones permiten organizar dónde se deben colocar determinados elementos de la página. Cada llamada a RenderSection puede especificar si esa sección es obligatoria u opcional:

<script type="text/javascript" src="~/scripts/global.js"></script>

@RenderSection("Scripts", required: false)

Si no se encuentra una sección obligatoria, se produce una excepción. Las vistas individuales especifican el contenido que se va a representar dentro de una sección con la sintaxis @section de Razor. Si una página o una vista define una sección, se debe representar (o se producirá un error).

Ejemplo de definición de @section en una vista de Razor Pages:

@section Scripts {
     <script type="text/javascript" src="~/scripts/main.js"></script>
}

En el código anterior, scripts/main.js se agrega a la sección scripts en una página o vista. Es posible que otras páginas o vistas de la misma aplicación no necesiten este script y no definan una sección de script.

El marcado siguiente usa el asistente de etiquetas parcial para representar _ValidationScriptsPartial.cshtml:

@section Scripts {
    <partial name="_ValidationScriptsPartial" />
}

El marcado anterior se ha generado mediante la Identity de scaffolding.

Las secciones definidas en una vista o una vista solo están disponibles en su página de diseño inmediato. No se puede hacer referencia a ellas desde líneas de código parcialmente ejecutadas, componentes de vista u otros elementos del sistema de vistas.

Omitir secciones

De forma predeterminada, el cuerpo y todas las secciones de una página de contenido deben representarse mediante la página de diseño. Para cumplir con esto, el motor de vistas de Razorcomprueba si el cuerpo y cada sección se han representado.

Para indicar al motor de vistas que pase por alto el cuerpo o las secciones, llame a los métodos IgnoreBody y IgnoreSection.

El cuerpo y todas las secciones de una página de Razor deben representarse o pasarse por alto.

Importar directivas compartidas

Las vistas y las páginas pueden usar directivas de Razor para la importación de espacios de nombres y usar la inserción de dependencias. Se pueden especificar varias directivas compartidas por muchas vistas en un archivo _ViewImports.cshtml común. El archivo _ViewImports es compatible con estas directivas:

  • @addTagHelper
  • @removeTagHelper
  • @tagHelperPrefix
  • @using
  • @model
  • @inherits
  • @inject
  • @namespace

El archivo no es compatible con otras características de Razor, como las funciones y las definiciones de sección.

Archivo _ViewImports.cshtml de ejemplo:

@using WebApplication1
@using WebApplication1.Models
@using WebApplication1.Models.AccountViewModels
@using WebApplication1.Models.ManageViewModels
@using Microsoft.AspNetCore.Identity
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

El archivo _ViewImports.cshtml para una aplicación ASP.NET Core MVC normalmente se coloca en la carpeta Pages (o Vistas). Un archivo _ViewImports.cshtml puede colocarse dentro de cualquier carpeta, en cuyo caso solo se aplicará a las páginas o vistas dentro de esa carpeta y sus subcarpetas. Los archivos _ViewImports se procesan a partir del nivel de raíz y, después, para cada carpeta que llevó a la ubicación de la propia página o vista. La configuración _ViewImports especificada en el nivel de raíz se puede reemplazar en el nivel de carpeta.

Por ejemplo, supongamos que:

  • El archivo _ViewImports.cshtml raíz incluye @model MyModel1 y @addTagHelper *, MyTagHelper1.
  • Un archivo de subcarpeta _ViewImports.cshtml incluye @model MyModel2 y @addTagHelper *, MyTagHelper2.

Las páginas y las vistas de la subcarpeta tendrán acceso a los asistentes de etiquetas y al modelo MyModel2.

Si hay varios _ViewImports.cshtml en la jerarquía de archivos, el comportamiento combinado de las directivas es:

  • @addTagHelper, @removeTagHelper: todos se ejecutan en orden
  • @tagHelperPrefix: el más cercano a la vista invalida los demás
  • @model: el más cercano a la vista invalida los demás
  • @inherits: el más cercano a la vista invalida los demás
  • @using: todos se incluyen y se omiten los duplicados
  • @inject: para cada propiedad, la más cercana a la vista invalida cualquier otra con el mismo nombre de propiedad

Ejecutar código antes de cada vista

El código que debe ejecutarse antes de cada vista o página se debería colocar en el archivo _ViewStart.cshtml. Por convención, el archivo _ViewStart.cshtml se encuentra en la carpeta Pages (o Vistas). Las instrucciones que aparecen en _ViewStart.cshtml se ejecutan antes de cada vista completa (no los diseños ni las vistas parciales). Al igual que ViewImports.cshtml, _ViewStart.cshtml tiene una estructura jerárquica. Si se define un archivo _ViewStart.cshtml en la carpeta de vista o de página, se ejecutará después del que esté definido en la raíz de la carpeta Páginas (o Vistas) (si existe).

Archivo _ViewStart.cshtml de ejemplo:

@{
    Layout = "_Layout";
}

El archivo anterior especifica que todas las vistas usarán el diseño _Layout.cshtml.

_ViewStart.cshtml y _ViewImports.cshtml normalmente no se colocan en la carpeta /Pages/Shared (o /Views/Shared). Las versiones de nivel de aplicación de estos archivos deben colocarse directamente en la carpeta /Pages (o /Views).