.NET

20 abr, 2017

ASP .NET Core – Criando serviços backend para aplicações móveis nativas – Parte 01

Publicidade

Neste artigo, eu vou mostrar como criar serviços backend usando ASP .NET Core MVC com suporte a aplicações móveis nativas (Android, iOS e Windows Phone).

Esta série de artigos se baseia neste original com adaptações e ajustes.

Para acompanhar nos hoje, você precisa ter instalado o Visual Studio Community 2017 com os seguintes workloads instalados:

O objetivo

Aplicações para dispositivos móveis podem se comunicar facilmente com serviços backend ASP .NET Core e este artigo vai demonstrar como criar serviços backend usando ASP .NET Core MVC para suportar aplicações mobiles nativas.

Podemos dividir as etapas a serem cumpridas aqui da seguinte forma:

  1. Criar o serviço ASP .NET Core;
  2. Criar a aplicação cliente Xamarin Forms.

Então, vamos iniciar criando o serviço ASP .NET Core.

Criando o projeto ASP .NET Core

Vamos criar uma Web API que exponha serviços para gerenciar tarefas. Esse serviço vai permitir acessar informações e realizar operações de inclusão, exclusão e atualização de dados via serviço web API a partir da aplicação cliente que será a aplicação mobile.

Abra no VS 2017 e no menu File clique em New Project. A seguir, selecione o template Visual C# -> .NET Core e marque ASP .NET Core Web Application (.NET Core). Depois, informe o nome TarefaAPI e clique no botão OK.

Na próxima janela, escolha a versão ASP .NET Core 1.1 e marque o template Web API sem autenticação e clique no botão OK:

Será criado um projeto usando o template Web API com toda a estrutura para uso. Esse projeto será o nosso ponto de partida.

Se você executá-lo agora vai obter o seguinte resultado:

Vamos iniciar alterando o arquivo Program.cs do projeto de forma a fazer com que a aplicação atenda a todas as requisições na porta 5000.

Para isso, abra o arquivo Program.cs e e inclua a linha de código .UseUrls(“http://*:5000”) no arquivo:

using Microsoft.AspNetCore.Hosting;
namespace TarefaAPI
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var host = new WebHostBuilder()
                .UseKestrel()
                .UseUrls("http://*:5000")
                .UseContentRoot(Directory.GetCurrentDirectory())
                .UseIISIntegration()
                .UseStartup<Startup>()
                .UseApplicationInsights()
                .Build();
            host.Run();
        }
    }
}

Vamos executar a aplicação diretamente e não via II Express, o qual ignora requisições não locais por padrão.

Dessa forma, para rodar a aplicação, vamos executar dotnet run a partir da linha de comando ou escolher o perfil da aplicação a partir da caixa de combinação Debug Target na barra de ferramentas do Visual Studio.

Criando o nosso modelo de domínio: a classe TarefaItem

Um modelo de domínio é um objeto que representa os dados em seu aplicativo. Neste exemplo, o único modelo que teremos é um item de tarefa.

Vamos criar uma pasta chamada “Models” no projeto para nesta pasta definir o modelo.

Na janela Solution Explorer, clique com o botão direito do mouse no projeto e selecione Add -> New Folder e informe o nome Models.

Nota: Você pode colocar classes do modelo em qualquer lugar em seu projeto, mas a pasta Models é usada por convenção.

Agora vamos criar uma classe TarefaItem. Clique com o botão direito do mouse na pasta Models e selecione Add ->Class e atribua o nome TarefaItem à classe e clique em Add.

Substitua o código gerado pelo código abaixo:

using System.ComponentModel.DataAnnotations;
namespace TarefaAPI.Models
{
    public class TarefaItem
    {
        [Required]
        public string ID { get; set; }
        [Required]
        public string Nome { get; set; }
        [Required]
        public string Notas { get; set; }
        public bool Concluido { get; set; }
    }
}

Definimos a classe TarefaItem contendo quatro propriedades, onde em cada propriedade usamos o atributo [Required], que indica que o campo é obrigatório.

Adicionando uma classe Repositório

O padrão de projeto Repository acrescenta uma camada de abstração no topo da camada de consultas e ajuda eliminar a lógica duplicada na implementação do código de suas consultas ao modelo de entidades.

Foi Martin Fowler quem definiu o padrão Repository no seu livro “Patterns of Enterprise Application Architecture“. Ficou da seguinte forma:”Intermedeia entre o domínio e as camadas de mapeamento de dados usando uma interface de coleção para acessar objetos de domínio” (numa tradução livre feita por mim).

Assim, um repositório é essencialmente uma coleção de objetos de domínio em memória, e, com base nisso, o padrão Repository permite realizar o isolamento entre a camada de acesso a dados (DAL) de sua aplicação e sua camada de apresentação (UI) e camada de negócios (BLL).

Ao utilizar o padrão Repository, você pode realizar a persistência e a separação de interesses em seu código de acesso a dados, visto que ele encapsula a lógica necessária para persistir os objetos do seu domínio na sua fonte de armazenamento de dados.

Vamos, então, implementar o padrão repositório, e, a abordagem clássica para fazer isso é criar uma interface contendo os métodos que desejamos expor e, a seguir, implementar essa interface na classe repositório.

Clique com o botão direito do mouse na pasta Models, selecione Add -> New Item e escolha o template Interface. Em seguida, informe o nome ITarefaRepositorio e clique no botão Add.

using System.Collections.Generic;
namespace TarefaAPI.Models
{
    public interface ITarefaRepositorio
    {
        bool ItemExiste(string id);
        IEnumerable<TarefaItem> All { get; }
        TarefaItem Find(string id);
        void Insert(TarefaItem item);
        void Update(TarefaItem item);
        void Delete(string id);
    }
}

Nesta interface, estamos definindo métodos para gerenciar as informações sobre o nosso modelo de domínio: TarefaItem. Vamos, agora, implementar essa interface.

Clique com o botão direito do mouse na pasta Models e selecione Add -> Class e informe o nome TarefaRepositorio e clique no botão Add;

A seguir, defina o código abaixo:

using System.Collections.Generic;
using System.Linq;
namespace TarefaAPI.Models
{
    public class TarefaRepositorio : ITarefaRepositorio
    {
        private List<TarefaItem> _tarefaLista;
        public TarefaRepositorio()
        {
            InitializeData();
        }
        private void InitializeData()
        {
            _tarefaLista = new List<TarefaItem>();
            var tarefaItem1 = new TarefaItem
            {
                ID = "T100",
                Nome = "Criando aplicações com Xamarin",
                Notas = "Xamarin University",
                Concluido = true
            };
            var tarefaItem2 = new TarefaItem
            {
                ID = "T200",
                Nome = "Desenvolvimento de aplicação multiplataforma",
                Notas = "Xamarin Studio/Visual Studio",
                Concluido = false
            };
            var tarefaItem3 = new TarefaItem
            {
                ID = "T300",
                Nome = "Publicando Aplicações",
                Notas = "Azure Cloud",
                Concluido = false,
            };
            _tarefaLista.Add(tarefaItem1);
            _tarefaLista.Add(tarefaItem2);
            _tarefaLista.Add(tarefaItem3);
        }
        public IEnumerable<TarefaItem> All
        {
            get { return _tarefaLista; }
        }
        public void Delete(string id)
        {
            _tarefaLista.Remove(this.Find(id));
        }
        public TarefaItem Find(string id)
        {
            return _tarefaLista.FirstOrDefault(item => item.ID == id);
        }
        public void Insert(TarefaItem item)
        {
            _tarefaLista.Add(item);
        }
        public bool ItemExiste(string id)
        {
            return _tarefaLista.Any(item => item.ID == id);
        }
        public void Update(TarefaItem item)
        {
            var tarefaItem = this.Find(item.ID);
            var index = _tarefaLista.IndexOf(tarefaItem);
            _tarefaLista.RemoveAt(index);
            _tarefaLista.Insert(index, item);
        }
    }
}

No código acima temos a implementação dos métodos definidos na interface ITarefaRepositorio.

Registrando o repositório (usando o contâiner DI)

Ao definir uma interface repositório, podemos desacoplar a classe de repositório do controlador MVC que a utiliza. Ao invés de instanciar uma classe TarefaRepositorio dentro do controlador, injetaremos uma interface ITarefaRepositorio usando o suporte interno no ASP.NET Core para injeção de dependência.

Esta abordagem facilita os testes unitários dos controladores. Os testes de unidade devem injetar uma versão simulada ou stub de ITarefaRepositorio. Dessa forma, o teste segmenta de forma restrita a lógica do controlador e não a camada de acesso a dados.

Para injetar o repositório no controlador, precisamos registrá-lo com o contâiner DI.

Abra o arquivo Startup.cs do projeto e no método ConfigureServices(), adicione o código em negrito:

using AspnetCore_WebApi.Models;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
...
public class Startup
{
       ...
     // This method gets called by the runtime. Use this method to add services to the container.
     public void ConfigureServices(<strong>IServiceCollection services</strong>)
     {
            // Add framework services.
            services.AddMvc();
            <strong>services.AddSingleton&lt;ITarefaRepositorio, TarefaRepositorio&gt;();</strong>
     }
   ...
}

O método ConfigureServices() é responsável por definir os serviços que a aplicação vai usar, incluindo recursos da plataforma como ASP .NET Core MVC e Entity Framework.

Na implementação da injeção de dependência do ASP.NET Core, vemos o conceito de lifetimes ou “tempo de vidas”. Um lifetime ou tempo de vida especifica quando um objeto DI-injetado é criado ou recriado. Existem três possibilidades:

  1. Transient: Criado a cada vez que são solicitados;
  2. Scoped: Criado uma vez por solicitação;
  3. Singleton: Criado na primeira vez que é solicitado. Cada solicitação subseqüente usa a instância que foi criada na primeira vez.

O parâmetro IServiceCollection permite configurar diferentes tipos de serviços, seja por criação de objeto ou correspondência a uma interface específica, e suporta os lifetimes mencionados.

No exemplo, estamos adicionando um serviço Singleton.

Incluindo um Controller no projeto

Se você não sabe o que é um Controller, então, pode ler os seguintes artigos:

Os Controllers ou Controladores são os componentes que lidam com a interação do usuário, trabalham com o modelo e, finalmente, selecionam uma exibição de renderização que mostra essa interface ao usuário. Por padrão, os controladores são colocados na pasta Controllers da solução.

Clique com o botão direito do mouse na pasta Controllers e selecione Add -> New Item.

A seguir, selecione o template Web API Controller Class e informe o nome TarefaController e clique no botão Add.

A seguir, inclua o seguinte código neste arquivo:

using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using TarefaAPI.Models;
namespace TarefaAPI.Controllers
{
    [Route("api/[controller]")]
    public class TarefaController : Controller
    {
        private readonly ITarefaRepositorio _tarefaRepositorio;
        public TarefaController(ITarefaRepositorio tarefaRepositorio)
        {
            _tarefaRepositorio = tarefaRepositorio;
        }
    }
}

Entendendo o código:

  • A classe deve herdar de Microsoft.AspNetCore.Mvc.Controller;
  • Adicionamos uma rota usando o atributo Rout para indicar que o controlador irá tratar requisições feitas a partir da URL API/Controller, onde Controller na rota será substituído pelo prefixo do nome do controlador, ou seja: API/tarefa;
  • Definimos uma variável _tarefaRepositorio do tipo da interface ITarefaRepositorio;
  • Requisitamos uma instância deste tipo no construtor que, em tempo de execução, será fornecida pelo container de injeção de dependência da ASP .NET Core.

Implementando os métodos CRUD

Para que esta API suporte as operações para gerenciar as informações, ela tem que suportar quatro diferentes verbos HTTP para realizar as operações CRUD na fonte de dados. Vamos, então, implementar e testar cada uma destas operações CRUD com seus respectivos verbos HTTP.

Vamos usar uma enumeração para definir os códigos de estados HTTP no código de forma mais amigável.

Abaixo, vemos o código da enum que serão usados nos métodos CRUD: Create, Edit e Delete.

     public enum ErrorCode
     {
TarefaItemNomeAndNotasRequired,
        TarefaItemIDInUse,
        RecordNotFound,
        CouldNotCreateItem,
        CouldNotUpdateItem,
        CouldNotDeleteItem
     }

1. Lendo itens de tarefa

Para obter uma lista de itens de tarefa, podemos aplicar o método HTTP GET usando atributo [HttpGet] conforme mostra o método List() a seguir:

        [HttpGet]
        public IActionResult List()
        {
            return Ok(_tarefaRepositorio.All);
        }

A rota para esta Action é a rota especificada no controlador. Você não é obrigado a usar o nome da Action como parte da rota, mas tem que assegurar que cada Action possua uma única rota.

Este método List retorna um código de resposta igual a 200 OK e todos os itens são serializados no formato JSON.

Para testar, vamos usar o aplicativo Postman.

Para instalar o Postman, acesse esse link ou abra o Google Chrome e digite postam e, a seguir clique aqui.

Na janela que será aberta, clique no botão – Usar no Chrome:

A seguir, registre uma conta e faça o login. Pronto! Já podemos usar o Postman.

A primeira coisa a fazer é levantar aplicação ASP .NET Core usando o comando: dotnet run. Para isso, abra uma janela de prompt de comando e se posicione na pasta do projeto onde está o arquivo package.json e digite: dotnet run.

Você verá a janela abaixo indicando que a aplicação está atendendo na porta 5000:

Obtenha o ip da sua máquina local usando o comando ipconfig em uma janela de prompt de comando e, a seguir, digite o comando abaixo no Postman.

Nota: Para testar as requisições no Postam, você também pode executar a aplicação teclando F5 e usar o endereço e porta informados na URL (exemplo: Localhost:XXXX/api/tarefa).

Como vemos, o envio da requisição GET usando a URL: http:192.168.1.18:5000/api/tarefa chama o método List() que retorna todos os itens.

2. Criando itens de tarefa

Por convenção, a criação de um novo item de tarefa é mapeado para um verbo HTTP POST.

Vamos criar o método Create usando o atributo [HttpPost] e aceitar um parâmetro ID e uma instância de TarefaItem.

        [HttpPost("{id}")]
        public IActionResult Create(string id, [FromBody]TarefaItem item)
        {
            try
            {
                if (item == null || !ModelState.IsValid)
                {
                    return BadRequest(ErrorCode.TodoItemNameAndNotesRequired.ToString());
                }
                bool itemExists = _tarefaRepositorio.ItemExiste(item.ID);
                if (itemExists)
                {
                    return StatusCode(StatusCodes.Status409Conflict, ErrorCode.TodoItemIDInUse.ToString());
                }
                _tarefaRepositorio.Insert(item);
            }
            catch (Exception)
            {
                return BadRequest(ErrorCode.CouldNotCreateItem.ToString());
            }
            return Ok(item);
        }

Entendendo o código:

Por convenção, a criação de novos itens de dados é mapeada para o verbo HTTP POST.

O método Create tem um atributo [HttpPost] aplicado a ele e aceita um parâmetro ID e uma instância TarefaItem.

Os atributos do verbo HTTP, como [HttpPost], aceitam opcionalmente uma cadeia de modelo de rota ({id} neste exemplo). Isso tem o mesmo efeito que adicionar um atributo [Route] para a Action.

Como o argumento item será passado no corpo do POST, este parâmetro é decorado com o atributo [FromBody].

Dentro do método, o item é verificado quanto à validade e existência prévia no armazenamento de dados e, se nenhum problema ocorrer, ele é adicionado usando o repositório. A verificação de ModelState.IsValid executa a validação do modelo, e deve ser feita em cada método de API que aceite a entrada do usuário.

O exemplo usa um enum contendo códigos de erro que são passados para o cliente móvel. Para testar a adição de um novos itens de tarefa usando o Postman, vamos fazer o seguinte:

  1. Defina o método HTTP para POST;
  2. Defina o endereço da URL para :  192.168.1.18:5000/api/tarefa/T400;
  3. Selecione a opção Body;
  4. Selecione a opção raw;
  5. Define o tipo para JSON;
  6. No editor chave-valor informe um item tarefa conforme mostrado abaixo.
  7. Clique no botão Send:
{
   "ID": "T400",
   "Nome": "Incluindo um novo item",
   "Notas": "testando POST",
   "Concluido" : false
}

O novo item é criado, retornado e exibido conforme a figura  acima.

3. Atualizando itens de tarefa

A atualização de um item existente de tarefa é mapeado para um verbo HTTP PUT:

        [HttpPut("{id}")]
        public IActionResult Edit(string id, [FromBody] TarefaItem item)
        {
            try
            {
                //Valida o modelo
                if (item == null || !ModelState.IsValid)
                {
                    //reproduz um Http Erro 400 - Requisição inválida
                    return BadRequest(ErrorCode.TarefaItemNomeENotasRequired.ToString());
                }
                //localiza o item via método Find do repositório
                var itemExistente = _tarefaRepositorio.Find(id);
                if (itemExistente == null)
                {
                    //reproduz um Http Erro 404 - Não Encontrado
                    return NotFound(ErrorCode.RegistroNaoEncontrado.ToString());
                }
                //atualiza o item chamando o método do repositorio
                _tarefaRepositorio.Update(item);
            }
            catch (Exception)
            {
                //reproduz um Http Erro 400 - Requisição inválida
                return BadRequest(ErrorCode.NaoPodeAtualizarItem.ToString());
            }
            return NoContent();
     }

De acordo com a especificação HTTP, uma requisição PUT requer que o cliente envie a entidade inteira atualizada (para atualização parcial usamos HTTP PATCH).

Usamos o método Update definido no repositório, que está sendo injetado via DI no construtor, para atualizar o item da tarefa.

Vamos enviar uma requisição PUT para alterar os valores do item de tarefa com id igual a ‘T100’ usando o Postman:

  1. Defina o método HTTP para PUT;
  2. Defina o endereço da URL para :  192.168.1.18:5000/api/tarefa/T100;
  3. Selecione a opção Body;
  4. Selecione a opção raw;
  5. Define o tipo para JSON;
  6. No editor chave-valor, informe um item tarefa com a chave que deseja alterar e informe os novos valores conforme mostrado abaixo:
    {
       "ID": "T100",
       "Nome": "alterando um item",
       "Notas": "testando PUT",
       "Concluido" : false
    }
  7. Clique no botão Send.
<a href="https://imasters.com.br/?attachment_id=114445" rel="attachment wp-att-114445"><img class="aligncenter size-large wp-image-114445" src="https://static.imasters.com.br/wp-content/uploads/2017/04/aspcore_mobile18-620x411.png" alt="" width="620" height="411" /></a>

Para ver o resultado e confirmar se o item com id igual a ‘T100’ foi alterado, basta definir o método GET, digitar na URL:  http://192.168.1.18:5000/api/tarefa no Postman e clicar em Send:

4. Deletando itens de tarefa

A exclusão de item existente de tarefa é mapeado para um verbo HTTP DELETE.

        [HttpDelete("{id}")]
        public IActionResult Delete(string id)
        {
            try
            {
                //localiza o item via método Find do repositório
                var item = _tarefaRepositorio.Find(id);
                if (item == null)
                {
                    //reproduz um Http Erro 404 - Não Encontrado
                    return NotFound(ErrorCode.RegistroNaoEncontrado.ToString());
                }
                //deleta item chamando o método do repositorio
                _tarefaRepositorio.Delete(id);
            }
            catch (Exception)
            {
                //reproduz um Http Erro 400 - Requisição inválida
                return BadRequest(ErrorCode.NaoPodeDeletarItem.ToString());
            }
            return NoContent();
        }

O método Delete usa HTTP DELETE. A resposta obtida é 204 (No Content).

Usamos o método Delete definido no repositório, que está sendo injetado via DI no construtor, para deletar o item de tarefa.

Vamos enviar uma requisição DELETE para excluir um item de tarefa com id igual a ‘T100’ usando o Postman.

  1. Defina o método HTTP para DELETE;
  2. Defina o endereço da URL para :  192.168.1.18:5000/api/tarefa/T100;
  3. Clique no botão Send:

Dessa forma, o item de tarefa com id igual a ‘T100’ foi excluído. O Retorno 204 No Content não exibe o resultado.

Para verificar, defina o método GET, digite a URL: http://192.168.1.18:5000/api/tarefa no Postman e clique em Send:

Dessa forma, temos os métodos All(), Create(), Update(), Edit() e Delete() implementados em nosso controlador.

Conforme você desenvolve os serviços de back-end para o seu aplicativo, você vai querer criar um conjunto consistente de convenções ou políticas para lidar com responsabilidades transversais. Por exemplo, no serviço mostrado acima, solicitações de registros específicos que não foram encontrados receberam uma resposta NotFound, em vez de uma resposta BadRequest.

Da mesma forma, os comandos feitos para este serviço passados em tipos vinculados ao modelo sempre verificaram ModelState.IsValid e retornaram um BadRequest para tipos de modelos inválidos.

Após identificar uma política comum para suas APIs, normalmente é possível encapsulá-la usando um filtro.

Concluímos, assim, a primeira etapa, criando um serviço Web API que expõe métodos para acessar, incluir, excluir e atualizar itens de tarefa.

No próximo artigo, vamos criar a aplicação cliente, a aplicação Xamarin Forms, que vai consumir esse serviço.