ASP.NET Web API + Entity Framework + Microsoft SQL Server + Angular. Parte 1





Introdução



Um breve curso sobre como criar um aplicativo Web simples usando tecnologias ASP.NET Core, Entity Framework, Microsoft SQL Server DBMS e Angular framework. Vamos testar a API Web por meio do aplicativo Postman .



O curso consiste em várias partes:



  1. Crie APIs da Web com ASP.NET Web API e Entity Framework Core.
  2. Implementação da IU em Angular.
  3. Adicionando autenticação ao aplicativo.
  4. Estenda o modelo de aplicativo e explore recursos adicionais do Entity Framework.


Parte 1. Construir APIs da Web com ASP.NET Web API e Entity Framework Core



Como exemplo, vamos considerar a aplicação clássica de uma lista de tarefas pendentes. Usarei o Visual Studio 2019 para desenvolver o aplicativo (o processo é semelhante no Visual Studio 2017).



Criação de projeto



Crie um novo projeto de aplicativo da Web ASP.NET Core no Visual Studio:







Nomeie o aplicativo e especifique o caminho para o diretório com o projeto:







E selecione o modelo de aplicativo API:







Modelo



Vamos criar um catálogo de modelos e adicionar a primeira classe TodoItem.cs ao novo catálogo, cujos objetos irão descrever algumas tarefas da lista de tarefas no aplicativo:



public class TodoItem
{
    public int Id { get; set; }
    public string TaskDescription { get; set; }
    public bool IsComplete { get; set; }
}


Usaremos Sql Server como um DBMS e o banco de dados será acessado por meio do Entity Framework Core e, primeiro, instalaremos a estrutura por meio do gerenciador de pacotes NuGet integrado:







Uma abordagem para trabalhar com o Entity Framework é a abordagem "Code-First". A essência da abordagem é que com base no modelo de aplicação (no nosso caso, o modelo representa uma única classe - TodoItem.cs), a estrutura do banco de dados (tabelas, chaves primárias, links) é formada, todo esse trabalho acontece “nos bastidores” e diretamente com Não trabalhamos com SQL. Um pré-requisito para a classe de modelo é a presença de um campo de chave primária. Por padrão, o Entity Framework procura um campo inteiro em cujo nome existe uma substring "id" e forma uma chave primária com base nele. Você pode substituir esse comportamento usando atributos especiais ou usando os recursos da API Fluent.



O principal componente para trabalhar com o Entity Framework é a classe de contexto do banco de dados, por meio da qual os dados nas tabelas são realmente acessados:



public class EFTodoDBContext : DbContext
{
    public EFTodoDBContext(DbContextOptions<EFTodoDBContext> options) : base(options) 
    { }
    public DbSet<TodoItem> TodoItems{ get; set; }
}


A classe base DbContext cria o contexto do banco de dados e fornece acesso à funcionalidade do Entity Framework.



Usaremos o SQL Server 2017 Express para armazenar dados de aplicativos . Strings de conexão são armazenados em um arquivo JSON chamado appsettings.json:



{
  "ConnectionStrings": {
    "DefaultConnection": "Server=.\\SQLEXPRESS;Database=Todo;Trusted_Connection=true"
  }
}


Em seguida, você precisa modificar a classe Startup.cs adicionando o seguinte código ao método ConfigureServices ():



services.AddDbContext<EFTodoDBContext>(options => options.UseSqlServer(Configuration["ConnectionStrings:DefaultConnection"]));


O método AddDbContext () configura os serviços fornecidos pelo Entity Framework Core para a classe de contexto do banco de dados EFTodoDBContext. O argumento para o método AddDbContext () é uma expressão lambda que recebe um objeto de opções que configura o banco de dados para a classe de contexto. Nesse caso, o banco de dados é configurado usando o método UseSqlServer () e especificando uma string de conexão.



Vamos definir as operações básicas para trabalhar com tarefas na interface ITodoRepository:



 public interface ITodoRepository
 {
    IEnumerable<TodoItem> Get();
    TodoItem Get(int id);
    void Create(TodoItem item);
    void Update(TodoItem item);
    TodoItem Delete(int id);
 }


Esta interface permite que não pensemos na implementação específica do data warehouse, talvez não tenhamos decidido exatamente sobre a escolha de um framework SGBD ou ORM, agora não importa, a classe que descreve o acesso aos dados herdará desta interface.

Vamos implementar um repositório que, como mencionado anteriormente, herdará de ITodoRepository e usará EFTodoDBContext como fonte de dados:



public class EFTodoRepository : ITodoRepository
{
    private EFTodoDBContext Context;
    public IEnumerable<TodoItem> Get()
    {
        return Context.TodoItems;
    }
    public TodoItem Get(int Id)
    {
        return Context.TodoItems.Find(Id);
    }
    public EFTodoRepository(EFTodoDBContext context)
    {
        Context = context;
    }
    public void Create(TodoItem item)
    {
        Context.TodoItems.Add(item);
        Context.SaveChanges();
    }
    public void Update(TodoItem updatedTodoItem)
    {
        TodoItem currentItem = Get(updatedTodoItem.Id);
        currentItem.IsComplete = updatedTodoItem.IsComplete;
        currentItem.TaskDescription = updatedTodoItem.TaskDescription;

        Context.TodoItems.Update(currentItem);
        Context.SaveChanges();
        }

    public TodoItem Delete(int Id)
    {
        TodoItem todoItem = Get(Id);

        if (todoItem != null)
        {
            Context.TodoItems.Remove(todoItem);
            Context.SaveChanges();
        }

        return todoItem;
    }    
}


Controlador



O controlador, cuja implementação será descrita a seguir, não saberá nada sobre o contexto dos dados do EFTodoDBContext, mas apenas utilizará a interface ITodoRepository em seu trabalho, o que permite alterar a fonte dos dados sem alterar o controlador. Esta abordagem Adam Freeman em seu livro "Entity Framework Core 2 para ASP.NET Core MVC para profissionais" chamou o padrão "Storage".



O controlador implementa manipuladores para métodos de solicitação HTTP padrão: GET, POST, PUT, DELETE, que irão alterar o estado de nossas tarefas descritas na classe TodoItem.cs.



Adicione a classe TodoController.cs ao diretório Controllers com o seguinte conteúdo:



[Route("api/[controller]")]
public class TodoController : Controller
{
    ITodoRepository TodoRepository;

    public TodoController(ITodoRepository todoRepository)
    {
        TodoRepository = todoRepository;
    }

    [HttpGet(Name = "GetAllItems")]
    public IEnumerable<TodoItem> Get()
    {
        return TodoRepository.Get();
    }

    [HttpGet("{id}", Name = "GetTodoItem")]
    public IActionResult Get(int Id)
    {
        TodoItem todoItem = TodoRepository.Get(Id);

        if (todoItem == null)
        {
            return NotFound();
        }

        return new ObjectResult(todoItem);
    }

    [HttpPost]
    public IActionResult Create([FromBody] TodoItem todoItem)
     {
        if (todoItem == null)
        {
            return BadRequest();
        }
        TodoRepository.Create(todoItem);
        return CreatedAtRoute("GetTodoItem", new { id = todoItem.Id }, todoItem);
    }

    [HttpPut("{id}")]
    public IActionResult Update(int Id, [FromBody] TodoItem updatedTodoItem)
    {
        if (updatedTodoItem == null || updatedTodoItem.Id != Id)
        {
            return BadRequest();
        }

        var todoItem = TodoRepository.Get(Id);
        if (todoItem == null)
        {
            return NotFound();
        }

        TodoRepository.Update(updatedTodoItem);
        return RedirectToRoute("GetAllItems");
    }

    [HttpDelete("{id}")]
    public IActionResult Delete(int Id)
    {
        var deletedTodoItem = TodoRepository.Delete(Id);

        if (deletedTodoItem == null)
        {
            return BadRequest();
        }

        return new ObjectResult(deletedTodoItem);
    }
 }


Um atributo que descreve o modelo de rota para acessar o controlador é especificado antes da definição da classe: [Route ("api / [controlador]")]. O TodoController estará acessível pela seguinte rota: https: // <host ip>: <port> / api / todo. [Controller] especifica o nome da classe do controlador em letras minúsculas, omitindo a parte "Controlador".



Antes de cada método ser definido no TodoController, um atributo especial do formulário é especificado: [<método HTTP> ("parâmetro", Nome = "alias do método")]. O atributo determina qual solicitação HTTP será processada por este método, o parâmetro que é passado no URI da solicitação e o alias do método com o qual a solicitação pode ser reenviada. Se você não especificar o atributo, por padrão, o framework MVC tentará encontrar o método mais adequado no controlador para processar a solicitação com base no nome do método e nos parâmetros especificados na solicitação, portanto, se você não especificar um atributo para o método Get () no controlador TodoController, então em uma solicitação HTTP usando o método GET: https: // <host ip>: <port> / api / todo, a infraestrutura definirá o método Get () do controlador para processar a solicitação.



Em seu construtor, o controlador recebe uma referência a um objeto do tipo ITodoRepository, mas até o momento a infraestrutura MVC não sabe qual objeto substituir na criação do controlador. Precisamos criar um serviço que resolva exclusivamente essa dependência, para isso faremos algumas alterações na classe Startup.cs adicionando o seguinte código ao método ConfigureServices ():



services.AddTransient<ITodoRepository, EFTodoRepository>();


O método AddTransient <ITodoRepository, EFTodoRepository> () define um serviço que cria uma nova instância da classe EFTodoRepository sempre que uma instância do tipo ITodoRepository é necessária, por exemplo em um controlador.



O código completo da classe Startup.cs:



public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddControllers();
        services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_3_0);
        services.AddDbContext<EFTodoDBContext>(options => options.UseSqlServer(Configuration["ConnectionStrings:DefaultConnection"]));
        services.AddTransient<ITodoRepository, EFTodoRepository>();
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }

        app.UseHttpsRedirection();
        app.UseRouting();
        app.UseAuthorization();
        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
        });
    }
 }


Migrações



Para que o Entity Framework gere o banco de dados e as tabelas do modelo, você deve usar o processo de migração do banco de dados. As migrações são um grupo de comandos que prepara o banco de dados para funcionar com o Entity Framework. Eles são usados ​​para criar e sincronizar o banco de dados. Os comandos podem ser executados no Console do gerenciador de pacotes e no Power Shell (Developer Power Shell). Usaremos o Console do gerenciador de pacotes, para trabalhar com o Entity Framework precisamos instalar o pacote Microsoft.EntityFrameworkCore.Tools:







Inicie o console do gerenciador de pacotes e execute o comando Add-Migration Initial :











Um novo diretório aparecerá no projeto - Migrations, no qual as classes de migração serão armazenadas, com base nos quais os objetos no banco de dados serão criados após a execução do comando Update-Database:







A API Web está pronta, executando o aplicativo no IIS Express local, podemos testar o controlador.



Testando WebAPI



Vamos criar uma nova coleção de solicitações no Postman chamada TodoWebAPI:







Como nosso banco de dados está vazio, vamos testar a criação de uma nova tarefa primeiro. No controlador, o método Create () é responsável pela criação de tarefas, que irão processar uma requisição HTTP enviada pelo método POST e conter um objeto TodoItem serializado em formato JSON no corpo da requisição. O atributo [FromBody] antes do parâmetro todoItem no método Create () diz à estrutura MVC para desserializar o objeto TodoItem do corpo da solicitação e passá-lo como um parâmetro para o método. Vamos criar uma solicitação no Postman que enviará uma solicitação ao webAPI para criar uma nova tarefa:







O método Create () após a criação bem-sucedida da tarefa redireciona a solicitação para o método Get () com o alias "GetTodoItem" e passa o Id da tarefa recém-criada como um parâmetro, como resultado, receberemos o objeto de tarefa criado no formato JSON em resposta à solicitação.



Enviando uma solicitação HTTP usando o método PUT e especificando um objeto já criado no URI Id (https: // localhost: 44370 / api / todo / 1), e passando um objeto com algumas alterações no formato JSON no corpo da solicitação, vamos alterar este objeto no banco de dados :







Com uma solicitação HTTP com o método GET sem especificar parâmetros, receberemos todos os objetos no banco de dados:







Uma solicitação HTTP com o método DELETE e especificando o ID do objeto no URI (https: // localhost: 44370 / api / todo / 2), excluirá o objeto do banco de dados e retornará JSON com tarefa remota:







Isso é tudo, na próxima parte implementaremos a interface do usuário usando a estrutura Angular JavaScript.



All Articles