in ASP.NET

ASP.NET MVC 6 – Razor com suporte ao Async e Flush

O Razor é uma view engine extremamente poderosa, com uma sintax simples e limpa de utilizar. Entre as novidades do ASP.NET 5 estão varias melhorias desta view engine (lembrando que o Razor é uma view engine independente do ASP.NET MVC, sendo utilizada para varias outras coisas).

Entre os principais recursos novos estão o suporte ao Async e o Flush parcial da resposta do html para o usuário, a idéia deste post é mostrar um pouco como estão estas duas novas features.

Async e Razor

Uma grande mudança foi a implementação do suporte ao desenvolvimento assíncrono também no Razor, com isto, podemos utilizar métodos ou até a nossa Model com Async, isto pode parecer um pouco estranho para alguns padrões de projetos MVC, mas vale lembrar que além do MVC o Razor é utilizado em varias outras coisas como geração de html para emails, entre outros usos.

Com isto podemos por exemplo, que nossa ViewModel utilize Tasks em propriedades e somente no momento de utilizar estes dados na View utilizamos o Await.

No exemplo abaixo eu implementei o HomeModel com duas propriedades que utilizam Tasks.

using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace WebApplication5.Models
{
    public class HomeModel
    {
        public Task<List<Produto>> Produtos { get; set; }
        public Task<List<Produto>> UltimosProdutos { get; set; }
    }
}

 

Para simular uma demora, implementei a seguinte classe responsável por trazer as listas de produto.

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using WebApplication5.Models;

namespace WebApplication5.Models
{
    public class ProdutoService
    {
        public Task<List<Produto>> LoadAsync()
        {
            return Task.Factory.StartNew(
                () =>
                {
                    Task.Delay(3000).Wait();
                    return new List<Produto>() {
                        new Produto() { Id=1,Titulo="Livro ASP.NET"} ,
                        new Produto() { Id=2,Titulo= "Livro C#" }
                    };
                });
        }
        public Task<List<Produto>> LoadUltimosProdutosAsync()
        {
            return Task.Factory.StartNew(
                () =>
                {
                    Task.Delay(4000).Wait();
                    return new List<Produto>() {
                        new Produto() { Id=1,Titulo="Ultimo Produto 1"} ,
                        new Produto() { Id=2,Titulo= "Ultimo Produto 2" }
                    };
                });
        }
    }
}

Na implementação no Controller, utilizo dois métodos que retornam Tasks.

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNet.Mvc;
using WebApplication5.Models;

namespace WebApplication5.Controllers
{
    public class HomeController : Controller
    {
        public IActionResult Index()
        {
            var produtoService = new ProdutoService();
            var homeModel = new HomeModel();
            homeModel.Produtos = produtoService.LoadAsync();
            homeModel.UltimosProdutos = produtoService.LoadUltimosProdutosAsync();

            return View(homeModel);
        }
}

Com esta nova versão podemos implementar o seguinte código na View, notem que eu utilizo o await no momento em que eu preciso dos dados.

@model WebApplication5.Models.HomeModel
@{
    ViewBag.Title = "Home Page";
}

<div class="jumbotron">
    <h1>Exemplo Async e Flush</h1>
    <p class="lead">ASP.NET is a free web framework for building great Web sites and Web applications using HTML, CSS and JavaScript.</p>
    <p><a href="http://asp.net/vnext" class="btn btn-primary btn-lg">Learn more &raquo;</a></p>
</div>

<div class="row">

    <div class="col-md-4">
        <h2>Exemplo Async</h2>
        <ul>
            @foreach (var item in await Model.Produtos)
            {
                <li>@item.Titulo</li>
            }
        </ul>

    </div>
    <div class="col-md-4">
        <h2>Exemplo Ultimos Produtos Async</h2>
        <ul>
            @foreach (var item in await Model.UltimosProdutos)
            {
                <li>@item.Titulo</li>
            }
        </ul>
    </div>
    <div class="col-md-4">
        <h2>Run & Deploy</h2>
        <ul>
            <li><a href="http://go.microsoft.com/fwlink/?LinkID=517851">Run your app locally</a></li>
            <li><a href="http://go.microsoft.com/fwlink/?LinkID=517852">Run your app on ASP.NET Core 5</a></li>
            <li><a href="http://go.microsoft.com/fwlink/?LinkID=517853">Run commands defined in your project.json</a></li>
            <li><a href="http://go.microsoft.com/fwlink/?LinkID=398609">Publish to Microsoft Azure Web Sites</a></li>
        </ul>
    </div>
</div>

 

Com isto temos nosta página funcionando, em menos do que o tempo total dos dois Wait, porém, notem que as demais requisições só acontecem após o final do download do html da página.

image 

Flush Incremental

Outra grande feature é a possibilidade de realizar o Flush do conteúdo (html) parcial para o usuário, com isto, podemos enviar e começar a renderizar partes do html como head e menus, enquanto métodos assíncronos são processados para renderizar informações principais da página.

Ao analisar a requisição para o request principal, basicamente o response é enviado sem avisar para o navegador qual é o tamanho total do html e também é enviado o header: “Transfer-Encoding:chunked”.

No exemplo abaixo, mostra como a Amazon.com utiliza este recurso para começar a baixar o outros recursos, mesmo antes do html ser completamente baixado

image

Vamos pegar o projeto que utilizei para explicar o suporte ao Async no Razor e implementar a seguinte mudança na View principal, primeiro envolver o conteúdo principal em uma section (a qual eu chamei na Layout) e após isto, chamar um metodo chamado FlushAsync, o qual envia para o navegador o html de tudo que estava acima dele na View.

@model WebApplication5.Models.HomeModel
@{
    ViewBag.Title = "Home Page";
}
@section body {
    <div class="jumbotron">
        <h1>Exemplo Async e Flush</h1>
        <p class="lead">ASP.NET is a free web framework for building great Web sites and Web applications using HTML, CSS and JavaScript.</p>
        <p><a href="http://asp.net/vnext" class="btn btn-primary btn-lg">Learn more &raquo;</a></p>
    </div>

    @{await FlushAsync(); }
    <div class="row">

        <div class="col-md-4">
            <h2>Exemplo Async</h2>
            <ul>
                @foreach (var item in await Model.Produtos)
                {
                    <li>@item.Titulo</li>
                }
            </ul>

        </div>
        <div class="col-md-4">
            <h2>Exemplo Ultimos Produtos Async</h2>
            <ul>
                @foreach (var item in await Model.UltimosProdutos)
                {
                    <li>@item.Titulo</li>
                }
            </ul>
        </div>
        <div class="col-md-4">
            <h2>Run & Deploy</h2>
            <ul>
                <li><a href="http://go.microsoft.com/fwlink/?LinkID=517851">Run your app locally</a></li>
                <li><a href="http://go.microsoft.com/fwlink/?LinkID=517852">Run your app on ASP.NET Core 5</a></li>
                <li><a href="http://go.microsoft.com/fwlink/?LinkID=517853">Run commands defined in your project.json</a></li>
                <li><a href="http://go.microsoft.com/fwlink/?LinkID=398609">Publish to Microsoft Azure Web Sites</a></li>
            </ul>
        </div>
    </div>
}

Com isto, todo o html que já está pronto é enviado para o navegador, e a conexão é mantida aberta, dando uma melhor experiex’x’’cia para o usuário.

Notem a diferença de como o carregamento de outros recursos é feito mesmo enquanto a requisição principal não foi finalizada. É possível notar a diferença de tempo, de 5,17s no primeiro exemplo para 4.36s neste exemplo final.

image

 

Bom, o ASP.NET ainda está em desenvolvimento, mas vale estudar e conhecer para onde esta tecnologia está caminhando.

O projeto de exemplo está no meu GitHub: https://github.com/rodolfofadino/ExampleRazorAsyncAndFlushASPNET/

Espero que este post seja útil, estou a disposição para dúvida, criticas e sugestões.

abs

Rodolfo

  • Anonymous

    Muito legal o FlushAsync! Tinha ouvido falar mas não tinha visto na prática.

    Mas me interessei, também, pelo await dentro da view! Queria saber se do jeito que você fez, a página demora 4 ou 7 segundos para carregar, ou seja, se ele roda as tasks em paralelo ou não.

    Caso ele não rode em paralelo, você poderia botar o FlushAsync logo depois do primeiro foreach pra renderizar aquele html enquanto esperamos o resultado da segunda task?

    Parabéns pelo post!

  • http://www.rodolfofadino.com.br/ Rodolfo Fadino

    Poderia ter posto sim, agora a pagina demora 4,36s como um todo (html css e js), na primeira parte do exemplo ela demorava 5,17s.

    Abs

  • Anonymous

    Então ele faz as chamadas em paralelo?
    Achei que ele ia parar na linha 18, esperar os 3 segundos, depois is parar na linha 28 e esperar os 4 segundos.

  • Alfredo Lotar

    Ótimo artigo. O suporte a programação assíncrona está cada vez mais presente em diversas tecnologias para desenvolvimento ASP.NET. Mas cuidado, programação assíncrona não é sinônimo de performance. Antes de implementar um recurso realize testes e compare cada opção.

  • http://www.rodolfofadino.com.br/ Rodolfo Fadino

    Valew Alfredo, realmente cada cenário vale realizar teste e comparar, um fato é que paralelizar alguns processamentos (IO, Network, Banco) ajudam e ainda melhoram a resposta do IIS.

    abs