Criar serviços de back-end para aplicativos móveis nativos com o ASP.NET CoreCreate backend services for native mobile apps with ASP.NET Core

Por Steve SmithBy Steve Smith

Os aplicativos móveis podem se comunicar com os serviços de back-end do ASP.NET Core.Mobile apps can communicate with ASP.NET Core backend services. Para obter instruções sobre como conectar os serviços Web locais dos simuladores do iOS e dos emuladores do Android, confira Conectar-se aos Serviços Web Locais em simuladores do iOS e emuladores do Android.For instructions on connecting local web services from iOS simulators and Android emulators, see Connect to Local Web Services from iOS Simulators and Android Emulators.

Exibir ou baixar o código de exemplo dos serviços de back-endView or download sample backend services code

Exemplo do aplicativo móvel nativoThe Sample Native Mobile App

Este tutorial demonstra como criar serviços de back-end usando o ASP.NET Core MVC para dar suporte a aplicativos móveis nativos.This tutorial demonstrates how to create backend services using ASP.NET Core MVC to support native mobile apps. Ele usa o aplicativo Xamarin Forms ToDoRest como seu cliente nativo, que inclui clientes nativos separados para dispositivos Android, iOS, Universal do Windows e Windows Phone.It uses the Xamarin Forms ToDoRest app as its native client, which includes separate native clients for Android, iOS, Windows Universal, and Window Phone devices. Siga o tutorial com links para criar o aplicativo nativo (e instale as ferramentas do Xamarin gratuitas necessárias), além de baixar a solução de exemplo do Xamarin.You can follow the linked tutorial to create the native app (and install the necessary free Xamarin tools), as well as download the Xamarin sample solution. A amostra do Xamarin inclui um projeto de serviços do ASP.NET Web API 2, que substitui o aplicativo ASP.NET Core deste artigo (sem nenhuma alteração exigida pelo cliente).The Xamarin sample includes an ASP.NET Web API 2 services project, which this article's ASP.NET Core app replaces (with no changes required by the client).

Aplicativo ToDoRest em execução em um smartphone Android

RecursosFeatures

O aplicativo ToDoRest é compatível com listagem, adição, exclusão e atualização de itens de tarefas pendentes.The ToDoRest app supports listing, adding, deleting, and updating To-Do items. Cada item tem uma ID, um Nome, Observações e uma propriedade que indica se ele já foi Concluído.Each item has an ID, a Name, Notes, and a property indicating whether it's been Done yet.

A exibição principal dos itens, conforme mostrado acima, lista o nome de cada item e indica se ele foi concluído com uma marca de seleção.The main view of the items, as shown above, lists each item's name and indicates if it's done with a checkmark.

Tocar no ícone + abre uma caixa de diálogo de adição de itens:Tapping the + icon opens an add item dialog:

Caixa de diálogo de adição de itens

Tocar em um item na tela da lista principal abre uma caixa de diálogo de edição, na qual o Nome do item, Observações e configurações de Concluído podem ser modificados, ou o item pode ser excluído:Tapping an item on the main list screen opens up an edit dialog where the item's Name, Notes, and Done settings can be modified, or the item can be deleted:

Caixa de diálogo de edição de itens

Esta amostra é configurada por padrão para usar os serviços de back-end hospedados em developer.xamarin.com, que permitem operações somente leitura.This sample is configured by default to use backend services hosted at developer.xamarin.com, which allow read-only operations. Para testá-la por conta própria no aplicativo ASP.NET Core criado na próxima seção em execução no computador, você precisará atualizar a constante RestUrl do aplicativo.To test it out yourself against the ASP.NET Core app created in the next section running on your computer, you'll need to update the app's RestUrl constant. Navegue para o projeto ToDoREST e abra o arquivo Constants.cs.Navigate to the ToDoREST project and open the Constants.cs file. Substitua o RestUrl por uma URL que inclui o endereço IP do computador (não localhost ou 127.0.0.1, pois esse endereço é usado no emulador do dispositivo, não no computador).Replace the RestUrl with a URL that includes your machine's IP address (not localhost or 127.0.0.1, since this address is used from the device emulator, not from your machine). Inclua o número da porta também (5000).Include the port number as well (5000). Para testar se os serviços funcionam com um dispositivo, verifique se você não tem um firewall ativo bloqueando o acesso a essa porta.In order to test that your services work with a device, ensure you don't have an active firewall blocking access to this port.

// URL of REST service (Xamarin ReadOnly Service)
//public static string RestUrl = "http://developer.xamarin.com:8081/api/todoitems{0}";

// use your machine's IP address
public static string RestUrl = "http://192.168.1.207:5000/api/todoitems/{0}";

Criando o projeto ASP.NET CoreCreating the ASP.NET Core Project

Crie um novo aplicativo Web do ASP.NET Core no Visual Studio.Create a new ASP.NET Core Web Application in Visual Studio. Escolha o modelo de Web API sem autenticação.Choose the Web API template and No Authentication. Nomeie o projeto como ToDoApi.Name the project ToDoApi.

Caixa de diálogo nova do aplicativo Web ASP.NET com modelo de projeto de Web API selecionado

O aplicativo deve responder a todas as solicitações feitas através da porta 5000.The application should respond to all requests made to port 5000. Atualize o Program.cs para incluir .UseUrls("http://*:5000") para ficar assim:Update Program.cs to include .UseUrls("http://*:5000") to achieve this:

var host = new WebHostBuilder()
    .UseKestrel()
    .UseUrls("http://*:5000")
    .UseContentRoot(Directory.GetCurrentDirectory())
    .UseIISIntegration()
    .UseStartup<Startup>()
    .Build();

Observação

Execute o aplicativo diretamente, em vez de por trás do IIS Express, que ignora solicitações não local por padrão.Make sure you run the application directly, rather than behind IIS Express, which ignores non-local requests by default. Execute dotnet run em um prompt de comando ou escolha o perfil de nome do aplicativo no menu suspenso Destino de Depuração na barra de ferramentas do Visual Studio.Run dotnet run from a command prompt, or choose the application name profile from the Debug Target dropdown in the Visual Studio toolbar.

Adicione uma classe de modelo para representar itens pendentes.Add a model class to represent To-Do items. Marque os campos obrigatórios usando o atributo [Required]:Mark required fields using the [Required] attribute:

using System.ComponentModel.DataAnnotations;

namespace ToDoApi.Models
{
    public class ToDoItem
    {
        [Required]
        public string ID { get; set; }

        [Required]
        public string Name { get; set; }

        [Required]
        public string Notes { get; set; }

        public bool Done { get; set; }
    }
}

Os métodos da API exigem alguma maneira de trabalhar com dados.The API methods require some way to work with data. Use a mesma interface IToDoRepository nos usos de exemplo originais do Xamarin:Use the same IToDoRepository interface the original Xamarin sample uses:

using System.Collections.Generic;
using ToDoApi.Models;

namespace ToDoApi.Interfaces
{
    public interface IToDoRepository
    {
        bool DoesItemExist(string id);
        IEnumerable<ToDoItem> All { get; }
        ToDoItem Find(string id);
        void Insert(ToDoItem item);
        void Update(ToDoItem item);
        void Delete(string id);
    }
}

Para esta amostra, a implementação apenas usa uma coleção particular de itens:For this sample, the implementation just uses a private collection of items:

using System.Collections.Generic;
using System.Linq;
using ToDoApi.Interfaces;
using ToDoApi.Models;

namespace ToDoApi.Services
{
    public class ToDoRepository : IToDoRepository
    {
        private List<ToDoItem> _toDoList;

        public ToDoRepository()
        {
            InitializeData();
        }

        public IEnumerable<ToDoItem> All
        {
            get { return _toDoList; }
        }

        public bool DoesItemExist(string id)
        {
            return _toDoList.Any(item => item.ID == id);
        }

        public ToDoItem Find(string id)
        {
            return _toDoList.FirstOrDefault(item => item.ID == id);
        }

        public void Insert(ToDoItem item)
        {
            _toDoList.Add(item);
        }

        public void Update(ToDoItem item)
        {
            var todoItem = this.Find(item.ID);
            var index = _toDoList.IndexOf(todoItem);
            _toDoList.RemoveAt(index);
            _toDoList.Insert(index, item);
        }

        public void Delete(string id)
        {
            _toDoList.Remove(this.Find(id));
        }

        private void InitializeData()
        {
            _toDoList = new List<ToDoItem>();

            var todoItem1 = new ToDoItem
            {
                ID = "6bb8a868-dba1-4f1a-93b7-24ebce87e243",
                Name = "Learn app development",
                Notes = "Attend Xamarin University",
                Done = true
            };

            var todoItem2 = new ToDoItem
            {
                ID = "b94afb54-a1cb-4313-8af3-b7511551b33b",
                Name = "Develop apps",
                Notes = "Use Xamarin Studio/Visual Studio",
                Done = false
            };

            var todoItem3 = new ToDoItem
            {
                ID = "ecfa6f80-3671-4911-aabe-63cc442c1ecf",
                Name = "Publish apps",
                Notes = "All app stores",
                Done = false,
            };

            _toDoList.Add(todoItem1);
            _toDoList.Add(todoItem2);
            _toDoList.Add(todoItem3);
        }
    }
}

Configure a implementação em Startup.cs:Configure the implementation in Startup.cs:

public void ConfigureServices(IServiceCollection services)
{
    // Add framework services.
    services.AddMvc();

    services.AddSingleton<IToDoRepository,ToDoRepository>();
}

Neste ponto, você está pronto para criar o ToDoItemsController.At this point, you're ready to create the ToDoItemsController.

Criando o controladorCreating the Controller

Adicione um novo controlador ao projeto, ToDoItemsController.Add a new controller to the project, ToDoItemsController. Ele deve herdar de Microsoft.AspNetCore.Mvc.Controller.It should inherit from Microsoft.AspNetCore.Mvc.Controller. Adicione um atributo Route para indicar que o controlador manipulará as solicitações feitas para caminhos que começam com api/todoitems.Add a Route attribute to indicate that the controller will handle requests made to paths starting with api/todoitems. O token [controller] na rota é substituído pelo nome do controlador (com a omissão do sufixo Controller) e é especialmente útil para rotas globais.The [controller] token in the route is replaced by the name of the controller (omitting the Controller suffix), and is especially helpful for global routes. Saiba mais sobre o roteamento.Learn more about routing.

O controlador requer um IToDoRepository para a função; solicite uma instância desse tipo usando o construtor do controlador.The controller requires an IToDoRepository to function; request an instance of this type through the controller's constructor. No tempo de execução, esta instância será fornecida com suporte do framework parainjeção de dependência.At runtime, this instance will be provided using the framework's support for dependency injection.

using System;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using ToDoApi.Interfaces;
using ToDoApi.Models;

namespace ToDoApi.Controllers
{
    [Route("api/[controller]")]
    public class ToDoItemsController : Controller
    {
        private readonly IToDoRepository _toDoRepository;

        public ToDoItemsController(IToDoRepository toDoRepository)
        {
            _toDoRepository = toDoRepository;
        }

Essa API é compatível com quatro verbos HTTP diferentes para executar operações CRUD (Criar, Ler, Atualizar, Excluir) na fonte de dados.This API supports four different HTTP verbs to perform CRUD (Create, Read, Update, Delete) operations on the data source. A mais simples delas é a operação Read, que corresponde a uma solicitação HTTP GET.The simplest of these is the Read operation, which corresponds to an HTTP GET request.

Lendo itensReading Items

A solicitação de uma lista de itens é feita com uma solicitação GET ao método List.Requesting a list of items is done with a GET request to the List method. O atributo [HttpGet] no método List indica que esta ação só deve lidar com as solicitações GET.The [HttpGet] attribute on the List method indicates that this action should only handle GET requests. A rota para esta ação é a rota especificada no controlador.The route for this action is the route specified on the controller. Você não precisa necessariamente usar o nome da ação como parte da rota.You don't necessarily need to use the action name as part of the route. Você precisa garantir que cada ação tem uma rota exclusiva e não ambígua.You just need to ensure each action has a unique and unambiguous route. Os atributos de roteamento podem ser aplicados nos níveis de método e controlador para criar rotas específicas.Routing attributes can be applied at both the controller and method levels to build up specific routes.

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

O método List retorna um código de resposta OK 200 e todos os itens de tarefas, serializados como JSON.The List method returns a 200 OK response code and all of the ToDo items, serialized as JSON.

Você pode testar o novo método de API usando uma variedade de ferramentas, como Postman. Veja abaixo:You can test your new API method using a variety of tools, such as Postman, shown here:

Console Postman mostrando uma solicitação GET para todoitems e corpo da resposta mostrando JSON para três itens retornados

Criando itensCreating Items

Por convenção, a criação de novos itens de dados é mapeada para o verbo HTTP POST.By convention, creating new data items is mapped to the HTTP POST verb. O método Create tem um atributo [HttpPost] aplicado a ele e aceita uma instância ToDoItem.The Create method has an [HttpPost] attribute applied to it, and accepts a ToDoItem instance. Como o argumento item será enviado no corpo de POST, este parâmetro será decorado com o atributo [FromBody].Since the item argument will be passed in the body of the POST, this parameter is decorated with the [FromBody] attribute.

Dentro do método, o item é verificado quanto à validade e existência anterior no armazenamento de dados e, se nenhum problema ocorrer, ele será adicionado usando o repositório.Inside the method, the item is checked for validity and prior existence in the data store, and if no issues occur, it's added using the repository. A verificação de ModelState.IsValid executa a validação do modelo e deve ser feita em todos os métodos de API que aceitam a entrada do usuário.Checking ModelState.IsValid performs model validation, and should be done in every API method that accepts user input.

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

A amostra usa uma enumeração que contém códigos de erro que são passados para o cliente móvel:The sample uses an enum containing error codes that are passed to the mobile client:

public enum ErrorCode
{
    TodoItemNameAndNotesRequired,
    TodoItemIDInUse,
    RecordNotFound,
    CouldNotCreateItem,
    CouldNotUpdateItem,
    CouldNotDeleteItem
}

Teste a adição de novos itens usando Postman escolhendo o verbo POST fornecendo o novo objeto no formato JSON no corpo da solicitação.Test adding new items using Postman by choosing the POST verb providing the new object in JSON format in the Body of the request. Você também deve adicionar um cabeçalho de solicitação que especifica um Content-Type de application/json.You should also add a request header specifying a Content-Type of application/json.

Console Postman mostrando um POST e resposta

O método retorna o item recém-criado na resposta.The method returns the newly created item in the response.

Atualizando itensUpdating Items

A modificação de registros é feita com as solicitações HTTP PUT.Modifying records is done using HTTP PUT requests. Além desta mudança, o método Edit é quase idêntico ao Create.Other than this change, the Edit method is almost identical to Create. Observe que, se o registro não for encontrado, a ação Edit retornará uma resposta NotFound (404).Note that if the record isn't found, the Edit action will return a NotFound (404) response.

[HttpPut]
public IActionResult Edit([FromBody] ToDoItem item)
{
    try
    {
        if (item == null || !ModelState.IsValid)
        {
            return BadRequest(ErrorCode.TodoItemNameAndNotesRequired.ToString());
        }
        var existingItem = _toDoRepository.Find(item.ID);
        if (existingItem == null)
        {
            return NotFound(ErrorCode.RecordNotFound.ToString());
        }
        _toDoRepository.Update(item);
    }
    catch (Exception)
    {
        return BadRequest(ErrorCode.CouldNotUpdateItem.ToString());
    }
    return NoContent();
}

Para testar com Postman, altere o verbo para PUT.To test with Postman, change the verb to PUT. Especifique os dados do objeto atualizado no corpo da solicitação.Specify the updated object data in the Body of the request.

Console Postman mostrando um PUT e resposta

Este método retornará uma resposta NoContent (204) quando obtiver êxito, para manter a consistência com a API já existente.This method returns a NoContent (204) response when successful, for consistency with the pre-existing API.

Excluindo itensDeleting Items

A exclusão de registros é feita por meio da criação de solicitações de exclusão para o serviço e por meio do envio do ID do item a ser excluído.Deleting records is accomplished by making DELETE requests to the service, and passing the ID of the item to be deleted. Assim como as atualizações, as solicitações de itens que não existem receberão respostas NotFound.As with updates, requests for items that don't exist will receive NotFound responses. Caso contrário, uma solicitação bem-sucedida receberá uma resposta NoContent (204).Otherwise, a successful request will get a NoContent (204) response.

[HttpDelete("{id}")]
public IActionResult Delete(string id)
{
    try
    {
        var item = _toDoRepository.Find(id);
        if (item == null)
        {
            return NotFound(ErrorCode.RecordNotFound.ToString());
        }
        _toDoRepository.Delete(id);
    }
    catch (Exception)
    {
        return BadRequest(ErrorCode.CouldNotDeleteItem.ToString());
    }
    return NoContent();
}

Observe que, ao testar a funcionalidade de exclusão, nada é necessário no Corpo da solicitação.Note that when testing the delete functionality, nothing is required in the Body of the request.

Console do Postman mostrando um DELETE e uma resposta

Convenções de Web API comunsCommon Web API Conventions

À medida que você desenvolve serviços de back-end para seu aplicativo, desejará criar um conjunto consistente de convenções ou políticas para lidar com preocupações paralelas.As you develop the backend services for your app, you will want to come up with a consistent set of conventions or policies for handling cross-cutting concerns. Por exemplo, no serviço mostrado acima, as solicitações de registros específicos que não foram encontrados receberam uma resposta NotFound, em vez de uma resposta BadRequest.For example, in the service shown above, requests for specific records that weren't found received a NotFound response, rather than a BadRequest response. Da mesma forma, os comandos feitos para esse serviço que passaram tipos associados a um modelo sempre verificaram ModelState.IsValid e retornaram um BadRequest para tipos de modelo inválidos.Similarly, commands made to this service that passed in model bound types always checked ModelState.IsValid and returned a BadRequest for invalid model types.

Depois de identificar uma diretiva comum para suas APIs, você geralmente pode encapsulá-la em um filtro.Once you've identified a common policy for your APIs, you can usually encapsulate it in a filter. Saiba mais sobre como encapsular políticas comuns da API em aplicativos ASP.NET Core MVC.Learn more about how to encapsulate common API policies in ASP.NET Core MVC applications.

Recursos adicionaisAdditional resources