Capítulo 5: Criação e publicação de uma API Web no Azure
Tendo estabelecido que os dados da aplicação dos técnicos devem ter como origem sistemas existentes através de uma API Web, Maria e Kiana trabalham em conjunto para determinar exatamente quais as informações necessárias, e em que formato. Em seguida, Kiana criará uma aplicação Web que expõe a API Web apropriada e providenciará para que seja alojada no Azure. A aplicação pode ligar-se ao Azure a partir de qualquer lugar com uma ligação sem fios.
Definir as operações da API Web: Gestão de inventário no terreno
O ecrã Procurar da secção Gestão de Inventário no Terreno da aplicação apresenta uma lista das peças para caldeiras e sistemas de ar condicionado (referidos simplesmente como peças para caldeiras). O ecrã Detalhes permite ao técnico ver mais informações sobre uma peça selecionada.
Na base de dados de inventário existente (denominada InventoryDB), as informações sobre as peças são mantidas numa única tabela denominada BoilerParts. Kiana determina que a API Web deve suportar os seguintes pedidos:
- Obtenha todas as peças de caldeiras.
- Obtenha os detalhes de uma peça baseado no ID da peça.
Definir as operações da API Web: Base de Dados de Conhecimento no Terreno
No sistema existente, a base de dados da base de dados de conhecimento (denominada KnowledgeDB) contém três tabelas que registam e gerem as relações entre as sugestões, os engenheiros e as peças:
- Sugestões, que contém os detalhes de uma sugestão. Cada sugestão é composta por uma única linha de resumo que identifica um determinado problema (o assunto), e por uma explicação mais detalhada que descreve como resolver o problema (o corpo). Cada sugestão também referencia uma parte e o engenheiro que registou a sugestão.
- BoilerParts, que contém uma lista das peças referenciadas por sugestões. Os detalhes das próprias peças estão armazenados na tabela BoilerParts na base de dados InventoryDB.
- Engenheiros, que lista os técnicos que foram autores de cada sugestão.
Atualmente, a peça da base de dados de conhecimento da aplicação contém apenas um ecrã Procurar de marcador de posição. Maria quer implementar a seguinte funcionalidade:
O técnico especifica uma termo de pesquisa no ecrã Procurar para encontrar todas as sugestões correspondentes. A correspondência pode estar no nome da pela a que a sugestão se refere, o texto no assunto ou corpo da sugestão, ou o nome de um técnico que é um perito num equipamento específico.
Quando todas as sugestões correspondentes forem encontradas, o técnico poderá selecionar uma sugestão para ver os seus detalhes.
Um técnico também pode adicionar novas sugestões à base de dados de conhecimento, bem como adicionar notas e comentários às sugestões existentes.
A base de dados de conhecimento é grande e está em crescimento, e a consulta em várias tabelas e colunas pode envolver uma lógica complexa que requer uma potência computacional significativo. Para reduzir a carga na API Web, kiana decide usar o Azure Cognitive Search para fornecer a funcionalidade de pesquisa, como descrito anteriormente. Para apoiar a aplicação, Kiana decide que as seguintes operações são necessárias a partir da API Web:
Encontre os detalhes de uma sugestão da base de dados de conhecimento especificada da tabela Sugestões.
Atualize uma sugestão da base de dados de conhecimento existente na tabela Sugestões.
Adicione uma nova sugestão da base de dados de conhecimento à tabela Sugestões, que também pode implicar a adição de linhas às tabelas BoilerParts e Engenheiros se a peça ou o engenheiro especificado não tiver atualmente nenhuma sugestão registada para elas. A rotina que realmente executa a lógica por detrás da adição de uma nova sugestão será implementada como uma aplicação lógica chamada Power Apps.
Definir as operações da API Web: Agendamento no terreno
Agendar compromissos de técnicos requer não apenas consultar, adicionar e remover compromissos, mas também registar informações sobre os clientes. O sistema de compromissos existente regista estes dados em três tabelas na base de dados SchedulesDB:
- Compromissos, que contém os detalhes de cada compromisso, incluindo a data, hora, problema, notas e técnico atribuído à tarefa.
- Clientes, que contém os detalhes de cada cliente, incluindo o respetivo nome, endereço e detalhes de contacto.
- Engenheiros, que lista cada técnico que participa nos compromissos.
Nota
A base de dados contém, na verdade, uma quarta tabela chamada AppointmentsStatus. Esta tabela contém uma lista dos valores válidos para o estado de um compromisso e é simplesmente uma pesquisa utilizada por outras peças do sistema de compromissos existente.
Kiana decide que as seguintes operações seriam úteis para a parte Agendamento no Terreno da aplicação:
- Encontre todos os compromissos para um técnico especificado.
- Encontre todos os compromissos para o dia atual para um técnico especificado.
- Encontre o compromisso agendado seguinte para um técnico especificado.
- Atualize os detalhes de um compromisso, como adicionar notas ou uma fotografia.
- Encontre detalhes sobre um cliente.
Criar a API Web: Gestão de inventário no terreno
Os sistemas existentes armazenam os dados através da Base de Dados SQL do Azure. Kiana decide criar a API Web através do Entity Framework Core, porque esta abordagem pode gerar muito do código que consulta, insere e atualiza automaticamente os dados. O modelo de API Web fornecido pela Microsoft também pode criar as descrições Swagger que descrevem cada operação na API. Estas descrições são úteis para testar as operações da API. Muitas ferramentas podem usar esta informação para integrar a API com outros serviços, como a Gestão de API do Azure.
Kiana começou com a funcionalidade Inventário no Terreno porque esta é a parte mais simples. As operações de Inventário no Terreno na API Web consultam uma única tabela, BoilerParts, na base de dados InventoryDB. Esta tabela contém as colunas mostradas na seguinte imagem.
Kiana adotou a abordagem "prioridade ao código" para criar a API Web e fez o seguinte:
Definiu a sua própria classe do modelo C# que espelhou a estrutura da tabela BoilerParts na base de dados InventoryDB.
Criou uma classe contexto do Entity Framework que a API Web utiliza para ligar à base de dados para executar consultas.
Configurou a classe de contexto para ligar à base de dados InventoryDB em Azure.
Utilizou as ferramentas da linha de comando Entity Framework para gerar uma classe controlador da API Web que implementa os pedidos HTTP REST para cada uma das operações que podem ser executadas em relação à tabela BoilerParts.
Utilizou a API Swagger para testar a API Web.
A imagem seguinte mostra a estrutura de alto nível da API Web.
Kiana utilizou o seguinte procedimento para criar a API Web utilizando as ferramentas da linha de comandos .NET 6.0 e o Visual Studio Code:
Abrir uma janela do Terminal no Visual Studio Code.
Execute o seguinte comando para criar um novo projeto da API Web denominado FieldEngineerApi.
dotnet new webapi -o FieldEngineerApi
Abra a pasta FieldEngineerApi.
Remova o controlador de exemplo WeatherForecastController.cs e o ficheiro da classe WeatherForecast.cs que foi criado pelo modelo da API Web.
Na janela do Terminal, adicione ao projeto os seguintes pacotes e ferramentas do Entity Framework, juntamente com o suporte para a utilização do SQL Server.
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
Na pasta FieldEngineerApi, crie uma nova pasta denominada Modelos.
Na pasta Modelos, crie um ficheiro de código C# denominado BoilerPart.cs.
Neste ficheiro, adicione as seguintes propriedades e campos. Estas propriedades e campos espelham a estrutura da tabela BoilerParts na base 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; } } }
Na pasta Modelos, crie outro ficheiro de código C# denominado InventoryContext.cs. Adicione o seguinte código a esta classe. A classe fornece a ligação entre o controlador (a ser criado a seguir) e a base 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; } } }
Edite o ficheiro appsettings.Development.json para o projeto e adicione uma secção ConnectionStrings com a seguinte cadeia de ligação InventoryDB. Substitua <server name> pelo nome do servidor da Base de Dados SQL que criou para guardar a base 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 efeitos deste guia, a cadeia de ligação contém o ID do utilizador e a palavra-passe para a base de dados. Num sistema de produção, nunca deve guardar estes itens em texto não encriptado num ficheiro de configuração.
Edite o ficheiro Startup.cs e adicione o seguinte através de diretivas à lista no início do ficheiro.
using FieldEngineerApi.Models; using Microsoft.EntityFrameworkCore;
Na classe Arranque, localize o método ConfigureServices. Adicione a seguinte instrução a este método.
public void ConfigureServices(IServiceCollection services) { services.AddDbContext<InventoryContext>(options => options.UseSqlServer(Configuration.GetConnectionString("InventoryDB"))); services.AddControllers(); ... }
Modifique o método Configurar e ative a IU do Swagger, mesmo quando a aplicação está a ser executada em modo de produção, tal como é mostrado (esta alteração envolve relocalizar as duas chamadas do 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
Esta alteração permite ao ponto final do Swagger ser exposto para integração da Gestão de API. Depois de configurada a Gestão de API, deve mover este código de volta para a instrução if e reimplementar a API Web. Nunca deixe o ponto final do Swagger aberto num sistema de produção.
Na janela do Terminal, execute o seguinte comando para gerar o controlador BoilerParts da classe do modelo BoilerPart e a classe de contexto InventárioContext.
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 caráter terminador da linha, ^, só é reconhecido pelo Windows. Se estiver a executar o Visual Studio Code num sistema Linux, utilize antes o caráter \.
Abra o ficheiro BoilerParts.cs na pasta Controladores e reveja o seu conteúdo. A classe BoilerPartsController expõe os seguintes métodos REST:
- GetBoilerParts(), que devolve uma lista de todos os objetos BoilerPart da base de dados.
- GetBoilerPart(long id), que obtém os detalhes da peça de caldeira especificada.
- PutBoilerPart(long id, BoilerPart boilerPart), que atualiza uma peça de caldeira na base de dados com os detalhes no objeto BoilerPart especificado como um parâmetro.
- PostBoilerPart(BoilerPart boilerPart), que cria uma nova peça de caldeira.
- DeleteBoilerPart(long id), que remove a peça de caldeira especificada da base e dados.
Nota
A aplicação do técnico necessita apenas dos dois métodos Get, mas os outros são úteis para a aplicação de gestão de inventário de ambiente de trabalho (não abrangida neste guia).
Compilar e criar a API Web.
dotnet build
A API Web deve ser compilada sem reportar quaisquer erros ou avisos.
Implementar a API Web em Azure: Gestão de inventário no terreno
Kiana implementou e testou a API Web ao executar as seguintes tarefas:
Através daa extensão da Conta Azure no Visual Studio Code, inicie sessão na sua subscrição do Azure.
A partir da janela do Terminal no Visual Studio Code, crie um novo grupo de recursos denominado webapi_rg na sua subscrição do Azure. No comando seguinte, substitua <location> pela sua região do Azure mais próxima.
az group create ^ --name webapi_rg ^ --location <location>
Crie um plano do Serviço de Aplicações do Azure para fornecer os recursos para hospedar a API Web.
az appservice plan create ^ --name webapi_plan ^ --resource-group webapi_rg ^ --sku F1
Nota
F1 é o SKU gratuito para os planos do Serviço de Aplicações. Fornece um débito e uma capacidade limitados, e é adequado apenas para fins de desenvolvimento.
Crie uma aplicação Web do Azure através do plano do Serviço de Aplicações. Substitua <webapp name> por um nome exclusivo para a aplicação Web.
az webapp create ^ --name <webapp name> ^ --resource-group webapi_rg ^ --plan webapi_plan
No Visual Studio Code, edite o ficheiro AppSettings.json e adicione a mesma cadeia de ligação que escreveu anteriormente para o ficheiro AppSettings.Development.json. Lembre-se de substituir <server name> pelo nome do servidor da Base de Dados SQL que criou para guardar a base 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": "*" }
Na janela do Terminal, coloque em pacote a API Web pronta para ser implementada para Azure.
dotnet publish -c Release -o ./publish
Este comando guarda os ficheiros em pacote para uma pasta denominada publicar.
No Visual Studio Code, clique com o botão direito do rato na pasta publicar e, em seguida, selecione Implementar na Aplicação Web.
Selecione o nome da aplicação Web que criou anteriormente no passo 4 (<webapp name>). No exemplo seguinte, a aplicação Web é denominada my-fieldengineer-webapp.
Na linha de comandos na caixa de diálogo do Visual Studio Code, selecione Implementar para aceitar o aviso e implementar a aplicação Web.
Verifique se a aplicação Web é implementada com êxito e, em seguida, navegue para o site.
O site será aberto numa nova janela do browser, mas apresentará um erro HTTP 404 (não encontrado). Isto porque as operações da API Web estão disponíveis através do ponto final da 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 de caldeira na base de dados InventoryDB.
Altere o URL no browser para https://<webapp name>.azurewebsites.net/swagger. A API Swagger deve aparecer. Esta é uma interface de utilizador gráfica que permite a um programador verificar e testar cada uma das operações numa API Web. Também funciona como uma ferramenta de documentação útil.
Selecione GET adjacente ao ponto final /api/BoilerParts/{id} e selecione Experimentar.
No campo id, introduza o ID de uma peça e, em seguida, selecione Executar. Esta ação chama o método GetBoilerPart(long id) no controlador BoilerParts. Devolverá um documento JSON com os detalhes da peça ou um erro HTTP 404 se não for encontrada nenhuma peça correspondente na base de dados.
Feche o browser e regresse ao Visual Studio Code.
Compilar e implementar a API Web: Base de Dados de Conhecimento no Terreno
As operações da Base de Dados de Conhecimento no Terreno na API Web funcionam em três tabelas na base de dados KnowledgeDB: Sugestões, BoilerParts e Engenheiros. A imagem seguinte mostra as relações entre estas tabelas e as colunas que contêm.
Kiana adotou uma abordagem semelhante para a Base de Dados de Conhecimento no Terreno que foi utilizada para a base de dados Gestão de Inventário no Terreno e realizou as seguintes tarefas:
Criar classes de modelo C# que espelham a estrutura da tabela Sugestões, BoilerParts e Engenheiros na base de dados KnowledgeDB. O código para cada uma destas classes é mostrado no seguinte.
Nota
A tabela BoilerParts na base de dados KnowledgeDB é diferente da tabela BoilerParts na base de dados InventoryDB. Para evitar um conflito de nomes, as classes de modelo para as tabelas na base de dados KnowledgeDB têm 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; } } }
Nota
O Id de engenheiro é uma cadeia, não um número. Isto porque os sistemas existentes utilizam GUIDs para identificar os técnicos e outros utilizadores.
// 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; } } }
Criar outra classe contexto do Entity Framework que a API Web utiliza para ligar à base 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; } } }
Edite o ficheiro appsettings.Development.json para o projeto e adicione a seguinte cadeia de ligação KnowledgDB à secção ConnectionStrings. Substitua <server name> pelo nome do servidor da Base de Dados SQL que criou para guardar a base 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 efeitos deste guia, a cadeia de ligação contém o ID do utilizador e a palavra-passe para a base de dados. Num sistema de produção, nunca deve guardar estes itens em texto não encriptado num ficheiro de configuração.
Editar o ficheiro Startup.cs e, no método ConfigureServices, adicione as seguintes instruções.
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 forma como os dados são serializados quando são obtidos. Algumas das classes de modelo têm referências a outras classes de modelo, que por sua vez podem referenciar mais classes de modelo. Algumas destas referências podem resultar em ciclos recursivos (Entidade A referencia Entidade B, que referencia de volta a Entidade A, que volta a referenciar Entidade B, e assim sucessivamente). A opção ReferenceLoopHandling faz com que o serializador ignore estes ciclos nos dados, e devolva apenas uma entidade e os objetos que referencia imediatamente, mas não mais.
Na janela do Terminal, execute o seguinte comando para gerar controladores a partir das classes de modelo KnowledgeBaseBoilerTip, KnowledgeBaseBoilerPart e KnowledgeBaseEngineer e a 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
Os três controladores devem ser criados na pasta Controladores.
Edite o ficheiro KnowledgeBaseBoilerPartController.cs. Este ficheiro contém o código para o controlador KnowledgeBaseBoilerPart. Deve seguir o mesmo padrão que a classe BoilerPartsController criada anteriormente, expondo os métodos REST que permitem a um cliente listar, consultar, inserir, atualizar e eliminar entidades. Adicione o seguinte método GetTipsForPart 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 devolve todas as sugestões da base de dados de conhecimento que referenciam uma peça especificada. Consulta a tabela Sugestões na base de dados através do objeto KnowledgeBaseContext para localizar esta informação.
Edite o ficheiro KnowledgeBaseEngineerController.cs e adicione o seguinte método à 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 localiza todas as sugestões da base de dados conhecimento publicadas pelo engenheiro especificado.
Na janela do Terminal, compile e crie a API Web.
dotnet build
A API Web deve ser compilada sem reportar quaisquer erros ou avisos.
Editar o ficheiro Descosettings.json e adicionar a cadeia de ligação para a base de dados KnowledgeDB. Esta cadeia deve ser a mesma que escreveu anteriormente para o ficheiro 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": "*" }
Na janela do Terminal, coloque em pacote a API Web pronta para ser implementada para Azure.
dotnet publish -c Release -o ./publish
No Visual Studio Code, clique com o botão direito do rato na pasta publicar e, em seguida, selecione Implementar na Aplicação Web. Implemente para a mesma aplicação Web do Azure que criou anteriormente. Permita que o assistente substitua a aplicação Web existente pelo novo código.
Quando a implementação estiver concluída, navegue para o site mas altere o URL no browser para https://<webapp name>.azurewebsites.net/swagger. As operações para os controladores KnowledgeBaseBoilerPart, KnowledgeBaseEngineer e KnowldgeBaseTip devem ser listadas para além das operações existentes BoilerParts. 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.
Compilar e Implementar a API Web: Agendamento no Terreno
As operações de Agendamento no Terreno utilizam as tabelas Compromissos, AppointmentStatuses (this is a simple lookup table that lists the valid appointment status values), Clientes e Engenheiros, mostradas na seguinte imagem. Estas tabelas são armazenadas na base de dados SchedulesDB.
Para criar as operações da API Web para a parte de Agendamento no Terreno do sistema, Kiana realizou as seguintes tarefas:
Criar classes de modelo C# que espelham a estrutura da tabela AppointmentStatus, Compromissos, Clientes e Engenheiros na base de dados SchedulesDB. O código seguinte mostra cada uma destas classes.
Nota
A classe de modelo para a tabela Engenheiros chama-se ScheduleEngineer para a distinguir do modelo para a tabela Engenheiros na base 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; } } }
Criar uma classe contexto do Entity Framework que a API Web utiliza para ligar à base 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; } } }
Edite o ficheiro appsettings.Development.json para o projeto e adicione a seguinte cadeia de ligação SchedulesDB à secção ConnectionStrings. Substitua <server name> pelo nome do servidor da Base de Dados SQL que criou para guardar a base 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": { ... } } }
Editar o ficheiro Startup.cs e, no método ConfigureServices, adicione a seguinte instrução.
public void ConfigureServices(IServiceCollection services) { services.AddDbContext<InventoryContext>...; services.AddDbContex\<KnowledgeBaseContext>...; services.AddDbContext<ScheduleContext>(options => options.UseSqlServer(Configuration.GetConnectionString("SchedulesDB"))); services.AddControllers().AddNewtonsoftJson(...); ... }
Na janela do Terminal, execute o seguinte comando para gerar controladores a partir das classes de modelo Compromisso, Cliente e ScheduleEngineer, e a classe de contexto ScheduleContext.
Nota
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
Edite o ficheiro AppointmentsController.cs. Na classe AppointmentsController, localize o método GetAppointments. Modifique a instruçãodevolver, tal como é mostrado. Esta alteração assegura que as informações de Cliente, Engenheiro e AppointmentStatus são obtidas como parte da operação GET; estes campos referenciam outras entidades que de outra forma seriam nulas devido ao mecanismo de carregamento em diferido 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(); } ... }
No mesmo ficheiro, modifique o método GetAppointment(long id), tal 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 povoa os campos Cliente, Engenheiro e AppointmentStatus de um compromisso quando quando é obtido (de outra forma, o carregamento em diferido deixaria estes campos vazios).
Localize o método PutAppointment e substitua-o pelo seguinte código. Esta versão do método PutAppointment assume os campos num compromisso que um utilizador pode modificar na aplicação, em vez de um objeto Compromisso concluído.
[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(); }
Nota
Regra geral, as operações PUT só devem modificar os dados que um utilizador deve ser autorizado a atualizar, e não necessariamente todos os campos de uma entidade.
Abra o ficheiro 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.
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 localiza todos os compromissos para o cliente especificado. O método GetNotes obtém todas as notas que o técnico tomou nas visitas anteriores ao cliente.
Editar o ficheiro Descosettings.json e adicionar a cadeia de ligação para a base de dados KnowledgeDB. Esta cadeia deve ser a mesma que escreveu anteriormente para o ficheiro 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": "*" }
Na janela do Terminal, compile e crie a API Web.
dotnet build
A API Web deve ser compilada sem reportar quaisquer erros ou avisos.
Na janela do Terminal, coloque em pacote a API Web pronta para ser implementada para Azure.
dotnet publish -c Release -o ./publish
No Visual Studio Code, clique com o botão direito do rato na pasta publicar e, em seguida, selecione Implementar na Aplicação Web. Implemente para a mesma aplicação Web do Azure que criou anteriormente. Permita que o assistente substitua a aplicação Web existente pelo novo código.
Quando a implementação estiver concluída, navegue para o site mas altere o URL no browser para https://<webapp name>.azurewebsites.net/swagger. Verifique se as operações para os controladores Compromissos, Cliente e ScheduleEngineer já estão disponíveis.
A API Web está agora pronta para ser incorporada na aplicação.