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;
#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. |