Layout no ASP.NET Core

Por Steve Smith e Dave Brock

Páginas e exibições com frequência compartilham elementos visuais e programáticos. Este artigo demonstra como:

  • Usar layouts comuns.
  • Compartilhar diretivas.
  • Executar o código comum antes de renderizar páginas ou modos de exibição.

Este documento discute os layouts para as duas abordagens diferentes ao ASP.NET Core MVC: Razor Pages e controladores com exibições. Para este tópico, as diferenças são mínimas:

  • Razor Pages estão na pasta Pages.
  • Controladores com exibições usam uma pasta Views pasta exibições.

O que é um layout

A maioria dos aplicativos Web tem um layout comum que fornece aos usuários uma experiência consistente durante sua navegação de uma página a outra. O layout normalmente inclui elementos comuns de interface do usuário, como o cabeçalho do aplicativo, elementos de menu ou de navegação e rodapé.

Page Layout example

Estruturas HTML comuns, como scripts e folhas de estilo, também são usadas frequentemente por muitas páginas em um aplicativo. Todos esses elementos compartilhados podem ser definidos em um arquivo de layout, que pode então ser referenciado por qualquer exibição usada no aplicativo. Os layouts reduzem o código duplicado nas exibições.

Por convenção, o layout padrão de um aplicativo ASP.NET Core é chamado _Layout.cshtml. Os arquivos de layout para novos projetos do ASP.NET Core criados com os modelos são:

  • Razor Pages: Pages/Shared/_Layout.cshtml

    Pages folder in Solution Explorer

  • Controlador com exibições: Views/Shared/_Layout.cshtml

    Views folder in Solution Explorer

O layout define um modelo de nível superior para exibições no aplicativo. Aplicativos não exigem um layout. Os aplicativos podem definir mais de um layout, com diferentes exibições que especificam layouts diferentes.

O código a seguir mostra o arquivo de layout para um modelo de projeto criado com um controlador e exibições:

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

Especificando um layout

Exibições Razor têm uma propriedade Layout. As exibições individuais especificam um layout com a configuração dessa propriedade:

@{
    Layout = "_Layout";
}

O layout especificado pode usar um caminho completo (por exemplo, /Pages/Shared/_Layout.cshtml ou /Views/Shared/_Layout.cshtml) ou um nome parcial (exemplo: _Layout). Quando um nome parcial for fornecido, o mecanismo de exibição do Razor pesquisará o arquivo de layout usando seu processo de descoberta padrão. A pasta em que o método do manipulador (ou controlador) existe é pesquisada primeiro, seguida pela pasta Shared. Esse processo de descoberta é idêntico àquele usado para descobrir exibições parciais.

Por padrão, todo layout precisa chamar RenderBody. Sempre que a chamada a RenderBody for feita, o conteúdo da exibição será renderizado.

Seções

Um layout, opcionalmente, pode referenciar uma ou mais seções, chamando RenderSection. As seções fornecem uma maneira de organizar o local em que determinados elementos da página devem ser colocados. Cada chamada a RenderSection pode especificar se essa seção é obrigatória ou opcional:

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

@RenderSection("Scripts", required: false)

Se uma seção obrigatória não for encontrada, uma exceção será gerada. As exibições individuais especificam o conteúdo a ser renderizado em uma seção usando a sintaxe @sectionRazor. Se uma página ou exibição definir uma seção, ela precisará ser renderizada (ou ocorrerá um erro).

Um exemplo de definição @section na exibição Razor Pages:

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

No código anterior, scripts/main.js é adicionado à seção scripts em uma página ou exibição. Outras páginas ou exibições no mesmo aplicativo podem não exigir esse script e não definirão uma seção de scripts.

A marcação a seguir usa o Auxiliar de Marca Parcial para renderizar _ValidationScriptsPartial.cshtml:

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

A marcação anterior foi gerada por scaffolding Identity.

As seções definidas em uma página ou exibição estão disponíveis apenas em sua página de layout imediata. Elas não podem ser referenciadas em parciais, componentes de exibição ou outras partes do sistema de exibição.

Ignorando seções

Por padrão, o corpo e todas as seções de uma página de conteúdo precisam ser renderizados pela página de layout. O mecanismo de exibição do Razor impõe isso acompanhando se o corpo e cada seção foram renderizados.

Para instruir o mecanismo de exibição a ignorar o corpo ou as seções, chame os métodos IgnoreBody e IgnoreSection.

O corpo e cada seção em uma página do Razor precisam ser renderizados ou ignorados.

Importando diretivas compartilhadas

Exibições e páginas podem usar diretivas do Razor para importar namespaces e usar injeção de dependência. Diretivas compartilhadas por muitas exibições podem ser especificadas em um arquivo _ViewImports.cshtml comum. O arquivo _ViewImports dá suporte às seguintes diretivas:

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

O arquivo não dá suporte a outros recursos do Razor, como funções e definições de seção.

Um arquivo _ViewImports.cshtml de exemplo:

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

O arquivo _ViewImports.cshtml para um aplicativo ASP.NET Core MVC normalmente é colocado na pasta Pages (ou Views). Um arquivo _ViewImports.cshtml pode ser colocado em qualquer pasta, caso em que só será aplicado a páginas ou exibições nessa pasta e em suas subpastas. Arquivos _ViewImports são processados começando no nível raiz e, em seguida, para cada pasta até o local da página ou da exibição em si. As configurações _ViewImports especificadas no nível raiz podem ser substituídas no nível da pasta.

Por exemplo, suponha:

  • O arquivo de nível raiz _ViewImports.cshtml inclui @model MyModel1 e @addTagHelper *, MyTagHelper1.
  • Um arquivo de subpasta _ViewImports.cshtml inclui @model MyModel2 e @addTagHelper *, MyTagHelper2.

Páginas e exibições na subpasta terão acesso a Auxiliares de Marca e ao modelo MyModel2.

Se vários arquivos _ViewImports.cshtml forem encontrados na hierarquia de arquivos, o comportamento combinado das diretivas será:

  • @addTagHelper, @removeTagHelper: todos são executados, em ordem
  • @tagHelperPrefix: o mais próximo à exibição substitui todos os outros
  • @model: o mais próximo à exibição substitui todos os outros
  • @inherits: o mais próximo à exibição substitui todos os outros
  • @using: todos são incluídos; duplicatas são ignoradas
  • @inject: para cada propriedade, o mais próximo à exibição substitui todos os outros com o mesmo nome de propriedade

Executando o código antes de cada exibição

Código que precisa ser executado antes de cada exibição ou página precisa ser colocada no arquivo _ViewStart.cshtml. Por convenção, o arquivo _ViewStart.cshtml está localizado na pasta Pages (ou Views). As instruções listadas em _ViewStart.cshtml são executadas antes de cada exibição completa (não layouts nem exibições parciais). Como ViewImports.cshtml, _ViewStart.cshtml é hierárquico. Se um arquivo _ViewStart.cshtml for definido na pasta de exibições ou páginas, ele será executado depois daquele definido na raiz da pasta Pages (ou Views) (se houver).

Um arquivo _ViewStart.cshtml de exemplo:

@{
    Layout = "_Layout";
}

O arquivo acima especifica que todas as exibições usarão o layout _Layout.cshtml.

_ViewStart.cshtml e _ViewImports.cshtmlnão são tipicamente colocados na pasta /Pages/Shared (ou /Views/Shared). As versões no nível do aplicativo desses arquivos devem ser colocadas diretamente na pasta /Pages (ou /Views).