Compartilhar via


Capítulo 5: Criando e publicando uma API Web no Azure

Tendo estabelecido que os dados para o aplicativo dos técnicos devem ser obtidos de sistemas existentes por meio de uma API Web, Maria e Kiana trabalham juntas para determinar exatamente quais informações são necessárias e em qual formato. Kiana criará um aplicativo Web que expõe a API Web apropriada e faz com que ela seja hospedada no Azure. O aplicativo pode se conectar ao Azure de qualquer lugar em que haja uma conexão sem fio.

Definindo as operações da API Web: gerenciamento de inventário de campo

A tela Procurar da seção Gerenciamento de inventário de campo do aplicativo exibe uma lista de peças para caldeiras e sistemas de ar-condicionado (chamadas simplesmente de peças da caldeira). A tela Detalhes permite que o técnico visualize mais informações sobre uma peça selecionada.

No banco de dados de inventário existente (chamado de InventoryDB), as informações sobre as peças são mantidas em uma única tabela chamada BoilerParts. Kiana determina que a API Web deve oferecer suporte às seguintes solicitações:

  • Obter todas as peças da caldeira.
  • Obter os detalhes de uma peça, fornecendo o ID da peça.

Definindo as operações da API Web: base de dados de conhecimento de campo

No sistema existente, o banco de dados da base de dados de conhecimento (denominado KnowledgeDB) contém três tabelas que registram e gerenciam as relações entre dicas, engenheiros e peças:

  • Dicas: que contém os detalhes de uma dica. Cada dica compreende um resumo de uma única linha identificando um problema específico (o assunto) e uma explicação mais detalhada que descreve como resolver o problema (o corpo). Cada dica também faz referência a uma peça e ao engenheiro que registrou a dica.
  • BoilerParts: que contém uma lista das peças referenciadas por dicas. Os detalhes das próprias peças são armazenados na tabela BoilerParts no banco de dados InventoryDB.
  • Engenheiros: que lista os técnicos que criaram cada dica.

Atualmente, a parte da base de dados de conhecimento do aplicativo contém apenas uma tela de espaço reservado Procurar. Maria deseja implementar a seguinte funcionalidade:

  • O técnico especifica um termo de pesquisa na tela Procurar para encontrar todas as dicas correspondentes. A correspondência pode estar no nome da peça a que a dica se refere, no texto do assunto ou no corpo da dica ou no nome de um técnico que é especialista em um determinado equipamento.

  • Quando todas as dicas correspondentes forem encontradas, o técnico pode selecionar uma dica para visualizar seus detalhes.

  • Um técnico também pode adicionar novas dicas à base de dados de conhecimento, bem como adicionar notas e comentários às dicas existentes.

    A base de dados de conhecimento é grande e está crescendo, por isso consultar várias tabelas e colunas pode envolver uma lógica complexa que requer um poder de computação significativo. Para reduzir a carga na API Web, Kiana decide usar a Pesquisa Cognitiva do Azure para fornecer a funcionalidade de pesquisa, conforme descrito anteriormente. Para oferecer suporte ao aplicativo, Kiana decide que as seguintes operações são necessárias na API Web:

  • Encontre os detalhes de uma dica de base de dados de conhecimento especificada da tabela Dicas.

  • Atualizar uma dica de base de dados de conhecimento existente na tabela Dicas.

  • Adicione uma nova dica da base de dados de conhecimento à tabela Dicas, o que também pode envolver a adição de linhas às tabelas BoilerParts e Engenheiros se a peça ou o engenheiro especificado não tiver dicas registradas em relação a eles no momento. A rotina que realmente executa a lógica por trás da adição de uma nova dica será implementada como um aplicativo lógico chamado do Power Apps.

Definindo as operações da API Web: agendamento de campo

O agendamento de compromissos técnicos requer não apenas consultar, adicionar e remover compromissos, mas também registrar informações sobre os clientes. O sistema de compromissos existente registra esses dados em três tabelas no banco de dados SchedulesDB:

  • Compromissos: que contém os detalhes de cada compromisso, inclusive data, hora, problema, notas e técnico atribuído à tarefa.
  • Clientes: que contém os detalhes de cada cliente, inclusive nome, endereço e detalhes de contato.
  • Engenheiros: que lista cada técnico que comparece aos compromissos.

Observação

O banco de dados contém, na verdade, uma quarta tabela chamada AppointmentsStatus. Esta tabela contém uma lista de valores válidos para o status de um compromisso e é simplesmente uma pesquisa usada por outras partes do sistema de compromissos existente.

Kiana decide que as seguintes operações seriam úteis para a parte de Agendamento de campo do aplicativo:

  • Encontrar todos os compromissos de um técnico específico.
  • Encontrar todos os compromissos do dia atual de um técnico específico.
  • Encontrar o próximo compromisso agendado de um técnico específico.
  • Atualizar os detalhes de um compromisso, como adicionar notas ou uma fotografia.
  • Encontrar detalhes sobre um cliente.

Criando a API Web: gerenciamento de inventário de campo

Os sistemas existentes armazenam dados usando o Banco de Dados SQL do Azure. Kiana decide criar a API Web usando o Entity Framework Core, porque essa abordagem pode gerar uma grande quantidade de código que consulta, insere e atualiza dados automaticamente. O modelo de API Web fornecido pela Microsoft também pode criar as descrições Swagger que descrevem cada operação na API. Essas descrições são úteis para testar as operações da API. Muitas ferramentas podem usar essas informações para integrar a API com outros serviços, como o Gerenciamento de API do Azure.

Kiana começou com a funcionalidade Inventário de campo porque esta é a parte mais simples. As operações de Inventário de campo na API Web consultam uma única tabela, BoilerParts, no banco de dados InventoryDB. Esta tabela contém as colunas mostradas na imagem a seguir.

A tabela BoilerParts mostrando as colunas Id, Name, CategoryId, Price, Overview, NumberInStock e ImageURL.

Kiana adotou a abordagem "primeiro o código" para criar a API Web e fez o seguinte:

  1. Definiu sua própria classe de modelo C# que espelhava a estrutura da tabela BoilerParts no banco de dados InventoryDB.

  2. Criou uma classe de contexto do Entity Framework que a API Web usa para se conectar ao banco de dados, a fim de realizar consultas.

  3. Configurou a classe de contexto para se conectar ao banco de dados InventoryDB no Azure.

  4. Usou as ferramentas de linha de comando do Entity Framework para gerar uma classe de controlador da API Web que implementa solicitações HTTP REST para cada uma das operações que podem ser realizadas na tabela BoilerParts.

  5. Usou a API Swagger para testar a API Web.

A imagem a seguir mostra a estrutura de alto nível da API Web.

Estrutura de alto nível da API Web de estoque.

Kiana usou o seguinte procedimento para criar a API Web usando ferramentas de linha de comando .NET 6.0 e o Visual Studio Code:

  1. Abra uma janela de terminal no Visual Studio Code.

    Nova janela de terminal no VS Code.

  2. Execute o seguinte comando para criar um novo projeto de API Web chamado FieldEngineerApi.

    dotnet new webapi -o FieldEngineerApi
    
  3. Abra a pasta FieldEngineerApi.

    Abra a pasta FieldEngineerApi.

  4. Remova o controlador de exemplo WeatherForecastController.cs e o arquivo de classe WeatherForecast.cs que foi criado pelo modelo de API Web.

    Exclua arquivos WeatherForecast.

  5. Na janela Terminal, adicione os seguintes pacotes e ferramentas do Entity Framework, junto com o suporte para o uso do SQL Server, ao projeto.

    dotnet add package Microsoft.EntityFrameworkCore.SqlServer
    
    dotnet add package Microsoft.VisualStudio.Web.CodeGeneration.Design
    
    dotnet add package Microsoft.EntityFrameworkCore.Design
    
    dotnet add package Microsoft.AspNetCore.Mvc.NewtonsoftJson
    
    dotnet tool install --global dotnet-ef
    
    dotnet tool install --global dotnet-aspnet-codegenerator
    
  6. Na pasta FieldEngineerApi, crie uma nova pasta chamada Modelos.

    Crie a pasta Modelos.

  7. Na pasta Modelos, crie um arquivo de código C# chamado BoilerPart.cs.

    Crie a classe BoilerPart.

  8. Neste arquivo, adicione as propriedades e os campos a seguir. Essas propriedades e esses campos refletem a estrutura da tabela BoilerParts no banco de dados InventoryDB.

    using System.Collections.Generic;
    using System.ComponentModel.DataAnnotations;
    using System.ComponentModel.DataAnnotations.Schema;
    
    namespace FieldEngineerApi.Models
    {
    
        public class BoilerPart
        {
            [Key]
            public long Id { get; set; }
    
            public string Name { get; set; }
    
            public string CategoryId { get; set; }
    
            [Column(TypeName = "money")]
            public decimal Price { get; set; }
    
            public string Overview { get; set; }
    
            public int NumberInStock { get; set; }
    
            public string ImageUrl { get; set; }
        }
    }
    
  9. Na pasta Modelos, crie outro arquivo de código C# chamado InventoryContext.cs. Adicione o código a seguir a esta classe. A classe fornece a conexão entre o controlador (a ser criado a seguir) e o banco de dados.

    using Microsoft.EntityFrameworkCore;
    
    namespace FieldEngineerApi.Models
    {
        public class InventoryContext : DbContext
        {
            public InventoryContext(DbContextOptions<InventoryContext> options)
                : base(options)
            {
    
            }
    
            public DbSet\<BoilerPart\> BoilerParts { get; set; }
        }
    }
    
  10. Edite o arquivo appsettings.Development.json do projeto e adicione uma seção ConnectionStrings com a seguinte cadeia de conexão InventoryDB. Substitua <server name> pelo nome do servidor de banco de dados SQL que você criou para conter o banco de dados InventoryDB.

    {
        "ConnectionStrings": {
            "InventoryDB": "Server=tcp*:<server name>*.database.windows.net,1433;Initial Catalog=InventoryDB;Persist Security Info=False;User ID=sqladmin;Password=Pa55w.rd;MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;"
        },
        "Logging": {
            "LogLevel": {
                "Default": "Information",
                "Microsoft": "Warning",
                "Microsoft.Hosting.Lifetime": "Information"
            }
        }
    }
    

    Importante

    Apenas para os fins deste guia, a cadeia de conexão contém a ID de usuário e a senha do banco de dados. Em um sistema de produção, nunca armazene esses itens em texto não criptografado em um arquivo de configuração.

  11. Edite o arquivo Startup.cs e adicione as diretivas usando a seguir à lista no início do arquivo.

    using FieldEngineerApi.Models;
    using Microsoft.EntityFrameworkCore;
    
  12. Na classe Inicialização, encontre o método ConfigureServices. Adicione a instrução a seguir a este método.

    public void ConfigureServices(IServiceCollection services)
    {
    
        services.AddDbContext<InventoryContext>(options =>
            options.UseSqlServer(Configuration.GetConnectionString("InventoryDB")));
    
        services.AddControllers();
        ...
    }
    
  13. Modifique o método Configurar e habilite a interface de usuário do Swagger mesmo quando o aplicativo estiver sendo executado no modo de produção, conforme mostrado (esta mudança envolve realocar as duas chamadas de método app.UseSwagger fora da instrução if).

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        app.UseSwagger();
        app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "FieldEngineerApi v1"));
    
        ...
    }
    

    Importante

    Essa mudança permite que o ponto de extremidade Swagger seja exposto para integração de gerenciamento de API. Depois que o gerenciamento de API for configurado, é necessário mover este código de volta para dentro da instrução if e reimplantar a API Web. Nunca deixe o ponto de extremidade Swagger aberto em um sistema de produção.

  14. Na janela Terminal, execute o comando a seguir para gerar o controlador BoilerParts da classe de modelo BoilerPart e da classe de contexto InventoryContext.

    dotnet aspnet-codegenerator controller ^
        -name BoilerPartsController -async -api ^
         -m BoilerPart -dc InventoryContext -outDir Controllers
    

    O controlador BoilerParts deve ser criado na pasta Controladores.

    [!NOTE] O caractere terminador de linha, ^, só é reconhecido pelo Windows. Se você estiver executando o Visual Studio Code em um sistema Linux, use o caractere \.

  15. Abra o arquivo BoilerParts.cs na pasta Controladores e reveja seu conteúdo. A classe BoilerPartsController expõe os seguintes métodos REST:

    • GetBoilerParts(): que retorna uma lista de todos os objetos BoilerPart do banco de dados.
    • GetBoilerPart(long id): que recupera os detalhes da peça da caldeira especificada.
    • PutBoilerPart(long id, BoilerPart boilerPart): que atualiza uma peça da caldeira no banco de dados com os detalhes no objeto BoilerPart especificado como um parâmetro.
    • PostBoilerPart(BoilerPart boilerPart): que cria uma nova peça da caldeira.
    • DeleteBoilerPart(long id): que remove do banco de dados a peça da caldeira especificada.

    Observação

    O aplicativo do técnico requer apenas os dois métodos Obter, mas os outros são úteis para o aplicativo de gerenciamento de inventário de desktop (não abordado neste guia).

  16. Compile e crie a API Web.

    dotnet build
    

A API Web deve ser criada sem relatar erros ou avisos.

Implantando a API Web no Azure: gerenciamento de inventário de campo

Kiana implantou e testou a API Web, executando as seguintes tarefas:

  1. Usando a extensão da conta do Azure no Visual Studio Code, conecte-se à sua assinatura do Azure.

  2. Na janela Terminal no Visual Studio Code, crie um novo grupo de recursos chamado webapi_rg em sua assinatura do Azure. No comando a seguir, substitua <location> pela sua região do Azure mais próxima.

    az group create ^
        --name webapi_rg ^
        --location <location>
    
  3. Crie um Plano do Serviço de Aplicativo do Azure para fornecer os recursos para hospedar a API Web.

    az appservice plan create ^
        --name webapi_plan ^
        --resource-group webapi_rg ^
        --sku F1
    

    Observação

    F1 é o SKU gratuito para Planos do Serviço de Aplicativo. Ele fornece taxa de transferência e capacidade limitadas e é adequado apenas para fins de desenvolvimento.

  4. Crie um aplicativo Web do Azure usando o Plano do Serviço de Aplicativo. Substitua <webapp name> por um nome exclusivo para o aplicativo Web.

    az webapp create ^
        --name <webapp name> ^
        --resource-group webapi_rg ^
        --plan webapi_plan
    
  5. No Visual Studio Code, edite o arquivo appSettings.json e adicione a mesma cadeia de conexão que você gravou anteriormente no arquivo appSettings.Development.json. Lembre-se de substituir <server name> pelo nome do servidor de banco de dados SQL que você criou para conter o banco de dados InventoryDB.

    {
        "ConnectionStrings": {
            "InventoryDB": "Server=tcp:<server name>.database.windows.net,1433;Initial Catalog=InventoryDB;Persist Security Info=False;User ID=sqladmin;Password=Pa55w.rd;MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;"**
        },
        "Logging": {
            "LogLevel": {
                "Default\: "Information",
                "Microsoft": "Warning",
                "Microsoft.Hosting.Lifetime": "Information"
            }
        },
        "AllowedHosts": "*"
    }
    
  6. Na janela Terminal, empacote a API Web pronta para implantação no Azure.

    dotnet publish -c Release -o ./publish
    

    Este comando salva os arquivos empacotados em uma pasta chamada publicar.

  7. No Visual Studio Code, clique com o botão direito na pasta publicar e, em seguida, selecione Implantar no aplicativo Web.

    Implante o aplicativo Web do VS Code.

  8. Selecione o nome do aplicativo Web que você criou anteriormente na etapa 4 (<webapp name>). No exemplo a seguir, o aplicativo Web é denominado como my-fieldengineer-webapp.

    Selecione o aplicativo Web.

  9. No prompt da caixa de diálogo do Visual Studio Code, selecione Implantar para aceitar o aviso e implantar o aplicativo Web.

    Aviso de implantação do VS Code.

  10. Verifique se o aplicativo Web foi implantado com êxito e navegue até o site.

    Navegue até a caixa de diálogo do site no VS Code.

  11. O site será aberto em uma nova janela do navegador, mas exibirá um erro HTTP 404 (não encontrado). Isso ocorre porque as operações da API Web estão disponíveis por meio do ponto de extremidade api em vez da raiz do site. Altere o URL para https://<webapp name>.azurewebsites.net/api/BoilerParts. Este URI invoca o método GetBoilerParts no controlador BoilerParts. A API Web deve responder com um documento JSON que lista todas as peças da caldeira no banco de dados InventoryDB.

    Lista de peças exibida no navegador da Web.

  12. Altere o URL no navegador para https://<webapp name>.azurewebsites.net/swagger. A API Swagger deve aparecer. Esta é uma interface gráfica do usuário que permite a um desenvolvedor verificar e testar cada uma das operações em uma API Web. Ela também atua como uma ferramenta de documentação útil.

    A interface de usuário do Swagger exibindo a lista de operações.

  13. Selecione GET adjacente ao ponto de extremidade /api/BoilerParts/{id} e, em seguida, selecione Experimentar.

    A tela "Experimentar" na interface de usuário do Swagger

  14. No campo id insira o ID de uma peça e selecione Executar. Esta ação chama o método GetBoilerPart(long id) no controlador BoilerParts. Ela retornará um documento JSON com os detalhes da peça ou um erro HTTP 404 se nenhuma peça correspondente for encontrada no banco de dados.

    A resposta na interface de usuário do Swagger.

  15. Feche o navegador da Web e volte para o Visual Studio Code.

Criando e implantando a API Web: base de dados de conhecimento de campo

As operações da base de dados de conhecimento de campo na API Web funcionam em três tabelas no banco de dados KnowledgeDB: Dicas, BoilerParts e Engenheiros. A imagem a seguir mostra os relacionamentos entre essas tabelas e as colunas que elas contêm.

Relacionamentos das tabelas da base de dados de conhecimento.

Kiana adotou uma abordagem semelhante para o banco de dados da Base de Dados de Conhecimento de Campo que usou para o banco de dados de Gerenciamento de Inventário de Campo e realizou as seguintes tarefas:

  1. Crie classes de modelo C# que espelham a estrutura das tabelas Dicas, BoilerParts e Engenheiros no banco de dados KnowledgeDB. O código para cada uma dessas classes é mostrado a seguir.

    Observação

    A tabela BoilerParts no banco de dados KnowledgeDB é diferente da tabela BoilerParts no banco de dados InventoryDB. Para evitar conflito de nomes, as classes de modelo para tabelas no banco de dados KnowledgeDB tem o prefixo KnowledgeBase.

    // KnowledgeBaseTips.cs
    
    using System.ComponentModel.DataAnnotations;
    
    namespace FieldEngineerApi.Models
    {
        public class KnowledgeBaseTip 
        {
            [Key]
            public long Id { get; set; }
    
            public long KnowledgeBaseBoilerPartId { get; set; }
    
            public virtual KnowledgeBaseBoilerPart KnowledgeBaseBoilerPart { get; set; }
    
            public string KnowledgeBaseEngineerId { get; set; }
    
            public virtual KnowledgeBaseEngineer KnowledgeBaseEngineer { get; set; }
    
            public string Subject { get; set; }
    
            public string Body { get; set; }
        }
    }
    

    Observação

    A ID do engenheiro é uma cadeia de caracteres, não um número. Isso ocorre porque os sistemas existentes usam GUIDs para técnicos de identidade e outros usuários.

    // KnowledgeBaseBoilerPart.cs
    
    using System.Collections.Generic;
    using System.ComponentModel.DataAnnotations;
    
    namespace FieldEngineerApi.Models
    {
        public class KnowledgeBaseBoilerPart
        {
            [Key]
            public long Id { get; set; }
    
            public string Name { get; set; }
    
            public string Overview { get; set; }
    
            public virtual ICollection<KnowledgeBaseTip> KnowledgeBaseTips { get; set; }
        }
    }
    
    // KnowledgeBaseEngineer.cs
    
    using System.Collections.Generic;
    using System.ComponentModel.DataAnnotations;
    
    namespace FieldEngineerApi.Models
    {
        public class KnowledgeBaseEngineer
        {
            [Key]
            public string Id { get; set; }
    
            [Required]
            public string Name { get; set; }
    
            public string ContactNumber { get; set; }
    
            public virtual ICollection<KnowledgeBaseTip> KnowledgeBaseTips { get; set; }
        }
    }
    
  2. Crie outra classe de contexto do Entity Framework que a API Web usa para se conectar ao banco de dados KnowledgeDB.

    // KnowledgeBaseContext.cs
    
    using Microsoft.EntityFrameworkCore;
    
    namespace FieldEngineerApi.Models
    {
        public class KnowledgeBaseContext : DbContext
        {
            public KnowledgeBaseContext(DbContextOptions<KnowledgeBaseContext> options)
                : base(options)
            {
    
            }   
    
            public DbSet<KnowledgeBaseBoilerPart> BoilerParts { get; set; }
    
            public DbSet<KnowledgeBaseEngineer> Engineers { get; set; }
    
            public DbSet<KnowledgeBaseTip> Tips { get; set; }
        }
    }
    
  3. Edite o arquivo appsettings.Development.json do projeto e adicione a seguinte cadeia de conexão KnowledgDB à seção ConnectionStrings. Substitua <server name> pelo nome do servidor de banco de dados SQL que você criou para conter o banco de dados KnowledgeDB.

    {
        "ConnectionStrings": {
            "InventoryDB": "Server=tcp:...",
            "KnowledgeDB": "Server=tcp:<server name>.database.windows.net,1433;Initial Catalog=KnowledgeDB;Persist Security Info=False;User ID=sqladmin;Password=Pa55w.rd;MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;"
        },
        "Logging": {
            ...
            }
        }
    }
    

    Importante

    Apenas para os fins deste guia, a cadeia de conexão contém a ID de usuário e a senha do banco de dados. Em um sistema de produção, nunca armazene esses itens em texto não criptografado em um arquivo de configuração.

  4. Edite o arquivo Startup.cs e, no método ConfigureServices, adicione as instruções a seguir.

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddDbContext<InventoryContext>...;
    
        services.AddDbContext<KnowledgeBaseContext>(options =>  
            options.UseSqlServer(Configuration.GetConnectionString("KnowledgeD")));
    
        services.AddControllers().AddNewtonsoftJson(
            options => options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore**
        );
    
        services.AddControllers();
        ...
    }
    

    A segunda instrução controla a maneira como os dados são serializados quando recuperados. Algumas das classes de modelo têm referências a outras classes de modelo, que por sua vez podem fazer referência a outras classes de modelo. Algumas dessas referências podem resultar em loops recursivos (a entidade A faz referência à entidade B, que faz referência à entidade A, que faz referência à entidade B novamente e assim por diante). A opção ReferenceLoopHandling faz com que o serializador ignore esses loops nos dados e retorne apenas uma entidade e os objetos aos quais ela faz referência imediatamente, mas nada além disso.

  5. Na janela Terminal, execute o comando a seguir para gerar controladores das classes de modelo KnowledgeBaseBoilerTip, KnowledgeBaseBoilerPart e KnowledgeBaseEngineer e da classe de contexto KnowledgeBaseContext.

    dotnet aspnet-codegenerator controller ^
        -name KnowledgeBaseTipController -async -api ^
        -m KnowledgeBaseTip ^
        -dc KnowledgeBaseContext -outDir Controllers
    
    dotnet aspnet-codegenerator controller ^
        -name KnowledgeBaseBoilerPartController -async -api ^
        -m KnowledgeBaseBoilerPart ^
        -dc KnowledgeBaseContext -outDir Controllers
    
    dotnet aspnet-codegenerator controller ^
        -name KnowledgeBaseEngineerController -async -api ^
        -m KnowledgeBaseEngineer ^
        -dc KnowledgeBaseContext -outDir Controllers
    

    Todos os três controladores devem ser criados na pasta Controladores.

  6. Edite o arquivo KnowledgeBaseBoilerPartController.cs. Este arquivo contém o código do controlador KnowledgeBaseBoilerPart. Ele deve seguir o mesmo padrão que a classe BoilerPartsController criada anteriormente, expondo métodos REST que permitem a um cliente listar, consultar, inserir, atualizar e excluir entidades. Adicione o método GetTipsForPart a seguir ao controlador.

    [Route("api/[controller]")]
    [ApiController]
    
    public class KnowledgeBaseBoilerPartController : ControllerBase
    {
        private readonly KnowledgeBaseContext _context;
    
        public KnowledgeBaseBoilerPartController(KnowledgeBaseContext context)
        {
            _context = context;
        }
    
        // GET: api/KnowledgeBaseBoilerPart/5/Tips
        [HttpGet("{id}/Tips")]
        public async Task<ActionResult<IEnumerable<KnowledgeBaseTip>>>GetTipsForPart(long id)
        {
            return await _context.Tips.Where(
                t => t.KnowledgeBaseBoilerPartId == id).ToListAsync();
        }
        ...
    }
    

    Este método retorna todas as dicas da base de dados de conhecimento que fazem referência a uma peça especificada. Ele consulta a tabela Dicas no banco de dados por meio do objeto KnowledgeBaseContext para encontrar estas informações.

  7. Edite o arquivo KnowledgeBaseEngineerController.cs e adicione o método a seguir à classe KnowledgeBaseEngineerController.

    [Route("api/[controller]")]
    [ApiController]
    public class KnowledgeBaseEngineerController : ControllerBase
    {
        private readonly KnowledgeBaseContext _context;
    
        public KnowledgeBaseEngineerController(KnowledgeBaseContext context)
        {
            _context = context;
        }
    
        // GET: api/KnowledgeBaseEngineer/5/Tips
        [HttpGet("{id}/Tips")]
        public async Task\<ActionResult<IEnumerable<KnowledgeBaseTip>>> GetTipsForEngineer(string id)
        {
            return await _context.Tips.Where(t => 
                t.KnowledgeBaseEngineerId == id).ToListAsync();
        }
    
        ...
    }
    

    O método GetTipsForEngineer encontra todas as dicas da base de dados de conhecimento publicadas pelo engenheiro especificado.

  8. Na janela Terminal, compile e crie a API Web.

    dotnet build
    

    A API Web deve ser criada sem relatar erros ou avisos.

  9. Edite o arquivo appSettings.json e adicione a cadeia de conexão do banco de dados KnowledgeDB. Esta cadeia de caracteres deve ser a mesma que você escreveu anteriormente para o arquivo appSettings.Development.json.

    {
        "ConnectionStrings": {
            "InventoryDB": ...,
            "KnowledgeDB": "Server=tcp:<server name>.database.windows.net,1433;Initial Catalog=KnowledgeDB;Persist Security Info=False;User ID=sqladmin;Password=Pa55w.rd;MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;"
        },
        "Logging": {
            ...
        },
        "AllowedHosts": "*"
    }
    
  10. Na janela Terminal, empacote a API Web pronta para implantação no Azure.

    dotnet publish -c Release -o ./publish
    
  11. No Visual Studio Code, clique com o botão direito na pasta publicar e, em seguida, selecione Implantar no aplicativo Web. Implante no mesmo aplicativo Web do Azure que você criou anteriormente. Permita que o assistente substitua o aplicativo Web existente pelo novo código.

  12. Quando a implantação estiver concluída, navegue até o site, mas altere a URL no navegador para https://<webapp name>.azurewebsites.net/swagger. As operações para os controladores KnowledgeBaseBoilerPart, KnowledgeBaseEngineer e KnowldgeBaseTip devem ser listadas além das operações BoilerParts existentes. Verifique se as operações KnowledgeBaseBoilerPart incluem uma operação GET para o URI /api/KnowledgeBaseBoilerPart/{id}/Tips e se as operações KnowledgeBaseEngineer incluem uma operação GET para o URI /api/KnowledgeBaseEngineer/{id}/Tips.

    Interface de usuário do Swagger com novas operações.

Criando e implantando a API Web: agendamento de campo

As operações de Agendamento de Campo usam as tabelas Compromissos, AppointmentStatuses (esta é uma tabela de pesquisa simples que lista os valores válidos de status de compromisso), Clientes e Engenheiros, mostradas na imagem a seguir. Essas tabelas são armazenadas no banco de dados SchedulesDB.

Relações de tabelas de compromissos e agendamento.

Para criar as operações de API Web para a parte de Agendamento de campo do sistema, Kiana executou as seguintes tarefas:

  1. Crie classes de modelo C# que espelham a estrutura das tabelas AppointmentStatus, Compromissos, Clientes e Engenheiros no banco de dados SchedulesDB. O código a seguir mostra cada uma dessas classes.

    Observação

    A classe de modelo para a tabela Engenheiros é denominada ScheduleEngineer para distingui-la do modelo para a tabela Engenheiros no banco de dados InventoryDB.

    // AppointmentStatus.cs
    
    using Newtonsoft.Json;
    using System.Collections.Generic;
    using System.ComponentModel.DataAnnotations;
    
    namespace FieldEngineerApi.Models
    {
        public class AppointmentStatus {
            [Key]
            public long Id { get; set; }
    
            public string StatusName { get; set; }
            [JsonIgnore]
            public virtual ICollection<Appointment> Appointments { get; set; }
        }
    }
    
    // Appointment.cs
    
    using System;
    using System.ComponentModel.DataAnnotations;
    
    namespace FieldEngineerApi.Models
    {
        public class Appointment
        {
            [Key]
            public long Id { get; set; }
    
            [Required]
            public long CustomerId { get; set; }
    
            public virtual Customer Customer { get; set; }
    
            public string ProblemDetails { get; set; }
    
            [Required]
            public long AppointmentStatusId { get; set; }
    
            public virtual AppointmentStatus AppointmentStatus { get; set; }
    
            public string EngineerId { get; set; }
    
            public virtual ScheduleEngineer Engineer { get ; set; }
    
            [Display(Name = "StartTime")]
            [DataType(DataType.DateTime)]
            [DisplayFormat(DataFormatString = "{0:MM/dd/yyyy H:mm:ss}")]
            public DateTime StartDateTime { get; set; }
    
            public string Notes { get; set; }
    
            public string ImageUrl { get; set; }
        }
    }
    
    // Customer.cs
    
    using System.Collections.Generic;
    using System.ComponentModel.DataAnnotations;
    
    namespace FieldEngineerApi.Models
    {
        public class Customer
        {
            [Key]
            public long Id { get; set; }
    
            [Required]
            public string Name { get; set; }
    
            public string Address { get; set; }
    
            public string ContactNumber { get; set; }
    
            public virtual ICollection<Appointment> Appointments { get; set; }
        }
    }
    
    // ScheduleEngineer.cs
    using Newtonsoft.Json;
    using System.ComponentModel.DataAnnotations;
    using System.Collections.Generic;
    
    namespace FieldEngineerApi.Models
    {
        public class ScheduleEngineer
        {
            [Key]
            public string Id { get; set; }
    
            [Required]
            public string Name { get; set; }
    
            public string ContactNumber { get; set; }
    
            [JsonIgnore]
            public virtual ICollection<Appointment> Appointments { get; set; }
        }
    }
    
  2. Crie uma classe de contexto do Entity Framework que a API Web usa para se conectar ao banco de dados SchedulesDB.

    // ScheduleContext.cs
    
    using System;
    using Microsoft.EntityFrameworkCore;
    
    namespace FieldEngineerApi.Models
    {
        public class ScheduleContext : DbContext
        {
            public ScheduleContext(DbContextOptions<ScheduleContext> options)
                : base(options)
            {
    
            }
    
            public DbSet<Appointment> Appointments { get; set; }
    
            public DbSet<AppointmentStatus> AppointmentStatuses { get; set; }
    
            public DbSet<Customer> Customers { get; set; }
    
            public DbSet<ScheduleEngineer> Engineers { get; set; }
        }
    }
    
  3. Edite o arquivo appsettings.Development.json do projeto e adicione a seguinte cadeia de conexão SchedulesDB à seção ConnectionStrings. Substitua <server name> pelo nome do servidor de banco de dados SQL que você criou para conter o banco de dados KnowledgeDB.

    {
        "ConnectionStrings": {
            "InventoryDB": "Server=tcp*: ...",
            "KnowledgeDB": "Server=tcp; ... ",
            "SchedulesDB": "Server=tcp:<server name>.database.windows.net,1433;Initial Catalog=SchedulesDB;Persist Security Info=False;User ID=sqladmin;Password=Pa55w.rd;MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;"
        },
        "Logging": {
            ...
            }
        }
    }
    
  4. Edite o arquivo Startup.cs e, no método ConfigureServices, adicione a instrução a seguir.

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddDbContext<InventoryContext>...;
    
        services.AddDbContex\<KnowledgeBaseContext>...;
    
        services.AddDbContext<ScheduleContext>(options =>
            options.UseSqlServer(Configuration.GetConnectionString("SchedulesDB")));
    
        services.AddControllers().AddNewtonsoftJson(...);
    
        ...
    }
    
  5. Na janela Terminal, execute o comando a seguir para gerar controladores das classes de modelo Compromisso, Cliente e ScheduleEngineer e da classe de contexto ScheduleContext.

    Observação

    Não crie um controlador separado para o modelo AppointmentStatus.

    dotnet aspnet-codegenerator controller ^
        -name AppointmentsController -async -api ^
        -m Appointment ^
        -dc ScheduleContext -outDir Controllers
    
    dotnet aspnet-codegenerator controller ^
        -name CustomerController -async -api ^
        -m Customer ^
        -dc ScheduleContext -outDir Controllers
    
    dotnet aspnet-codegenerator controller ^
        -name ScheduleEngineerController -async -api ^
        -m ScheduleEngineer ^
        -dc ScheduleContext -outDir Controllers
    
  6. Edite o arquivo AppointmentsController.c. Na classe AppointmentsController, encontre o método GetAppointments. Modifique a instrução retornar, conforme mostrado. Essa mudança garante que as informações de Cliente, Engenheiro e AppointmentStatus sejam recuperadas como parte da operação GET; esses campos fazem referência a outras entidades que, de outra forma, seriam deixadas nulas devido ao mecanismo de carregamento lento do Entity Framework.

    public class AppointmentsController : ControllerBase
    {
        private readonly ScheduleContext _context;
    
        public AppointmentsController(ScheduleContext context)
        {
            _context = context;
        }
    
        // GET: api/Appointments
    
        [HttpGet]
        public async Task<ActionResult<IEnumerable<Appointment>>> GetAppointments()
        {
            return await _context.Appointments
                .Include(c => c.Customer)
                .Include(e => e.Engineer)
                .Include(s => s.AppointmentStatus)
                .ToListAsync();
        }
    
        ...
    }
    
  7. No mesmo arquivo, modifique o método GetAppointment(long id), como mostrado.

    // GET: api/Appointments/5
    [HttpGet("{id}")]
    public async Task<ActionResult<Appointment>> GetAppointment(long id)
    {
        var appointment = _context.Appointments
            .Where(a => a.Id == id)
            .Include(c => c.Customer)
            .Include(e => e.Engineer)
            .Include(s => s.AppointmentStatus);
    
        var appData = await appointment.FirstOrDefaultAsync();
        if (appData == null)
        {
            return NotFound();
        }
    
        return appData;
    }
    

    Esta versão do método preenche os campos Cliente, Engenheiro e AppointmentStatus de um compromisso quando ele é recuperado (o carregamento lento deixaria esses campos vazios de outra forma).

  8. Encontre o método PutAppointment e substitua-o pelo código a seguir. Esta versão do método PutAppointment usa os campos em um compromisso que um usuário pode modificar no aplicativo em vez de um objeto Compromisso completo.

    [HttpPut("{id}")]
    public async Task<IActionResult> PutAppointment(long id,
        string problemDetails, string statusName,
        string notes, string imageUrl)
    {
    
        var statusId = _context.AppointmentStatuses.First(s => 
            s.StatusName == statusName).Id;
    
        var appointment = _context.Appointments.First(e => 
            e.Id == id);
    
        if (appointment == null)
        {
            return BadRequest();
        }
    
        appointment.ProblemDetails = problemDetails;
        appointment.AppointmentStatusId = statusId;
        appointment.Notes = notes;
        appointment.ImageUrl = imageUrl;
        _context.Entry(appointment).State = EntityState.Modified;
    
        try
        {
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!AppointmentExists(id))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }
    
        return NoContent();
    }
    

    Observação

    Como regra geral, as operações PUT devem modificar apenas os dados que um usuário deve ter permissão para atualizar, não necessariamente todos os campos em uma entidade.

  9. Obra o arquivo ScheduleEngineerController.cs e adicione o seguinte método GetScheduleEngineerAppointments à classe ScheduleEngineerController.

    [Route("api/[controller]")]
    [ApiController]
    public class ScheduleEngineerController : ControllerBase
    {
        private readonly ScheduleContext _context;
    
        public ScheduleEngineerController(ScheduleContext context)
        {
            _context = context;
        }
    
        // GET: api/ScheduleEngineer/5/Appointments
        [HttpGet("{id}/Appointments")]
    
        public async Task<ActionResult<IEnumerable<Appointment>>> GetScheduleEngineerAppointments(string id)
        {
            return await _context.Appointments
                .Where(a => a.EngineerId == id)
                .OrderByDescending(a => a.StartDateTime)
                .Include(c => c.Customer)
                .Include(e => e.Engineer)
                .Include(s => s.AppointmentStatus)
                .ToListAsync();
        }
    
        ...
    }
    
    These methods retrieve the appointments for the specified technician.
    
    
  10. Edit the CustomerController.cs file and add the GetAppointments and GetNotes methods, as shown, to the CustomerController class.

    [Route("api/[controller]")]
    [ApiController]
    public class CustomerController : ControllerBase
    {
        private readonly ScheduleContext _context;
    
        public CustomerController(ScheduleContext context)
        {
            _context = context;
        }
    
        //GET: api/Customers/5/Appointments
        [HttpGet("{id}/Appointments")]
        public async Task<ActionResult<IEnumerable<Appointment>>> GetAppointments(long id)
        {
            return await _context.Appointments
                .Where(a => a.CustomerId == id)
                .OrderByDescending(a => a.StartDateTime)
                .ToListAsync();
        }
    
        //GET: api/Customers/5/Notes
        [HttpGet("{id}/Notes")]
        public async Task<ActionResult<IEnumerable<object>>> GetNotes(long id)
        {
            return await _context.Appointments
                .Where(a => a.CustomerId == id)
                .OrderByDescending(a => a.StartDateTime)
                .Select(a => 
                    new {a.StartDateTime, a.ProblemDetails, a.Notes})
                .ToListAsync();
        }
    
        ...
    }
    

    O método GetAppointments encontra todos os compromissos para o cliente especificado. O método GetNotes recupera todas as notas que o técnico fez em visitas anteriores ao cliente.

  11. Edite o arquivo appSettings.json e adicione a cadeia de conexão do banco de dados KnowledgeDB. Esta cadeia de caracteres deve ser a mesma que você escreveu anteriormente para o arquivo appSettings.Development.json.

    {
        "ConnectionStrings": {
            "InventoryDB": ...,
            "KnowledgeDB": ...,
            "SchedulesDB": "Server=tcp:<server name>.database.windows.net,1433;Initial Catalog=SchedulesDB;Persist Security Info=False;User ID=sqladmin;Password=Pa55w.rd;MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;"
        },
        "Logging": {
            ...
        },
        "AllowedHosts": "*"
    }
    
  12. Na janela Terminal, compile e crie a API Web.

    dotnet build
    

    A API Web deve ser criada sem relatar erros ou avisos.

  13. Na janela Terminal, empacote a API Web pronta para implantação no Azure.

    dotnet publish -c Release -o ./publish
    
  14. No Visual Studio Code, clique com o botão direito na pasta publicar e, em seguida, selecione Implantar no aplicativo Web. Implante no mesmo aplicativo Web do Azure que você criou anteriormente. Permita que o assistente substitua o aplicativo Web existente pelo novo código.

  15. Quando a implantação estiver concluída, navegue até o site, mas altere a URL no navegador para https://<webapp name>.azurewebsites.net/swagger. Verifique se as operações para os controladores Compromissos, Cliente e ScheduleEngineer agora estão disponíveis.

A API Web agora está pronta para ser incorporada ao aplicativo.