ASP .NET Core - Gerenciador de Despesas Pessoais com Gráfico (EF Core) - I


 Hoje vamos criar uma aplicação ASP .NET Core para fazer o gerenciamento de despesas pessoais e exibir um gráfico das despesas usando os recursos do Entity Framework Core 2.0.     

Este é um artigo essencialmente prático onde vamos criar um gerenciador de despesas pessoais usando o ASP.NET Core 2.1 e a abordagem Code-First do Entity Framework Core. Criaremos um gerenciador de despesas que rastreia suas despesas diárias e fornece gráficos comparativos para mostrar um resumo de despesas por período.

Vamos usar uma caixa de diálogo modal para manipular as entradas do usuário e para mostrar o gráfico de resumo de despesas mensal e semanal usando os recursos do Highcharts, portanto, esse aplicativo será um Singe Page Application ou Aplicativo de Página Única (SPA).

Veja abaixo a aplicação funcionando:

Os pré-requisitos necessários são:

Agora mãos à obra...

Criando o projeto ASP .NET Core no VS 2017

Após esse procedimento abra o VS 2017 Community e selecione File ->New Project;

Selecione .NET Core e o template ASP .NET Core Web Application e informe o nome MinhasFinancas e clique em OK;

Na próxima janela selecione ASP .NET Core 2.1 e o template Web Application (Model-View-Controller) e clique em OK:

 

Vamos agora incluir uma referência ao pacote Microsoft.EntityFrameworkCore.Tools para poder ter acesso aos recursos do Migrations para poder criar a tabela e o banco de dados usados no projeto.

Como vamos usar a abordagem Code-First do EF Core vamos precisar do Migrations.

No menu acesse Manage Nuget Packages for Solution e clique na guia Browse;

A seguir selecione o pacote indicado , marque todos os projetos e clique em Install:

Os pacotes para acessar o banco de dados SQL Server e o pacote do Entity Framework Core já estão referenciados por padrão no projeto.

Adicionando o modelo de domínio e a classe de contexto na Aplicação

Clique com o botão direito do mouse sobre a pasta Models do projeto e a seguir crie uma classe chamada RelatorioDespesa com o código abaixo:

Esta classe contém as propriedades do nosso modelo de domínio.

Observe que estou usando os recursos do Data Annotations aplicando diversos atributos para configurar as propriedades para gerar as colunas da tabela no banco de dados conforme desejamos.

A seguir vamos criar a classe de contexto na pasta Models com o nome de AppDbContext com o código abaixo:

A classe de contexto herda de DbContext e definie uma propriedade DbSet<> que mapeia a nossa entidade para a tabela RelatorioDespesas e definimos uma instância de DbContextOptions que vamos configurar no arquivo Startup no método ConfigureServices onde vamos definir o provedor do banco de dados e a string de conexão usada.

Abrindo a classe Startup definimos no método ConfigureServices o código conforme mostrado abaixo para registrar o nosso contexto como um serviço :

A string de conexão será obtida do arquivo appsettings.json cujo código é visto a seguir:

Na string de conexão definimos o servidor SQL Server usado e o nome do banco de dados: Financas

Criando o banco de dados e a tabela

Ja podemos usar os recursos do Migrations para criar o banco de dados e a tabela.

Abra uma janela do Package Manager Console e digite o comando : add-migration InicialDepesas

Esse comando irá criar o script de migração.

Para aplicar o script digite o comando : update-database

Esses comandos irão criar a pasta Migrations no projeto e os arquivos de script e o arquivo AppDbContextModelSnapshot.cs que armazena informações sobre as migrações aplicadas.

Abrindo o SQL Server Management Studio podemos verificar a criação do banco de dados Financas e da tabela RelatorioDespesas com a estrutura desejada:

Criando a camada de acesso a dados da aplicação

Clique com o botão direito do mouse sobre o projeto e a seguir selecione Add->New Folder e informe o nome DAL.

A seguir vamos incluir nesta classe a interface IFinancasDAL e a sua implementação, a classe FinancasDAL que será a nossa camada de acesso a dados. Poderiamos acessar os dados diretamente do controlador via EF Core mas vamos criar uma camada de acesso a dados para desacoplar nosso código do EF Core.

Abaixo temos o código da interface IFinancasDAL contendo todos os métodos que precisamos para gerenciar as despesas :

Agora crie a classe FinancasDAL que implementa a interface IFinancasDAL com o código abaixo:

using Microsoft.EntityFrameworkCore;
using MinhasFinancas.Models;
using System;
using System.Collections.Generic;
using System.Linq;
namespace MinhasFinancas.DAL
{
    public class FinancasDAL : IFinancasDAL
    {
        public FinancasDAL() { }
        private readonly AppDbContext db;
        public FinancasDAL(AppDbContext context)
        {
            db = context;
        }
        public IEnumerable<RelatorioDespesa> GetAllDespesas()
        {
            try
            {
                return db.RelatorioDespesas.ToList();
            }
            catch { throw; }
        }
        // Filtra os registros com base na string de busca
        public IEnumerable<RelatorioDespesa> GetFiltraDespesa(string criterio)
        {
            List<RelatorioDespesa> desp = new List<RelatorioDespesa>();
            try
            {
                desp = GetAllDespesas().ToList();
                return desp.Where(x => x.ItemNome.IndexOf(criterio, StringComparison.OrdinalIgnoreCase) != -1);
            }
            catch{ throw; }
        }
        //Adicionar uma nova despesas
        public void AddDespesa(RelatorioDespesa despesa)
        {
            try
            {
                db.RelatorioDespesas.Add(despesa);
                db.SaveChanges();
            }
            catch{ throw; }
        }
        //atualizar uma despesa    
        public int UpdateDespesa(RelatorioDespesa despesa)
        {
            try
            {
                db.Entry(despesa).State = EntityState.Modified;
                db.SaveChanges();
                return 1;
            }
            catch { throw; }
        }
        //Obter uma despesa pelo seu id
        public RelatorioDespesa GetDespesa(int id)
        {
            try
            {
                RelatorioDespesa despesa = db.RelatorioDespesas.Find(id);
                return despesa;
            }
            catch{ throw;  }
        }
        //Deletar uma despesa
        public void DeleteDespesa(int id)
        {
            try
            {
                RelatorioDespesa desp = db.RelatorioDespesas.Find(id);
                db.RelatorioDespesas.Remove(desp);
                db.SaveChanges();
            }
            catch{ throw; }
        }
        //Calcula despesa semestral
        public Dictionary<string, decimal> CalculaDespesaPeriodo(int periodo)
        {
            Dictionary<string, decimal> SomaDespesasPeriodo = new Dictionary<string, decimal>();
            decimal despAlimentacao = db.RelatorioDespesas.Where
                                (cat => cat.Categoria == "Alimentacao" && (cat.DataDespesa > DateTime.Now.AddMonths(-periodo)))
                                .Select(cat => cat.Valor)
                                .Sum();
            decimal despCompras = db.RelatorioDespesas.Where
                               (cat => cat.Categoria == "Compras" && (cat.DataDespesa > DateTime.Now.AddMonths(-periodo)))
                               .Select(cat => cat.Valor)
                               .Sum();
            decimal despTransporte = db.RelatorioDespesas.Where
                               (cat => cat.Categoria == "Transporte" && (cat.DataDespesa > DateTime.Now.AddMonths(-periodo)))
                               .Select(cat => cat.Valor)
                               .Sum();
            decimal despSaude = db.RelatorioDespesas.Where
                               (cat => cat.Categoria == "Saude" && (cat.DataDespesa > DateTime.Now.AddMonths(-periodo)))
                               .Select(cat => cat.Valor)
                               .Sum();
            decimal despMoradia = db.RelatorioDespesas.Where
                               (cat => cat.Categoria == "Moradia" && (cat.DataDespesa > DateTime.Now.AddMonths(-periodo)))
                               .Select(cat => cat.Valor)
                               .Sum();
            decimal despLazer = db.RelatorioDespesas.Where
                               (cat => cat.Categoria == "Lazer" && (cat.DataDespesa > DateTime.Now.AddMonths(-periodo)))
                               .Select(cat => cat.Valor)
                               .Sum();
            SomaDespesasPeriodo.Add("Alimentacao", despAlimentacao);
            SomaDespesasPeriodo.Add("Compras", despCompras);
            SomaDespesasPeriodo.Add("Transporte", despTransporte);
            SomaDespesasPeriodo.Add("Saude", despSaude);
            SomaDespesasPeriodo.Add("Moradia", despMoradia);
            SomaDespesasPeriodo.Add("Lazer", despLazer);

            return SomaDespesasPeriodo;
        }
        //Calcula despesa mensal
        public Dictionary<string, decimal> CalculaDespesaPeriodoSemanal(int periodo)
        {
            Dictionary<string, decimal> SomaDespesasPeriodoSemanal = new Dictionary<string, decimal>();
            decimal despAlimentacao = db.RelatorioDespesas.Where
                                (cat => cat.Categoria == "Alimentacao" && (cat.DataDespesa > DateTime.Now.AddDays(-periodo)))
                                .Select(cat => cat.Valor)
                                .Sum();
            decimal despCompras = db.RelatorioDespesas.Where
                               (cat => cat.Categoria == "Compras" && (cat.DataDespesa > DateTime.Now.AddDays(-periodo)))
                               .Select(cat => cat.Valor)
                               .Sum();
            decimal despTransporte = db.RelatorioDespesas.Where
                               (cat => cat.Categoria == "Transporte" && (cat.DataDespesa > DateTime.Now.AddDays(-periodo)))
                               .Select(cat => cat.Valor)
                               .Sum();
            decimal despSaude = db.RelatorioDespesas.Where
                               (cat => cat.Categoria == "Saude" && (cat.DataDespesa > DateTime.Now.AddDays(-periodo)))
                               .Select(cat => cat.Valor)
                               .Sum();
            decimal despMoradia = db.RelatorioDespesas.Where
                               (cat => cat.Categoria == "Moradia" && (cat.DataDespesa > DateTime.Now.AddDays(-periodo)))
                               .Select(cat => cat.Valor)
                               .Sum();
            decimal despLazer = db.RelatorioDespesas.Where
                               (cat => cat.Categoria == "Lazer" && (cat.DataDespesa > DateTime.Now.AddDays(-periodo)))
                               .Select(cat => cat.Valor)
                               .Sum();
            SomaDespesasPeriodoSemanal.Add("Alimentacao", despAlimentacao);
            SomaDespesasPeriodoSemanal.Add("Compras", despCompras);
            SomaDespesasPeriodoSemanal.Add("Transporte", despTransporte);
            SomaDespesasPeriodoSemanal.Add("Saude", despSaude);
            SomaDespesasPeriodoSemanal.Add("Moradia", despMoradia);
            SomaDespesasPeriodoSemanal.Add("Lazer", despLazer);
            return SomaDespesasPeriodoSemanal;
        }
    }
}

Nesta classe temos os métodos para realizar as operações CRUD via Entity Framework Core onde usamos a injeção de dependência para obter uma instância da classe de contexto.

Vamos a seguir criar o nosso controlador para acessar os métodos da nossa camada de acesso a dados e gerenciar as informações das despesas.

Usando a injeção de dependência nativa

A idéia da Injeção de Dependência é que, quando uma classe for criada, ela deve ter suas classes dependentes injetadas ao invés de criar essas classes. Isto proporciona uma situação na qual obtemos um fraco acoplamento e uma alta coesão.

Vamos usar esse recurso registrando a nossa interface IFinancasDAL e a classe concreta FinancasDAL como um serviço no método ConfigureServices da classe Startup:

No nosso exemplo usamos o tempo de vida Transient e informando que quando tivermos uma referência a uma instância da interface queremos que seja injetado a instância da classe concreta.

Criando a Web API : Incluindo o Controller ClienteController

Clique com o botão direito sobre a pasta Controllers e selecione Add -> New Item;

A seguir selecione o template Web-> API Controller Class e informe o nome DespesaController:

Inclua o código abaixo no controlador DespesaController gerado:

using Microsoft.AspNetCore.Mvc;
using MinhasFinancas.DAL;
using MinhasFinancas.Models;
using System;
using System.Collections.Generic;
using System.Linq;
namespace MinhasFinancas.Controllers
{
    public class DespesaController : Controller
    {
        private readonly IFinancasDAL _dal;
        public DespesaController(IFinancasDAL dal)
        {
            _dal = dal;
        }
        // GET: Despesas
        public IActionResult Index(string criterio)
        {
            var lstDespesas = _dal.GetAllDespesas().ToList();
            if (!String.IsNullOrEmpty(criterio))
            {
                lstDespesas = _dal.GetFiltraDespesa(criterio).ToList();
            }
            return View(lstDespesas);
        }
        public ActionResult AddEditDespesa(int itemId)
        {
            RelatorioDespesa model = new RelatorioDespesa();
            if (itemId > 0)
            {
                model = _dal.GetDespesa(itemId);
            }
            return PartialView("_despesaForm", model);
        }
        [HttpPost]
        public ActionResult Create(RelatorioDespesa novaDespesa)
        {
            if (ModelState.IsValid)
            {
                if (novaDespesa.ItemId > 0)
                {
                    _dal.UpdateDespesa(novaDespesa);
                }
                else
                {
                    _dal.AddDespesa(novaDespesa);
                }
            }
            return RedirectToAction("Index");
        }
        [HttpPost]
        public IActionResult Delete(int id)
        {
            _dal.DeleteDespesa(id);
            return RedirectToAction("Index");
        }
        public ActionResult DespesaResumo()
        {
            return PartialView("_despesaReport");
        }
        public JsonResult GetDepesaPorPeriodo()
        {
            Dictionary<string, decimal> despesaPeriodo = _dal.CalculaDespesaPeriodo(7);
            return new JsonResult(despesaPeriodo);
        }
        public JsonResult GetDepesaPorPeriodoSemanal()
        {
            Dictionary<string, decimal> despesaPeriodoSemanal = _dal.CalculaDespesaPeriodoSemanal(7);
            return new JsonResult(despesaPeriodoSemanal);
        }
    }
}

Neste código temos uma instância private readonly de nossa interface IFinancasDAL, e, o construtor toma uma instância de IFinancasDAL e define nossa instância particular para a instância passada.  Esta é a injeção de dependência via construtor em ação.

Neste momento a estrutura da nossa solução pode ser vista a seguir:

A lógica do nosso backend esta pronta. Vamos agora definir o código do lado do cliente.

Na próxima parte do artigo vamos definir as views que irão exibir as informações e interagir com o usuário.

"Vinde a mim, todos os que estais cansados e oprimidos, e eu vos aliviarei."(disse Jesus)
Mateus 11:28

Veja os Destaques e novidades do SUPER DVD Visual Basic (sempre atualizado) : clique e confira !

Quer migrar para o VB .NET ?

Quer aprender C# ??

Quer aprender os conceitos da Programação Orientada a objetos ?

Quer aprender o gerar relatórios com o ReportViewer no VS 2013 ?

Referências:


José Carlos Macoratti