Quando pensamos em template engine para o ASP.NET, a primeira coisa que vem a mente é o Razor. É uma unanimidade o quanto esta template engine é poderosa e, graças ao suporte constante da Microsoft, sempre recebe novos recursos, como os Razor Componentes. Também tem o fato que ela está integrada ao ASP.NET Core, o que facilita o seu uso. Mas ela não é a única opção de template engine para o ASP.NET Core.
Neste quesito se destaca a biblioteca Fluid, que adiciona ao ASP.NET Core suporte a template engine Liquid.
Curso Windows Server 2016 - Internet Information Services
Conhecer o cursoLiquid
Liquid é uma template engine open source criada pela empresa Shopify para ser utilizado na criação de temas do seu CMS (sistema de gerenciamento de conteúdo). Possuindo uma sintaxe simples, ele facilita a definição de templates principalmente para quem não está habituado com programação.
Criada em 2006, com o tempo esta template engine passou a ser adotada por outras soluções, principalmente sistemas CMS, como o Orchard Core, que é um framework CMS para ASP.NET Core.
Criando a aplicação que utilizará o Liquid
Diferente do Razor, o Liquid pode ser implementado até em um projeto console (para fazer isso com o Razor é necessário utilizar alguma biblioteca de terceiro). Mas para este artigo, trabalharemos com um projeto web e veremos como substituir o Razor pelo Liquid.
Desta forma, inicialmente iremos criar uma aplicação web vazia:
dotnet new web -n SimpleWebLiquid
Em seguida, adicione no projeto a referência da biblioteca Fluid:
dotnet add package Fluid.MvcViewEngine
Por fim, habilite o Fluid no método ConfigureServices
da classe Startup
:
public void ConfigureServices(IServiceCollection services) => services.AddMvc().AddFluid();
Como criamos uma aplicação web vazia, também é importante adicionar as rotas dos controllers no método Configure
desta classe:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseStaticFiles();
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Pessoa}/{action=Index}/{id?}");
});
}
Acima é definido como controller padrão Pessoa
, pois o criaremos a seguir.
Definindo o modelo e controller do exemplo
Para exemplificar o uso do Liquid, será definido no projeto um model Pessoa
:
public class Pessoa
{
public int Id { get; set; }
public string Nome { get; set; }
}
Um repositório simples para ele:
public class PessoaRepository
{
private static Dictionary<int, Pessoa> pessoas = new Dictionary<int, Pessoa>();
public List<Pessoa> GetAll(){
return pessoas.Values.ToList();
}
public void Add(Pessoa pessoa){
pessoa.Id = pessoas.Count + 1;
pessoas.Add(pessoa.Id, pessoa);
}
}
E por fim, o controller:
public class PessoaController: Controller
{
public readonly PessoaRepository repository;
public PessoaController() => repository = new PessoaRepository();
public IActionResult Index()
{
var pessoas = repository.GetAll();
return View(pessoas);
}
public IActionResult Create() => View();
[HttpPost]
public IActionResult Create([FromForm] Pessoa pessoa)
{
repository.Add(pessoa);
return RedirectToAction(nameof(Index));
}
}
Note que este controller não diferente muito de um controller definido quando estamos trabalhando com o Razor. A diferença é em relação ao não uso do validador contra ataques XSRF/CSRF (mesmo sem o Razor, isso pode ser configurado na classe Startup
, mas não faz parte do escopo deste artigo) e a definição da anotação [FromForm]
no parâmetro da nossa action Create
. Como não iremos trabalhar com modelos fortemente tipados nos templates, também não é possível validar os dados do modelo. Isso teria que ser feito “manualmente”.
Criando os templates Liquid
Assim como ocorre com o uso do Razor, o controller também irá procurar pelos templates Liquid das actions dentro de uma pasta com seu nome em Views
. Assim, no projeto, crie esta estrutura de pastas:
+-- Views
| +-- Pessoa
A biblioteca Fluid segue algumas conversões do Razor. Caso haja um arquivo chamado _ViewStart na pasta, ele será o primeiro arquivo carregado. Assim, dentro de Views
crie um arquivo chamado _ViewStart.liquid e adicione o conteúdo abaixo:
{% layout '_Layout' %}
A tag {% layout [template] %}
é utilizada para definir o template padrão utilizado nas views da pasta atual e todas as subpastas, funcionando como uma “master page”.
Acima, estamos indicando como template um arquivo chamado _Layout, assim o crie dentro da pasta atual (não se esqueça da extensão .liquid) e adicione o conteúdo abaixo:
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{{ ViewData['Title'] }}</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css">
</head>
<body>
<header>
<nav class="navbar navbar-expand-sm nnavbar-light bg-white border-bottom box-shadow mb-3">
<div class="container">
Exemplo de Liquid
</div>
</nav>
</header>
<div class="container">
<main role="main" class="pb-3">
{% renderbody %}
</main>
</div>
<footer class="border-top footer text-muted">
<div class="container">
© 2020 - Treinaweb
</div>
</footer>
<script src="https://code.jquery.com/jquery-3.4.1.slim.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js"></script>
</body>
</html>
No código acima é importante frisar a tag {% renderbody %}
. Ela indica o ponto onde o conteúdo das views será renderizado na template. Funcionando como o @RenderBody()
do Razor.
Também note o trecho de exibição do título:
<title>{{ ViewData['Title'] }}</title>
Nele estamos utilizando duas chaves. Estas chaves são utilizadas para indicar objetos que devem ser renderizados. Desta forma, quando este template for processado o conteúdo de ViewData['Title']
será exibido no HTML gerado.
Com o nosso template definido, podemos criar dentro da pasta Pessoa
, a view de listagem (Index.liquid):
<p>
<a href="/Pessoa/Create">Adicionar pessoa</a>
</p>
<table class="table">
<thead>
<tr>
<th>
Id
</th>
<th>
Nome
</th>
</tr>
</thead>
<tbody>
{% for p in Model %}
<tr>
<td>
{{ p.Id }}
</td>
<td>
{{ p.Nome }}
</td>
</tr>
{% endfor %}
</tbody>
</table>
E de cadastrado (Create.liquid):
<div class="row">
<div class="col-md-4">
<form action="/Pessoa/Create" method="post">
<div class="form-group">
<label for="Nome" class="control-label"></label>
<input name="Nome" id="Nome class="form-control" />
</div>
<div class="form-group">
<input type="submit" value="Salvar" class="btn btn-primary" />
</div>
</form>
</div>
</div>
<div>
<a href="/Pessoa/Index">Voltar para listagem</a>
</div>
Na view Index, note que estamos utilizando um laço for:
{% for p in Model %}
Este laço percorre um objeto Model
. Para que o template reconheça a estrutura deste model, é necessário que ele seja registrado no construtor da classe Startup
:
static Startup() => TemplateContext.GlobalMemberAccessStrategy.Register<Pessoa>();
Pronto, agora podemos testar a nossa aplicação.
Curso C# (C Sharp) - ASP.NET MVC
Conhecer o cursoA mágica acontecendo
Ao executar a aplicação e acessá-la, nos é exibido a tela de listagem:
Também podemos adicionar algumas pessoas:
E notaremos que o nosso template Liquid está funcionando corretamente:
Devo migrar todas as minhas aplicações para o Liquid?
O suporte do Liquid ao ASP.NET não significa que você deve utilizá-lo em todas as suas aplicações. Como dito no início, o Razor é uma poderosa ferramenta, que não deve ser substituída sem necessidade.
O Liquid é uma template engine mais acessível para pessoas com pouco conhecimento em programação. Então quando estiver trabalhando em um projeto onde as pessoas que irão lidar com as templates possuem esta característica, você deve dar uma olhada nesta template engine.
Você pode ver o código completo da aplicação demonstrada neste artigo no meu Github.