Acesse dinamicamente tabelas de bancos de dados SQL Server através de um frontend em Blazor Server e API .NET CORE 3.1 com funcionalidades de paginação, ordenação e filtros.
Depois das primeiras aventuras usando Blazor, e após ter escrito os artigos Blazor: O Começo do Fim do Javascript? e Blazor: Muito mais que um WebForms de Luxo, resolvi dar um passo adiante e converter 100% uma aplicação existente.
Imagine uma tela que contém um GRID exibindo registros de uma tabela de bancos de dados SQL SERVER onde seja possível ordernar, paginar e filtrar esses registros.
Pois bem, essa aplicação vem sendo evoluída desde o WebForms, passando pelo MVC, VueJS e chegando agora no Blazor Server.
O frontend que utilizei é uma adaptação com bootstrap e layout criado pelo desenvolvedor fullstack Lucas Juliano.
Funcionalidades dessa versão:
– API ASP.NET CORE 3.1;
– Frontend Blazor Server;
– Múltiplas conexões com SQL Server;
– Seleção de qualquer tabela do banco de dados;
– Ordenação de uma coluna;
– Filtro de uma ou mais colunas (tipos de dados numerico ou string);
– Paginação completa;
– Uso do FSL.Framework;
– Desenvolvimento usando Design Patterns;
– Todos os projetos usam as últimas versões de seus frameworks .NET CORE 3.1 e .NET Standard 2.1;
Table of Contents
#Blazor Server: FSL.Framework
Algumas informações importantes para quem está chegando agora. Se você é um desenvolvedor .NET mas nunca viu nada de .NET Core, sugiro você ler antes o artigo .NET Core para DesenvolvedoreS .NET.
Para essa solução de consultas dinâmicas SQL Server, usei como base o código fonte do artigo Crie seu Framework em ASP.NET CORE 3 e Blazor.
Depois que conclui esse artigo, evolui o framework FSL.Framework criado com novas funcionalidades e melhorias.
#Blazor Server: Diagrama e .csproj
O diagrama abaixo representa todos os 6 projetos dentro da solution.
As duas aplicações web (em azul) FSL.Database.BlazorSrv a FSL.Database.Api utilizam a biblioteca de negócios FSL.Database.Core que possui funcionalidades e classes genéricas para toda a solução.
E também, essas aplicações usam como base as biobliotecas do FSL.Framework Core, Api e Web.
#Blazor Server: Workflow e endpoints
A aplicação Blazor Server irá consumir uma API feita em ASP.NET CORE 3.1 conforme já mencionado no diagrama de arquitetura.
O código fonte já foi preparado para fazer um publish e hospedar no IIS.
E também, estou utilizando um banco de dados local SQL Server Express para realizar as consultas.
Após publicação da API (endereço local http://localhost/fsl-database-api/), é possível chamá-la através do Postman.
A API possui três endpoints POST que retornam todas as tabelas de um banco de dados, todas as colunas de uma determinada tabela e todos os registros de uma tabela.
Endpoint api/database/tables:
{
"DataSource" : ".\\SQLEXPRESS2008R2",
"DatabaseName" : "consulta_cep",
"User" : "sa",
"Password" : "1234567890",
"TableName" : "",
"OrderBy" : "",
"OrderTypeStr" : "Asc",
"PerPage" : "10",
"Columns":[]
}
Esse endpoint irá retornar uma lista de nomes de tabelas.
Endpoint api/database/columns:
{
"DataSource" : ".\\SQLEXPRESS2008R2",
"DatabaseName" : "consulta_cep",
"User" : "sa",
"Password" : "1234567890",
"TableName" : "tb_cep", // here
"OrderBy" : "",
"OrderTypeStr" : "Asc",
"PerPage" : "10"
}
Esse endpoint irá retornar uma lista de colunas, com propriedades nome da coluna e tipo de dados.
Endpoint api/database/data:
{
"DataSource" : ".\\SQLEXPRESS2008R2",
"DatabaseName" : "consulta_cep",
"User" : "sa",
"Password" : "1234567890",
"TableName" : "tb_cep", // here
"OrderBy" : "cod_cep", // here
"OrderTypeStr" : "Asc", //here
"PerPage" : "10" // here
}
Por fim o endpoint de dados, que retornará todos os registros da tabela tb_cep, ordenados pela coluna cod_cep, da página 1 com 10 registros por página.
Com a API funcionando, vamos ver agora o frontend no Blazor.
#Blazor Server: fonte da API
Depois de testado a API, vamos dar uma olhada em seu código fonte.
Para essa solução, só estão sendo utilizados as classes DatabaseController e DatabaseQuerySqlRepository.
Os três endpoints mencionados anteriormente e testados no Postman são:
[Route("api/database")]
[ApiController]
public sealed class DatabaseController : ControllerBase
{
private readonly IDatabaseQueryRepository _databaseQueryRepository;
public DatabaseController(
IDatabaseQueryRepository databaseQueryRepository)
{
_databaseQueryRepository = databaseQueryRepository;
}
[HttpPost("tables")]
public async Task<IActionResult> PostTablesAsync(
[FromBody] Core.Models.DatabaseRequest request)
{
var data = await _databaseQueryRepository.GetAllTables(request);
return Ok(data.ToResult());
}
[HttpPost("columns")]
public async Task<IActionResult> PostColumnsAsync(
[FromBody] Core.Models.DatabaseRequest request)
{
var data = await _databaseQueryRepository.GetAllColumnsFromTable(
request,
request.TableName);
return Ok(data.ToResult());
}
[HttpPost("data")]
public async Task<IActionResult> PostDataAsync(
[FromBody] Core.Models.DatabaseRequest request)
{
var data = await _databaseQueryRepository.GetDataAsync(request);
return Ok(data.ToResult());
}
}
A classe DatabaseQuerySqlRepository implementa a inteface IDatabaseQueryRepository, retorna as informações de um banco de dados SQL Server através de parâmetros de entrada definidos na classe DatabaseRequest.
Destaque para o método que monta SELECT com paginação no SQL Server:
private string BuildQuery(
DatabaseRequest request)
{
var page = (request.Page <= 0) ? 1 : request.Page;
var rows = (request.Rows <= 0) ? 10 : request.Rows;
var select = BuildSelect(request);
var where = BuildWhere(request);
var sql = $@"
DECLARE @p_num_page AS INT
DECLARE @p_page_size AS INT
SET @p_num_page = {page}
SET @p_page_size = {rows}
SET @p_num_page = ((@p_num_page * @p_page_size) - @p_page_size) + 1
SET @p_page_size = @p_num_page + @p_page_size - 1
BEGIN WITH [query] AS (SELECT ROW_NUMBER() OVER (ORDER BY {request.OrderBy} {request.OrderType.ToString().ToUpper()}) AS [row_num], {select} FROM [{request.DatabaseName}].[dbo].[{request.TableName}] WITH (NOLOCK) {where}) SELECT * FROM [query] CROSS JOIN (SELECT COUNT(*) AS [total_records] FROM [query]) AS [counters] WHERE ([row_num] BETWEEN @p_num_page AND @p_page_size) END ";
return sql;
}
Você pode acessar todo o código fonte completo diretamente no meu github.
#Blazor Server: Frontend
Além dos componentes básicos utilizados em Blazor: Muito mais que um WebForms de Luxo, criei mais algums como Paging.razor, PageItem.razor e FieldContainer.razor.
O PageItem.razor é responsável pelos botões de paginação First, Next, Previous e Last:
@inherits PageItemComponent
<li class="page-item @IsDisabled(IsTrue)">
<a class="page-link"
href="#"
tabindex="-1"
aria-disabled="true"
@onclick="OnPagingAsync">@Title</a>
</li>
public class PageItemComponent : ComponentBase
{
[Parameter]
public string Title { get; set; }
[Parameter]
public bool IsTrue { get; set; }
[Parameter]
public int RequestedPage { get; set; }
[Parameter]
public EventCallback<int> OnClickAsync { get; set; }
protected async Task OnPagingAsync(
EventArgs e)
{
await OnClickAsync.InvokeAsync(RequestedPage);
}
protected string IsDisabled(
bool isOk)
{
return !isOk ? "disabled" : "";
}
}
O Paging.razor é responsável pela exibição dos botões de paginação, labels de quantidade de registros e página atual:
@inherits PagingComponent
@{
var pages = Count > 0 ? Convert.ToInt32(Math.Ceiling(Convert.ToDecimal(TotalRecords) / Convert.ToDecimal(Rows))) : 0;
var firstPage = Page > 1;
var nextPage = Page < pages;
var previousPage = firstPage;
var lastPage = nextPage;
}
<nav class="pt-4">
<div>
<strong>Current Page:</strong>
<span class="badge badge-secondary">@(Page)</span>
|
<strong>Pages: </strong>
<span class="badge badge-secondary">@(pages)</span>
|
<strong>Records:</strong>
<span class="badge badge-secondary">@(TotalRecords)</span>
</div>
<ul class="pagination justify-content-end" style="margin-top:-30px">
<PageItem Title="First"
IsTrue="firstPage"
RequestedPage="1"
OnClickAsync="OnClickAsync"></PageItem>
<PageItem Title="Previous"
IsTrue="previousPage"
RequestedPage="Page - 1"
OnClickAsync="OnClickAsync"></PageItem>
<PageItem Title="Next"
IsTrue="nextPage"
RequestedPage="Page + 1"
OnClickAsync="OnClickAsync"></PageItem>
<PageItem Title="Last"
IsTrue="lastPage"
RequestedPage="pages"
OnClickAsync="OnClickAsync"></PageItem>
</ul>
</nav>
public class PagingComponent : ComponentBase
{
[Parameter]
public int Page { get; set; }
[Parameter]
public int TotalRecords { get; set; }
[Parameter]
public int Rows { get; set; }
[Parameter]
public int Count { get; set; }
[Parameter]
public EventCallback<int> OnClickAsync { get; set; }
}
O FieldContainer.razor é responsável pela exibição dos campos de parametrização do banco de dados:
@inherits FieldContainerComponent
<div class="col-md-@Cols mb-@Cols">
<label class="badge badge-pill badge-dark">@Title</label>
<div class="input-group">
<CascadingValue Value=this>
@ChildContent
</CascadingValue>
</div>
</div>
public class FieldContainerComponent : ComponentBase
{
[Parameter]
public RenderFragment ChildContent { get; set; }
[Parameter]
public string Title { get; set; }
[Parameter]
public int Cols { get; set; }
}
E o mais importante deles, o Database.razor que é o frontend completo que engloba todos os componente anteriores.
FieldContainer.razor usado em Database.razor:
<div class="form-row pb-3 mt-2">
<FieldContainer Cols="2" Title="Data Source">
<TextBox Id="tbxDataSource"
@bind-Text="@Request.DataSource"
TextValueChanged="ConectionChangedAsync"></TextBox>
</FieldContainer>
<FieldContainer Cols="2" Title="Initial Catalog">
<TextBox Id="tbxInitialCatalog"
@bind-Text="@Request.DatabaseName"
TextValueChanged="ConectionChangedAsync"></TextBox>
</FieldContainer>
<FieldContainer Cols="2" Title="User">
<TextBox Id="tbxUser"
@bind-Text="@Request.User"
TextValueChanged="ConectionChangedAsync"></TextBox>
</FieldContainer>
<FieldContainer Cols="2" Title="Password">
<TextBox Id="tbxPassword"
@bind-Text="@Request.Password"
TextMode="TextBoxMode.Password"
TextValueChanged="ConectionChangedAsync"></TextBox>
</FieldContainer>
<FieldContainer Cols="3" Title="Table Name">
<DropDownList Id="ddlTables"
DataSource="@Tables"
SelectedValue="@Request.TableName"
SelectedValueChanged="@OnTableChangedAsync" />
</FieldContainer>
<FieldContainer Cols="1" Title="Per Page">
<TextBox Id="tbxPerPage"
@bind-Text="@Request.PerPage"
TextMode="TextBoxMode.Number"
TextValueChanged="ValueChangedAsync"></TextBox>
</FieldContainer>
</div>
Você pode acessar todo o código fonte completo de Database.razor diretamente no meu github.
Por fim, tudo isso não seria possível se não configurássemos o Startup.cs para usar o FSL.Framework e todas as injeções de dependência.
public void ConfigureServices(
IServiceCollection services)
{
services
.AddBlazorFslFramework(Configuration) // here
.Config(opt => // here
{
opt.AddDefaultConfiguration();
opt.AddConfiguration<MyConfiguration>();
opt.AddFactoryService<DefaultFactoryService>();
opt.AddTransient<IApiClientProvider, HttpClientApiClientProvider>();
opt.AddSingleton<IApiClientService, DatabaseApiClientService>();
opt.AddSingleton<IDatabaseQueryRepository, ApiDatabaseQueryRepository>();
});
}
public void Configure(
IApplicationBuilder app,
IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
}
app.UseBlazorFslFramework(); // here
}
#Considerações finais
A aplicação web Blazor Server usa o arquivo AppSettings.JSON para guardar a URL da API que traz os dados do banco SQL Server.
Para o componente Database funcionar é necessário colocar o namespace FSL.DatabaseQuery.BlazorSrv.Components no arquivo _Imports.razor.
Eu utilizei o template padrão do Visual Studio Preview para criar a aplicação Blazor Server, porém limpeialgumas coisas como por exemplo arquivo Site.css.
Essa solução pode te dar um norte (e muitas outras ideias) caso queira programar em .NET CORE e/ou Blazor.
Obrigado e até a próxima 🙂
Obs.: artigo publicado e não revisado.
Artigos sobre Blazor e ASP.NET CORE:
Crie seu Framework em ASP.NET CORE 3 e Blazor
Cookies: Identity no Blazor e ASP.NET CORE 3.0
Blazor: Muito mais que um WebForms de Luxo
Benchmark: ASP.NET 4.8 vs ASP.NET CORE 3.0
AppSettings: 6 Formas de Ler o Config no ASP.NET CORE 3.0
JWT: Customizando o Identity no ASP.NET CORE 3.0
Crie um Gerenciador de Arquivos do Zero em .NET Core e VueJS
IIS: Como Hospedar Aplicação .NET Core em 10 Passos
.NET Core para Desenvolvedores .NET
Blazor: O Começo do Fim do Javascript?
| Faça download completo do código fonte no github. |






