ASP.NET MVC 4 - Criando uma Web API com suporte a operações CRUD


Baseado no artigo: http://www.asp.net/web-api/overview/creating-web-apis/creating-a-web-api-that-supports-crud-operations

Se você não conhece nada sobre o recurso Web API da ASP .NET MVC 4 sugiro que leia os seguintes artigos :

  1. ASP.NET MVC 4 - Apresentação
  2. ASP.NET MVC 4 - Usando Web API
  3. ASP.NET MVC 4 - Usando Web API - JavaScript e jQuery - II

Neste artigo eu vou mostrar como dar apoio a operações CRUD (Create, Reate, Update e Delete) em um serviço de HTTP usando a ASP.NET Web API.

A palavra CRUD é um acrônimo que significa "Create,Read, Udpate e Delete", que são as quatro operações básicas de um banco de dados. Muitos serviços HTTP também modelam operações CRUD através de REST ou REST-like APIs.

Neste artigo , você vai construir uma Web API muito simples para gerenciar uma lista de produtos. Cada produto conterá um nome, preço e categoria (como "papelaria" ou "informática"), além de uma identificação do produto.

A nossa API produtos irá expor os seguintes métodos:

Ação Método HTTP URI relativa
Obter uma lista de produtos  GET   /api/produtos
Obter um produto pelo seu ID  GET   /api/produtos/id
Obter os produtos pela sua categoria  GET   /api/produtos/?categoria=categoria
Criar um novo produto  POST   /api/produtos
Atualizar um produto  PUT   /api/produtos/id
Deletar um produto  DELETE   /api/produtos/id

Observe que algumas das URIs incluem a identificação do produto no caminho. Por exemplo, para obter o produto cuja identificação é de 28, o cliente envia uma solicitação GET para http://hostname/api/produtos/28.

Os quatro principais métodos HTTP (GET, PUT, POST e DELETE) podem ser mapeados para operações CRUD como segue:

  1. GET retorna a representação do recurso em uma URI especificada. GET não deve ter efeitos colaterais sobre o servidor.
  2. PUT Atualiza um recurso em uma URI especificada. PUT também pode ser usado para criar um novo recurso em uma URI especificada, se o servidor permitir que os clientes especifiquem novo URIs.
  3. POST cria um novo recurso. O servidor atribui a URI para o novo objeto e retorna esta URI como parte da mensagem de resposta.
  4. DELETE exclui um recurso em uma URI especificada.

Eu vou utilizar o Visual Web Developer 2010 Express Edition ou Visual Studio 2010.(Você tem que instalar o recurso ASP .NET MVC4)

Criando uma nova Web API

Abra então o Visual Web Developer 2010 Express Edition e no menu File clique em New Project;

A seguir selecione a linguagem Visual Basic e clique em Web a selecione o template ASP .NET MVC 4 Web Application informando o nome ProdutosRepositorio e clicando no botão OK;

Na janela de diálogo New ASP.NET MVC 4 Project selecione o template Web API , o view Engine Razor , e clique no botão OK;

Será criado um projeto com uma estrutura já pronta e contendo alguns recursos definidos.

Definindo o Model

Vamos definir um Model em nosso projeto.

Um model ou Modelo é um objeto que representa os dados do nosso aplicativo. A ASP.NET Web API pode serializar automaticamente o seu modelo para JSON, XML, ou outro formato, e depois gravar os dados serializados no corpo da mensagem de resposta HTTP. Enquanto um cliente pode ler o formato de serialização, e pode também desserializar o objeto. Além disso, muitos clientes podem analisar o XML ou o JSON e também podem indicar qual o formato desejam tratar, definindo o cabeçalho Accept na mensagem de solicitação HTTP.

Clique com o botão direito sobre a pasta Models e selecione a opção Add e a seguir Class;

A seguir informe o nome da classe como Produto.vb  e clique no botão Add e digite o código abaixo neste arquivo:

Public Class Produto

   Public Property Id As Integer
   Public
Property Nome As String
   Public
Property Categoria As String
   Public
Property Preco As Decimal

End Class

Incluindo um repositório

Precisamos armazenar uma coleção de produtos. É uma boa ideia separar a coleção da nossa implementação do serviço. Dessa forma, podemos alterar o armazenamento de backup sem reescrever a classe de serviço. Este tipo de projeto é chamado padrão de repositório. Vamos iniciar definindo uma interface genérica para o repositório.

Clique com o botão direito sobre a pasta Models e selecione a opção Add e a seguir New Item;

Na janela Add New Item selecione o template Interface e informe o nome IProdutoRepositorio.vb e clique no botão Add;

 A  seguir digite o código abaixo para implementar a interface :

Public Interface IProdutoRepositorio

   Function GetTodos() As IEnumerable(Of Produto)
   Function GetPorID(id As Integer) As Produto
   Function Adicionar(item As Produto) As Produto
   Sub Remover(id As Integer)
   Function Atualizar(item As Produto) As Boolean

End Interface

O próximo passo será incluir uma classe chamada ProdutoRepositorio que implementar a interface que acabamos de criar.

Clique com o botão direito sobre a pasta Models e selecione a opção Add e a seguir Class;

A seguir informe o nome da classe como Produto.vb  e clique no botão Add e digite o código abaixo neste arquivo:

Public Class ProdutoRepositorio
    Implements IProdutoRepositorio
    Private produtos As New List(Of Produto)()
    Private _proximoId As Integer = 1
Public Sub New()       
Adicionar(New Produto() With { _
.Id = 1, .Nome = "Caneta", .Categoria = "Papelaria", .Preco = 1.39D _
})
Adicionar(New Produto() With { _
.Id = 2, .Nome = "Table Ipad", .Categoria = "Informática", .Preco = 993.75D _
})
Adicionar(New Produto() With { _
.Id = 3, .Nome = "NoteBook CCE", .Categoria = "Informática", .Preco = 1016.99D _
})
End Sub
    Public Function Adicionar(item As Produto) As Produto Implements IProdutoRepositorio.Adicionar
        item.Id = _proximoId + 1
        produtos.Add(item)
        Return item
    End Function
    Public Function Atualizar(item As Produto) As Boolean Implements IProdutoRepositorio.Atualizar
        Dim index As Integer = produtos.FindIndex(Function(p) p.Id = item.Id)
        If index = -1 Then
            Return False
        End If
        produtos.RemoveAt(index)
        produtos.Add(item)
        Return True
    End Function
    Public Function GetPorID(id As Integer) As Produto Implements IProdutoRepositorio.GetPorID
        Return produtos.Find(Function(p) p.Id = id)
    End Function
    Public Function GetTodos() As System.Collections.Generic.IEnumerable(Of Produto) Implements IProdutoRepositorio.GetTodos
        Return produtos
    End Function
    Public Sub Remover(id As Integer) Implements IProdutoRepositorio.Remover
        produtos.RemoveAll(Function(p) p.Id = id)
    End Sub
End Class

 

O repositório mantém a lista na memória local. Por ser mais simples fizemos assim neste exemplo mas em um aplicativo real, você iria armazenar os dados externamente, seja em um banco de dados ou no armazenamento em nuvem. Com o padrão repositório padrão, será mais fácil alterar a implementação mais tarde.

Definindo um novo Controller

Como já definimos  o nosso modelo vamos agora definir um novo controlador para nossa aplicação.

Um controlador é um objeto que lida com requisições HTTP. O assistente para novo projeto cria dois controladores para você quando criamos o projeto. Para vê-los, expanda a pasta Controllers no Solution Explorer. São eles:

  1.  HomeController : é um controlador ASP.NET MVC  tradicional. É responsável por servir páginas HTML para o site, e não é diretamente relacionada à Web API;
     
  2.  ValuesController : é um exemplo de controlador WebAPI;

 

Para criar um novo controlador clique com o botão direito sobre a pasta Controllers e selecione Add e a seguir Controller;

Na janela Add Controller informe o nome ProdutosController e selecione o template Empty API controller e clique no botão Add;

Obs: Cuidado para não Selecionar Empty MVC Controller

Um arquivo ProdutosController.vb será criado na pasta Controllers. Digite o código abaixo neste arquivo:

Imports System.Net
Imports System.Web.Http
Imports System.Net.Http

Public Class ProdutosController
      Inherits ApiController

      Shared ReadOnly repository As IProdutoRepositorio = New ProdutoRepositorio()

End Class

O código define um campo que trata uma instância de IProdutoRepositorio.

Vamos agora implementar os métodos no controlador para os serviços que desejamos expor.

1- Método que obtém uma lista de produtos ( método HTTP GET , URI : /api/produtos )

 Public Function GetAllProducts() As IEnumerable(Of Produto)
     Return repositorio.GetTodos()
 End Function

O nome do método começa com "Get", então, por convenção, ele mapeia para solicitações GET. Além disso, o método não tem parâmetros, de modo que mapeia para um URI sem "id" no caminho.

2- Método que obtém  um produto pelo seu Id ( método HTTP GET , URI : /api/produtos/id )

 Public Function GetProduto(id As Integer) As Produto

        Dim item As Produto = repositorio.GetPorID(id)
        If item Is Nothing Then
            Throw New HttpResponseException(New HttpResponseMessage(HttpStatusCode.NotFound))
        End If
        Return item

End Function

Este método também nome começa com "Get", mas o método tem um parâmetro chamado ID. Este parâmetro é mapeado para o segmento "id" no caminho URI. A ASP.NET Web API converte automaticamente o ID para o tipo de dados correto (int) para o parâmetro.

O método getProduto lança uma exceção do tipo HttpResponseException se o ID não for válido. Essa exceção será traduzida pelo Framework em erro 404 (não encontrado).

3- Método que obtém  os produtos pela categoria informada ( método HTTP GET , URI : /api/produtos?categoria=categoria )

 Public Function GetProdutosPorCategoria(categoria As String) As IEnumerable(Of Produto)
      Return repositorio.GetTodos().Where(Function(p) String.Equals(p.Categoria, categoria, StringComparison.OrdinalIgnoreCase))
 End Function

4- Método que cria um novo Produto

Para criar um novo produto, o cliente envia uma requisição HTTP POST, com o novo produto no corpo da mensagem da requisição.

    Public Function PostProduto(item As Produto) As Produto
        item = repositorio.Adicionar(item)
        Return item
    End Function

Para lidar com solicitações POST, vamos definir um método cujo nome começa com "Post". O método utiliza um parâmetro de tipo de produto. Por padrão, os parâmetros com tipos complexos são desserializados a partir do corpo da requisição. Portanto, esperamos que o cliente envie-nos uma representação serializada de um objeto produto, usando XML ou JSON para a serialização.

Esta implementação vai funcionar, mas ela não esta completa pois falta o seguinte:

Então vamos usar os recursos da ASP.NET Web API que torna fácil  manipular a mensagem de resposta HTTP. A seguir temos uma implementação mais completa para o método acima:

 Public Function PostProduto(item As Produto) As HttpResponseMessage
        item = repositorio.Adicionar(item)
        Dim response = Request.CreateResponse(Of Produto)(HttpStatusCode.Created, item)
        Dim uri As String = Url.Link("DefaultApi", New With { _
            .id = item.Id _
        })
        response.Headers.Location = New Uri(uri)
        Return response
    End Function

Observe que o tipo de retorno do método agora é HttpResponseMessage. Ao retornar um HttpResponseMessage em vez de um produto, nós podemos controlar os detalhes da mensagem de resposta HTTP, incluindo o código de status e o cabeçalho Location.

O método CreateResponse cria um HttpResponseMessage e grava automaticamente uma representação serializada do objeto produto no corpo da mensagem de resposta.

5- Método que atualiza um produto

 Public Sub PutProduto(id As Integer, produto As Produto)
        produto.Id = id
        If Not repositorio.Atualizar(produto) Then
            Throw New HttpResponseException(New HttpResponseMessage(HttpStatusCode.NotFound))
        End If
    End Sub

O nome do método começa com "Put", de forma que a Web API faz a correspondência para uma requisição PUT. O método tem dois parâmetros, a identificação do produto e o produto atualizado. O parâmetro id é retirado do caminho URI, e o parâmetro produto é deserializado a partir do corpo da requisição. Por padrão, o framework ASP.NET Web API toma os tipos de parâmetros simples da rota e os tipos complexos do corpo da solicitação.

6- Método que deleta um produto

  Public Function DeletaProduto(id As Integer) As HttpResponseMessage
        repositorio.Remover(id)
        Return New HttpResponseMessage(HttpStatusCode.NoContent)
    End Function

 

De acordo com a especificação HTTP, o método DELETE deve ser idempotente, o que significa que  as diversas requisições DELETE para a mesma URI tem que ter o mesmo efeito que uma simples requisição DELETE. Portanto, o método não deve retornar um código de erro se o produto já foi deletado.

Idempotente quer dizer que múltiplas requisições ao mesmo recurso usando o método devem ter o mesmo resultado que teria uma requisição apenas. A título de curiosidade, idempotente é a propriedade de um número que, multiplicado por ele mesmo, tem ele mesmo como resultado (n x n = n), em termos de números reais, apenas 0 e 1 têm essa propriedade. Em termos de métodos de requisição HTTP, os métodos GET, HEAD, PUT e DELETE são os que possuem a propriedade de ser idempotentes. (http://www.dicionarioinformal.com.br/idempotente/)

Se uma solicitação DELETE for bem-sucedida, ela pode retornar o status 200(OK), com uma entidade de corpo que descreve o estado, o status 202(Aceito), se a exclusão ainda está pendente ou o status 204(No Content) sem corpo da entidade. Neste exemplo, o método retorna o status 204.

Dessa forma o código completo do nosso controlador ficou assim:

Imports System.Net
Imports System.Web.Http
Imports System.Net.Http
Public Class ProdutosController
    Inherits ApiController
    Shared ReadOnly repositorio As IProdutoRepositorio = New ProdutoRepositorio()
    Public Function GetAllProducts() As IEnumerable(Of Produto)
        Return repositorio.GetTodos()
    End Function
    Public Function GetProduto(id As Integer) As Produto
        Dim item As Produto = repositorio.GetPorID(id)
        If item Is Nothing Then
            Throw New HttpResponseException(New HttpResponseMessage(HttpStatusCode.NotFound))
        End If
        Return item
    End Function
    Public Function GetProdutosPorCategoria(categoria As String) As IEnumerable(Of Produto)
        Return repositorio.GetTodos().Where(Function(p) String.Equals(p.Categoria, categoria, StringComparison.OrdinalIgnoreCase))
    End Function
    Public Function PostProduto(item As Produto) As HttpResponseMessage
        item = repositorio.Adicionar(item)
        Dim response = Request.CreateResponse(Of Produto)(HttpStatusCode.Created, item)
        Dim uri As String = Url.Link("DefaultApi", New With { _
            .id = item.Id _
        })
        response.Headers.Location = New Uri(uri)
        Return response
    End Function
    Public Sub PutProduto(id As Integer, produto As Produto)
        produto.Id = id
        If Not repositorio.Atualizar(produto) Then
            Throw New HttpResponseException(New HttpResponseMessage(HttpStatusCode.NotFound))
        End If
    End Sub
    Public Function DeletaProduto(id As Integer) As HttpResponseMessage
        repositorio.Remover(id)
        Return New HttpResponseMessage(HttpStatusCode.NoContent)
    End Function
End Class

 

Assim demos mostramos a criação de uma nova WEB API que expõe os serviços para realizar as operações CRUD.

Na continuação deste artigo vamos implementar a interface (a view) para que possamos usar os recursos implementados de maneira amigável.

Pegue o projeto completo aqui: ProdutosRepositorio.zip (sem a pasta packages: Você deverá criar o projeto e substituir os arquivos principais)

Col 1:3 Graças damos a Deus, Pai de nosso Senhor Jesus Cristo, orando sempre por vós,
Col 1:4
desde que ouvimos falar da vossa fé em Cristo Jesus, e do amor que tendes a todos os santos,
Col 1:5
por causa da esperança que vos está reservada nos céus, da qual antes ouvistes pela palavra da verdade do evangelho,
Col 1:6
que já chegou a vós, como também está em todo o mundo, frutificando e crescendo, assim como entre vós desde o dia em que ouvistes e conhecestes a graça de Deus em verdade,
Col 1:7
segundo aprendestes de Epafras, nosso amado conservo, que por nós é fiel ministro de Cristo.

Col 1:8
O qual também nos declarou o vosso amor no Espírito.

Referências:


José Carlos Macoratti