Featured image of post SPAs renderizadas no servidor com ASP.NET e sem Javascript

SPAs renderizadas no servidor com ASP.NET e sem Javascript

Como construir aplicativos de página única com ASP.NET e HTMX

Introdução Link to this section

Aplicativos de página única (SPA) proporcionam uma melhor experiência do usuário, removendo carregamentos de página inteira, proporcionando transições suaves entre as páginas e melhorias de desempenho dependendo da escala do aplicativo, mas eles vêm com alguns custos que podem não valer a pena para alguns tipos de aplicativos. O principal custo é a complexidade adicional de um framework SPA como Angular ou Vue.

Neste post, mostrarei como usar ASP.NET e HTMX para construir aplicativos de página única sem usar JavaScript e frameworks SPA.

O que é HTMX Link to this section

HTMX é uma biblioteca que nos permite acessar recursos modernos do navegador, estendendo o HTML, sem a necessidade de usar Javascript diretamente. Ele nos dá acesso a AJAX, CSS Transitions, WebSockets e muito mais, incluindo extensões.

Ele simplifica a criação de aplicativos responsivos que oferecem aos nossos usuários uma melhor experiência sem a complexidade do Javascript.

HTMX é realmente poderoso, mas neste post vou me concentrar no atributo hx-boost.

Aplicação base Link to this section

Usarei um antigo Aplicativo de exemplo do Razor Pages por Damian Edwards para mostrar como é fácil fazer um SPA com ASP.NET e HTMX.

Fiz algumas alterações, incluindo a atualização do .NET Core 2.0 para o .NET 7. Essas alterações estão no branch sample-base do repositório de amostra: AspNetCoreSPAHtmx.

💡 Estou usando Razor pages, mas o que mostrarei também é aplicável ao ASP.NET MVC.

Vamos abrir o _Layout.cshtml e adicionar a referência ao HTMX no final da tag <body>:

1
2
3
4
    ...

    <script src="https://unpkg.com/htmx.org@1.9.6"></script>
</body>

HTMX tem um atributo hx-boost que nos permite transformar nosso aplicativo de várias páginas em um aplicativo de página única (SPA) com apenas alguns ajustes, fazendo com que as tags <a> e <form> usem solicitações AJAX em vez de recarregamentos de página.

Ele funciona em todos os filhos do elemento ao qual é aplicado, então vamos incluí-lo na tag <nav> para que todos os links do menu usem AJAX:

<nav class="navbar navbar-inverse navbar-fixed-top" hx-boost="true" hx-target="#main-content">

Por padrão, hx-boost aplica a resposta AJAX à tag <body>, então precisamos especificar o id do destino (neste exemplo, #main-content) no atributo hx-target.

Agora, vamos criar uma div com id main-content para receber o conteúdo da resposta dentro da div body-content:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<div class="container body-content">
    <div id="main-content">
        @RenderBody()
    </div>

    <hr />

    <footer>
        <p>Carregamento de página inteira em @DateTime.Now</p>
        <p>&copy; 2017 - Razor Pages</p>
    </footer>
</div>

ℹ️ Observe que a hora do carregamento da página inteira está no rodapé, para que possamos verificar se as solicitações são feitas com AJAX.

Removendo o layout para solicitações HTMX Link to this section

Todas as solicitações feitas com HTMX terão um cabeçalho Hx-Request definido como true.

Vamos verificar isso no arquivo _ViewStart.cshtml e remover o layout da página para solicitações HTMX:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
@{
    @if (!ViewContext.HttpContext.Request.Headers["Hx-Request"].Contains("true"))
    {
        Layout = "_Layout";
    }
    else
    {
        Layout = "";
    }
}

Configurando os formulários Link to this section

Agora, também precisamos incluir os atributos hx-boost e hx-target nas tags <form> nos arquivos Index.cshtml, New.cshtml e Edit.cshtml:

<form method="post" class="form-horizontal" hx-boost="true" hx-target="#main-content">

Agora podemos testar o aplicativo. Observe que a hora do carregamento da página no rodapé não muda:

Olhando para o console do navegador, podemos ver que a solicitação foi feita:

O conteúdo do formulário no payload da solicitação:

A resposta sem o conteúdo _Layout.cshtml:

A resposta renderizada na página:

💡 A validação de formulário ASP.NET também funcionará:

Alterando o título da página de acordo com o carregamento da página Link to this section

Até agora, o título da página não está sendo alterado, porque está definido no arquivo _Layout.cshtml, que é carregado apenas no primeiro carregamento da página:

<title>@ViewData["Title"] - Razor Pages Sample + HTMX</title>

O atributo hx-boost mudará automaticamente o título da página se a resposta retornar uma tag <title>. Para fazer isso, vamos criar um novo layout para as solicitações HTMX.

Crie um _LayoutHtmxBoost.cshtml com o seguinte:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
@if (!string.IsNullOrEmpty(ViewData["Title"]!.ToString()))
{
    <head>
        <title>@ViewData["Title"] - Razor Pages Sample + HTMX</title>
    </head>
}

@RenderBody()

@RenderSection("Scripts", required: false)

Observe que retornamos uma tag <title> se o ViewData["Title"] tiver algum valor.

Agora, vamos alterar o arquivo _ViewStart.cshtml para usar o layout _LayoutHtmxBoost para solicitações HTMX:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
@{
    @if (!ViewContext.HttpContext.Request.Headers["Hx-Request"].Contains("true"))
    {
        Layout = "_Layout";
    }
    else if (ViewContext.HttpContext.Request.Headers["Hx-Boosted"].Contains("true"))
    {
        Layout = "_LayoutHtmxBoost";
    }
    else
    {
        Layout = "";
    }
}

⚠️ Estamos verificando o cabeçalho Hx-Boosted, solicitações HTMX não impulsionadas não usam este layout.

💡 HTMX automaticamente envia as páginas para o histórico do navegador:

Indicando o status de carregamento Link to this section

Isso é legal, mas não temos um indicador de que a solicitação está sendo processada. Para isso, podemos usar o atributo hx-indicator, especificando o id do elemento a ser mostrado enquanto aguarda a solicitação.

O hx-indicator funciona adicionando a classe css htmx-request ao elemento especificado, que definirá a opacidade como 1. Para que isso funcione, primeiro precisamos ocultar o elemento com uma opacidade de 0, ou usar a classe css htmx-indicator que faz isso com uma transação css.

Primeiro, criei uma página parcial com um carregador de Pure CSS Loaders com o nome _LoadSpinner, e defini o id como spinner e adicionei a classe css htmx-indicator:

1
2
3
<div id="spinner" class="htmx-indicator">
    <div class="lds-ring"><div></div><div></div><div></div><div></div></div>
</div>

Então, adicionei no menu:

1
2
3
4
5
6
7
<div class="collapse navbar-collapse" id="header-navbar-collapse">
    <ul class="nav navbar-nav">
        <li><a asp-page="/Index">Home</a></li>
        <li><a asp-page="/Customers/Index">Customers</a></li>
        <li><partial name="_LoadSpinner" /></li>
    </ul>
</div>

Por último, adiciono o atributo hx-indicator com o valor #spinner ao elemento <nav>:

<nav class="navbar navbar-inverse navbar-fixed-top" hx-boost="true" hx-target="#main-content" hx-indicator="#spinner">

e todos os elementos <form>:

<form method="post" class="form-horizontal" hx-boost="true" hx-target="#main-content" hx-indicator="#spinner">

💡 Para facilitar a visualização do indicador, incluí um atraso de 500 milissegundos em todos os métodos GET/POST das páginas:

1
2
3
4
5
6
public async Task<IActionResult> OnGetAsync(int id)
{
    await Task.Delay(TimeSpan.FromMilliseconds(500));

    ...
}

Executando o aplicativo novamente, podemos ver o indicador mostrando em todas as ações:

Código fonte completo Link to this section

Repositório GitHub

💬 Like or have something to add? Leave a comment below.
Ko-fi
GitHub Sponsor
Licensed under CC BY-NC-SA 4.0
Criado com Hugo
Tema Stack desenvolvido por Jimmy