Desenvolver aplicações web na plataforma .NET é uma tarefa que se torna cada vez mais simples há cada lançamento de atualizações da Microsoft. O Visual Studio, aliado aos plugins e componentes mantidos pela companhia, facilita a criação e automatização de códigos, CRUDS, integrações, dentre outros, ao passo em que viabiliza que o desenvolvedor foque em partes mais importantes do software como um todo. Em contrapartida, cada vez mais complexidades como a comunicação direta com determinados frameworks, toda a parte front-end, o banco de dados, são adicionados ao sistema final, o que exige certa proficiência do time para unificar tudo de forma concisa e funcional.
O Bootstrap é um dos frameworks front-end mais usados na atualidade e a forma como ele simplifica a criação e customização de componentes web tornou-se não só seu maior diferencial, como a principal razão por ser adotado pela maioria das plataformas, bem como respectivos desenvolvedores. Todos os tipos de aplicações se encaixam bem no visual/estrutura do Bootstrap, desde aplicações corporativas, educacionais, financeiras, de notícias, isso sem falar do lado responsivo bem encapsulado pelo framework para todos os tipos de dispositivos.
E quando se trata de mesclar o que há de melhor nos dois mundos, chegamos num ponto onde a maioria dos desenvolvedores entrava: como integrar tecnologias tão distintas de forma limpa, sem criar enlaces que tornem a manutenção do sistema difícil no futuro? A resposta mais simples está em conhecer bem ambas as tecnologias. A plataforma .NET, especificamente, fornece inúmeros recursos para lidar com elementos web de forma simplificada, incluindo diversas bibliotecas JavaScript que já têm inclusão automática feita pelo gerenciador de dependências do Visual Studio, como o jQuery, por exemplo.
Neste artigo, trataremos de expor as principais facetas desse tipo de processo, através da criação de uma aplicação totalmente responsiva de gerenciamento escolar, alunos, notas, disciplinas, etc. de modo a expor o passo a passo requerido na hora de incutir estilos, elementos e widgets do Bootstrap em conjunto com o desenvolvimento de scripts web, bem como toda a parte JavaScript do cliente. Neste, especialmente, faremos uso do framework Shield UI, já conhecido dos desenvolvedores .NET por sua fácil integração com a plataforma, com o jQuery, dentre outros.
Configurando o ambiente
Antes de qualquer coisa, certifique-se de que tem o ambiente devidamente configurado com uma versão recente do Visual Studio, além das opções “ASP.NET and web development” e “.NET Core cross-platform development” quando estiver instalando a IDE. Isso é tudo para que possamos criar um projeto do tipo ASP.NET Core MVC. Para isso, seleciona a opção “Arquivo > Novo > Projeto...” e, em seguida, nos modelos instalados, selecione a opção tal como mostrado na Figura 1.
Em seguida, selecione a opção que representa o tipo de aplicação para web, que encapsula tanto os controladores do MVC quanto o modelo RESTful, tal como demonstrado na Figura 2.
Para iniciar as configurações do Shield UI no nosso projeto, basta clicar com o botão direito no mesmo e selecionar a opção “Gerenciar pacotes do NuGet...”. Na aba “Procurar”, digite o texto “ShieldUI.AspNetCore.Mvc”, clique em pesquisar e na opção que aparecer clique em “Instalar”. Caso algum erro seja apresentado durante esse procedimento, revise as versões máximas que o Shield UI suporta (ele informará a mesma no Console de erros, uma vez que a última versão que o autor estiver usando pode não mais ser a mesma da data de escrita deste artigo).
Uma vez com o app criado e a dependência do Shield UI devidamente adicionada ao projeto, precisamos importar suas classes de inicialização nos arquivos de configuração do projeto .NET. A primeira delas no arquivo de views /_ViewImports.cshtml, o qual define o resumo de recursos externos que as páginas web da webapplication terão acesso liberado. A mesma conta apenas com os tag helpers padrão da Microsoft, bem como o do próprio projeto. Portanto, adicione a seguinte linha de código ao fim do arquivo:
@using ShieldUI.AspNetCore.Mvc
Asegunda configuração, já no código C#, consiste em adicionar a inicialização e respectivo uso da API do Shield UI no arquivo de startup do projeto, o Startup.cs. Há dois métodos que devemos alterar: o primeiro é método Configure(), que recebe o objeto de IApplicationBuilder para configuração de recursos específicos que a aplicação fará uso. Vejamos na Listagem 1 como o mesmo deve ficar.
Listagem 1. Método Configure() pós configuração do Shield UI.
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseBrowserLink();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
app.UseShieldUI();
}
Perceba logo no início do método o recurso de diferenciação de ambientes de desenvolvimento, fornecido através do objeto IHostEnvironment, o qual facilita a forma como configuramos e direcionamos determinadas implementações para ambientes específicos: desenvolvimento, teste, produção, etc. Se desejar, o leitor pode trabalhar também definindo testes específicos de gráficos ou telas que funcionarão apenas a nível local ou no QA, por exemplo, flexibilizando muito o processo inteiro. As demais configurações são bem simples e usuais, a considerar o fato de já estarmos definindo também a rota padrão da Home (ação de Index) para o roteamento de requisições e endpoints (o default define qual o endpoint referente à página inicial). Por fim, temos a configuração que diz ao projeto para usar os recursos disponibilizados pelo Shield UI. Isso só é possível graças à disponibilização do objeto em um segundo método: o método ConfigureServices() (vide Listagem 2).
Listagem 2. Configuração do método ConfigureServices().
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddMvc();
// add Shield UI
services.AddShieldUI();
}
Este, por sua vez, é bem conhecido dos desenvolvedores .NET MVC, uma vez que adiciona os recursos do framework da plataforma para o tipo de projeto selecionado: MVC. Além disso, também estamos adicionando os serviços do Shield UI, para que se torne acessíveis do ponto de vista gerencial pelo build da ferramenta. Para garantir que as configurações surtam o devido efeito, adicione no bloco de imports do início do arquivo o seguinte import:
using ShieldUI.AspNetCore.Mvc;
Nosso próximo passo será a adição dos recursos web do framework Shield UI, arquivos JavaScript e CSS, que trazem os bundles necessários para construir a interface web final, via componentização. Para isso, efetue o download do pacote mais recente JavaScript do Shield UI (vide seção Links), descompacte-o e renomeie o nome do diretório raiz de modo a remover os números de versão, bem como quaisquer caracteres especiais. Adicione o mesmo à pasta wwwroot/lib do projeto, tal como vemos na Figura 3.
O Shield UI tem dependência explicita nos pacotes do jQuery, o qual já vem adicionado por padrão no projeto. Para ter nosso primeiro exemplo funcional, precisamos importar os arquivos de CSS e JavaScript na tag <head> da nossa página. Para garantir que os mesmos serão importados em todas por padrão, faremos isso diretamente nos arquivos de layout. Portanto, abra o arquivo /Views/Shared/_Layout.cshtml e adicione as linhas de código apresentadas na Listagem 3 antes do fechamento da tag em questão.
Listagem 3. Importação dos scripts e estilos do Shield UI + jQuery.
<script src="~/lib/jquery/dist/jquery.js"></script>
<script src="~/lib/shieldui/js/shieldui-all.min.js"></script>
<link rel="stylesheet" href="~/lib/shieldui/css/light/all.min.css" />
</head>
Isso será o bastante para inicializar os recursos estáticos do framework. Como primeiro exemplo, vamos usar o arquivo /Views/Home/Index.cshtml para criar um gráfico de resumo das matrículas realizadas nos últimos anos. Para isso, altere todo o conteúdo da referida página para o demonstrado na Listagem 4.
Listagem 4. Conteúdo de exemplo do gráfico de resumo de matrículas.
@{
ViewData["Title"] = "Resumo de Matrículas";
}
<h1>Resumo: 2016-2017</h1>
@(Html.ShieldChart()
.Name("grafico_matriculas")
.Tooltip(tooltip => tooltip.AxisMarkers(axisMarkers => axisMarkers
.Enabled(true)
.Mode(ShieldUI.AspNetCore.Mvc.Chart.TooltipAxisMarkerMode.XY)
.Width(1)
.ZIndex(3)))
.Theme("dark")
.PrimaryHeader(header => header.Text("Cenário do Semestre")
.Style(style => style.FontSize("25")))
.SecondaryHeader(header => header
.Text("<i>Demonstrativo Inicial (Incluindo Anseios de Matrícula)</i>")
.Style(style => style.Color("yellow")))
.Export(true)
.AxisX(axis => axis.CategoricalValues(
"2016.1", "2016.2", "2017.1", "2017.2", "2018.1"
))
.DataSeries(series =>
series.Area()
.Name("Qtd Alunos Matriculados")
.Data(new object[] {
345, 234, 398, 323, 0
})
)
)
Todo o código autogerado pelo Visual Studio, com Bootstrap e carousel, foi substituído por nada mais que um script C# que faz uso direto das classes do ShielUI. Vejamos alguns pontos importantes:
- Sempre que for inicializar um novo gráfico via biblioteca do Shield UI, usar a função SheidlChart(), agora disponível no objeto Html do ASP.NET MVC;
- A função Tooltip() define as configurações dos marcadores do gráficos, isto é, os pontos onde um dado cruza com outro, ou onde o mesmo dado encontra seu delimitador. No exemplo, estamos fazendo uso de gráfico simples de duas dimensões, x e y, no plano cartesiano. Dito isso, todo o resto é gerenciado pela biblioteca que já trata automaticamente tais dimensões;
- O tema usado, função Theme(), é o “dark”. Mas o leitor tem em mãos uma série de outras opções passíveis de visualização na pasta /css da biblioteca que importamos no /wwwroot;
- As funções PrimaryHeader() e SecondaryHeader() definem os esquemas de estilização da biblioteca, que permite, via functions e lambdas, a incorporação de estilos web nos elementos. Aqui, estamos definindo apenas texto, cores e tamanho de fonte, mas como o leitor pode notar a flexibilidade vai além: quaisquer tags e configurações HTML são permitidas dentro do texto padrão;
- A função Export() viabiliza a liberação de botões de exportação no topo do gráfico, por padrão para imagem e impressão;
- A função AxisX() se encarrega de receber as dimensões distribuídas ao longo do eixo x (horizontal) do gráfico. Os valores usados referem-se aos semestres levados em questão na definição do mesmo gráfico de matrículas;
- A função DataSeries(), como o nome sugere, é responsável por definir os valores da área ocupado por cada dimensão y no gráfico. Aqui, estamos informando valores cheios (inteiros) através de um vetor de object[]. Isso é o bastante, já que somente uma dimensão está sendo considerada nesse passo.
O resultado do gráfico pode ser visualizado na Figura 4. Para executar o projeto, basta salvar todos os arquivos alterados e executar via atalho F5.
CRUD
As operações possíveis sobre os diferentes tipos de gráficos no Shield UI são inúmeras, mas seguem praticamente a mesma linha de implementação. Uma gama de métodos disponibilizados pode ou não ser usados em conjunto para alcançar diferentes resultados. Em função disso, vejamos agora como lidar com métodos básicos de CRUD usando o widget de grid do framework, o qual nos possibilita criar facilmente tabelas editáveis, ordenáveis, selecionáveis, etc.
Para tanto, faremos uso de um modelo de dados simples, com uma entidade de Aluno que conterá algumas propriedades básicas a serem cadastradas, visualizadas, editadas e/ou removidas. Em vista de facilitar o trabalho com a base de dados, faremos uso do provider InMemory do Entity Framework Core para criar e lidar com operações de banco em memória, facilitando bastante o trabalho de testes e do próprio desenvolvimento. Portanto, acesse novamente o gerenciador de pacotes do NuGet e busque pelo seguinte pacote “Microsoft.EntityFrameworkCore.InMemory”, instale-o no nosso projeto.
Após isso, precisamos fazer com que o namespace do Entity Framework esteja visível na classe de Startup.cs. Para isso, adicione as seguintes linhas de código ao final da importação de classes:
using Microsoft.EntityFrameworkCore;
using Educa_App.Models;
Isso será o suficiente para que tanto o framework quanto as classes de modelo que criaremos sejam enxergáveis pelo contexto do ASP.NET. No método ConfigureServices(), adicione também o seguinte trecho de código ao final:
services.AddDbContext<AlunoContext>(opt => opt.UseInMemoryDatabase());
Não se preocupe se der algum erro com a classe AlunoContext ou com o namespace. Isso acontece porque precisamos criar também o diretório onde tais arquivos existirão. Logo, crie uma nova pasta no projeto chamadO “Models” clicando com o botão direito no projeto e selecionando a opção “Adicionar > Nova pasta”. Em seguida, crie duas novas classes: Aluno.cs (vide Listagem 5) e AlunoContext.cs (vide Listagem 6). Veja que a primeira representa apenas um POJO de objeto simples, com atributos e get’s/set’s para os mesmos compondo um domínio de alunos. A segunda, por sua vez, herda de DbContext para incutir funcionalidade referente às operações de banco de dados, fornecendo métodos default de CRUD através do objeto de modelo passado, no caso Aluno.
Listagem 5. Conteúdo da classe Aluno.cs.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Educa_App.Models
{
public class Aluno
{
public long Id { get; set; }
public string Nome { get; set; }
public int Idade { get; set; }
public DateTime DtNasc { get; set; }
public bool IsMatriculado { get; set; }
public decimal Valor { get; set; }
}
}
Listagem 6. Conteúdo da classe AlunoContext.cs.
namespace Educa_App.Models
{
public class AlunoContext : DbContext
{
public AlunoContext(DbContextOptions<AlunoContext> options) : base(options)
{
}
public DbSet<Aluno> Alunos { get; set; }
}
}
Agora precisamos lidar com a criação dos métodos de serviço, especificamente Restful, para tratar as requisições de CRUD de cada aluno. Portanto, clique com o botão direito na pasta de Controllers do projeto e selecione a opção “Adicionar > Novo Item” e, em seguida, a opção representada na Figura 5.
Depois de criada, você notará que a classe já vem com uma estrutura mínima pré-construída para métodos de CRUD, a despeito dos benefícios de usar essa versão mais recente do Visual Studio. Isso já ajuda bastante a adaptar o modelo ao nosso CRUD em específico, além de atrelar as operações ao respectivo banco de dados. A Listagem 7 traz todo o conteúdo da classe recém gerada.
Listagem 7. Endpoints do serviço de Alunos.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Educa_App.Models;
namespace Educa_App.Controllers
{
[Route("api/[controller]")]
public class AlunoController : Controller
{
private readonly AlunoContext _context;
public AlunoController(AlunoContext context)
{
_context = context;
if (_context.Alunos.Count() == 0)
{
_context.Alunos.Add(new Aluno { Nome = "João Caruzo", Idade = 43,
DtNasc = DateTime.Today, IsMatriculado = true, Valor = 355.34m });
_context.Alunos.Add(new Aluno { Nome = "Joana D'Arc", Idade = 22,
DtNasc = DateTime.Today, IsMatriculado = false, Valor = 456.00m });
_context.Alunos.Add(new Aluno { Nome = "Alisson Matos", Idade = 19,
DtNasc = DateTime.Today, IsMatriculado = false, Valor = 456.00m });
_context.Alunos.Add(new Aluno { Nome = "Maria Silva", Idade = 29,
DtNasc = DateTime.Today, IsMatriculado = false, Valor = 456.00m });
_context.Alunos.Add(new Aluno { Nome = "Celton Souza", Idade = 23,
DtNasc = DateTime.Today, IsMatriculado = true, Valor = 456.00m });
_context.SaveChanges();
}
}
// GET: api/aluno
[HttpGet]
public IEnumerable<Aluno> Get()
{
return _context.Alunos.ToList();
}
// GET api/aluno/5
[HttpGet("{id}", Name = "GetAluno")]
public IActionResult Get(int id)
{
var aluno = _context.Alunos.FirstOrDefault(t => t.Id == id);
if (aluno == null)
{
return NotFound();
}
return new ObjectResult(aluno);
}
// POST api/aluno
[HttpPost]
public IActionResult Post([FromBody] Aluno aluno)
{
if (aluno == null)
{
return BadRequest();
}
_context.Alunos.Add(aluno);
_context.SaveChanges();
return CreatedAtRoute("GetAluno", new { id = aluno.Id }, aluno);
}
// PUT api/aluno/5
[HttpPut("{id}")]
public IActionResult Put(long id, [FromBody] Aluno aluno)
{
if (aluno == null || aluno.Id != id)
{
return BadRequest();
}
var alunoUpdate = _context.Alunos.FirstOrDefault(t => t.Id == id);
if (alunoUpdate == null)
{
return NotFound();
}
alunoUpdate.Nome = aluno.Nome;
alunoUpdate.Idade = aluno.Idade;
alunoUpdate.DtNasc = aluno.DtNasc;
alunoUpdate.Valor = aluno.Valor;
alunoUpdate.IsMatriculado = aluno.IsMatriculado;
_context.Alunos.Update(alunoUpdate);
_context.SaveChanges();
return new NoContentResult();
}
// DELETE api/aluno/5
[HttpDelete("{id}")]
public IActionResult Delete(int id)
{
var todo = _context.Alunos.First(t => t.Id == id);
if (todo == null)
{
return NotFound();
}
_context.Alunos.Remove(todo);
_context.SaveChanges();
return new NoContentResult();
}
}
}
Veja que, logo no início, já temos a primeira rota para a raiz do controller definida através do endpoint /api/, o qual obviamente pode ser alterado a bel-prazer. O construtor da classe, por sua vez, se encarregará de iniciar o contexto que criamos anteriormente para assegurar um objeto centralizado para as operações de banco. Veja que, via injeção de dependências, já recebemos o referido objeto por parâmetro, apenas associando-o a uma variável global e, em seguida, criando uma lista de cinco alunos na base em memória, apenas para que nosso exemplo já tenha valores em banco para testes. O leitor pode ficar à vontade para customizar tal inicialização como quiser.
Em relação aos demais métodos de serviço, perceba que cada um vem com um comentário logo acima da sua assinatura, para facilitar o entendimento sobre como devemos chamá-lo via requisição, bem como que método HTTP corresponde a cada um. Vejamos:
- Método Get() – sem parâmetros: esse método se responsabiliza pela busca de todos os itens de Aluno na base. Veja como é simples acessar qualquer valor via Entity Framework, o trabalho se traduz apenas em usar os métodos apropriados. No caso, ToList() se encarrega de converter a lista de linhas da base em um enum C# que, por sua vez, será convertido em JSON pelo ASP.NET;
- Método Get() – com parâmetros: aqui fazemos uso do método FirstOrDefault() do Entity Framework para buscar um item no contexto via chave primária, ou id. Os valores são retornados por padrão nos objetos que implementam a interface IActionResult, o que também será automaticamente traduzido em JSON pelo ASP.NET. Caso nenhum valor exista, o status NOT_FOUND do HTTP será devidamente retornado;
- Método Post(): o método de cadastro, efetivamente, do nosso CRUD se traduz via requisição POST com os dados do aluno em formato JSON enviados no corpo do request. O trecho [FromBody] do código garante que a conversão será feita de forma automática, desde que respeitados os devidos nomes e tipos de cada atributo. Um BAD_REQUEST será retornado caso a conversão falhe ou nada esteja contido no body, caso contrário apenas adicionamos o mesmo objeto sem nenhuma alteração no contexto e o salvamos em seguida (via SaveChanges()). Por fim, o método CreatedAtRoute() do ASP.NET será usado como suporte para retornar como resposta o recurso recém-criado, respeitando assim os requisitos do Restful. Veja que o primeiro parâmetro, “GetAluno”, se refere ao nome do primeiro método Get() que criamos. Isso é bastante útil quando queremos referenciar recursos dentro do código;
- Método Put(): nosso método de atualização lida com o recebimento de dois parâmetros: o id do recurso a ser processado (que deve vir na própria URL) e o objeto em JSON no corpo do request também convertido. Se tudo estiver ok com o objeto, buscamos o mesmo objeto no banco e alteramos cada uma de suas propriedades. Isso porque o Entity Framework só consegue atualizar entidades que estão gerenciadas, isto é, que acabaram de vir do banco ou estão associadas de alguma forma com a sessão do banco. No fim, efetuamos o update, salvamos as alterações e retornamos um status NO_CONTENT indicando que tudo aconteceu com sucesso;
- Método Delete(): a deleção não é muito diferente dos demais, recebendo o id do recurso a ser removido e chamando o respectivo método de banco para tal operação.
NOTA: O leitor pode fazer uso de quaisquer ferramentas de teste de serviços, como o Postman, por exemplo, para testar cada um dos métodos antes de prosseguir para o consumo dos mesmos nas telas. Isso é útil, pois tais ferramentas facilitam o teste e correções de bugs como um todo.
Em se tratando do código Shield UI e as exibições de tela, reaproveitaremos a página de Contato pré-criada pela inicialização do projeto. Para isso, precisaremos alterar em três lugares distintos:
- Abra o controller HomeController.cs e altere o nome do método Contact para Alunos;
- Renomeie o arquivo /Views/Home/Contact.cshtml para /Views/Home/Alunos.cshtml;
- Abra o arquivo de layout /Views/Shared/_Layout.cshtml e altere o item de menu referente a Contact para Alunos tanto no atributo asp-action quanto no conteúdo.
Abra agora o arquivo Alunos.cshtml e remova todo o seu conteúdo. Como se trata de uma implementação extensa, a dividiremos em duas partes (Listagens 8 e 9) para facilitar o entendimento.
Listagem 8. Código da grid Shield UI – parte 1.
@{
ViewData["Title"] = "Lista de Alunos";
}
<h1>Lista de Alunos</h1>
@(Html.ShieldGrid()
.Name("tabela_alunos")
.DataSource(ds => ds
.Remote(rb => rb
.ReadConfiguration(r => r
.Add("type", "GET")
.Add("url", "/api/aluno")
.Add("dataType", "json"))
.ModifyObject(m => m
.Modify(ShieldUI.AspNetCore.Mvc.DataSource.ModifyOptions.Create,
@<text>function(alunos, success, error) {
var novoAluno = alunos[0];
$.ajax({
type: "POST",
url: "/api/aluno",
dataType: "json",
data: JSON.stringify(novoAluno.data),
contentType: "application/json",
complete: function(xhr) {
if (xhr.readyState == 4) {
if (xhr.status == 201) {
var location = xhr.getResponseHeader("Location");
novoAluno.data.Id =
+location.replace(/^.*?\/([\d]+)$/, "$1");
success();
return;
}
}
error(xhr);
}
});
}</text>)
.Modify(ShieldUI.AspNetCore.Mvc.DataSource.ModifyOptions.Update,
@<text>function(alunos, success, error) {
$.ajax({
type: "PUT",
url: "/api/aluno/" + alunos[0].data.Id,
dataType: "json",
contentType: "application/json",
data: JSON.stringify(alunos[0].data)
}).then(success, error);
}</text>)
.Modify(ShieldUI.AspNetCore.Mvc.DataSource.ModifyOptions.Remove,
@<text>
function(alunos, success, error) {
$.ajax({
type: "DELETE",
url: "/api/aluno/" + alunos[0].data.Id
}).then(success, error);
}</text>)))
.Events(eb => eb
.Error(@<text>function(event) {
if (event.errorType == "transport") {
alert("Erro transport: " + event.error.statusText);
// recarrega o datasource se a operação que falhou for salva
if (event.operation == "save") {
this.read();
}
} else {
// outro erro de datasource error - validação, etc
switch (event.path.trim()) {
case "Nome":
event.editor.element.css("border-color", "red");
alert("O campo Nome não pode estar vazio");
break;
case "Idade":
event.editor.element.css("border-color", "red");
alert("O campo Idade não pode estar vazio");
break;
case "DtNasc":
event.editor._wrapper.css("border-color", "red");
event.editor._visibleInput.addClass("invalid-back");
alert("O campo Data de Nasc. não pode estar vazio");
break;
default:
alert(event.errorType + " erro: " + event.error);
break;
}
}
}</text>))
.Schema(sb => sb
.Fields("Id", fb => fb.Path("id").Type(ShieldUI.AspNetCore.Mvc
.DataSource.FieldType.Number).Nullable(false))
.Fields("Nome", fb => fb.Path("nome").Type(ShieldUI.AspNetCore.Mvc
.DataSource.FieldType.String).Nullable(false).Validator
(@<text>nomeValidator</text>))
.Fields("Idade", fb => fb.Path("idade")
.Type(ShieldUI.AspNetCore.Mvc.DataSource.FieldType.Number)
.Nullable(false).Validator(@<text>idadeValidator</text>))
.Fields("DtNasc", fb => fb.Path("dtNasc").Type(ShieldUI.AspNetCore
.Mvc.DataSource.FieldType.Date).Nullable(false))
.Fields("Valor", fb => fb.Path("valor").Type(ShieldUI.AspNetCore.Mvc
.DataSource.FieldType.Number).Nullable(false))
.Fields("IsMatriculado", fb => fb.Path("isMatriculado")
.Type(ShieldUI.AspNetCore.Mvc.DataSource.FieldType.Boolean))))
Aqui podemos ver definições bem semelhantes às que fizemos no primeiro gráfico Shield UI. A inicialização, exceto pelo método ShieldGrid() dessa vez, seguem o mesmo padrão. O método DataSource() é obrigatório pois é nele onde efetuamos as principais relativas à fonte de dados, no caso os serviços Restful. Vejamos a sequência de chamadas por método:
- Método ReadConfiguration(): definimos a URL do endpoint relativo ao serviço que provê os dados em lista, tal como o tipo de método HTTP (GET), a URL (no caso, URI interna ao projeto) e o tipo de dado (json);
- Método ModifyObject():
lida com uma série de alterações na forma como os dados serão alterados para
exibição na grid. Veja que temos um método Modify() para cada uma das operações
de CRUD suportadas pelo widget de grid do Shield UI:
o CREATE: aqui fazemos uso do pseudo-elemento do Razor @<text> para mesclar funções JavaScript com o código do ASP.NET Core. Isso é importante para flexibilizar a forma como o browser lidará com certos fluxos. Por exemplo, no cadastro informamos quais os aspectos da chamada Ajax serão considerados no ato, tais como a URL, o método HTTP, tipo de dado, o dado em si (atributo data com o valor recuperado do formulário e transformando em JSON) e a função de complete() que tratará o estado da requisição (se state for 4 e status HTTP for 201, tudo ocorreu com sucesso);
o UPDATE: efetua os mesmos procedimentos, porém concatenando a URL de requisição com o Id do aluno em questão recuperado dos dados da linha na grid;
o DELETE: bem parecido com o de atualização pela montagem da URL, porém com menos atributos configurados na requisição, dada a simplicidade dessa chamada. - Método Events(): lida com os eventos no que tange à grid em si. Aqui, tratamos apenas os de erro, mais uma vez com função JavaScript. A checagem é bem simples: se o tipo do erro for de transporte, recarregamos a grid buscando mais uma vez todos os itens (via função read() do API JS do Shield UI); caso contrário, o erro provavelmente é de alguma das validações efetuadas nos campos do formulário. Se assim for, verificamos qual o item do form está com problema e marcamos com uma borda vermelha o respectivo campo de input, além de exibir uma mensagem de alerta. Se não for erro de validação, o bloco default do switch se encarregará de exibir uma mensagem de erro mais genérica com detalhes do erro;
- Método Schema(): aqui definimos as configurações referentes aos campos que serão retornados pelo nosso serviço. Cada campo tem um nome no serviço em si (definido na função Path()) e um nome próprio para que o Shield UI consiga manipulá-lo (passado como primeiro argumento nas funções Fields()). Além disso, cada campo precisa ter seu tipo definido (Number, Date, String, etc.), bem como as respectivas validações: se é nullable ou não, ou que tipo de validator será usado especificamente (os validators definidos serão vistos mais adiante).
Listagem 9. Código da grid Shield UI – parte 2.
.Sorting(true)
.RowHover(true)
.Columns(cb => cb
.Field("Id").Title("Id").Width(80)
.Attributes(ab => ab.Style("text-align: center;"))
.HeaderAttributes(ha => ha.Style("text-align: center;")))
.Columns(cb => cb.Field("Nome").Title("Nome").Width(200)
.Format(@<text>
function(value) {
return "<strong>" + value + "</strong>";
}
</text>))
.Columns(cb => cb
.Field("Idade").Title("Idade").Width(80)
.Attributes(ab => ab.Style("text-align: center;"))
.HeaderAttributes(ha => ha.Style("text-align: center;")))
.Columns(cb => cb
.Field("DtNasc").Title("Dat Nasc.").Format("{0:dd/MM/yyyy}").Width(120))
.Columns(cb => cb
.Field("Valor").Title("Valor").Width(120)
.Format("{0:c}")
.Attributes(ab => ab.Style("text-align: left;"))
.HeaderAttributes(ha => ha.Style("text-align: left;")))
.Columns(cb => cb
.Field("IsMatriculado").Title("Matriculado?").Width(100)
.Attributes(ab => ab.Style("text-align: center;"))
.HeaderAttributes(ha => ha.Style("text-align: center;")))
.Columns(cb => cb.Width(200).Title("Ações")
.Buttons(b => b.CommandName("edit").Caption("Editar"))
.Buttons(b => b.CommandName("delete").Caption("Deletar"))
.HeaderAttributes(ha => ha.Style("text-align: center;")))
.ToolBar(tb => tb.Buttons(b => b.CommandName("insert")
.Caption("Novo Aluno"))
.Position(ShieldUI.AspNetCore.Mvc.Grid.PositionOptions.Top))
.ToolBar(tb => tb.Buttons(b => b.Caption("Recarregar lista")
.Click(@<text> function(e) {
var grid = this;
$.ajax({
type: "GET",
url: "/api/aluno"
}).done(function() {
grid.dataSource.read();
});
}<text>))
.Position(ShieldUI.AspNetCore.Mvc.Grid.PositionOptions.Bottom))
.PagingConfiguration(pb => pb.PageSize(4))
.Editing(eb => eb.Enabled(true)
.Type(ShieldUI.AspNetCore.Mvc.Grid.EditingTypeOptions.Row)))
<script type="text/javascript">
function idadeValidator(value) {
if (value <= 0) {
return undefined;
} else {
return value;
}
}
function nomeValidator(value) {
if (value == "") {
return undefined;
} else {
return value;
}
}
</script>
Vejamos nessa continuação de código da Listagem 7 algumas de suas principais definições:
- Métodos Sorting() e RowHover(): se encarregam de definir se a grid será ordenável por coluna e se terá hover (efeito quando o mouse passar sobre cada linha da tabela), respectivamente;
- Métodos Columns():
definem as características visuais de cada uma das colunas da tabela. Veja que
aqui tentamos mixar o máximo possível de combinações com os métodos e
configurações disponíveis pelo Shield UI para colunas. Por exemplo:
o Método Format(): serve para formatar os dados de diversas maneiras, seja via função JavaScript (como vemos na coluna de nome do aluno, sempre em negrito), seja via regex de data (data de nascimento, por exemplo), valores monetários (campo de valor), etc.;
o Métodos Attributes() e HeaderAttributes(): definem um atalho para incutir estilos CSS inline em cada uma das linhas e cabeçalho da coluna, respectivamente. Aqui, o estamos usando para definir os alinhamentos do conteúdo de cada coluna;
o Método Field(): deve referenciar os mesmos nomes de cada campo definidos nos Fields() anteriormente. - Método ToolBar(): aqui o usamos duas vezes para criar dois botões na parte superior e inferior, para criar novos itens na tabela e recarregar os mesmos do servidor, respectivamente. Os métodos CommandName() são um atalho muito útil do Shield UI para chamar funções de CRUD disponíveis no grid, bastando passar o nome de cada comando (insert, update, etc.). Em relação ao segundo botão, veja que mais uma vez chamamos via @<text> um Ajax que busca novamente todos os itens no servidor;
- Método PagingConfiguration(): aqui definimos qual o tamanho de cada página para a paginação do widget de grid;
- Método Editing(): essa configuração é importante para dizer ao Shield UI grid que desejamos editar os valores de suas linhas (o valor definido no método Type() configura qual o tipo de edição – Row);
- Funções JavaScript: as funções idadeValidator() e nomeValidator() são funções básicas apenas para validar se os valores informados não estão vazios. Obviamente, as validações podem ser mais robustas dadas às complexidades do seu projeto, bem como efetuar chamadas Ajax para tal.
CRUD finalizado, agora é só reexecutar a aplicação e testar suas funcionalidades. As Figuras 6 a 8 demonstram alguns fluxos de funcionamento da tela.
Uma vez entendidos os conceitos mais centrais do framework e a forma como cada método se encadeia em chamadas, usar o Shield UI se torna uma tarefa fácil e intuitiva. Até mesmo quando precisamos migrar para outras versões do ASP.NET ou demais tecnologias que o Shield UI suporta, o trabalho não se torna maçante. Por exemplo, vejamos como adaptar a nossa página About tal como vemos na Listagem 10. Nele, estamos efetuando uma mescla entre os componentes de tabs, um dos widgets HTML também fornecidos pelo Shield UI para flexibilizar a componentização de páginas web, e outros tipos de gráficos do mesmo.
Listagem 10. Código com gráficos e tabs.
@{
ViewData["Title"] = "About";
}
<h1>About</h1>
<div id="tabsContainer">
@(Html.ShieldTabs()
.Name("tabsDiv")
.Html(@<text>
<ul>
<li>Média Etária</li>
<li>Mercury</li>
</ul>
<div>
@(Html.ShieldChart()
.Name("chart1")
.Theme("dark")
.Export(false)
.PrimaryHeader(header => header.Text("Distribuição Etária dos Alunos"))
.ChartLegend(legend => legend.Enabled(true))
.SeriesSettings(setting => setting.Pie(pie => pie
.EnablePointSelection(true)))
.DataSeries(dataSeries => dataSeries.Pie()
.CollectionAlias("Faixa")
.Data(new object[]
{
new object[] {"Até 20 anos", 40},
new object[] {"Dos 20 aos 30 anos", 30},
new { collectionAlias = "Dos 30 acima", y = 30, selected = true }
})))
</div>
<div>
@(Html.ShieldChart()
.Name("chart2")
.Theme("dark")
.IsInverted(true)
.Tooltip(tooltip => tooltip.AxisMarkers(axisMarkers => axisMarkers
.Enabled(true)
.Mode(ShieldUI.AspNetCore.Mvc.Chart.TooltipAxisMarkerMode.X)
.Width(1)
.ZIndex(3)))
.PrimaryHeader(header => header.Text("Balanço por Semestre"))
.Export(false)
.AxisX(axis => axis.CategoricalValues("2016.1", "2016.2", "2017.1"))
.DataSeries(series =>
series.Bar()
.Name("Em milhares de reais")
.Data(new object[] { 100, 320, 453 })))
</div>
</text>))
</div>
Exceto por alguns trechos, o leitor pode perceber a semelhança, inclusive com o primeiro chart que criamos no início do artigo. A flexibilização vai além, podendo atingir até o consumo de serviços para popular os valores em vez dos fixos que vemos, cruzamento de dados, mixing entre widgets de várias formas, além de, é claro, ter todo o poder do HTML/CSS/JavaScript em mãos para mesclar com os códigos do ASP.NET Core.
É claro que quando se trata da seleção da melhor ferramenta/biblioteca de mercado, dada a quantidade expressiva de opções, a tarefa tem de ir um pouco além e analisar fatores como integração, multilinguagem, facilidade de manuseio, etc. O Shield UI consegue abraçar bem todos os cenários justamente pela flexibilidade em se desenvolver para .Net, Java, JavaScript, etc. e poder portar isso em diferentes plataformas. Todavia, é paga para propósitos comerciais e aí cabe à sua equipe/empresa analisar as condições e viabilidade mediante todos o poder que ele te dá, como vimos. Boa sorte e bons estudos!
Links
- Download do Shield UI