Blazor - Rotas e Navegação

As rotas no Blazor funcionam de forma extremamente simples, e se você já conhece um pouco de Razor Pages, vai se sentir em casa aqui.

Como funcionam as rotas no Blazor

Basta um atributo @page em um arquivo .razor para que ele se torne automaticamente uma rota.

Por isso, precisamos nos policiar para não criar rotas iguais. Dessa forma, recomendo estruturar bem as pastas e organizar o projeto desde o início.

Por baixo dos panos, o compilador transforma a diretiva @page em um RouteAttribute na classe gerada. Em seguida, o componente Router procura essas classes e renderiza aquela cujo template combina com a URL.

Indo direto ao ponto, toda diretiva @page define uma rota. O valor precisa ser uma string e sempre começar com uma barra.

@page "/counter"
@* https://localhost:xxxx/counter *@

Assim, se criarmos um arquivo chamado About.razor na pasta Components/Pages com o conteúdo abaixo, teremos uma nova página.

@page "/about"

<h1>About this book</h1>

O resultado aparece sempre que navegamos para a URL https://localhost:xxxx/about.

Página About de uma aplicação mostrando como as rotas no Blazor exibem o componente na URL /about
O componente About responde na URL definida pela diretiva @page.

Um mesmo componente também pode ter mais de uma diretiva @page. Nesse caso, ele responde em todas as URLs informadas.

@page "/about"
@page "/about-us"

<h1>About this book</h1>

Rotas com vários segmentos

Da mesma forma, podemos alterar a URL para ter mais segmentos e continuar com um resultado parecido.

@page "/about/this-book"

<h1>About this book</h1>

Isso acontece porque o uso de barras e hífens na URL é plenamente permitido. Então, a renderização da página segue normalmente.

Página About acessada pela URL com dois segmentos /about/this-book
Segmentos extras na URL não mudam o comportamento da página.

Organização de pastas e rotas no Blazor

Pouco importa onde a sua página está. Se o arquivo começa com a diretiva @page, o Blazor o adiciona às rotas.

Estrutura de pastas com o arquivo Manage.razor dentro de Admin e Account no projeto
O arquivo Manage.razor fica em uma subpasta dentro de outra subpasta.

No exemplo, temos um arquivo Manage.razor dentro de duas subpastas, com a diretiva @page mostrada abaixo.

@page "/admin/account/manage"

<h1>Manage Your Account</h1>

Como resultado, a página aparece normalmente, já que a pasta onde ela está não faz diferença.

Página Manage Your Account renderizada na URL /admin/account/manage
A rota vem da diretiva, e não da localização do arquivo.

Você não precisa seguir a convenção das pastas no nome da URL. Embora o Manage.razor esteja em Admin/Account, a URL poderia ser outra, ainda que manter o padrão seja recomendado.

Além disso, você não precisa seguir a estrutura de exemplo dos templates. Inclusive, recomendo separar o projeto por funcionalidades ou áreas, em vez de usar uma única pasta para páginas e componentes.

FocusOnNavigate e acessibilidade

Outro elemento que encontramos é o FocusOnNavigate. Ele fica no arquivo Routes.razor e ajuda na seleção de elementos.

<Router AppAssembly="@typeof(Program).Assembly">
    <Found Context="routeData">
        <RouteView RouteData="@routeData" DefaultLayout="@typeof(Layout.MainLayout)" />
        <FocusOnNavigate RouteData="@routeData" Selector="h1" />
    </Found>
</Router>

No exemplo acima, sempre que a navegação chega a uma das rotas no Blazor que existem, o foco vai para o primeiro h1 da página.

Esse comportamento melhora a usabilidade e a acessibilidade. Afinal, ele facilita a navegação sem mouse e ajuda leitores de tela a anunciar a mudança de página.

Roteamento estático e interativo no .NET 8

Nos Blazor Web Apps do .NET 8, o Router resolve as rotas no Blazor de duas formas. A primeira é o roteamento estático, que acontece durante a renderização no servidor.

Nesse caso, o roteador usa o endpoint routing do ASP.NET Core e o caminho da requisição HTTP para decidir qual componente renderizar.

A segunda forma é o roteamento interativo. Ela entra em ação quando atribuímos um modo interativo ao componente Routes, como <Routes @rendermode="InteractiveServer" />.

Então, o roteador passa a observar a URL da barra de endereços. Ou seja, ele troca de componente sem fazer uma nova requisição ao servidor.

Se você quiser entender esses modos em detalhe, vale ler o artigo sobre render modes do Blazor.

Rotas no Blazor em diferentes projetos

É possível ter componentes e páginas em projetos distintos, seja para reuso ou simplesmente para organizar a solução.

Nesse caso, usamos o AdditionalAssemblies para indicar assemblies adicionais além do atual.

<Router
    AppAssembly="@typeof(App).Assembly"
    AdditionalAssemblies="new[] { typeof(Component1).Assembly }">
    @* ... elementos do Router ... *@
</Router>

Assim, o Blazor também procura páginas nos projetos informados nessa propriedade.

No .NET 8, existe ainda um segundo ponto de configuração. Para o roteamento estático funcionar, o Program.cs do servidor também precisa conhecer esses assemblies.

app.MapRazorComponents<App>()
    .AddAdditionalAssemblies(typeof(BlazorSample.Client._Imports).Assembly);

Em outras palavras, as rotas no Blazor dependem de duas configurações quando os componentes ficam em um projeto .Client ou em uma biblioteca de classes Razor.

Parâmetros de rotas no Blazor

Uma ação muito comum é passar parâmetros pela URL durante a navegação.

No Blazor, assim como no Razor Pages, especificamos os parâmetros entre chaves dentro da rota.

@page "/route-parameter-1/{text}"

Em seguida, vinculamos o parâmetro a uma propriedade, decorando ela com o atributo Parameter.

@code {
    [Parameter]
    public string? Text { get; set; }
}

O atributo Parameter só funciona com propriedades. Variáveis comuns não recebem o valor neste caso.

Além disso, mantenha o mesmo nome entre a propriedade e o parâmetro. A comparação ignora maiúsculas e minúsculas, então text e Text combinam sem atrito.

@page "/route-parameter-1/{text}"

<h1>Blazor is @Text!</h1>

@code {
    [Parameter]
    public string? Text { get; set; }
}

Dessa forma, ao chamar https://localhost:xxxx/route-parameter-1/awesome, a tela exibe o texto Blazor is awesome.

Em contrapartida, se acessarmos a mesma URL sem o parâmetro, recebemos um erro 404, de página não encontrada.

Parâmetros opcionais em rotas no Blazor

Como vimos, assim que declaramos um parâmetro, ele passa a fazer parte da rota.

Por isso, quando o parâmetro não aparece, a rota fica incompleta e não podemos acessá-la.

Assim como o C# tem os Nullable Types, aqui temos os parâmetros opcionais. Para defini-los, usamos um ponto de interrogação.

@page "/route-parameter-1/{text?}"

Com o sinal no fim do parâmetro, ele se torna opcional nas rotas no Blazor. Então, a URL https://localhost:xxxx/route-parameter-1 passa a ser válida.

Vale lembrar que https://localhost:xxxx/route-parameter-1/awesome continua válida, já que ela informa o parâmetro.

Para receber o valor, declare a propriedade como anulável. Afinal, ela pode vir nula e causar um NullReferenceException.

Por fim, podemos atribuir um valor padrão quando nenhum parâmetro chegar. A documentação recomenda o OnParametersSet para essa tarefa.

@page "/route-parameter-1/{text?}"

<h1>Blazor is @Text!</h1>

@code {
    [Parameter]
    public string? Text { get; set; }

    protected override void OnParametersSet()
    {
        Text = Text ?? "fantastic";
    }
}

O OnInitialized também funcionaria na primeira renderização. Porém, se o usuário navegar de /route-parameter-1/awesome para /route-parameter-1, a instância continua viva e o OnInitialized não executa de novo.

Restrições de parâmetros

Além de especificar um parâmetro, podemos limitar seu tipo. Dessa forma, a própria rota já faz parte da validação.

Com as restrições, as rotas no Blazor também retornam o erro 404 quando o valor não atende ao tipo.

Para adicionar uma restrição, basta informar o tipo depois do separador de dois pontos.

@page "/user/{Id:int}"

Assim, se tentarmos abrir /user/test, recebemos um erro 404. Embora exista um valor para Id, ele não é um inteiro.

A tabela abaixo mostra as restrições disponíveis, segundo a documentação de roteamento do ASP.NET Core.

Restrição Exemplo Valores aceitos Cultura invariante
bool {active:bool} true, FALSE Não
datetime {dob:datetime} 2016-12-31, 2016-12-31 7:32pm Sim
decimal {price:decimal} 49.99, -1,000.01 Sim
double {weight:double} 1.234, -1,001.01e8 Sim
float {weight:float} 1.234, -1,001.01e8 Sim
guid {id:guid} CD2C1638-1638-72D5-1638-DEADBEEF1638 Não
int {id:int} 123456789, -123456789 Sim
long {ticks:long} 123456789, -123456789 Sim

As restrições que convertem para tipos como int ou DateTime sempre usam a cultura invariante. Ou seja, elas assumem que a URL não varia de idioma.

Restrições e parâmetros opcionais juntos

Também podemos combinar restrições e parâmetros opcionais. Só precisamos prestar atenção à ordem dos sinais.

Nesse caso, o sinal de opcional sempre vem no fim do parâmetro, enquanto a restrição continua depois dos dois pontos.

@page "/user/{Id:int}/{Option:bool?}"

Catch all em rotas do Blazor

Além dos parâmetros convencionais, existe o curinga, ou wildcard, representado por um asterisco.

Quando usamos o asterisco, o parâmetro captura todo o restante da URL, incluindo as barras.

@page "/catch-all/{*pageRoute}"

@code {
    [Parameter]
    public string? PageRoute { get; set; }
}

Assim, se chamarmos /catch-all/this/is/a/test, todos os segmentos depois de catch-all vão para a propriedade. Portanto, o valor de PageRoute fica this/is/a/test.

Esse recurso atende situações em que você quer segmentar a URL por conta de SEO, mas manter uma única rota para um trecho inteiro.

Vale saber que o parâmetro catch all sempre é do tipo string e precisa ficar no fim da rota.

Query strings

Você com certeza já acessou um site com algo como ?chave=valor na URL. Essa combinação se chama query string.

Com ela, passamos parâmetros que não afetam a rota, mas complementam a URL.

Por exemplo, podemos chamar https://balta.io/search?term=SQL para informar um termo de busca via query string.

@* https://balta.io/search/SQL       -> parâmetro via rota *@
@* https://balta.io/search?term=SQL  -> parâmetro via query string *@

Diferente dos parâmetros, a query string não exige nada especial nas rotas no Blazor.

@page "/search"

Em compensação, precisamos do atributo SupplyParameterFromQuery na propriedade.

@code {
    [SupplyParameterFromQuery]
    public string? Term { get; set; }
}

Dessa forma, dizemos que a propriedade Term recebe o valor da chave term da query string.

No .NET 8, o atributo Parameter deixou de ser obrigatório nesse cenário. Nas versões anteriores, era preciso usar os dois atributos juntos.

Se a chave da URL tiver outro nome, usamos a propriedade Name, como em [SupplyParameterFromQuery(Name = "q")].

Agora, ao chamar https://balta.io/search?term=SQL, a tela exibe o texto Term: SQL.

O código completo da página fica assim:

@page "/search"

<h1>Search example</h1>

<p>Term: @Term</p>

@code {
    [SupplyParameterFromQuery]
    public string? Term { get; set; }
}

Além de string, a query string aceita tipos como bool, DateTime, decimal, Guid, int e long, inclusive em versões anuláveis e arrays.

Eventos de navegação nas rotas no Blazor

O componente Router também oferece o evento OnNavigateAsync. Ele executa quando o usuário acessa uma rota pela primeira vez ou navega para outra.

<Router AppAssembly="typeof(App).Assembly"
    OnNavigateAsync="OnNavigateAsync">
    @* ... *@
</Router>

@code {
    private async Task OnNavigateAsync(NavigationContext context)
    {
        // registra a visita ou pré-carrega dados da próxima página
        await Task.CompletedTask;
    }
}

O NavigationContext traz o caminho de destino e um CancellationToken. Assim, se o usuário mudar de página no meio do processo, o código pode interromper o trabalho antigo.

Quando a navegação demora, podemos ainda exibir um aviso com o parâmetro Navigating do Router.

<Navigating>
    <p>Loading the requested page...</p>
</Navigating>

Com prerendering ativo, o OnNavigateAsync executa duas vezes. Por isso, cuidado com efeitos colaterais, como gravar a mesma visita em duplicidade.

Conclusão sobre rotas no Blazor

As rotas no Blazor começam com uma simples diretiva @page e evoluem para parâmetros, restrições, query strings e eventos de navegação.

Com o NavLink, o menu indica a página ativa sem esforço. Já o NavigationManager resolve a navegação feita por código.

Portanto, organize bem as pastas, evite rotas duplicadas e use as restrições para validar a URL logo na entrada. Para conhecer melhor o framework, confira também uma visão geral do Blazor.

FAQ

Como criar uma rota em um componente Blazor?

Adicione a diretiva @page com o caminho desejado no topo do arquivo .razor. O componente passa a responder naquela URL, independentemente da pasta onde está.

O NavLink gera uma tag <a>, mas também aplica a classe active quando o href combina com a URL atual. A tag <a> comum não tem esse comportamento.

Como tornar um parâmetro de rota opcional?

Coloque um ponto de interrogação no fim do parâmetro, como em {text?}. Declare também a propriedade como anulável.

Preciso do atributo Parameter para ler query strings no .NET 8?

Não, no .NET 8 basta o atributo SupplyParameterFromQuery. Nas versões anteriores, os dois atributos precisavam aparecer juntos.

O que acontece quando uma restrição de rota não é atendida?

O roteador não encontra um componente compatível e a aplicação retorna 404. Por exemplo, /user/test falha em uma rota {Id:int}.