Tutorial: Creación de una API web con ASP.NET Core

Nota:

Esta no es la versión más reciente de este artículo. Para la versión actual, consulte la versión ASP.NET Core 8.0 de este artículo.

Por Rick Anderson y Kirk Larkin

En este tutorial se enseñan los conceptos básicos de la creación de una API web basada en controlador que usa una base de datos. Otro enfoque para crear API en ASP.NET Core consiste en crear API mínimas. Para obtener ayuda para elegir entre las API mínimas y las API basadas en controlador, consulte Introducción a las API. Para ver un tutorial sobre cómo crear una API mínima, consulte Tutorial: Creación de una API mínima con ASP.NET Core.

Información general

En este tutorial se crea la siguiente API:

API Descripción Cuerpo de la solicitud Cuerpo de la respuesta
GET /api/todoitems Obtener todas las tareas pendientes None Matriz de tareas pendientes
GET /api/todoitems/{id} Obtener un elemento por identificador None Tarea pendiente
POST /api/todoitems Incorporación de un nuevo elemento Tarea pendiente Tarea pendiente
PUT /api/todoitems/{id} Actualizar un elemento existente Tarea pendiente None
DELETE /api/todoitems/{id}     Eliminar un elemento None None

En el diagrama siguiente, se muestra el diseño de la aplicación.

The client is represented by a box on the left. It submits a request and receives a response from the application, a box drawn on the right. Within the application box, three boxes represent the controller, the model, and the data access layer. The request comes into the application's controller, and read/write operations occur between the controller and the data access layer. The model is serialized and returned to the client in the response.

Requisitos previos

Creación de un proyecto web

  • En el menú Archivo, seleccione Nuevo>Proyecto.
  • Escriba API web en el cuadro de búsqueda.
  • Seleccione la plantilla API web de ASP.NET Core y seleccione Siguiente.
  • En el cuadro de diálogo Configurar el nuevo proyecto, asigne al proyecto el nombre TodoApi y seleccione Siguiente.
  • En el cuadro de diálogo Información adicional:
    • Confirme que el Marco es .NET 8.0 (Compatibilidad a largo plazo).
    • Confirme que la casilla Use controllers(uncheck to use minimal APIs) [Usar controladores (desactivar para usar API mínimas)] está activada.
    • Confirme que la casilla Habilitar compatibilidad con OpenAPI está activada.
    • Seleccione Crear.

Adición de un paquete NuGet

Se debe agregar un paquete NuGet para admitir la base de datos que se usa en este tutorial.

  • En el menú Herramientas, seleccione Administrador de paquetes NuGet > Administrar paquetes NuGet para la solución.
  • Seleccione la pestaña Examinar.
  • Escriba Microsoft.EntityFrameworkCore.InMemory en el cuadro de búsqueda y, después, seleccione Microsoft.EntityFrameworkCore.InMemory.
  • Active la casilla Proyecto en el panel derecho y, después, seleccione Instalar.

Nota

Para obtener instrucciones sobre cómo agregar paquetes a aplicaciones .NET, consulte los artículos de Instalación y administración de paquetes en Flujo de trabajo de consumo de paquetes (NuGet documentación). Confirme las versiones correctas del paquete en NuGet.org.

Prueba del proyecto

La plantilla de proyecto crea una API WeatherForecast compatible con Swagger.

Presione Ctrl+F5 para ejecutarla sin el depurador.

Visual Studio muestra el siguiente cuadro de diálogo cuando un proyecto aún no está configurado para usar SSL:

This project is configured to use SSL. To avoid SSL warnings in the browser you can choose to trust the self-signed certificate that IIS Express has generated. Would you like to trust the IIS Express SSL certificate?

Haga clic en si confía en el certificado SSL de IIS Express.

Se muestra el cuadro de diálogo siguiente:

Security warning dialog

Si acepta confiar en el certificado de desarrollo, seleccione .

Para obtener información sobre cómo confiar en el explorador Firefox, consulte Error de certificado SEC_ERROR_INADEQUATE_KEY_USAGE de Firefox.

Visual Studio inicia el explorador predeterminado y navega hasta https://localhost:<port>/swagger/index.html, donde <port> es un número de puerto elegido aleatoriamente en la creación del proyecto.

Se abre la página de Swagger /swagger/index.html. Seleccione GET>Try it out>Execute (GET > Probar > Ejecutar). La página muestra lo siguiente:

  • Comando de Curl para probar la API WeatherForecast
  • Dirección URL para probar la API WeatherForecast
  • Código de respuesta, cuerpo y encabezados
  • Un cuadro de lista desplegable con los tipos de elementos multimedia y el esquema y valor de ejemplo.

Si no aparece la página Swagger, consulte este problema de GitHub.

Swagger se usa para generar una documentación útil y páginas de ayuda para API web. En este tutorial se usa Swagger para probar la aplicación. Para más información sobre Swagger, vea Documentación de la API web de ASP.NET Core con Swagger/OpenAPI.

Copie y pegue la URL de solicitud en el explorador: https://localhost:<port>/weatherforecast.

Se devuelve un JSON similar al siguiente ejemplo:

[
    {
        "date": "2019-07-16T19:04:05.7257911-06:00",
        "temperatureC": 52,
        "temperatureF": 125,
        "summary": "Mild"
    },
    {
        "date": "2019-07-17T19:04:05.7258461-06:00",
        "temperatureC": 36,
        "temperatureF": 96,
        "summary": "Warm"
    },
    {
        "date": "2019-07-18T19:04:05.7258467-06:00",
        "temperatureC": 39,
        "temperatureF": 102,
        "summary": "Cool"
    },
    {
        "date": "2019-07-19T19:04:05.7258471-06:00",
        "temperatureC": 10,
        "temperatureF": 49,
        "summary": "Bracing"
    },
    {
        "date": "2019-07-20T19:04:05.7258474-06:00",
        "temperatureC": -1,
        "temperatureF": 31,
        "summary": "Chilly"
    }
]

Incorporación de una clase de modelo

Un modelo es un conjunto de clases que representan los datos que la aplicación administra. El modelo para esta aplicación es la clase TodoItem.

  • En el Explorador de soluciones, haga clic con el botón derecho en el proyecto. Seleccione Agregar>Nueva carpeta. Asigne a la carpeta el nombre Models.
  • Haga clic con el botón derecho en la carpeta Models y seleccione Agregar>Clase. Asigne a la clase el nombre TodoItem y seleccione Agregar.
  • Reemplace el código de plantilla por lo siguiente:
namespace TodoApi.Models;

public class TodoItem
{
    public long Id { get; set; }
    public string? Name { get; set; }
    public bool IsComplete { get; set; }
}

La propiedad Id funciona como clave única en una base de datos relacional.

Las clases de modelo pueden ir en cualquier lugar del proyecto, pero, por convención, se usa la carpeta Models .

Incorporación de un contexto de base de datos

El contexto de base de datos es la clase principal que coordina la funcionalidad de Entity Framework para un modelo de datos. Esta clase se crea derivándola de la clase Microsoft.EntityFrameworkCore.DbContext.

  • Haga clic con el botón derecho en la carpeta Models y seleccione Agregar>Clase. Asigne a la clase el nombre TodoContext y haga clic en Agregar.
  • Escriba el siguiente código:

    using Microsoft.EntityFrameworkCore;
    
    namespace TodoApi.Models;
    
    public class TodoContext : DbContext
    {
        public TodoContext(DbContextOptions<TodoContext> options)
            : base(options)
        {
        }
    
        public DbSet<TodoItem> TodoItems { get; set; } = null!;
    }
    

Registro del contexto de base de datos

En ASP.NET Core, los servicios (como el contexto de la base de datos) deben registrarse con el contenedor de inserción de dependencias (DI). El contenedor proporciona el servicio a los controladores.

Actualice Program.cs con el siguiente código resaltado:

using Microsoft.EntityFrameworkCore;
using TodoApi.Models;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
builder.Services.AddDbContext<TodoContext>(opt =>
    opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

El código anterior:

  • Agrega directivas using.
  • Agrega el contexto de base de datos para el contenedor de DI.
  • Especifica que el contexto de base de datos usará una base de datos en memoria.

Scaffolding de un controlador

  • Haga clic con el botón derecho en la carpeta Controllers.

  • Seleccione Agregar>Nuevo elemento con scaffolding.

  • Seleccione Controlador de API con acciones mediante Entity Framework y, después, seleccione Agregar.

  • En el cuadro de diálogo Add API Controller with actions, using Entity Framework (Agregar controlador de API con acciones mediante Entity Framework):

    • Seleccione TodoItem (TodoApi.Models) en Clase de modelo.
    • Seleccione TodoContext (TodoApi.Models) en Clase de contexto de datos.
    • Seleccione Agregar.

    Si se produce un error en la operación de scaffolding, seleccione Agregar para probar el scaffolding una segunda vez.

El código generado:

  • Marca la clase con el atributo [ApiController]. Este atributo indica que el controlador responde a las solicitudes de la API web. Para información sobre comportamientos específicos que permite el atributo, vea Creación de una API web con ASP.NET Core.
  • Utiliza la inserción de dependencias para insertar el contexto de base de datos (TodoContext) en el controlador. El contexto de base de datos se usa en cada uno de los métodos CRUD del controlador.

Las plantillas de ASP.NET Core para:

  • Controladores con vistas incluyen [action] en la plantilla de ruta.
  • Controladores de API no incluyen [action] en la plantilla de ruta.

Cuando el token [action] no está en la plantilla de ruta, el nombre de la acción (nombre del método) no se incluye en el punto de conexión. Es decir, el nombre del método asociado a la acción no se usa en la ruta coincidente.

Actualización del método create de PostTodoItem

Actualice la instrucción "return" en PostTodoItem para usar el operador nameof:

[HttpPost]
public async Task<ActionResult<TodoItem>> PostTodoItem(TodoItem todoItem)
{
    _context.TodoItems.Add(todoItem);
    await _context.SaveChangesAsync();

    //    return CreatedAtAction("GetTodoItem", new { id = todoItem.Id }, todoItem);
    return CreatedAtAction(nameof(GetTodoItem), new { id = todoItem.Id }, todoItem);
}

El código anterior es un método HTTP POST, indicado por el atributo [HttpPost]. El método obtiene el valor de TodoItem del cuerpo de la solicitud HTTP.

Para más información, vea Enrutamiento mediante atributos con atributos Http[Verb].

El método CreatedAtAction realiza las acciones siguientes:

  • Devuelve un código de estado HTTP 201 cuando se ha ejecutado correctamente. HTTP 201 es la respuesta estándar de un método HTTP POST que crea un recurso en el servidor.
  • Agrega un encabezado Location a la respuesta. El encabezado Location especifica el URI de la tarea pendiente recién creada. Para obtener más información, consulte 10.2.2 201 creado.
  • Hace referencia a la acción GetTodoItem para crear el identificador URI del encabezado Location. La palabra clave nameof de C# se usa para evitar que se codifique de forma rígida el nombre de acción en la llamada a CreatedAtAction.

Prueba de PostTodoItem

  • Presione Ctrl+F5 para ejecutar la aplicación.

  • En la ventana del explorador Swagger, seleccione POST /api/TodoItems y, a continuación, seleccione Probarlo.

  • En la ventana Cuerpo de la solicitud, actualice JSON. Por ejemplo,

    {
      "name": "walk dog",
      "isComplete": true
    }
    
  • Seleccione Ejecutar.

    Swagger POST

Prueba del URI del encabezado de ubicación

En el POST anterior, la interfaz de usuario de Swagger muestra el encabezado de ubicación en Encabezados de respuesta. Por ejemplo, location: https://localhost:7260/api/TodoItems/1. El encabezado de ubicación muestra el URI al recurso creado.

Para probar el encabezado de ubicación:

  • En la ventana del explorador Swagger, seleccione GET /api/TodoItems/{id} y, a continuación, seleccione Probarlo.

  • Escriba 1 en el cuadro de entrada id y, a continuación, seleccione Ejecutar.

    Swagger GET

Examen de los métodos GET

Se implementan dos puntos de conexión GET:

  • GET /api/todoitems
  • GET /api/todoitems/{id}

En la sección anterior se muestra un ejemplo de la ruta /api/todoitems/{id}.

Siga las instrucciones POST para agregar otro elemento de tareas pendientes y, a continuación, pruebe la ruta /api/todoitems mediante Swagger.

Esta aplicación utiliza una base de datos en memoria. Si la aplicación se detiene y se inicia, la solicitud GET precedente no devolverá ningún dato. Si no se devuelve ningún dato, publique los datos en la aplicación con POST.

Enrutamiento y rutas URL

El atributo [HttpGet] indica un método que responde a una solicitud HTTP GET. La ruta de dirección URL para cada método se construye como sigue:

  • Comience por la cadena de plantilla en el atributo Route del controlador:

    [Route("api/[controller]")]
    [ApiController]
    public class TodoItemsController : ControllerBase
    
  • Reemplace [controller] por el nombre del controlador, que convencionalmente es el nombre de clase de controlador sin el sufijo "Controller". En este ejemplo, el nombre de clase de controlador es TodoItemsController; por tanto, el nombre del controlador es "TodoItems". El enrutamiento en ASP.NET Core no distingue entre mayúsculas y minúsculas.

  • Si el atributo [HttpGet] tiene una plantilla de ruta (por ejemplo, [HttpGet("products")]), anéxela a la ruta de acceso. En este ejemplo no se usa una plantilla. Para más información, vea Enrutamiento mediante atributos con atributos Http[Verb].

En el siguiente método GetTodoItem, "{id}" es una variable de marcador de posición correspondiente al identificador único de la tarea pendiente. Al invocar a GetTodoItem, el valor "{id}" de la URL se proporciona al método en su parámetro id.

[HttpGet("{id}")]
public async Task<ActionResult<TodoItem>> GetTodoItem(long id)
{
    var todoItem = await _context.TodoItems.FindAsync(id);

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

    return todoItem;
}

Valores devueltos

El tipo de valor devuelto de los métodos GetTodoItems y GetTodoItem es ActionResult<T> type. ASP.NET Core serializa automáticamente el objeto a JSON y escribe el JSON en el cuerpo del mensaje de respuesta. El código de respuesta de este tipo de valor devuelto es 200 OK, suponiendo que no haya ninguna excepción no controlada. Las excepciones no controladas se convierten en errores 5xx.

Los tipos de valores devueltos ActionResult pueden representar una gama amplia de códigos de estado HTTP. Por ejemplo, GetTodoItem puede devolver dos valores de estado diferentes:

  • Si no hay ningún elemento que coincida con el identificador solicitado, el método devolverá un código de error de estado 404NotFound.
  • En caso contrario, el método devuelve 200 con un cuerpo de respuesta JSON. La devolución de item genera una respuesta HTTP 200.

Método PutTodoItem

Examine el método PutTodoItem:

[HttpPut("{id}")]
public async Task<IActionResult> PutTodoItem(long id, TodoItem todoItem)
{
    if (id != todoItem.Id)
    {
        return BadRequest();
    }

    _context.Entry(todoItem).State = EntityState.Modified;

    try
    {
        await _context.SaveChangesAsync();
    }
    catch (DbUpdateConcurrencyException)
    {
        if (!TodoItemExists(id))
        {
            return NotFound();
        }
        else
        {
            throw;
        }
    }

    return NoContent();
}

PutTodoItem es similar a PostTodoItem, salvo por el hecho de que usa HTTP PUT. La respuesta es 204 Sin contenido. Según la especificación HTTP, una solicitud PUT requiere que el cliente envíe toda la entidad actualizada, no solo los cambios. Para admitir actualizaciones parciales, use HTTP PATCH.

Prueba del método PutTodoItem

En este ejemplo se usa una base de datos en memoria que se debe inicializar cada vez que se inicia la aplicación. Debe haber un elemento en la base de datos antes de que realice una llamada PUT. Llame a GET para asegurarse de que hay un elemento en la base de datos antes de realizar una llamada PUT.

Con la interfaz de usuario de Swagger, use el botón PUT para actualizar el valor TodoItem que tiene el identificador = 1 y establezca su nombre en "feed fish". Observe que la respuesta es HTTP 204 No Content.

Método DeleteTodoItem

Examine el método DeleteTodoItem:

[HttpDelete("{id}")]
public async Task<IActionResult> DeleteTodoItem(long id)
{
    var todoItem = await _context.TodoItems.FindAsync(id);
    if (todoItem == null)
    {
        return NotFound();
    }

    _context.TodoItems.Remove(todoItem);
    await _context.SaveChangesAsync();

    return NoContent();
}

Prueba del método DeleteTodoItem

Use la interfaz de usuario de Swagger para eliminar el valor TodoItem que tiene el identificador = 1. Observe que la respuesta es HTTP 204 No Content.

Prueba con otras herramientas

Hay muchas otras herramientas que se pueden usar para probar las API web, por ejemplo:

Para más información, consulte:

Prevención del exceso de publicación

Actualmente, la aplicación de ejemplo expone todo el objeto TodoItem. Las aplicaciones de producción suelen limitar los datos que se escriben y se devuelven mediante un subconjunto del modelo. Hay varias razones para ello y la seguridad es una de las principales. El subconjunto de un modelo se suele conocer como un objeto de transferencia de datos (DTO), modelo de entrada o modelo de vista. En este tutorial, se usa DTO.

Se puede usar un DTO para:

  • Evitar el exceso de publicación.
  • Ocultar las propiedades que los clientes no deben ver.
  • Omitir algunas propiedades para reducir el tamaño de la carga.
  • Acoplar los gráficos de objetos que contienen objetos anidados. Los gráficos de objetos acoplados pueden ser más cómodos para los clientes.

Para mostrar el enfoque del DTO, actualice la clase TodoItem a fin de que incluya un campo secreto:

namespace TodoApi.Models
{
    public class TodoItem
    {
        public long Id { get; set; }
        public string? Name { get; set; }
        public bool IsComplete { get; set; }
        public string? Secret { get; set; }
    }
}

El campo secreto debe ocultarse en esta aplicación, pero una aplicación administrativa podría decidir exponerlo.

Compruebe que puede publicar y obtener el campo secreto.

Cree un modelo de DTO:

namespace TodoApi.Models;

public class TodoItemDTO
{
    public long Id { get; set; }
    public string? Name { get; set; }
    public bool IsComplete { get; set; }
}

Actualice el valor de TodoItemsController para usar TodoItemDTO:

using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using TodoApi.Models;

namespace TodoApi.Controllers;

[Route("api/[controller]")]
[ApiController]
public class TodoItemsController : ControllerBase
{
    private readonly TodoContext _context;

    public TodoItemsController(TodoContext context)
    {
        _context = context;
    }

    // GET: api/TodoItems
    [HttpGet]
    public async Task<ActionResult<IEnumerable<TodoItemDTO>>> GetTodoItems()
    {
        return await _context.TodoItems
            .Select(x => ItemToDTO(x))
            .ToListAsync();
    }

    // GET: api/TodoItems/5
    // <snippet_GetByID>
    [HttpGet("{id}")]
    public async Task<ActionResult<TodoItemDTO>> GetTodoItem(long id)
    {
        var todoItem = await _context.TodoItems.FindAsync(id);

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

        return ItemToDTO(todoItem);
    }
    // </snippet_GetByID>

    // PUT: api/TodoItems/5
    // To protect from overposting attacks, see https://go.microsoft.com/fwlink/?linkid=2123754
    // <snippet_Update>
    [HttpPut("{id}")]
    public async Task<IActionResult> PutTodoItem(long id, TodoItemDTO todoDTO)
    {
        if (id != todoDTO.Id)
        {
            return BadRequest();
        }

        var todoItem = await _context.TodoItems.FindAsync(id);
        if (todoItem == null)
        {
            return NotFound();
        }

        todoItem.Name = todoDTO.Name;
        todoItem.IsComplete = todoDTO.IsComplete;

        try
        {
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException) when (!TodoItemExists(id))
        {
            return NotFound();
        }

        return NoContent();
    }
    // </snippet_Update>

    // POST: api/TodoItems
    // To protect from overposting attacks, see https://go.microsoft.com/fwlink/?linkid=2123754
    // <snippet_Create>
    [HttpPost]
    public async Task<ActionResult<TodoItemDTO>> PostTodoItem(TodoItemDTO todoDTO)
    {
        var todoItem = new TodoItem
        {
            IsComplete = todoDTO.IsComplete,
            Name = todoDTO.Name
        };

        _context.TodoItems.Add(todoItem);
        await _context.SaveChangesAsync();

        return CreatedAtAction(
            nameof(GetTodoItem),
            new { id = todoItem.Id },
            ItemToDTO(todoItem));
    }
    // </snippet_Create>

    // DELETE: api/TodoItems/5
    [HttpDelete("{id}")]
    public async Task<IActionResult> DeleteTodoItem(long id)
    {
        var todoItem = await _context.TodoItems.FindAsync(id);
        if (todoItem == null)
        {
            return NotFound();
        }

        _context.TodoItems.Remove(todoItem);
        await _context.SaveChangesAsync();

        return NoContent();
    }

    private bool TodoItemExists(long id)
    {
        return _context.TodoItems.Any(e => e.Id == id);
    }

    private static TodoItemDTO ItemToDTO(TodoItem todoItem) =>
       new TodoItemDTO
       {
           Id = todoItem.Id,
           Name = todoItem.Name,
           IsComplete = todoItem.IsComplete
       };
}

Compruebe que no puede publicar ni obtener el campo secreto.

Llamar a la API web con JavaScript

Consulte Tutorial: Llamada a una API web de ASP.NET Core con JavaScript.

Serie de vídeos de API web

Vea Vídeo: Serie para principiantes: API web.

Patrones de aplicación web confiable

Consulte El patrón de aplicación web confiable para .NETen los vídeos de YouTube y el artículo para obtener instrucciones sobre cómo crear una aplicación moderna, confiable, que se puede probar, rentable y escalable de ASP.NET Core, ya sea desde cero o refactorizando una aplicación existente.

Agregar compatibilidad con la autenticación a una API web

ASP.NET Core Identity agrega la funcionalidad de inicio de sesión de la interfaz de usuario (IU) a las aplicaciones web de ASP.NET Core. Para proteger las API web y las SPA, use una de las siguientes opciones:

Duende Identity Server es un marco de OpenID Connect y OAuth 2.0 para ASP.NET Core. Duende Identity Server permite las siguientes características de seguridad:

  • Autenticación como servicio (AaaS)
  • Inicio de sesión único (SSO) mediante varios tipos de aplicaciones
  • Control de acceso para API
  • Federation Gateway

Importante

Duende Software puede requerir que pague una tarifa de licencia para el uso en producción de Duende Identity Server. Para más información, vea Migración de ASP.NET Core 5.0 a 6.0.

Para más información, consulte la documentación de Duende Identity Server (sitio web de Duende Software).

Publicar en Azure

Para obtener más información sobre la implementación en Azure, consulte Inicio rápido: Implementación de una aplicación web de ASP.NET.

Recursos adicionales

Vea o descargue el código de ejemplo para este tutorial. Vea cómo descargarlo.

Para obtener más información, vea los siguientes recursos:

En este tutorial se enseñan los conceptos básicos de la creación de una API web basada en controlador que usa una base de datos. Otro enfoque para crear API en ASP.NET Core consiste en crear API mínimas. Para obtener ayuda para elegir entre las API mínimas y las API basadas en controlador, consulte Introducción a las API. Para ver un tutorial sobre cómo crear una API mínima, consulte Tutorial: Creación de una API mínima con ASP.NET Core.

Información general

En este tutorial se crea la siguiente API:

API Descripción Cuerpo de la solicitud Cuerpo de la respuesta
GET /api/todoitems Obtener todas las tareas pendientes None Matriz de tareas pendientes
GET /api/todoitems/{id} Obtener un elemento por identificador None Tarea pendiente
POST /api/todoitems Incorporación de un nuevo elemento Tarea pendiente Tarea pendiente
PUT /api/todoitems/{id} Actualizar un elemento existente Tarea pendiente None
DELETE /api/todoitems/{id}     Eliminar un elemento None None

En el diagrama siguiente, se muestra el diseño de la aplicación.

The client is represented by a box on the left. It submits a request and receives a response from the application, a box drawn on the right. Within the application box, three boxes represent the controller, the model, and the data access layer. The request comes into the application's controller, and read/write operations occur between the controller and the data access layer. The model is serialized and returned to the client in the response.

Requisitos previos

Creación de un proyecto web

  • En el menú Archivo, seleccione Nuevo>Proyecto.
  • Escriba API web en el cuadro de búsqueda.
  • Seleccione la plantilla API web de ASP.NET Core y seleccione Siguiente.
  • En el cuadro de diálogo Configurar el nuevo proyecto, asigne al proyecto el nombre TodoApi y seleccione Siguiente.
  • En el cuadro de diálogo Información adicional:
    • Confirme que Framework es .NET 7.0 (o posterior).
    • Confirme que la casilla Use controllers(uncheck to use minimal APIs) [Usar controladores (desactivar para usar API mínimas)] está activada.
    • Seleccione Crear.

Nota

Para obtener instrucciones sobre cómo agregar paquetes a aplicaciones .NET, consulte los artículos de Instalación y administración de paquetes en Flujo de trabajo de consumo de paquetes (NuGet documentación). Confirme las versiones correctas del paquete en NuGet.org.

Prueba del proyecto

La plantilla de proyecto crea una API WeatherForecast compatible con Swagger.

Presione Ctrl+F5 para ejecutarla sin el depurador.

Visual Studio muestra el siguiente cuadro de diálogo cuando un proyecto aún no está configurado para usar SSL:

This project is configured to use SSL. To avoid SSL warnings in the browser you can choose to trust the self-signed certificate that IIS Express has generated. Would you like to trust the IIS Express SSL certificate?

Haga clic en si confía en el certificado SSL de IIS Express.

Se muestra el cuadro de diálogo siguiente:

Security warning dialog

Si acepta confiar en el certificado de desarrollo, seleccione .

Para obtener información sobre cómo confiar en el explorador Firefox, consulte Error de certificado SEC_ERROR_INADEQUATE_KEY_USAGE de Firefox.

Visual Studio inicia el explorador predeterminado y navega hasta https://localhost:<port>/swagger/index.html, donde <port> es un número de puerto elegido aleatoriamente.

Se abre la página de Swagger /swagger/index.html. Seleccione GET>Try it out>Execute (GET > Probar > Ejecutar). La página muestra lo siguiente:

  • Comando de Curl para probar la API WeatherForecast
  • Dirección URL para probar la API WeatherForecast
  • Código de respuesta, cuerpo y encabezados
  • Un cuadro de lista desplegable con los tipos de elementos multimedia y el esquema y valor de ejemplo.

Si no aparece la página Swagger, consulte este problema de GitHub.

Swagger se usa para generar una documentación útil y páginas de ayuda para API web. Este tutorial se centra en cómo crear una API web. Para más información sobre Swagger, vea Documentación de la API web de ASP.NET Core con Swagger/OpenAPI.

Copie y pegue la URL de solicitud en el explorador: https://localhost:<port>/weatherforecast.

Se devuelve un JSON similar al siguiente ejemplo:

[
    {
        "date": "2019-07-16T19:04:05.7257911-06:00",
        "temperatureC": 52,
        "temperatureF": 125,
        "summary": "Mild"
    },
    {
        "date": "2019-07-17T19:04:05.7258461-06:00",
        "temperatureC": 36,
        "temperatureF": 96,
        "summary": "Warm"
    },
    {
        "date": "2019-07-18T19:04:05.7258467-06:00",
        "temperatureC": 39,
        "temperatureF": 102,
        "summary": "Cool"
    },
    {
        "date": "2019-07-19T19:04:05.7258471-06:00",
        "temperatureC": 10,
        "temperatureF": 49,
        "summary": "Bracing"
    },
    {
        "date": "2019-07-20T19:04:05.7258474-06:00",
        "temperatureC": -1,
        "temperatureF": 31,
        "summary": "Chilly"
    }
]

Incorporación de una clase de modelo

Un modelo es un conjunto de clases que representan los datos que la aplicación administra. El modelo para esta aplicación es la clase TodoItem.

  • En el Explorador de soluciones, haga clic con el botón derecho en el proyecto. Seleccione Agregar>Nueva carpeta. Asigne a la carpeta el nombre Models.
  • Haga clic con el botón derecho en la carpeta Models y seleccione Agregar>Clase. Asigne a la clase el nombre TodoItem y seleccione Agregar.
  • Reemplace el código de plantilla por lo siguiente:
namespace TodoApi.Models;

public class TodoItem
{
    public long Id { get; set; }
    public string? Name { get; set; }
    public bool IsComplete { get; set; }
}

La propiedad Id funciona como clave única en una base de datos relacional.

Las clases de modelo pueden ir en cualquier lugar del proyecto, pero, por convención, se usa la carpeta Models .

Incorporación de un contexto de base de datos

El contexto de base de datos es la clase principal que coordina la funcionalidad de Entity Framework para un modelo de datos. Esta clase se crea derivándola de la clase Microsoft.EntityFrameworkCore.DbContext.

Adición de paquetes NuGet

  • En el menú Herramientas, seleccione Administrador de paquetes NuGet > Administrar paquetes NuGet para la solución.
  • Seleccione la pestaña Examinar y escriba Microsoft.EntityFrameworkCore.InMemory en el cuadro de búsqueda.
  • En el panel izquierdo, seleccione Microsoft.EntityFrameworkCore.InMemory.
  • Active la casilla Proyecto en el panel derecho y, después, seleccione Instalar.

Adición del contexto de la base de datos TodoContext

  • Haga clic con el botón derecho en la carpeta Models y seleccione Agregar>Clase. Asigne a la clase el nombre TodoContext y haga clic en Agregar.
  • Escriba el siguiente código:

    using Microsoft.EntityFrameworkCore;
    
    namespace TodoApi.Models;
    
    public class TodoContext : DbContext
    {
        public TodoContext(DbContextOptions<TodoContext> options)
            : base(options)
        {
        }
    
        public DbSet<TodoItem> TodoItems { get; set; } = null!;
    }
    

Registro del contexto de base de datos

En ASP.NET Core, los servicios (como el contexto de la base de datos) deben registrarse con el contenedor de inserción de dependencias (DI). El contenedor proporciona el servicio a los controladores.

Actualice Program.cs con el siguiente código resaltado:

using Microsoft.EntityFrameworkCore;
using TodoApi.Models;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
builder.Services.AddDbContext<TodoContext>(opt =>
    opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

El código anterior:

  • Agrega directivas using.
  • Agrega el contexto de base de datos para el contenedor de DI.
  • Especifica que el contexto de base de datos usará una base de datos en memoria.

Scaffolding de un controlador

  • Haga clic con el botón derecho en la carpeta Controllers.

  • Seleccione Agregar>Nuevo elemento con scaffolding.

  • Seleccione Controlador de API con acciones mediante Entity Framework y, después, seleccione Agregar.

  • En el cuadro de diálogo Add API Controller with actions, using Entity Framework (Agregar controlador de API con acciones mediante Entity Framework):

    • Seleccione TodoItem (TodoApi.Models) en Clase de modelo.
    • Seleccione TodoContext (TodoApi.Models) en Clase de contexto de datos.
    • Seleccione Agregar.

    Si se produce un error en la operación de scaffolding, seleccione Agregar para probar el scaffolding una segunda vez.

El código generado:

  • Marca la clase con el atributo [ApiController]. Este atributo indica que el controlador responde a las solicitudes de la API web. Para información sobre comportamientos específicos que permite el atributo, vea Creación de una API web con ASP.NET Core.
  • Utiliza la inserción de dependencias para insertar el contexto de base de datos (TodoContext) en el controlador. El contexto de base de datos se usa en cada uno de los métodos CRUD del controlador.

Las plantillas de ASP.NET Core para:

  • Controladores con vistas incluyen [action] en la plantilla de ruta.
  • Controladores de API no incluyen [action] en la plantilla de ruta.

Cuando el token [action] no está en la plantilla de ruta, el nombre de la acción (nombre del método) no se incluye en el punto de conexión. Es decir, el nombre del método asociado a la acción no se usa en la ruta coincidente.

Actualización del método create de PostTodoItem

Actualice la instrucción "return" en PostTodoItem para usar el operador nameof:

[HttpPost]
public async Task<ActionResult<TodoItem>> PostTodoItem(TodoItem todoItem)
{
    _context.TodoItems.Add(todoItem);
    await _context.SaveChangesAsync();

    //    return CreatedAtAction("GetTodoItem", new { id = todoItem.Id }, todoItem);
    return CreatedAtAction(nameof(GetTodoItem), new { id = todoItem.Id }, todoItem);
}

El código anterior es un método HTTP POST, indicado por el atributo [HttpPost]. El método obtiene el valor de TodoItem del cuerpo de la solicitud HTTP.

Para más información, vea Enrutamiento mediante atributos con atributos Http[Verb].

El método CreatedAtAction realiza las acciones siguientes:

  • Devuelve un código de estado HTTP 201 cuando se ha ejecutado correctamente. HTTP 201 es la respuesta estándar de un método HTTP POST que crea un recurso en el servidor.
  • Agrega un encabezado Location a la respuesta. El encabezado Location especifica el URI de la tarea pendiente recién creada. Para obtener más información, consulte 10.2.2 201 creado.
  • Hace referencia a la acción GetTodoItem para crear el identificador URI del encabezado Location. La palabra clave nameof de C# se usa para evitar que se codifique de forma rígida el nombre de acción en la llamada a CreatedAtAction.

Prueba de PostTodoItem

  • Presione Ctrl+F5 para ejecutar la aplicación.

  • En la ventana del explorador Swagger, seleccione POST /api/TodoItems y, a continuación, seleccione Probarlo.

  • En la ventana Cuerpo de la solicitud, actualice JSON. Por ejemplo,

    {
      "name": "walk dog",
      "isComplete": true
    }
    
  • Seleccione Ejecutar.

    Swagger POST

Prueba del URI del encabezado de ubicación

En el POST anterior, la interfaz de usuario de Swagger muestra el encabezado de ubicación en Encabezados de respuesta. Por ejemplo, location: https://localhost:7260/api/TodoItems/1. El encabezado de ubicación muestra el URI al recurso creado.

Para probar el encabezado de ubicación:

  • En la ventana del explorador Swagger, seleccione GET /api/TodoItems/{id} y, a continuación, seleccione Probarlo.

  • Escriba 1 en el cuadro de entrada id y, a continuación, seleccione Ejecutar.

    Swagger GET

Examen de los métodos GET

Se implementan dos puntos de conexión GET:

  • GET /api/todoitems
  • GET /api/todoitems/{id}

En la sección anterior se muestra un ejemplo de la ruta /api/todoitems/{id}.

Siga las instrucciones POST para agregar otro elemento de tareas pendientes y, a continuación, pruebe la ruta /api/todoitems mediante Swagger.

Esta aplicación utiliza una base de datos en memoria. Si la aplicación se detiene y se inicia, la solicitud GET precedente no devolverá ningún dato. Si no se devuelve ningún dato, publique los datos en la aplicación con POST.

Enrutamiento y rutas URL

El atributo [HttpGet] indica un método que responde a una solicitud HTTP GET. La ruta de dirección URL para cada método se construye como sigue:

  • Comience por la cadena de plantilla en el atributo Route del controlador:

    [Route("api/[controller]")]
    [ApiController]
    public class TodoItemsController : ControllerBase
    
  • Reemplace [controller] por el nombre del controlador, que convencionalmente es el nombre de clase de controlador sin el sufijo "Controller". En este ejemplo, el nombre de clase de controlador es TodoItemsController; por tanto, el nombre del controlador es "TodoItems". El enrutamiento en ASP.NET Core no distingue entre mayúsculas y minúsculas.

  • Si el atributo [HttpGet] tiene una plantilla de ruta (por ejemplo, [HttpGet("products")]), anéxela a la ruta de acceso. En este ejemplo no se usa una plantilla. Para más información, vea Enrutamiento mediante atributos con atributos Http[Verb].

En el siguiente método GetTodoItem, "{id}" es una variable de marcador de posición correspondiente al identificador único de la tarea pendiente. Al invocar a GetTodoItem, el valor "{id}" de la URL se proporciona al método en su parámetro id.

[HttpGet("{id}")]
public async Task<ActionResult<TodoItem>> GetTodoItem(long id)
{
    var todoItem = await _context.TodoItems.FindAsync(id);

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

    return todoItem;
}

Valores devueltos

El tipo de valor devuelto de los métodos GetTodoItems y GetTodoItem es ActionResult<T> type. ASP.NET Core serializa automáticamente el objeto a JSON y escribe el JSON en el cuerpo del mensaje de respuesta. El código de respuesta de este tipo de valor devuelto es 200 OK, suponiendo que no haya ninguna excepción no controlada. Las excepciones no controladas se convierten en errores 5xx.

Los tipos de valores devueltos ActionResult pueden representar una gama amplia de códigos de estado HTTP. Por ejemplo, GetTodoItem puede devolver dos valores de estado diferentes:

  • Si no hay ningún elemento que coincida con el identificador solicitado, el método devolverá un código de error de estado 404NotFound.
  • En caso contrario, el método devuelve 200 con un cuerpo de respuesta JSON. La devolución de item genera una respuesta HTTP 200.

Método PutTodoItem

Examine el método PutTodoItem:

[HttpPut("{id}")]
public async Task<IActionResult> PutTodoItem(long id, TodoItem todoItem)
{
    if (id != todoItem.Id)
    {
        return BadRequest();
    }

    _context.Entry(todoItem).State = EntityState.Modified;

    try
    {
        await _context.SaveChangesAsync();
    }
    catch (DbUpdateConcurrencyException)
    {
        if (!TodoItemExists(id))
        {
            return NotFound();
        }
        else
        {
            throw;
        }
    }

    return NoContent();
}

PutTodoItem es similar a PostTodoItem, salvo por el hecho de que usa HTTP PUT. La respuesta es 204 Sin contenido. Según la especificación HTTP, una solicitud PUT requiere que el cliente envíe toda la entidad actualizada, no solo los cambios. Para admitir actualizaciones parciales, use HTTP PATCH.

Prueba del método PutTodoItem

En este ejemplo se usa una base de datos en memoria que se debe inicializar cada vez que se inicia la aplicación. Debe haber un elemento en la base de datos antes de que realice una llamada PUT. Llame a GET para asegurarse de que hay un elemento en la base de datos antes de realizar una llamada PUT.

Con la interfaz de usuario de Swagger, use el botón PUT para actualizar el valor TodoItem que tiene el identificador = 1 y establezca su nombre en "feed fish". Observe que la respuesta es HTTP 204 No Content.

Método DeleteTodoItem

Examine el método DeleteTodoItem:

[HttpDelete("{id}")]
public async Task<IActionResult> DeleteTodoItem(long id)
{
    var todoItem = await _context.TodoItems.FindAsync(id);
    if (todoItem == null)
    {
        return NotFound();
    }

    _context.TodoItems.Remove(todoItem);
    await _context.SaveChangesAsync();

    return NoContent();
}

Prueba del método DeleteTodoItem

Use la interfaz de usuario de Swagger para eliminar el valor TodoItem que tiene el identificador = 1. Observe que la respuesta es HTTP 204 No Content.

Prueba con http-repl, Postman o curl

http-repl, Postman y curl se suelen usar para probar las API. Swagger usa curl y muestra el comando curl que envió.

Para obtener instrucciones sobre estas herramientas, consulte los vínculos siguientes:

Para obtener más información sobre http-repl, vea Prueba de las API web con HttpRepl.

Prevención del exceso de publicación

Actualmente, la aplicación de ejemplo expone todo el objeto TodoItem. Las aplicaciones de producción suelen limitar los datos que se escriben y se devuelven mediante un subconjunto del modelo. Hay varias razones para ello y la seguridad es una de las principales. El subconjunto de un modelo se suele conocer como un objeto de transferencia de datos (DTO), modelo de entrada o modelo de vista. En este tutorial, se usa DTO.

Se puede usar un DTO para:

  • Evitar el exceso de publicación.
  • Ocultar las propiedades que los clientes no deben ver.
  • Omitir algunas propiedades para reducir el tamaño de la carga.
  • Acoplar los gráficos de objetos que contienen objetos anidados. Los gráficos de objetos acoplados pueden ser más cómodos para los clientes.

Para mostrar el enfoque del DTO, actualice la clase TodoItem a fin de que incluya un campo secreto:

namespace TodoApi.Models
{
    public class TodoItem
    {
        public long Id { get; set; }
        public string? Name { get; set; }
        public bool IsComplete { get; set; }
        public string? Secret { get; set; }
    }
}

El campo secreto debe ocultarse en esta aplicación, pero una aplicación administrativa podría decidir exponerlo.

Compruebe que puede publicar y obtener el campo secreto.

Cree un modelo de DTO:

namespace TodoApi.Models;

public class TodoItemDTO
{
    public long Id { get; set; }
    public string? Name { get; set; }
    public bool IsComplete { get; set; }
}

Actualice el valor de TodoItemsController para usar TodoItemDTO:

using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using TodoApi.Models;

namespace TodoApi.Controllers;

[Route("api/[controller]")]
[ApiController]
public class TodoItemsController : ControllerBase
{
    private readonly TodoContext _context;

    public TodoItemsController(TodoContext context)
    {
        _context = context;
    }

    // GET: api/TodoItems
    [HttpGet]
    public async Task<ActionResult<IEnumerable<TodoItemDTO>>> GetTodoItems()
    {
        return await _context.TodoItems
            .Select(x => ItemToDTO(x))
            .ToListAsync();
    }

    // GET: api/TodoItems/5
    // <snippet_GetByID>
    [HttpGet("{id}")]
    public async Task<ActionResult<TodoItemDTO>> GetTodoItem(long id)
    {
        var todoItem = await _context.TodoItems.FindAsync(id);

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

        return ItemToDTO(todoItem);
    }
    // </snippet_GetByID>

    // PUT: api/TodoItems/5
    // To protect from overposting attacks, see https://go.microsoft.com/fwlink/?linkid=2123754
    // <snippet_Update>
    [HttpPut("{id}")]
    public async Task<IActionResult> PutTodoItem(long id, TodoItemDTO todoDTO)
    {
        if (id != todoDTO.Id)
        {
            return BadRequest();
        }

        var todoItem = await _context.TodoItems.FindAsync(id);
        if (todoItem == null)
        {
            return NotFound();
        }

        todoItem.Name = todoDTO.Name;
        todoItem.IsComplete = todoDTO.IsComplete;

        try
        {
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException) when (!TodoItemExists(id))
        {
            return NotFound();
        }

        return NoContent();
    }
    // </snippet_Update>

    // POST: api/TodoItems
    // To protect from overposting attacks, see https://go.microsoft.com/fwlink/?linkid=2123754
    // <snippet_Create>
    [HttpPost]
    public async Task<ActionResult<TodoItemDTO>> PostTodoItem(TodoItemDTO todoDTO)
    {
        var todoItem = new TodoItem
        {
            IsComplete = todoDTO.IsComplete,
            Name = todoDTO.Name
        };

        _context.TodoItems.Add(todoItem);
        await _context.SaveChangesAsync();

        return CreatedAtAction(
            nameof(GetTodoItem),
            new { id = todoItem.Id },
            ItemToDTO(todoItem));
    }
    // </snippet_Create>

    // DELETE: api/TodoItems/5
    [HttpDelete("{id}")]
    public async Task<IActionResult> DeleteTodoItem(long id)
    {
        var todoItem = await _context.TodoItems.FindAsync(id);
        if (todoItem == null)
        {
            return NotFound();
        }

        _context.TodoItems.Remove(todoItem);
        await _context.SaveChangesAsync();

        return NoContent();
    }

    private bool TodoItemExists(long id)
    {
        return _context.TodoItems.Any(e => e.Id == id);
    }

    private static TodoItemDTO ItemToDTO(TodoItem todoItem) =>
       new TodoItemDTO
       {
           Id = todoItem.Id,
           Name = todoItem.Name,
           IsComplete = todoItem.IsComplete
       };
}

Compruebe que no puede publicar ni obtener el campo secreto.

Llamar a la API web con JavaScript

Consulte Tutorial: Llamada a una API web de ASP.NET Core con JavaScript.

Serie de vídeos de API web

Vea Vídeo: Serie para principiantes: API web.

Patrones de aplicación web confiable

Consulte El patrón de aplicación web confiable para .NETen los vídeos de YouTube y el artículo para obtener instrucciones sobre cómo crear una aplicación moderna, confiable, que se puede probar, rentable y escalable de ASP.NET Core, ya sea desde cero o refactorizando una aplicación existente.

Agregar compatibilidad con la autenticación a una API web

ASP.NET Core Identity agrega la funcionalidad de inicio de sesión de la interfaz de usuario (IU) a las aplicaciones web de ASP.NET Core. Para proteger las API web y las SPA, use una de las siguientes opciones:

Duende Identity Server es un marco de OpenID Connect y OAuth 2.0 para ASP.NET Core. Duende Identity Server permite las siguientes características de seguridad:

  • Autenticación como servicio (AaaS)
  • Inicio de sesión único (SSO) mediante varios tipos de aplicaciones
  • Control de acceso para API
  • Federation Gateway

Importante

Duende Software puede requerir que pague una tarifa de licencia para el uso en producción de Duende Identity Server. Para más información, vea Migración de ASP.NET Core 5.0 a 6.0.

Para más información, consulte la documentación de Duende Identity Server (sitio web de Duende Software).

Publicar en Azure

Para obtener más información sobre la implementación en Azure, consulte Inicio rápido: Implementación de una aplicación web de ASP.NET.

Recursos adicionales

Vea o descargue el código de ejemplo para este tutorial. Vea cómo descargarlo.

Para obtener más información, vea los siguientes recursos:

En este tutorial se enseñan los conceptos básicos de la creación de una API web basada en controlador que usa una base de datos. Otro enfoque para crear API en ASP.NET Core consiste en crear API mínimas. Para obtener ayuda para elegir entre las API mínimas y las API basadas en controlador, consulte Introducción a las API. Para ver un tutorial sobre cómo crear una API mínima, consulte Tutorial: Creación de una API mínima con ASP.NET Core.

En este tutorial aprenderá a:

  • Crear un proyecto de API web.
  • Agregar una clase de modelo y un contexto de base de datos.
  • Aplicar scaffolding a un controlador con métodos CRUD.
  • Configurar el enrutamiento, las rutas de acceso URL y los valores devueltos.
  • Llame a la API web con http-repl.

Al final, tendrá una API web que pueda administrar los elementos "to-do" almacenados en una base de datos.

Información general

En este tutorial se crea la siguiente API:

API Descripción Cuerpo de la solicitud Cuerpo de la respuesta
GET /api/todoitems Obtener todas las tareas pendientes None Matriz de tareas pendientes
GET /api/todoitems/{id} Obtener un elemento por identificador None Tarea pendiente
POST /api/todoitems Incorporación de un nuevo elemento Tarea pendiente Tarea pendiente
PUT /api/todoitems/{id} Actualizar un elemento existente Tarea pendiente None
DELETE /api/todoitems/{id}     Eliminar un elemento None None

En el diagrama siguiente, se muestra el diseño de la aplicación.

The client is represented by a box on the left. It submits a request and receives a response from the application, a box drawn on the right. Within the application box, three boxes represent the controller, the model, and the data access layer. The request comes into the application's controller, and read/write operations occur between the controller and the data access layer. The model is serialized and returned to the client in the response.

Requisitos previos

Creación de un proyecto web

  • En el menú Archivo, seleccione Nuevo>Proyecto.
  • Escriba API web en el cuadro de búsqueda.
  • Seleccione la plantilla API web de ASP.NET Core y seleccione Siguiente.
  • En el cuadro de diálogo Configurar el nuevo proyecto, asigne al proyecto el nombre TodoApi y seleccione Siguiente.
  • En el cuadro de diálogo Información adicional:
    • Confirme que el Marco es .NET 6.0 (Compatibilidad a largo plazo).
    • Confirme que la casilla Use controllers(uncheck to use minimal APIs) [Usar controladores (desactivar para usar API mínimas)] está activada.
    • Seleccione Crear.

Nota:

Para obtener instrucciones sobre cómo agregar paquetes a aplicaciones .NET, consulte los artículos de Instalación y administración de paquetes en Flujo de trabajo de consumo de paquetes (NuGet documentación). Confirme las versiones correctas del paquete en NuGet.org.

Prueba del proyecto

La plantilla de proyecto crea una API WeatherForecast compatible con Swagger.

Presione Ctrl+F5 para ejecutarla sin el depurador.

Visual Studio muestra el siguiente cuadro de diálogo cuando un proyecto aún no está configurado para usar SSL:

This project is configured to use SSL. To avoid SSL warnings in the browser you can choose to trust the self-signed certificate that IIS Express has generated. Would you like to trust the IIS Express SSL certificate?

Haga clic en si confía en el certificado SSL de IIS Express.

Se muestra el cuadro de diálogo siguiente:

Security warning dialog

Si acepta confiar en el certificado de desarrollo, seleccione .

Para obtener información sobre cómo confiar en el explorador Firefox, consulte Error de certificado SEC_ERROR_INADEQUATE_KEY_USAGE de Firefox.

Visual Studio inicia el explorador predeterminado y navega hasta https://localhost:<port>/swagger/index.html, donde <port> es un número de puerto elegido aleatoriamente.

Se abre la página de Swagger /swagger/index.html. Seleccione GET>Try it out>Execute (GET > Probar > Ejecutar). La página muestra lo siguiente:

  • Comando de Curl para probar la API WeatherForecast
  • Dirección URL para probar la API WeatherForecast
  • Código de respuesta, cuerpo y encabezados
  • Un cuadro de lista desplegable con los tipos de elementos multimedia y el esquema y valor de ejemplo.

Si no aparece la página Swagger, consulte este problema de GitHub.

Swagger se usa para generar una documentación útil y páginas de ayuda para API web. Este tutorial se centra en cómo crear una API web. Para más información sobre Swagger, vea Documentación de la API web de ASP.NET Core con Swagger/OpenAPI.

Copie y pegue la URL de solicitud en el explorador: https://localhost:<port>/weatherforecast.

Se devuelve un JSON similar al siguiente ejemplo:

[
    {
        "date": "2019-07-16T19:04:05.7257911-06:00",
        "temperatureC": 52,
        "temperatureF": 125,
        "summary": "Mild"
    },
    {
        "date": "2019-07-17T19:04:05.7258461-06:00",
        "temperatureC": 36,
        "temperatureF": 96,
        "summary": "Warm"
    },
    {
        "date": "2019-07-18T19:04:05.7258467-06:00",
        "temperatureC": 39,
        "temperatureF": 102,
        "summary": "Cool"
    },
    {
        "date": "2019-07-19T19:04:05.7258471-06:00",
        "temperatureC": 10,
        "temperatureF": 49,
        "summary": "Bracing"
    },
    {
        "date": "2019-07-20T19:04:05.7258474-06:00",
        "temperatureC": -1,
        "temperatureF": 31,
        "summary": "Chilly"
    }
]

Actualización de launchUrl

En Properties\launchSettings.json, actualice launchUrl de "swagger" a "api/todoitems":

"launchUrl": "api/todoitems",

Dado que Swagger se quitará, el marcado anterior cambia la dirección URL que se inicia con el método GET del controlador agregado en las secciones siguientes.

Incorporación de una clase de modelo

Un modelo es un conjunto de clases que representan los datos que la aplicación administra. El modelo para esta aplicación es una clase TodoItem única.

  • En el Explorador de soluciones, haga clic con el botón derecho en el proyecto. Seleccione Agregar>Nueva carpeta. Asigne a la carpeta el nombre Models.

  • Haga clic con el botón derecho en la carpeta Models y seleccione Agregar>Clase. Asigne a la clase el nombre TodoItem y seleccione Agregar.

  • Reemplace el código de plantilla por lo siguiente:

namespace TodoApi.Models
{
    public class TodoItem
    {
        public long Id { get; set; }
        public string? Name { get; set; }
        public bool IsComplete { get; set; }
    }
}

La propiedad Id funciona como clave única en una base de datos relacional.

Las clases de modelo pueden ir en cualquier lugar del proyecto, pero, por convención, se usa la carpeta Models .

Incorporación de un contexto de base de datos

El contexto de base de datos es la clase principal que coordina la funcionalidad de Entity Framework para un modelo de datos. Esta clase se crea derivándola de la clase Microsoft.EntityFrameworkCore.DbContext.

Adición de paquetes NuGet

  • En el menú Herramientas, seleccione Administrador de paquetes NuGet > Administrar paquetes NuGet para la solución.
  • Seleccione la pestaña Examinar y escriba Microsoft.EntityFrameworkCore.InMemory en el cuadro de búsqueda.
  • En el panel izquierdo, seleccione Microsoft.EntityFrameworkCore.InMemory.
  • Active la casilla Proyecto en el panel derecho y, después, seleccione Instalar.

Adición del contexto de la base de datos TodoContext

  • Haga clic con el botón derecho en la carpeta Models y seleccione Agregar>Clase. Asigne a la clase el nombre TodoContext y haga clic en Agregar.
  • Escriba el siguiente código:

    using Microsoft.EntityFrameworkCore;
    using System.Diagnostics.CodeAnalysis;
    
    namespace TodoApi.Models
    {
        public class TodoContext : DbContext
        {
            public TodoContext(DbContextOptions<TodoContext> options)
                : base(options)
            {
            }
    
            public DbSet<TodoItem> TodoItems { get; set; } = null!;
        }
    }
    

Registro del contexto de base de datos

En ASP.NET Core, los servicios (como el contexto de la base de datos) deben registrarse con el contenedor de inserción de dependencias (DI). El contenedor proporciona el servicio a los controladores.

Actualice Program.cs con el siguiente código:

using Microsoft.EntityFrameworkCore;
using TodoApi.Models;


var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

builder.Services.AddControllers();
builder.Services.AddDbContext<TodoContext>(opt =>
    opt.UseInMemoryDatabase("TodoList"));
//builder.Services.AddSwaggerGen(c =>
//{
//    c.SwaggerDoc("v1", new() { Title = "TodoApi", Version = "v1" });
//});

var app = builder.Build();

// Configure the HTTP request pipeline.
if (builder.Environment.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
    //app.UseSwagger();
    //app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "TodoApi v1"));
}

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

El código anterior:

  • Quita las llamadas de Swagger.
  • Quita las directivas using sin usar.
  • Agrega el contexto de base de datos para el contenedor de DI.
  • Especifica que el contexto de base de datos usará una base de datos en memoria.

Scaffolding de un controlador

  • Haga clic con el botón derecho en la carpeta Controllers.

  • Seleccione Agregar>Nuevo elemento con scaffolding.

  • Seleccione Controlador de API con acciones mediante Entity Framework y, después, seleccione Agregar.

  • En el cuadro de diálogo Add API Controller with actions, using Entity Framework (Agregar controlador de API con acciones mediante Entity Framework):

    • Seleccione TodoItem (TodoApi.Models) en Clase de modelo.
    • Seleccione TodoContext (TodoApi.Models) en Clase de contexto de datos.
    • Seleccione Agregar.

    Si se produce un error en la operación de scaffolding, seleccione Agregar para probar el scaffolding una segunda vez.

El código generado:

  • Marca la clase con el atributo [ApiController]. Este atributo indica que el controlador responde a las solicitudes de la API web. Para información sobre comportamientos específicos que permite el atributo, vea Creación de una API web con ASP.NET Core.
  • Utiliza la inserción de dependencias para insertar el contexto de base de datos (TodoContext) en el controlador. El contexto de base de datos se usa en cada uno de los métodos CRUD del controlador.

Las plantillas de ASP.NET Core para:

  • Controladores con vistas incluyen [action] en la plantilla de ruta.
  • Controladores de API no incluyen [action] en la plantilla de ruta.

Si el token [action] no está en la plantilla de ruta, el nombre de la acción se excluye de la ruta. Es decir, el nombre del método asociado a la acción no se usa en la ruta coincidente.

Actualización del método create de PostTodoItem

Actualice la instrucción "return" en PostTodoItem para usar el operador nameof:

[HttpPost]
public async Task<ActionResult<TodoItem>> PostTodoItem(TodoItem todoItem)
{
    _context.TodoItems.Add(todoItem);
    await _context.SaveChangesAsync();

    //return CreatedAtAction("GetTodoItem", new { id = todoItem.Id }, todoItem);
    return CreatedAtAction(nameof(GetTodoItem), new { id = todoItem.Id }, todoItem);
}

El código anterior es un método HTTP POST, indicado por el atributo [HttpPost]. El método obtiene el valor de tareas pendientes del cuerpo de la solicitud HTTP.

Para más información, vea Enrutamiento mediante atributos con atributos Http[Verb].

El método CreatedAtAction realiza las acciones siguientes:

  • Devuelve un código de estado HTTP 201 cuando se ha ejecutado correctamente. HTTP 201 es la respuesta estándar para un método HTTP POST que crea un recurso en el servidor.
  • Agrega un encabezado Location a la respuesta. El encabezado Location especifica el URI de la tarea pendiente recién creada. Para obtener más información, consulte 10.2.2 201 creado.
  • Hace referencia a la acción GetTodoItem para crear el identificador URI del encabezado Location. La palabra clave nameof de C# se usa para evitar que se codifique de forma rígida el nombre de acción en la llamada a CreatedAtAction.

Instalación de http-repl

En este tutorial se usa http-repl para probar la API web.

  • Ejecute el comando siguiente en el símbolo del sistema:

    dotnet tool install -g Microsoft.dotnet-httprepl
    

    Nota:

    De manera predeterminada, la arquitectura de los binarios .NET que se van a instalar representa la arquitectura del sistema operativo que se está ejecutando en ese momento. Para especificar una arquitectura de SO diferente, consulte dotnet tool install, --arch option. Para más información, consulte la incidencia de GitHub dotnet/AspNetCore.Docs #29262.

  • Si no tiene instalados el entorno de ejecución ni el SDK de .NET 6.0, instale el entorno de ejecución de .NET 6.0.

Prueba de PostTodoItem

  • Presione Ctrl+F5 para ejecutar la aplicación.

  • Abra una nueva ventana de terminal y ejecute los siguientes comandos. Si la aplicación usa un número de puerto diferente, reemplace 5001 en el comando httprepl por su número de puerto.

    httprepl https://localhost:5001/api/todoitems
    post -h Content-Type=application/json -c "{"name":"walk dog","isComplete":true}"
    

    Este es un ejemplo del resultado del comando:

    HTTP/1.1 201 Created
    Content-Type: application/json; charset=utf-8
    Date: Tue, 07 Sep 2021 20:39:47 GMT
    Location: https://localhost:5001/api/TodoItems/1
    Server: Kestrel
    Transfer-Encoding: chunked
    
    {
      "id": 1,
      "name": "walk dog",
      "isComplete": true
    }
    

Prueba del URI del encabezado de ubicación

Para probar el encabezado de ubicación, cópielo y péguelo en un comando get httprepl.

En el ejemplo siguiente se supone que todavía está en una sesión httprepl. Si finalizó la sesión httprepl anterior, reemplace connect por httprepl en los siguientes comandos:

connect https://localhost:5001/api/todoitems/1
get

Este es un ejemplo del resultado del comando:

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Tue, 07 Sep 2021 20:48:10 GMT
Server: Kestrel
Transfer-Encoding: chunked

{
  "id": 1,
  "name": "walk dog",
  "isComplete": true
}

Examen de los métodos GET

Se implementan dos puntos de conexión GET:

  • GET /api/todoitems
  • GET /api/todoitems/{id}

Acaba de ver un ejemplo de la ruta /api/todoitems/{id}. Pruebe la ruta /api/todoitems:

connect https://localhost:5001/api/todoitems
get

Este es un ejemplo del resultado del comando:

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Tue, 07 Sep 2021 20:59:21 GMT
Server: Kestrel
Transfer-Encoding: chunked

[
  {
    "id": 1,
    "name": "walk dog",
    "isComplete": true
  }
]

Esta vez, el JSON devuelto es una matriz de un elemento.

Esta aplicación utiliza una base de datos en memoria. Si la aplicación se detiene y se inicia, la solicitud GET precedente no devolverá ningún dato. Si no se devuelve ningún dato, publique los datos en la aplicación con POST.

Enrutamiento y rutas URL

El atributo [HttpGet] indica un método que responde a una solicitud HTTP GET. La ruta de dirección URL para cada método se construye como sigue:

  • Comience por la cadena de plantilla en el atributo Route del controlador:

    [Route("api/[controller]")]
    [ApiController]
    public class TodoItemsController : ControllerBase
    
  • Reemplace [controller] por el nombre del controlador, que convencionalmente es el nombre de clase de controlador sin el sufijo "Controller". En este ejemplo, el nombre de clase de controlador es TodoItemsController; por tanto, el nombre del controlador es "TodoItems". El enrutamiento en ASP.NET Core no distingue entre mayúsculas y minúsculas.

  • Si el atributo [HttpGet] tiene una plantilla de ruta (por ejemplo, [HttpGet("products")]), anéxela a la ruta de acceso. En este ejemplo no se usa una plantilla. Para más información, vea Enrutamiento mediante atributos con atributos Http[Verb].

En el siguiente método GetTodoItem, "{id}" es una variable de marcador de posición correspondiente al identificador único de la tarea pendiente. Al invocar a GetTodoItem, el valor "{id}" de la URL se proporciona al método en su parámetro id.

[HttpGet("{id}")]
public async Task<ActionResult<TodoItem>> GetTodoItem(long id)
{
    var todoItem = await _context.TodoItems.FindAsync(id);

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

    return todoItem;
}

Valores devueltos

El tipo de valor devuelto de los métodos GetTodoItems y GetTodoItem es ActionResult<T> type. ASP.NET Core serializa automáticamente el objeto a JSON y escribe el JSON en el cuerpo del mensaje de respuesta. El código de respuesta de este tipo de valor devuelto es 200 OK, suponiendo que no haya ninguna excepción no controlada. Las excepciones no controladas se convierten en errores 5xx.

Los tipos de valores devueltos ActionResult pueden representar una gama amplia de códigos de estado HTTP. Por ejemplo, GetTodoItem puede devolver dos valores de estado diferentes:

  • Si no hay ningún elemento que coincida con el identificador solicitado, el método devolverá un código de error de estado 404NotFound.
  • En caso contrario, el método devuelve 200 con un cuerpo de respuesta JSON. Devolver item genera una respuesta HTTP 200.

Método PutTodoItem

Examine el método PutTodoItem:

[HttpPut("{id}")]
public async Task<IActionResult> PutTodoItem(long id, TodoItem todoItem)
{
    if (id != todoItem.Id)
    {
        return BadRequest();
    }

    _context.Entry(todoItem).State = EntityState.Modified;

    try
    {
        await _context.SaveChangesAsync();
    }
    catch (DbUpdateConcurrencyException)
    {
        if (!TodoItemExists(id))
        {
            return NotFound();
        }
        else
        {
            throw;
        }
    }

    return NoContent();
}

PutTodoItem es similar a PostTodoItem, salvo por el hecho de que usa HTTP PUT. La respuesta es 204 Sin contenido. Según la especificación HTTP, una solicitud PUT requiere que el cliente envíe toda la entidad actualizada, no solo los cambios. Para admitir actualizaciones parciales, use HTTP PATCH.

Si recibe un error al llamar a PutTodoItem en la siguiente sección, llame a GET para asegurarse de que hay un elemento en la base de datos.

Prueba del método PutTodoItem

En este ejemplo se usa una base de datos en memoria que se debe inicializar cada vez que se inicia la aplicación. Debe haber un elemento en la base de datos antes de que realice una llamada PUT. Llame a GET para asegurarse de que hay un elemento en la base de datos antes de realizar una llamada PUT.

Actualice el elemento to-do que tiene el Id = 1 y establezca su nombre en "feed fish":

connect https://localhost:5001/api/todoitems/1
put -h Content-Type=application/json -c "{"id":1,"name":"feed fish","isComplete":true}"

Este es un ejemplo del resultado del comando:

HTTP/1.1 204 No Content
Date: Tue, 07 Sep 2021 21:20:47 GMT
Server: Kestrel

Método DeleteTodoItem

Examine el método DeleteTodoItem:

[HttpDelete("{id}")]
public async Task<IActionResult> DeleteTodoItem(long id)
{
    var todoItem = await _context.TodoItems.FindAsync(id);
    if (todoItem == null)
    {
        return NotFound();
    }

    _context.TodoItems.Remove(todoItem);
    await _context.SaveChangesAsync();

    return NoContent();
}

Prueba del método DeleteTodoItem

Elimine la tarea pendiente con el identificador 1:

connect https://localhost:5001/api/todoitems/1
delete

Este es un ejemplo del resultado del comando:

HTTP/1.1 204 No Content
Date: Tue, 07 Sep 2021 21:43:00 GMT
Server: Kestrel

Prevención del exceso de publicación

Actualmente, la aplicación de ejemplo expone todo el objeto TodoItem. Las aplicaciones de producción suelen limitar los datos que se escriben y se devuelven mediante un subconjunto del modelo. Hay varias razones para ello y la seguridad es una de las principales. El subconjunto de un modelo se suele conocer como un objeto de transferencia de datos (DTO), modelo de entrada o modelo de vista. En este tutorial, se usa DTO.

Se puede usar un DTO para:

  • Evitar el exceso de publicación.
  • Ocultar las propiedades que los clientes no deben ver.
  • Omitir algunas propiedades para reducir el tamaño de la carga.
  • Acoplar los gráficos de objetos que contienen objetos anidados. Los gráficos de objetos acoplados pueden ser más cómodos para los clientes.

Para mostrar el enfoque del DTO, actualice la clase TodoItem a fin de que incluya un campo secreto:

namespace TodoApi.Models
{
    public class TodoItem
    {
        public long Id { get; set; }
        public string? Name { get; set; }
        public bool IsComplete { get; set; }
        public string? Secret { get; set; }
    }
}

El campo secreto debe ocultarse en esta aplicación, pero una aplicación administrativa podría decidir exponerlo.

Compruebe que puede publicar y obtener el campo secreto.

Cree un modelo de DTO:

namespace TodoApi.Models
{
    public class TodoItemDTO
    {
        public long Id { get; set; }
        public string? Name { get; set; }
        public bool IsComplete { get; set; }
    }
}

Actualice el valor de TodoItemsController para usar TodoItemDTO:

using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using TodoApi.Models;

namespace TodoApi.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class TodoItemsController : ControllerBase
    {
        private readonly TodoContext _context;

        public TodoItemsController(TodoContext context)
        {
            _context = context;
        }

        // GET: api/TodoItems
        [HttpGet]
        public async Task<ActionResult<IEnumerable<TodoItemDTO>>> GetTodoItems()
        {
            return await _context.TodoItems
                .Select(x => ItemToDTO(x))
                .ToListAsync();
        }

        // GET: api/TodoItems/5
        [HttpGet("{id}")]
        public async Task<ActionResult<TodoItemDTO>> GetTodoItem(long id)
        {
            var todoItem = await _context.TodoItems.FindAsync(id);

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

            return ItemToDTO(todoItem);
        }
        // PUT: api/TodoItems/5
        // To protect from overposting attacks, see https://go.microsoft.com/fwlink/?linkid=2123754
        [HttpPut("{id}")]
        public async Task<IActionResult> UpdateTodoItem(long id, TodoItemDTO todoItemDTO)
        {
            if (id != todoItemDTO.Id)
            {
                return BadRequest();
            }

            var todoItem = await _context.TodoItems.FindAsync(id);
            if (todoItem == null)
            {
                return NotFound();
            }

            todoItem.Name = todoItemDTO.Name;
            todoItem.IsComplete = todoItemDTO.IsComplete;

            try
            {
                await _context.SaveChangesAsync();
            }
            catch (DbUpdateConcurrencyException) when (!TodoItemExists(id))
            {
                return NotFound();
            }

            return NoContent();
        }
        // POST: api/TodoItems
        // To protect from overposting attacks, see https://go.microsoft.com/fwlink/?linkid=2123754
        [HttpPost]
        public async Task<ActionResult<TodoItemDTO>> CreateTodoItem(TodoItemDTO todoItemDTO)
        {
            var todoItem = new TodoItem
            {
                IsComplete = todoItemDTO.IsComplete,
                Name = todoItemDTO.Name
            };

            _context.TodoItems.Add(todoItem);
            await _context.SaveChangesAsync();

            return CreatedAtAction(
                nameof(GetTodoItem),
                new { id = todoItem.Id },
                ItemToDTO(todoItem));
        }

        // DELETE: api/TodoItems/5
        [HttpDelete("{id}")]
        public async Task<IActionResult> DeleteTodoItem(long id)
        {
            var todoItem = await _context.TodoItems.FindAsync(id);

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

            _context.TodoItems.Remove(todoItem);
            await _context.SaveChangesAsync();

            return NoContent();
        }

        private bool TodoItemExists(long id)
        {
            return _context.TodoItems.Any(e => e.Id == id);
        }

        private static TodoItemDTO ItemToDTO(TodoItem todoItem) =>
            new TodoItemDTO
            {
                Id = todoItem.Id,
                Name = todoItem.Name,
                IsComplete = todoItem.IsComplete
            };
    }
}

Compruebe que no puede publicar ni obtener el campo secreto.

Llamar a la API web con JavaScript

Consulte Tutorial: Llamada a una API web de ASP.NET Core con JavaScript.

Serie de vídeos de API web

Vea Vídeo: Serie para principiantes: API web.

Agregar compatibilidad con la autenticación a una API web

ASP.NET Core Identity agrega la funcionalidad de inicio de sesión de la interfaz de usuario (IU) a las aplicaciones web de ASP.NET Core. Para proteger las API web y las SPA, use una de las siguientes opciones:

Duende Identity Server es un marco de OpenID Connect y OAuth 2.0 para ASP.NET Core. Duende Identity Server permite las siguientes características de seguridad:

  • Autenticación como servicio (AaaS)
  • Inicio de sesión único (SSO) mediante varios tipos de aplicaciones
  • Control de acceso para API
  • Federation Gateway

Importante

Duende Software puede requerir que pague una tarifa de licencia para el uso en producción de Duende Identity Server. Para más información, vea Migración de ASP.NET Core 5.0 a 6.0.

Para más información, consulte la documentación de Duende Identity Server (sitio web de Duende Software).

Publicar en Azure

Para obtener más información sobre la implementación en Azure, consulte Inicio rápido: Implementación de una aplicación web de ASP.NET.

Recursos adicionales

Vea o descargue el código de ejemplo para este tutorial. Vea cómo descargarlo.

Para obtener más información, vea los siguientes recursos:

En este tutorial se enseñan los conceptos básicos de la creación de una API web basada en controlador que usa una base de datos. Otro enfoque para crear API en ASP.NET Core consiste en crear API mínimas. Para obtener ayuda para elegir entre las API mínimas y las API basadas en controlador, consulte Introducción a las API. Para ver un tutorial sobre cómo crear una API mínima, consulte Tutorial: Creación de una API mínima con ASP.NET Core.

En este tutorial aprenderá a:

  • Crear un proyecto de API web.
  • Agregar una clase de modelo y un contexto de base de datos.
  • Aplicar scaffolding a un controlador con métodos CRUD.
  • Configurar el enrutamiento, las rutas de acceso URL y los valores devueltos.
  • Llamar a la API web con Postman.

Al final, tendrá una API web que pueda administrar los elementos "to-do" almacenados en una base de datos.

Información general

En este tutorial se crea la siguiente API:

API Descripción Cuerpo de la solicitud Cuerpo de la respuesta
GET /api/todoitems Obtener todas las tareas pendientes None Matriz de tareas pendientes
GET /api/todoitems/{id} Obtener un elemento por identificador None Tarea pendiente
POST /api/todoitems Incorporación de un nuevo elemento Tarea pendiente Tarea pendiente
PUT /api/todoitems/{id} Actualizar un elemento existente Tarea pendiente None
DELETE /api/todoitems/{id}     Eliminar un elemento None None

En el diagrama siguiente, se muestra el diseño de la aplicación.

The client is represented by a box on the left. It submits a request and receives a response from the application, a box drawn on the right. Within the application box, three boxes represent the controller, the model, and the data access layer. The request comes into the application's controller, and read/write operations occur between the controller and the data access layer. The model is serialized and returned to the client in the response.

Requisitos previos

Creación de un proyecto web

  • En el menú Archivo, seleccione Nuevo>Proyecto.
  • Seleccione la plantilla API web de ASP.NET Core y haga clic en Siguiente.
  • Asigne al proyecto el nombre TodoApi y haga clic en Crear.
  • En el cuadro de diálogo Crear una aplicación web ASP.NET Core, confirme que las opciones .NET Core y ASP.NET Core 5.0 están seleccionadas. Seleccione la plantilla API y haga clic en Crear.

VS new project dialog

Nota:

Para obtener instrucciones sobre cómo agregar paquetes a aplicaciones .NET, consulte los artículos de Instalación y administración de paquetes en Flujo de trabajo de consumo de paquetes (NuGet documentación). Confirme las versiones correctas del paquete en NuGet.org.

Prueba del proyecto

La plantilla de proyecto crea una API WeatherForecast compatible con Swagger.

Presione Ctrl+F5 para ejecutarla sin el depurador.

Visual Studio muestra el siguiente cuadro de diálogo cuando un proyecto aún no está configurado para usar SSL:

This project is configured to use SSL. To avoid SSL warnings in the browser you can choose to trust the self-signed certificate that IIS Express has generated. Would you like to trust the IIS Express SSL certificate?

Haga clic en si confía en el certificado SSL de IIS Express.

Se muestra el cuadro de diálogo siguiente:

Security warning dialog

Si acepta confiar en el certificado de desarrollo, seleccione .

Para obtener información sobre cómo confiar en el explorador Firefox, consulte Error de certificado SEC_ERROR_INADEQUATE_KEY_USAGE de Firefox.

Visual Studio inicia lo siguiente:

  • El servidor web de IIS Express.
  • El explorador predeterminado, y navega hasta https://localhost:<port>/swagger/index.html, donde <port> es un número de puerto elegido aleatoriamente.

Se abre la página de Swagger /swagger/index.html. Seleccione GET>Try it out>Execute (GET > Probar > Ejecutar). La página muestra lo siguiente:

  • Comando de Curl para probar la API WeatherForecast
  • Dirección URL para probar la API WeatherForecast
  • Código de respuesta, cuerpo y encabezados
  • Cuadro de lista desplegable con los tipos de medios y el esquema y valor de ejemplo

Si no aparece la página Swagger, consulte este problema de GitHub.

Swagger se usa para generar una documentación útil y páginas de ayuda para API web. Este tutorial se centra en cómo crear una API web. Para más información sobre Swagger, vea Documentación de la API web de ASP.NET Core con Swagger/OpenAPI.

Copie y pegue la URL de solicitud en el explorador: https://localhost:<port>/weatherforecast.

Se devuelve un JSON similar al siguiente:

[
    {
        "date": "2019-07-16T19:04:05.7257911-06:00",
        "temperatureC": 52,
        "temperatureF": 125,
        "summary": "Mild"
    },
    {
        "date": "2019-07-17T19:04:05.7258461-06:00",
        "temperatureC": 36,
        "temperatureF": 96,
        "summary": "Warm"
    },
    {
        "date": "2019-07-18T19:04:05.7258467-06:00",
        "temperatureC": 39,
        "temperatureF": 102,
        "summary": "Cool"
    },
    {
        "date": "2019-07-19T19:04:05.7258471-06:00",
        "temperatureC": 10,
        "temperatureF": 49,
        "summary": "Bracing"
    },
    {
        "date": "2019-07-20T19:04:05.7258474-06:00",
        "temperatureC": -1,
        "temperatureF": 31,
        "summary": "Chilly"
    }
]

Actualización de launchUrl

En Properties\launchSettings.json, actualice launchUrl de "swagger" a "api/todoitems":

"launchUrl": "api/todoitems",

Dado que Swagger se quitará, el marcado anterior cambia la dirección URL que se inicia con el método GET del controlador agregado en las secciones siguientes.

Incorporación de una clase de modelo

Un modelo es un conjunto de clases que representan los datos que la aplicación administra. El modelo para esta aplicación es una clase TodoItem única.

  • En el Explorador de soluciones, haga clic con el botón derecho en el proyecto. Seleccione Agregar>Nueva carpeta. Asigne a la carpeta el nombre Models.

  • Haga clic con el botón derecho en la carpeta Models y seleccione Agregar>Clase. Asigne a la clase el nombre TodoItem y seleccione Agregar.

  • Reemplace el código de plantilla por lo siguiente:

namespace TodoApi.Models
{
    public class TodoItem
    {
        public long Id { get; set; }
        public string Name { get; set; }
        public bool IsComplete { get; set; }
    }
}

La propiedad Id funciona como clave única en una base de datos relacional.

Las clases de modelo pueden ir en cualquier lugar del proyecto, pero, por convención, se usa la carpeta Models .

Incorporación de un contexto de base de datos

El contexto de base de datos es la clase principal que coordina la funcionalidad de Entity Framework para un modelo de datos. Esta clase se crea derivándola de la clase Microsoft.EntityFrameworkCore.DbContext.

Adición de paquetes NuGet

  • En el menú Herramientas, seleccione Administrador de paquetes NuGet > Administrar paquetes NuGet para la solución.
  • Seleccione la pestaña Examinar y escriba Microsoft.EntityFrameworkCore.InMemory en el cuadro de búsqueda.
  • En el panel izquierdo, seleccione Microsoft.EntityFrameworkCore.InMemory.
  • Active la casilla Proyecto en el panel derecho y, después, seleccione Instalar.

NuGet Package Manager

Adición del contexto de la base de datos TodoContext

  • Haga clic con el botón derecho en la carpeta Models y seleccione Agregar>Clase. Asigne a la clase el nombre TodoContext y haga clic en Agregar.
  • Escriba el siguiente código:

    using Microsoft.EntityFrameworkCore;
    
    namespace TodoApi.Models
    {
        public class TodoContext : DbContext
        {
            public TodoContext(DbContextOptions<TodoContext> options)
                : base(options)
            {
            }
    
            public DbSet<TodoItem> TodoItems { get; set; }
        }
    }
    

Registro del contexto de base de datos

En ASP.NET Core, los servicios (como el contexto de la base de datos) deben registrarse con el contenedor de inserción de dependencias (DI). El contenedor proporciona el servicio a los controladores.

Actualice Startup.cs con el siguiente código:

// Unused usings removed
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.EntityFrameworkCore;
using TodoApi.Models;

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

        public IConfiguration Configuration { get; }

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers();

            services.AddDbContext<TodoContext>(opt =>
                                               opt.UseInMemoryDatabase("TodoList"));
            //services.AddSwaggerGen(c =>
            //{
            //    c.SwaggerDoc("v1", new OpenApiInfo { Title = "TodoApi", Version = "v1" });
            //});
        }

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

            app.UseHttpsRedirection();
            app.UseRouting();

            app.UseAuthorization();

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

El código anterior:

  • Quita las llamadas de Swagger.
  • Elimina las declaraciones using no utilizadas.
  • Agrega el contexto de base de datos para el contenedor de DI.
  • Especifica que el contexto de base de datos usará una base de datos en memoria.

Scaffolding de un controlador

  • Haga clic con el botón derecho en la carpeta Controllers.

  • Seleccione Agregar>Nuevo elemento con scaffolding.

  • Seleccione Controlador de API con acciones mediante Entity Framework y, después, seleccione Agregar.

  • En el cuadro de diálogo Add API Controller with actions, using Entity Framework (Agregar controlador de API con acciones mediante Entity Framework):

    • Seleccione TodoItem (TodoApi.Models) en Clase de modelo.
    • Seleccione TodoContext (TodoApi.Models) en Clase de contexto de datos.
    • Seleccione Agregar.

El código generado:

  • Marca la clase con el atributo [ApiController]. Este atributo indica que el controlador responde a las solicitudes de la API web. Para información sobre comportamientos específicos que permite el atributo, vea Creación de una API web con ASP.NET Core.
  • Utiliza la inserción de dependencias para insertar el contexto de base de datos (TodoContext) en el controlador. El contexto de base de datos se usa en cada uno de los métodos CRUD del controlador.

Las plantillas de ASP.NET Core para:

  • Controladores con vistas incluyen [action] en la plantilla de ruta.
  • Controladores de API no incluyen [action] en la plantilla de ruta.

Si el token [action] no está en la plantilla de ruta, el nombre de la acción se excluye de la ruta. Es decir, el nombre del método asociado a la acción no se usa en la ruta coincidente.

Actualización del método create de PostTodoItem

Actualice la instrucción "return" en PostTodoItem para usar el operador nameof:

// POST: api/TodoItems
[HttpPost]
public async Task<ActionResult<TodoItem>> PostTodoItem(TodoItem todoItem)
{
    _context.TodoItems.Add(todoItem);
    await _context.SaveChangesAsync();

    //return CreatedAtAction("GetTodoItem", new { id = todoItem.Id }, todoItem);
    return CreatedAtAction(nameof(GetTodoItem), new { id = todoItem.Id }, todoItem);
}

El código anterior es un método HTTP POST, indicado por el atributo [HttpPost]. El método obtiene el valor de tareas pendientes del cuerpo de la solicitud HTTP.

Para más información, vea Enrutamiento mediante atributos con atributos Http[Verb].

El método CreatedAtAction realiza las acciones siguientes:

  • Devuelve un código de estado HTTP 201 cuando se ha ejecutado correctamente. HTTP 201 es la respuesta estándar para un método HTTP POST que crea un recurso en el servidor.
  • Agrega un encabezado Location a la respuesta. El encabezado Location especifica el URI de la tarea pendiente recién creada. Para más información, consulte 201 creado.
  • Hace referencia a la acción GetTodoItem para crear el identificador URI del encabezado Location. La palabra clave nameof de C# se usa para evitar que se codifique de forma rígida el nombre de acción en la llamada a CreatedAtAction.

Instalación de Postman

En este tutorial se usa Postman para probar la API web.

  • Instale Postman.
  • Inicie la aplicación web.
  • Inicie Postman.
  • Deshabilite Comprobación del certificado SSL:
    • Postman para Windows: seleccione Archivo>Configuración (pestaña General), deshabilite Comprobación del certificado SSL.
    • Postman para macOS: seleccione Postman>Configuración (pestaña General), deshabilite Comprobación del certificado SSL.

      Advertencia

      Vuelva a habilitar la comprobación del certificado SSL tras probar el controlador.

Prueba de PostTodoItem con Postman

  • Cree una nueva solicitud.

  • Establezca el método HTTP en POST.

  • Establezca el URI en https://localhost:<port>/api/todoitems. Por ejemplo: https://localhost:5001/api/todoitems.

  • Seleccione la pestaña Cuerpo.

  • Seleccione el botón de radio Raw (Sin formato).

  • Establezca el tipo en JSON (application/json).

  • En el cuerpo de la solicitud, introduzca JSON para una tarea pendiente:

    {
      "name":"walk dog",
      "isComplete":true
    }
    
  • Seleccione Enviar.

    Postman with create request

Prueba del URI del encabezado de ubicación

El URI del encabezado de ubicación se puede probar en el explorador. Copie y pegue el URI del encabezado de ubicación en el explorador.

Para probarlo en Postman:

  • Seleccione la pestaña Encabezados en el panel Respuesta.

  • Copie el valor de encabezado Ubicación:

    Headers tab of the Postman console

  • Establezca el método HTTP en GET.

  • Establezca el URI en https://localhost:<port>/api/todoitems/1. Por ejemplo: https://localhost:5001/api/todoitems/1.

  • Seleccione Enviar.

Examen de los métodos GET

Se implementan dos puntos de conexión GET:

  • GET /api/todoitems
  • GET /api/todoitems/{id}

Llame a los dos puntos de conexión desde un explorador o Postman para probar la aplicación. Por ejemplo:

  • https://localhost:5001/api/todoitems
  • https://localhost:5001/api/todoitems/1

La llamada a GetTodoItems genera una respuesta similar a la siguiente:

[
  {
    "id": 1,
    "name": "Item1",
    "isComplete": false
  }
]

Prueba de Get con Postman

  • Cree una nueva solicitud.
  • Establezca el método HTTP en GET.
  • Establezca el URI de solicitud en https://localhost:<port>/api/todoitems. Por ejemplo: https://localhost:5001/api/todoitems.
  • Establezca Vista de dos paneles en Postman.
  • Seleccione Enviar.

Esta aplicación utiliza una base de datos en memoria. Si la aplicación se detiene y se inicia, la solicitud GET precedente no devolverá ningún dato. Si no se devuelve ningún dato, publique los datos en la aplicación con POST.

Enrutamiento y rutas URL

El atributo [HttpGet] indica un método que responde a una solicitud HTTP GET. La ruta de dirección URL para cada método se construye como sigue:

  • Comience por la cadena de plantilla en el atributo Route del controlador:

    [Route("api/[controller]")]
    [ApiController]
    public class TodoItemsController : ControllerBase
    {
        private readonly TodoContext _context;
    
        public TodoItemsController(TodoContext context)
        {
            _context = context;
        }
    
  • Reemplace [controller] por el nombre del controlador, que convencionalmente es el nombre de clase de controlador sin el sufijo "Controller". En este ejemplo, el nombre de clase de controlador es TodoItemsController; por tanto, el nombre del controlador es "TodoItems". El enrutamiento en ASP.NET Core no distingue entre mayúsculas y minúsculas.

  • Si el atributo [HttpGet] tiene una plantilla de ruta (por ejemplo, [HttpGet("products")]), anéxela a la ruta de acceso. En este ejemplo no se usa una plantilla. Para más información, vea Enrutamiento mediante atributos con atributos Http[Verb].

En el siguiente método GetTodoItem, "{id}" es una variable de marcador de posición correspondiente al identificador único de la tarea pendiente. Al invocar a GetTodoItem, el valor "{id}" de la URL se proporciona al método en su parámetro id.

// GET: api/TodoItems/5
[HttpGet("{id}")]
public async Task<ActionResult<TodoItem>> GetTodoItem(long id)
{
    var todoItem = await _context.TodoItems.FindAsync(id);

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

    return todoItem;
}

Valores devueltos

El tipo de valor devuelto de los métodos GetTodoItems y GetTodoItem es ActionResult<T> type. ASP.NET Core serializa automáticamente el objeto a JSON y escribe el JSON en el cuerpo del mensaje de respuesta. El código de respuesta de este tipo de valor devuelto es 200 OK, suponiendo que no haya ninguna excepción no controlada. Las excepciones no controladas se convierten en errores 5xx.

Los tipos de valores devueltos ActionResult pueden representar una gama amplia de códigos de estado HTTP. Por ejemplo, GetTodoItem puede devolver dos valores de estado diferentes:

  • Si no hay ningún elemento que coincida con el identificador solicitado, el método devolverá un código de error de estado 404NotFound.
  • En caso contrario, el método devuelve 200 con un cuerpo de respuesta JSON. Devolver item genera una respuesta HTTP 200.

Método PutTodoItem

Examine el método PutTodoItem:

// PUT: api/TodoItems/5
[HttpPut("{id}")]
public async Task<IActionResult> PutTodoItem(long id, TodoItem todoItem)
{
    if (id != todoItem.Id)
    {
        return BadRequest();
    }

    _context.Entry(todoItem).State = EntityState.Modified;

    try
    {
        await _context.SaveChangesAsync();
    }
    catch (DbUpdateConcurrencyException)
    {
        if (!TodoItemExists(id))
        {
            return NotFound();
        }
        else
        {
            throw;
        }
    }

    return NoContent();
}

PutTodoItem es similar a PostTodoItem, salvo por el hecho de que usa HTTP PUT. La respuesta es 204 Sin contenido. Según la especificación HTTP, una solicitud PUT requiere que el cliente envíe toda la entidad actualizada, no solo los cambios. Para admitir actualizaciones parciales, use HTTP PATCH.

Si recibe un error al llamar a PutTodoItem, llame a GET para asegurarse de que hay un elemento en la base de datos.

Prueba del método PutTodoItem

En este ejemplo se usa una base de datos en memoria que se debe inicializar cada vez que se inicia la aplicación. Debe haber un elemento en la base de datos antes de que realice una llamada PUT. Llame a GET para asegurarse de que hay un elemento en la base de datos antes de realizar una llamada PUT.

Actualice el elemento to-do que tiene el Id = 1 y establezca su nombre en "feed fish":

  {
    "Id":1,
    "name":"feed fish",
    "isComplete":true
  }

En la imagen siguiente, se muestra la actualización de Postman:

Postman console showing 204 (No Content) response

Método DeleteTodoItem

Examine el método DeleteTodoItem:

// DELETE: api/TodoItems/5
[HttpDelete("{id}")]
public async Task<IActionResult> DeleteTodoItem(long id)
{
    var todoItem = await _context.TodoItems.FindAsync(id);
    if (todoItem == null)
    {
        return NotFound();
    }

    _context.TodoItems.Remove(todoItem);
    await _context.SaveChangesAsync();

    return NoContent();
}

Prueba del método DeleteTodoItem

Use Postman para eliminar una tarea pendiente:

  • Establezca el método en DELETE.
  • Establezca el URI del objeto que quiera eliminar (por ejemplo, https://localhost:5001/api/todoitems/1).
  • Seleccione Enviar.

Prevención del exceso de publicación

Actualmente, la aplicación de ejemplo expone todo el objeto TodoItem. Las aplicaciones de producción suelen limitar los datos que se escriben y se devuelven mediante un subconjunto del modelo. Hay varias razones para ello y la seguridad es una de las principales. El subconjunto de un modelo se suele conocer como un objeto de transferencia de datos (DTO), modelo de entrada o modelo de vista. En este artículo, se usa DTO.

Se puede usar un DTO para:

  • Evitar el exceso de publicación.
  • Ocultar las propiedades que los clientes no deben ver.
  • Omitir algunas propiedades para reducir el tamaño de la carga.
  • Acoplar los gráficos de objetos que contienen objetos anidados. Los gráficos de objetos acoplados pueden ser más cómodos para los clientes.

Para mostrar el enfoque del DTO, actualice la clase TodoItem a fin de que incluya un campo secreto:

namespace TodoApi.Models
{
    public class TodoItem
    {
        public long Id { get; set; }
        public string Name { get; set; }
        public bool IsComplete { get; set; }
        public string Secret { get; set; }
    }
}

El campo secreto debe ocultarse en esta aplicación, pero una aplicación administrativa podría decidir exponerlo.

Compruebe que puede publicar y obtener el campo secreto.

Cree un modelo de DTO:

public class TodoItemDTO
{
    public long Id { get; set; }
    public string Name { get; set; }
    public bool IsComplete { get; set; }
}

Actualice el valor de TodoItemsController para usar TodoItemDTO:

// GET: api/TodoItems
[HttpGet]
public async Task<ActionResult<IEnumerable<TodoItemDTO>>> GetTodoItems()
{
    return await _context.TodoItems
        .Select(x => ItemToDTO(x))
        .ToListAsync();
}

[HttpGet("{id}")]
public async Task<ActionResult<TodoItemDTO>> GetTodoItem(long id)
{
    var todoItem = await _context.TodoItems.FindAsync(id);

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

    return ItemToDTO(todoItem);
}

[HttpPut("{id}")]
public async Task<IActionResult> UpdateTodoItem(long id, TodoItemDTO todoItemDTO)
{
    if (id != todoItemDTO.Id)
    {
        return BadRequest();
    }

    var todoItem = await _context.TodoItems.FindAsync(id);
    if (todoItem == null)
    {
        return NotFound();
    }

    todoItem.Name = todoItemDTO.Name;
    todoItem.IsComplete = todoItemDTO.IsComplete;

    try
    {
        await _context.SaveChangesAsync();
    }
    catch (DbUpdateConcurrencyException) when (!TodoItemExists(id))
    {
        return NotFound();
    }

    return NoContent();
}

[HttpPost]
public async Task<ActionResult<TodoItemDTO>> CreateTodoItem(TodoItemDTO todoItemDTO)
{
    var todoItem = new TodoItem
    {
        IsComplete = todoItemDTO.IsComplete,
        Name = todoItemDTO.Name
    };

    _context.TodoItems.Add(todoItem);
    await _context.SaveChangesAsync();

    return CreatedAtAction(
        nameof(GetTodoItem),
        new { id = todoItem.Id },
        ItemToDTO(todoItem));
}

[HttpDelete("{id}")]
public async Task<IActionResult> DeleteTodoItem(long id)
{
    var todoItem = await _context.TodoItems.FindAsync(id);

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

    _context.TodoItems.Remove(todoItem);
    await _context.SaveChangesAsync();

    return NoContent();
}

private bool TodoItemExists(long id) =>
     _context.TodoItems.Any(e => e.Id == id);

private static TodoItemDTO ItemToDTO(TodoItem todoItem) =>
    new TodoItemDTO
    {
        Id = todoItem.Id,
        Name = todoItem.Name,
        IsComplete = todoItem.IsComplete
    };

Compruebe que no puede publicar ni obtener el campo secreto.

Llamar a la API web con JavaScript

Consulte Tutorial: Llamada a una API web de ASP.NET Core con JavaScript.

Agregar compatibilidad con la autenticación a una API web

ASP.NET Core Identity agrega la funcionalidad de inicio de sesión de la interfaz de usuario (IU) a las aplicaciones web de ASP.NET Core. Para proteger las API web y las SPA, use una de las siguientes opciones:

Duende Identity Server es un marco de OpenID Connect y OAuth 2.0 para ASP.NET Core. Duende Identity Server permite las siguientes características de seguridad:

  • Autenticación como servicio (AaaS)
  • Inicio de sesión único (SSO) mediante varios tipos de aplicaciones
  • Control de acceso para API
  • Federation Gateway

Importante

Duende Software puede requerir que pague una tarifa de licencia para el uso en producción de Duende Identity Server. Para más información, vea Migración de ASP.NET Core 5.0 a 6.0.

Para más información, consulte la documentación de Duende Identity Server (sitio web de Duende Software).

Publicar en Azure

Para obtener más información sobre la implementación en Azure, consulte Inicio rápido: Implementación de una aplicación web de ASP.NET.

Recursos adicionales

Vea o descargue el código de ejemplo para este tutorial. Vea cómo descargarlo.

Para obtener más información, vea los siguientes recursos:

En este tutorial se enseñan los conceptos básicos de la creación de una API web basada en controlador que usa una base de datos. Otro enfoque para crear API en ASP.NET Core consiste en crear API mínimas. Para obtener ayuda para elegir entre las API mínimas y las API basadas en controlador, consulte Introducción a las API. Para ver un tutorial sobre cómo crear una API mínima, consulte Tutorial: Creación de una API mínima con ASP.NET Core.

En este tutorial aprenderá a:

  • Crear un proyecto de API web.
  • Agregar una clase de modelo y un contexto de base de datos.
  • Aplicar scaffolding a un controlador con métodos CRUD.
  • Configurar el enrutamiento, las rutas de acceso URL y los valores devueltos.
  • Llamar a la API web con Postman.

Al final, tendrá una API web que pueda administrar los elementos "to-do" almacenados en una base de datos.

Información general

En este tutorial se crea la siguiente API:

API Descripción Cuerpo de la solicitud Cuerpo de la respuesta
GET /api/todoitems Obtener todas las tareas pendientes None Matriz de tareas pendientes
GET /api/todoitems/{id} Obtener un elemento por identificador None Tarea pendiente
POST /api/todoitems Incorporación de un nuevo elemento Tarea pendiente Tarea pendiente
PUT /api/todoitems/{id} Actualizar un elemento existente Tarea pendiente None
DELETE /api/todoitems/{id}     Eliminar un elemento None None

En el diagrama siguiente, se muestra el diseño de la aplicación.

The client is represented by a box on the left. It submits a request and receives a response from the application, a box drawn on the right. Within the application box, three boxes represent the controller, the model, and the data access layer. The request comes into the application's controller, and read/write operations occur between the controller and the data access layer. The model is serialized and returned to the client in the response.

Requisitos previos

Creación de un proyecto web

  • En el menú Archivo, seleccione Nuevo>Proyecto.
  • Seleccione la plantilla Aplicación web ASP.NET Core y haga clic en Siguiente.
  • Asigne al proyecto el nombre TodoApi y haga clic en Crear.
  • En el cuadro de diálogo Crear una aplicación web ASP.NET Core, confirme que las opciones .NET Core y ASP.NET Core 3.1 estén seleccionadas. Seleccione la plantilla API y haga clic en Crear.

VS new project dialog

Nota:

Para obtener instrucciones sobre cómo agregar paquetes a aplicaciones .NET, consulte los artículos de Instalación y administración de paquetes en Flujo de trabajo de consumo de paquetes (NuGet documentación). Confirme las versiones correctas del paquete en NuGet.org.

Prueba de la API

La plantilla del proyecto crea una API WeatherForecast. Llame al método Get desde un explorador para probar la aplicación.

Presione Ctrl+F5 para ejecutar la aplicación. Visual Studio inicia un explorador y navega hasta https://localhost:<port>/weatherforecast, donde <port> es un número de puerto elegido aleatoriamente.

Si aparece un cuadro de diálogo en que se le pregunta si debe confiar en el certificado de IIS Express, seleccione . En el cuadro de diálogo Advertencia de seguridad que aparece a continuación, seleccione .

Se devuelve un JSON similar al siguiente:

[
    {
        "date": "2019-07-16T19:04:05.7257911-06:00",
        "temperatureC": 52,
        "temperatureF": 125,
        "summary": "Mild"
    },
    {
        "date": "2019-07-17T19:04:05.7258461-06:00",
        "temperatureC": 36,
        "temperatureF": 96,
        "summary": "Warm"
    },
    {
        "date": "2019-07-18T19:04:05.7258467-06:00",
        "temperatureC": 39,
        "temperatureF": 102,
        "summary": "Cool"
    },
    {
        "date": "2019-07-19T19:04:05.7258471-06:00",
        "temperatureC": 10,
        "temperatureF": 49,
        "summary": "Bracing"
    },
    {
        "date": "2019-07-20T19:04:05.7258474-06:00",
        "temperatureC": -1,
        "temperatureF": 31,
        "summary": "Chilly"
    }
]

Incorporación de una clase de modelo

Un modelo es un conjunto de clases que representan los datos que la aplicación administra. El modelo para esta aplicación es una clase TodoItem única.

  • En el Explorador de soluciones, haga clic con el botón derecho en el proyecto. Seleccione Agregar>Nueva carpeta. Asigne a la carpeta el nombre Models.

  • Haga clic con el botón derecho en la carpeta Models y seleccione Agregar>Clase. Asigne a la clase el nombre TodoItem y seleccione Agregar.

  • Reemplace el código de plantilla por el código siguiente:

public class TodoItem
{
    public long Id { get; set; }
    public string Name { get; set; }
    public bool IsComplete { get; set; }
}

La propiedad Id funciona como clave única en una base de datos relacional.

Las clases de modelo pueden ir en cualquier lugar del proyecto, pero, por convención, se usa la carpeta Models .

Incorporación de un contexto de base de datos

El contexto de base de datos es la clase principal que coordina la funcionalidad de Entity Framework para un modelo de datos. Esta clase se crea derivándola de la clase Microsoft.EntityFrameworkCore.DbContext.

Adición de paquetes NuGet

  • En el menú Herramientas, seleccione Administrador de paquetes NuGet > Administrar paquetes NuGet para la solución.
  • Seleccione la pestaña Examinar y escriba Microsoft.EntityFrameworkCore.InMemory en el cuadro de búsqueda.
  • Seleccione Microsoft.EntityFrameworkCore.InMemory en el panel izquierdo.
  • Active la casilla Proyecto en el panel derecho y, después, seleccione Instalar.

NuGet Package Manager

Adición del contexto de la base de datos TodoContext

  • Haga clic con el botón derecho en la carpeta Models y seleccione Agregar>Clase. Asigne a la clase el nombre TodoContext y haga clic en Agregar.
  • Escriba el siguiente código:

    using Microsoft.EntityFrameworkCore;
    
    namespace TodoApi.Models
    {
        public class TodoContext : DbContext
        {
            public TodoContext(DbContextOptions<TodoContext> options)
                : base(options)
            {
            }
    
            public DbSet<TodoItem> TodoItems { get; set; }
        }
    }
    

Registro del contexto de base de datos

En ASP.NET Core, los servicios (como el contexto de la base de datos) deben registrarse con el contenedor de inserción de dependencias (DI). El contenedor proporciona el servicio a los controladores.

Actualice Startup.cs con el siguiente código resaltado:

// Unused usings removed
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.EntityFrameworkCore;
using TodoApi.Models;

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

        public IConfiguration Configuration { get; }

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddDbContext<TodoContext>(opt =>
               opt.UseInMemoryDatabase("TodoList"));
            services.AddControllers();
        }

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

            app.UseHttpsRedirection();

            app.UseRouting();

            app.UseAuthorization();

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

El código anterior:

  • Elimina las declaraciones using no utilizadas.
  • Agrega el contexto de base de datos para el contenedor de DI.
  • Especifica que el contexto de base de datos usará una base de datos en memoria.

Scaffolding de un controlador

  • Haga clic con el botón derecho en la carpeta Controllers.

  • Seleccione Agregar>Nuevo elemento con scaffolding.

  • Seleccione Controlador de API con acciones mediante Entity Framework y, después, seleccione Agregar.

  • En el cuadro de diálogo Add API Controller with actions, using Entity Framework (Agregar controlador de API con acciones mediante Entity Framework):

    • Seleccione TodoItem (TodoApi.Models) en Clase de modelo.
    • Seleccione TodoContext (TodoApi.Models) en Clase de contexto de datos.
    • Seleccione Agregar.

El código generado:

  • Marca la clase con el atributo [ApiController]. Este atributo indica que el controlador responde a las solicitudes de la API web. Para información sobre comportamientos específicos que permite el atributo, vea Creación de una API web con ASP.NET Core.
  • Utiliza la inserción de dependencias para insertar el contexto de base de datos (TodoContext) en el controlador. El contexto de base de datos se usa en cada uno de los métodos CRUD del controlador.

Las plantillas de ASP.NET Core para:

  • Controladores con vistas incluyen [action] en la plantilla de ruta.
  • Controladores de API no incluyen [action] en la plantilla de ruta.

Si el token [action] no está en la plantilla de ruta, el nombre de la acción se excluye de la ruta. Es decir, el nombre del método asociado a la acción no se usa en la ruta coincidente.

Examen del método create de PostTodoItem

Reemplace la instrucción return en PostTodoItem para usar el operador nameof:

// POST: api/TodoItems
[HttpPost]
public async Task<ActionResult<TodoItem>> PostTodoItem(TodoItem todoItem)
{
    _context.TodoItems.Add(todoItem);
    await _context.SaveChangesAsync();

    //return CreatedAtAction("GetTodoItem", new { id = todoItem.Id }, todoItem);
    return CreatedAtAction(nameof(GetTodoItem), new { id = todoItem.Id }, todoItem);
}

El código anterior es un método HTTP POST, indicado por el atributo [HttpPost]. El método obtiene el valor de tareas pendientes del cuerpo de la solicitud HTTP.

Para más información, vea Enrutamiento mediante atributos con atributos Http[Verb].

El método CreatedAtAction realiza las acciones siguientes:

  • Devuelve un código de estado HTTP 201 cuando se ha ejecutado correctamente. HTTP 201 es la respuesta estándar para un método HTTP POST que crea un recurso en el servidor.
  • Agrega un encabezado Location a la respuesta. El encabezado Location especifica el URI de la tarea pendiente recién creada. Para más información, consulte 201 creado.
  • Hace referencia a la acción GetTodoItem para crear el identificador URI del encabezado Location. La palabra clave nameof de C# se usa para evitar que se codifique de forma rígida el nombre de acción en la llamada a CreatedAtAction.

Instalación de Postman

En este tutorial se usa Postman para probar la API web.

  • Instale Postman.
  • Inicie la aplicación web.
  • Inicie Postman.
  • Deshabilite Comprobación del certificado SSL:
    • Postman para Windows: Postman para Windows Archivo>Configuración (pestaña General), deshabilite Comprobación del certificado SSL.
    • Postman para macOS: Postman para Windows Postman>Configuración (pestaña General), deshabilite Comprobación del certificado SSL.

      Advertencia

      Vuelva a habilitar la comprobación del certificado SSL tras probar el controlador.

Prueba de PostTodoItem con Postman

  • Cree una nueva solicitud.

  • Establezca el método HTTP en POST.

  • Establezca el URI en https://localhost:<port>/api/todoitems. Por ejemplo: https://localhost:5001/api/todoitems.

  • Seleccione la pestaña Cuerpo.

  • Seleccione el botón de radio Raw (Sin formato).

  • Establezca el tipo en JSON (application/json).

  • En el cuerpo de la solicitud, introduzca JSON para una tarea pendiente:

    {
      "name":"walk dog",
      "isComplete":true
    }
    
  • Seleccione Enviar.

    Postman with create request

Prueba del URI del encabezado de ubicación con Postman

  • Seleccione la pestaña Encabezados en el panel Respuesta.

  • Copie el valor de encabezado Ubicación:

    Headers tab of the Postman console

  • Establezca el método HTTP en GET.

  • Establezca el URI en https://localhost:<port>/api/todoitems/1. Por ejemplo: https://localhost:5001/api/todoitems/1.

  • Seleccione Enviar.

Examen de los métodos GET

Estos métodos implementan dos puntos de conexión GET:

  • GET /api/todoitems
  • GET /api/todoitems/{id}

Llame a los dos puntos de conexión desde un explorador o Postman para probar la aplicación. Por ejemplo:

  • https://localhost:5001/api/todoitems
  • https://localhost:5001/api/todoitems/1

La llamada a GetTodoItems genera una respuesta similar a la siguiente:

[
  {
    "id": 1,
    "name": "Item1",
    "isComplete": false
  }
]

Prueba de Get con Postman

  • Cree una nueva solicitud.
  • Establezca el método HTTP en GET.
  • Establezca el URI de solicitud en https://localhost:<port>/api/todoitems. Por ejemplo: https://localhost:5001/api/todoitems.
  • Establezca Vista de dos paneles en Postman.
  • Seleccione Enviar.

Esta aplicación utiliza una base de datos en memoria. Si la aplicación se detiene y se inicia, la solicitud GET precedente no devolverá ningún dato. Si no se devuelve ningún dato, publique los datos en la aplicación con POST.

Enrutamiento y rutas URL

El atributo [HttpGet] indica un método que responde a una solicitud HTTP GET. La ruta de dirección URL para cada método se construye como sigue:

  • Comience por la cadena de plantilla en el atributo Route del controlador:

    [Route("api/[controller]")]
    [ApiController]
    public class TodoItemsController : ControllerBase
    {
        private readonly TodoContext _context;
    
        public TodoItemsController(TodoContext context)
        {
            _context = context;
        }
    
  • Reemplace [controller] por el nombre del controlador, que convencionalmente es el nombre de clase de controlador sin el sufijo "Controller". En este ejemplo, el nombre de clase de controlador es TodoItemsController; por tanto, el nombre del controlador es "TodoItems". El enrutamiento en ASP.NET Core no distingue entre mayúsculas y minúsculas.

  • Si el atributo [HttpGet] tiene una plantilla de ruta (por ejemplo, [HttpGet("products")]), anéxela a la ruta de acceso. En este ejemplo no se usa una plantilla. Para más información, vea Enrutamiento mediante atributos con atributos Http[Verb].

En el siguiente método GetTodoItem, "{id}" es una variable de marcador de posición correspondiente al identificador único de la tarea pendiente. Al invocar a GetTodoItem, el valor "{id}" de la URL se proporciona al método en su parámetro id.

// GET: api/TodoItems/5
[HttpGet("{id}")]
public async Task<ActionResult<TodoItem>> GetTodoItem(long id)
{
    var todoItem = await _context.TodoItems.FindAsync(id);

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

    return todoItem;
}

Valores devueltos

El tipo de valor devuelto de los métodos GetTodoItems y GetTodoItem es ActionResult<T> type. ASP.NET Core serializa automáticamente el objeto a JSON y escribe el JSON en el cuerpo del mensaje de respuesta. El código de respuesta para este tipo de valor devuelto es el 200, suponiendo que no haya ninguna excepción no controlada. Las excepciones no controladas se convierten en errores 5xx.

Los tipos de valores devueltos ActionResult pueden representar una gama amplia de códigos de estado HTTP. Por ejemplo, GetTodoItem puede devolver dos valores de estado diferentes:

  • Si no hay ningún elemento que coincida con el identificador solicitado, el método devolverá un código de error 404 NotFound.
  • En caso contrario, el método devuelve 200 con un cuerpo de respuesta JSON. Devolver item genera una respuesta HTTP 200.

Método PutTodoItem

Examine el método PutTodoItem:

// PUT: api/TodoItems/5
[HttpPut("{id}")]
public async Task<IActionResult> PutTodoItem(long id, TodoItem todoItem)
{
    if (id != todoItem.Id)
    {
        return BadRequest();
    }

    _context.Entry(todoItem).State = EntityState.Modified;

    try
    {
        await _context.SaveChangesAsync();
    }
    catch (DbUpdateConcurrencyException)
    {
        if (!TodoItemExists(id))
        {
            return NotFound();
        }
        else
        {
            throw;
        }
    }

    return NoContent();
}

PutTodoItem es similar a PostTodoItem, salvo por el hecho de que usa HTTP PUT. La respuesta es 204 Sin contenido. Según la especificación HTTP, una solicitud PUT requiere que el cliente envíe toda la entidad actualizada, no solo los cambios. Para admitir actualizaciones parciales, use HTTP PATCH.

Si recibe un error al llamar a PutTodoItem, llame a GET para asegurarse de que hay un elemento en la base de datos.

Prueba del método PutTodoItem

En este ejemplo se usa una base de datos en memoria que se debe inicializar cada vez que se inicia la aplicación. Debe haber un elemento en la base de datos antes de que realice una llamada PUT. Llame a GET para asegurarse de que hay un elemento en la base de datos antes de realizar una llamada PUT.

Actualice el elemento to-do que tiene el Id = 1 y establezca su nombre en "feed fish":

  {
    "id":1,
    "name":"feed fish",
    "isComplete":true
  }

En la imagen siguiente, se muestra la actualización de Postman:

Postman console showing 204 (No Content) response

Método DeleteTodoItem

Examine el método DeleteTodoItem:

// DELETE: api/TodoItems/5
[HttpDelete("{id}")]
public async Task<ActionResult<TodoItem>> DeleteTodoItem(long id)
{
    var todoItem = await _context.TodoItems.FindAsync(id);
    if (todoItem == null)
    {
        return NotFound();
    }

    _context.TodoItems.Remove(todoItem);
    await _context.SaveChangesAsync();

    return todoItem;
}

Prueba del método DeleteTodoItem

Use Postman para eliminar una tarea pendiente:

  • Establezca el método en DELETE.
  • Establezca el URI del objeto que quiera eliminar (por ejemplo, https://localhost:5001/api/todoitems/1).
  • Seleccione Enviar.

Prevención del exceso de publicación

Actualmente, la aplicación de ejemplo expone todo el objeto TodoItem. Las aplicaciones de producción suelen limitar los datos que se escriben y se devuelven mediante un subconjunto del modelo. Hay varias razones para ello y la seguridad es una de las principales. El subconjunto de un modelo se suele conocer como un objeto de transferencia de datos (DTO), modelo de entrada o modelo de vista. En este artículo, se usa DTO.

Se puede usar un DTO para:

  • Evitar el exceso de publicación.
  • Ocultar las propiedades que los clientes no deben ver.
  • Omitir algunas propiedades para reducir el tamaño de la carga.
  • Acoplar los gráficos de objetos que contienen objetos anidados. Los gráficos de objetos acoplados pueden ser más cómodos para los clientes.

Para mostrar el enfoque del DTO, actualice la clase TodoItem a fin de que incluya un campo secreto:

public class TodoItem
{
    public long Id { get; set; }
    public string Name { get; set; }
    public bool IsComplete { get; set; }
    public string Secret { get; set; }
}

El campo secreto debe ocultarse en esta aplicación, pero una aplicación administrativa podría decidir exponerlo.

Compruebe que puede publicar y obtener el campo secreto.

Cree un modelo de DTO:

public class TodoItemDTO
{
    public long Id { get; set; }
    public string Name { get; set; }
    public bool IsComplete { get; set; }
}

Actualice el valor de TodoItemsController para usar TodoItemDTO:

    [HttpGet]
    public async Task<ActionResult<IEnumerable<TodoItemDTO>>> GetTodoItems()
    {
        return await _context.TodoItems
            .Select(x => ItemToDTO(x))
            .ToListAsync();
    }

    [HttpGet("{id}")]
    public async Task<ActionResult<TodoItemDTO>> GetTodoItem(long id)
    {
        var todoItem = await _context.TodoItems.FindAsync(id);

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

        return ItemToDTO(todoItem);
    }

    [HttpPut("{id}")]
    public async Task<IActionResult> UpdateTodoItem(long id, TodoItemDTO todoItemDTO)
    {
        if (id != todoItemDTO.Id)
        {
            return BadRequest();
        }

        var todoItem = await _context.TodoItems.FindAsync(id);
        if (todoItem == null)
        {
            return NotFound();
        }

        todoItem.Name = todoItemDTO.Name;
        todoItem.IsComplete = todoItemDTO.IsComplete;

        try
        {
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException) when (!TodoItemExists(id))
        {
            return NotFound();
        }

        return NoContent();
    }

    [HttpPost]
    public async Task<ActionResult<TodoItemDTO>> CreateTodoItem(TodoItemDTO todoItemDTO)
    {
        var todoItem = new TodoItem
        {
            IsComplete = todoItemDTO.IsComplete,
            Name = todoItemDTO.Name
        };

        _context.TodoItems.Add(todoItem);
        await _context.SaveChangesAsync();

        return CreatedAtAction(
            nameof(GetTodoItem),
            new { id = todoItem.Id },
            ItemToDTO(todoItem));
    }

    [HttpDelete("{id}")]
    public async Task<IActionResult> DeleteTodoItem(long id)
    {
        var todoItem = await _context.TodoItems.FindAsync(id);

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

        _context.TodoItems.Remove(todoItem);
        await _context.SaveChangesAsync();

        return NoContent();
    }

    private bool TodoItemExists(long id) =>
         _context.TodoItems.Any(e => e.Id == id);

    private static TodoItemDTO ItemToDTO(TodoItem todoItem) =>
        new TodoItemDTO
        {
            Id = todoItem.Id,
            Name = todoItem.Name,
            IsComplete = todoItem.IsComplete
        };       
}

Compruebe que no puede publicar ni obtener el campo secreto.

Llamar a la API web con JavaScript

Consulte Tutorial: Llamada a una API web de ASP.NET Core con JavaScript.

Agregar compatibilidad con la autenticación a una API web

ASP.NET Core Identity agrega la funcionalidad de inicio de sesión de la interfaz de usuario (IU) a las aplicaciones web de ASP.NET Core. Para proteger las API web y las SPA, use una de las siguientes opciones:

Duende Identity Server es un marco de OpenID Connect y OAuth 2.0 para ASP.NET Core. Duende Identity Server permite las siguientes características de seguridad:

  • Autenticación como servicio (AaaS)
  • Inicio de sesión único (SSO) mediante varios tipos de aplicaciones
  • Control de acceso para API
  • Federation Gateway

Importante

Duende Software puede requerir que pague una tarifa de licencia para el uso en producción de Duende Identity Server. Para más información, vea Migración de ASP.NET Core 5.0 a 6.0.

Para más información, consulte la documentación de Duende Identity Server (sitio web de Duende Software).

Publicar en Azure

Para obtener más información sobre la implementación en Azure, consulte Inicio rápido: Implementación de una aplicación web de ASP.NET.

Recursos adicionales

Vea o descargue el código de ejemplo para este tutorial. Vea cómo descargarlo.

Para obtener más información, vea los siguientes recursos: