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





Introducción



Un curso corto sobre la creación de una aplicación web simple utilizando tecnologías ASP.NET Core, Entity Framework, Microsoft SQL Server DBMS y Angular framework. Probaremos la API web a través de la aplicación Postman .



El curso consta de varias partes:



  1. Cree API web con ASP.NET Web API y Entity Framework Core.
  2. Implementación de la interfaz de usuario en Angular.
  3. Añadiendo autenticación a la aplicación.
  4. Amplíe el modelo de aplicación y explore características adicionales de Entity Framework.


Parte 1. Cree API web con ASP.NET Web API y Entity Framework Core



Como ejemplo, consideraremos la aplicación ahora clásica de una lista de tareas pendientes. Para desarrollar la aplicación, usaré Visual Studio 2019 (en Visual Studio 2017, el proceso es similar).



Creación de proyectos



Cree un nuevo proyecto de aplicación web ASP.NET Core en Visual Studio:







nombre la aplicación y especifique la ruta al directorio con el proyecto:







y seleccione la plantilla de aplicación API:







Modelo



Creemos un catálogo de Modelos y agreguemos el TodoItem.cs de primera clase al nuevo catálogo, cuyos objetos describirán algunas de las tareas de la lista de tareas pendientes en la aplicación:



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


Usaremos Sql Server como DBMS, y se accederá a la base de datos a través de Entity Framework Core, y primero instalaremos el marco a través del administrador de paquetes NuGet integrado:







Un enfoque para trabajar con Entity Framework es el enfoque "Code-First". La esencia del enfoque es que con base en el modelo de aplicación (en nuestro caso, el modelo representa una sola clase - TodoItem.cs), se forma la estructura de la base de datos (tablas, claves primarias, enlaces), todo este trabajo ocurre "detrás de escena" y directamente con No trabajamos con SQL. Un requisito previo para la clase modelo es la presencia de un campo de clave principal; de forma predeterminada, Entity Framework busca un campo entero en cuyo nombre hay una subcadena "id" y forma una clave principal basada en él. Puede anular este comportamiento usando atributos personalizados o usando las capacidades de Fluent API.



El componente principal al trabajar con Entity Framework es la clase de contexto de la base de datos, a través de la cual se accede realmente a los datos de las tablas:



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


La clase base DbContext crea el contexto de la base de datos y proporciona acceso a la funcionalidad de Entity Framework.



Usaremos SQL Server 2017 Express para almacenar datos de aplicaciones . Las cadenas de conexión se almacenan en un archivo JSON llamado appsettings.json:



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


A continuación, debe modificar la clase Startup.cs agregando el siguiente código al método ConfigureServices ():



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


El método AddDbContext () configura los servicios proporcionados por Entity Framework Core para la clase de contexto de la base de datos EFTodoDBContext. El argumento del método AddDbContext () es una expresión lambda que recibe un objeto de opciones que configura la base de datos para la clase de contexto. En este caso, la base de datos se configura usando el método UseSqlServer () y especificando una cadena de conexión.



Definamos las operaciones básicas para trabajar con tareas en la interfaz de ITodoRepository:



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


Esta interfaz nos permite no pensar en la implementación específica del data warehouse, tal vez no nos decidimos exactamente por la elección de un marco DBMS u ORM, ahora no importa, la clase que describe el acceso a los datos heredará de esta interfaz.

Implementemos un repositorio, que, como se mencionó anteriormente, heredará de ITodoRepository y usará EFTodoDBContext como fuente de datos:



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



El controlador, cuya implementación se describirá a continuación, no sabrá nada sobre el contexto de datos de EFTodoDBContext, sino que solo utilizará la interfaz ITodoRepository en su trabajo, lo que permite cambiar la fuente de datos sin cambiar el controlador. Este enfoque de Adam Freeman en su libro "Entity Framework Core 2 para ASP.NET Core MVC para profesionales" llamó el patrón de "Almacenamiento".



El controlador implementa controladores para los métodos de solicitud HTTP estándar: GET, POST, PUT, DELETE, que cambiarán el estado de nuestras tareas descritas en la clase TodoItem.cs.



Agregue la clase TodoController.cs al directorio Controllers con el siguiente contenido:



[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);
    }
 }


Antes de la definición de la clase, hay un atributo que describe la plantilla de ruta para acceder al controlador: [Route ("api / [controller]")]. TodoController será accesible a través de la siguiente ruta: https: // <host ip>: <port> / api / todo. [Controlador] especifica el nombre de la clase del controlador en minúsculas, omitiendo la parte "Controlador".



Antes de cada definición de método en TodoController, se especifica un atributo especial de la forma: [<método HTTP> ("parámetro", Nombre = "alias de método")]. El atributo determina qué solicitud HTTP será procesada por este método, el parámetro que se pasa en el URI de la solicitud y el alias del método con el que se puede reenviar la solicitud. Si no especifica el atributo, entonces, de forma predeterminada, el marco MVC intentará encontrar el método más adecuado en el controlador para procesar la solicitud en función del nombre del método y los parámetros especificados en la solicitud, por lo que si no especifica un atributo para el método Get () en el controlador TodoController, entonces en una solicitud HTTP usando el método GET: https: // <host ip>: <port> / api / todo, la infraestructura definirá el método Get () del controlador para procesar la solicitud.



En su constructor, el controlador recibe una referencia a un objeto de tipo ITodoRepository, pero hasta ahora la infraestructura MVC no sabe qué objeto sustituir al crear el controlador. Necesitamos crear un servicio que resuelva de manera única esta dependencia, para esto haremos algunos cambios en la clase Startup.cs agregando el siguiente código al método ConfigureServices ():



services.AddTransient<ITodoRepository, EFTodoRepository>();


El método AddTransient <ITodoRepository, EFTodoRepository> () define un servicio que crea una nueva instancia de la clase EFTodoRepository cada vez que se requiere una instancia del tipo ITodoRepository, por ejemplo en un controlador.



El código completo para la clase 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();
        });
    }
 }


Migraciones



Para que Entity Framework genere la base de datos y las tablas a partir del modelo, debe utilizar el proceso de migración de la base de datos. Las migraciones son un grupo de comandos que prepara la base de datos para trabajar con Entity Framework. Se utilizan para crear y sincronizar la base de datos. Los comandos se pueden ejecutar tanto en Package Manager Console como en Power Shell (Developer Power Shell). Usaremos la Consola del Administrador de paquetes, para trabajar con Entity Framework necesitaremos instalar el paquete Microsoft.EntityFrameworkCore.Tools:







Inicie la Consola del Administrador de paquetes y ejecute el comando Add-Migration Initial :











Aparecerá un nuevo directorio en el proyecto - Migraciones, en el que se almacenarán las clases de migración, sobre la base de qué objetos en la base de datos se crearán después de que se ejecute el comando Actualizar-Base de datos:







API web está lista, ejecutando la aplicación en IIS Express local, podemos probar el funcionamiento del controlador.



Prueba de WebAPI



Creemos una nueva colección de solicitudes en Postman llamada TodoWebAPI:







dado que nuestra base de datos está vacía, primero probemos la creación de una nueva tarea. En el controlador, el método Create () es responsable de crear tareas, que procesará una solicitud HTTP enviada por el método POST y contendrá un objeto TodoItem serializado en formato JSON en el cuerpo de la solicitud. El atributo [FromBody] antes del parámetro todoItem en el método Create () le dice al marco MVC que deserialice el objeto TodoItem del cuerpo de la solicitud y lo pase como parámetro al método. Creemos una solicitud en Postman que enviará una solicitud a la webAPI para crear una nueva tarea:







El método Create () después de la creación exitosa de la tarea redirige la solicitud al método Get () con el alias "GetTodoItem" y pasa el Id de la tarea recién creada como parámetro, como resultado de lo cual recibiremos el objeto de tarea creado en formato JSON en respuesta a la solicitud.



Al enviar una solicitud HTTP usando el método PUT y especificando un objeto ya creado en el URI Id (https: // localhost: 44370 / api / todo / 1), y en el cuerpo de la solicitud pasando un objeto con algunos cambios en formato JSON, cambiaremos este objeto en la base de datos :







Con una solicitud HTTP con el método GET sin especificar parámetros, recibiremos todos los objetos en la base de datos:







Una solicitud HTTP con el método DELETE y especificando el Id del objeto en la URI (https: // localhost: 44370 / api / todo / 2), eliminará el objeto de la base de datos y devolverá JSON con tarea remota:







Eso es todo, en la siguiente parte implementaremos la interfaz de usuario utilizando el marco de JavaScript Angular.



All Articles