Habilitación de operaciones CRUD en ASP.NET Web API 1

por Mike Wasson

Descargar el proyecto completado

En este tutorial se muestra cómo admitir operaciones CRUD en un servicio HTTP mediante ASP.NET Web API para ASP.NET 4.x.

Versiones de software que se usan en el tutorial

  • Visual Studio 2012
  • Web API 1 (también funciona con Web API 2)

Las siglas CRUD provienen de "Create, Read, Update, and Delete", que significa "Crear, Leer, Actualizar y Eliminar" y son las cuatro operaciones básicas de las bases de datos. Muchos servicios HTTP también modelan operaciones CRUD a través de las API REST o de tipo REST.

En este tutorial, compilará una API web muy sencilla para administrar una lista de productos. Cada producto contendrá un nombre, un precio y una categoría (como "juguetes" o "hardware"), además de un id. de producto.

La API de productos expondrá los métodos siguientes.

Action Método HTTP Dirección URL relativa
Obtención de una lista de todos los productos OBTENER /api/products
Obtener un producto según el id. OBTENER /api/products/id
Obtención de un producto por categoría OBTENER /api/products?category=category
Crear un nuevo producto PUBLICAR /api/products
actualización de un producto PUT /api/products/id
Eliminar un producto Delete /api/products/id

Observe que algunos de los URI incluyen el id. del producto en la ruta de acceso. Por ejemplo, para obtener el producto cuyo id. es 28, el cliente envía una solicitud GET para http://hostname/api/products/28.

Recursos

La API de productos define los URI de dos tipos de recursos:

Resource URI
Lista de todos los productos. /api/products
Producto individual. /api/products/id

Métodos

Los cuatro métodos HTTP principales (GET, PUT, POST y DELETE) se pueden asignar a las operaciones CRUD de la siguiente manera:

  • GET recupera la representación del recurso en un URI especificado. GET no debe tener efectos secundarios en el servidor.
  • PUT actualiza un recurso en un URI especificado. PUT también se puede usar para crear un nuevo recurso en un URI especificado si el servidor permite a los clientes especificar nuevos URI. En este tutorial, la API no admitirá la creación a través de PUT.
  • POST crea un nuevo recurso. El servidor asigna el URI del nuevo objeto y devuelve este URI como parte del mensaje de respuesta.
  • DELETE elimina un recurso en un URI especificado.

Nota: El método PUT reemplaza toda la entidad del producto. Es decir, se espera que el cliente envíe una representación completa del producto actualizado. Si desea admitir actualizaciones parciales, se prefiere el método PATCH. En este tutorial no se implementa PATCH.

Creación de un proyecto de API web

Comience ejecutando Visual Studio y seleccione Nuevo proyecto en la página Iniciar. O bien, en el menú Archivo, seleccione Nuevo y, a continuación, Proyecto.

En el panel Plantillas, seleccione Plantillas instaladas y expanda el nodo Visual C#. En Visual C#, seleccione Web. En la lista de plantillas de proyecto, seleccione Aplicación web ASP.NET MVC 4. Asigne al proyecto el nombre "ProductStore" y haga clic en Aceptar.

Screenshot of the new project window, showing the menu options and highlighting the path to create an A S P dot NET M V C 4 Web Application.

En el cuadro de diálogo Nuevo proyecto ASP.NET MVC 4, seleccione API web y haga clic en Aceptar.

Screenshot of the new A S P dot NET project, showing boxed images of available templates and highlighting the Web A P I template, in blue.

Agregar un modelo

Un modelo es un objeto que representa los datos de la aplicación. En ASP.NET Web API, puede usar objetos CLR fuertemente tipados como modelos y se serializarán automáticamente en XML o JSON para el cliente.

En el caso de la ProductStore API, nuestros datos constan de productos, por lo que crearemos una nueva clase denominada Product.

Si el Explorador de soluciones no está visible, haga clic en el menú Ver y seleccione Explorador de soluciones. En el Explorador de soluciones, haga clic con el botón derecho en la carpeta Modelos. En el menú contextual, seleccione Agregar y, a continuación, Clase. Asigne a la clase el nombre "Product".

Screenshot of the solution explorer menu, highlighting the selection for models to show an additional menu to select the add class option.

Agregue las propiedades siguientes a la clase Product.

namespace ProductStore.Models
{
    public class Product
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Category { get; set; }
        public decimal Price { get; set; }
    }
}

Adición de un repositorio

Necesitamos almacenar una colección de productos. Es buena idea separar la colección de nuestra implementación de servicio. De este modo, podemos cambiar la memoria auxiliar sin escribir la clase de servicio. Este tipo de diseño se denomina patrón de repositorio. Comience definiendo una interfaz genérica para el repositorio.

En el Explorador de soluciones, haga clic con el botón derecho en la carpeta Modelos. Seleccione Agregar y, a continuación, Nuevo elemento.

Screenshot of the solution explorer menu, which highlights the models option and brings up a menu to add a new item.

En el panel Plantillas, seleccione Plantillas instaladas y expanda el nodo C#. En C#, seleccione Código. En la lista de plantillas de código, seleccione Interfaz. Asigne a la interfaz el nombre "IProductRepository".

Screenshot of the templates pane, showing the installed templates menu, which highlights the code and interface options in grey.

Agregue la siguiente implementación:

namespace ProductStore.Models
{
    public interface IProductRepository
    {
        IEnumerable<Product> GetAll();
        Product Get(int id);
        Product Add(Product item);
        void Remove(int id);
        bool Update(Product item);
    }
}

Ahora agregue otra clase a la carpeta Modelos, denominada "ProductRepository". Esta clase implementará la interfaz IProductRepository. Agregue la siguiente implementación:

namespace ProductStore.Models
{
    public class ProductRepository : IProductRepository
    {
        private List<Product> products = new List<Product>();
        private int _nextId = 1;

        public ProductRepository()
        {
            Add(new Product { Name = "Tomato soup", Category = "Groceries", Price = 1.39M });
            Add(new Product { Name = "Yo-yo", Category = "Toys", Price = 3.75M });
            Add(new Product { Name = "Hammer", Category = "Hardware", Price = 16.99M });
        }

        public IEnumerable<Product> GetAll()
        {
            return products;
        }

        public Product Get(int id)
        {
            return products.Find(p => p.Id == id);
        }

        public Product Add(Product item)
        {
            if (item == null)
            {
                throw new ArgumentNullException("item");
            }
            item.Id = _nextId++;
            products.Add(item);
            return item;
        }

        public void Remove(int id)
        {
            products.RemoveAll(p => p.Id == id);
        }

        public bool Update(Product item)
        {
            if (item == null)
            {
                throw new ArgumentNullException("item");
            }
            int index = products.FindIndex(p => p.Id == item.Id);
            if (index == -1)
            {
                return false;
            }
            products.RemoveAt(index);
            products.Add(item);
            return true;
        }
    }
}

El repositorio mantiene la lista en la memoria local. Esto está bien para un tutorial, pero en una aplicación real almacenaría los datos externamente, ya sea en una base de datos o en el almacenamiento en la nube. El patrón del repositorio facilita el cambio de la implementación más adelante.

Adición de un controlador de API web

Si ha trabajado con ASP.NET MVC, ya está familiarizado con los controladores. En ASP.NET Web API los controladores son una clase que controla las solicitudes HTTP del cliente. El asistente del nuevo proyecto creó dos controladores cuando se creó el proyecto. Para verlos, expanda la carpeta Controladores en el Explorador de soluciones.

  • HomeController es un controlador ASP.NET MVC tradicional. Es responsable de publicar las páginas HTML del sitio y no está directamente relacionado con nuestra API web.
  • ValuesController es un controlador WebAPI de ejemplo.

Continúe y elimine ValuesController; para ello, haga clic con el botón derecho en el archivo en el Explorador de soluciones y seleccione Eliminar. Ahora agregue un nuevo controlador, como se indica a continuación:

En el Explorador de soluciones, haga clic con el botón derecho en la carpeta Controllers. Seleccione Agregar y, luego, Controlador.

Screenshot of the solution explorer menu, highlighting the controllers category, which brings another menu, highlighting the path to add a controller.

En el asistente Agregar controlador, asigne al controlador el nombre "ProductsController". En la lista desplegable Plantilla, seleccione Controlador de API vacío. A continuación, haga clic en Agregar.

Screenshot of the add controller window, showing the controller name field to enter a name, and a dropdown templates list, under scaffolding options.

Nota:

No es necesario colocar los controladores en una carpeta denominada Controladores. El nombre de la carpeta no es importante; es simplemente una manera cómoda de organizar los archivos de origen.

El asistente para agregar controladores creará un archivo denominado ProductsController.cs en la carpeta Controladores. Si este archivo no está abierto, haga doble clic en el archivo para abrirlo. Agregue la siguiente instrucción using :

using ProductStore.Models;

Agregue un campo que contenga una instancia de IProductRepository.

public class ProductsController : ApiController
{
    static readonly IProductRepository repository = new ProductRepository();
}

Nota:

Llamar a new ProductRepository() en el controlador no es el mejor diseño, ya que vincula el controlador a una implementación determinada de IProductRepository. Para obtener un mejor enfoque, consulte Uso del solucionador de dependencias de API web.

Obtención de un recurso

La ProductStore API expondrá varias acciones de "lectura" como métodos HTTP GET. Cada acción corresponderá a un método de la clase de ProductsController.

Action Método HTTP Dirección URL relativa
Obtención de una lista de todos los productos OBTENER /api/products
Obtener un producto según el id. OBTENER /api/products/id
Obtención de un producto por categoría OBTENER /api/products?category=category

Para obtener la lista de todos los productos, agregue este método a la clase de ProductsController:

public class ProductsController : ApiController
{
    public IEnumerable<Product> GetAllProducts()
    {
        return repository.GetAll();
    }
    // ....
}

El nombre del método comienza por "Get", por lo que por convención se asigna a las solicitudes GET. Además, dado que el método no tiene parámetros, se asigna a un URI que no contiene ningún segmento"id" en la ruta de acceso.

Para obtener un producto por el id., agregue este método a la clase de ProductsController:

public Product GetProduct(int id)
{
    Product item = repository.Get(id);
    if (item == null)
    {
        throw new HttpResponseException(HttpStatusCode.NotFound); 
    }
    return item;
}

El nombre de este método también comienza por "Get", pero el método tiene un parámetro denominado id. Este parámetro se asigna al segmento "id" de la ruta de acceso del URI. El marco de ASP.NET Web API convierte automáticamente el id. al tipo de datos correcto (int) del parámetro.

El método GetProduct produce una excepción de tipo HttpResponseException si id no es válido. El marco traducirá esta excepción en un error 404 (no encontrado).

Por último, agregue un método para buscar productos por categoría:

public IEnumerable<Product> GetProductsByCategory(string category)
{
    return repository.GetAll().Where(
        p => string.Equals(p.Category, category, StringComparison.OrdinalIgnoreCase));
}

Si el URI de solicitud tiene una cadena de consulta, la API web intenta hacer coincidir los parámetros de consulta con los parámetros del método de controlador. Por lo tanto, un URI con la forma "api/products?category=category" se asignará a este método.

Creación de un recurso

A continuación, agregaremos un método a la clase de ProductsControllerpara crear un nuevo producto. Esta es una implementación sencilla del método:

// Not the final implementation!
public Product PostProduct(Product item)
{
    item = repository.Add(item);
    return item;
}

Tenga en cuenta dos cosas sobre este método:

  • El nombre del método comienza por "Post...". Para crear un nuevo producto, el cliente envía una solicitud HTTP POST.
  • El método toma un parámetro del tipo Producto. En la API web, los parámetros con tipos complejos se deserializan desde el cuerpo de la solicitud. Por lo tanto, esperamos que el cliente envíe una representación serializada de un objeto de producto, en formato XML o JSON.

Esta implementación funcionará, pero no es lo bastante completa. Lo ideal es que la respuesta HTTP incluya lo siguiente:

  • Código de respuesta: de forma predeterminada, el marco de la API web establece el código de estado de respuesta en 200 (Correcto). Pero según el protocolo HTTP/1.1, cuando una solicitud POST da como resultado la creación de un recurso, el servidor debe responder con el estado 201 (creado).
  • Ubicación: cuando el servidor crea un recurso, debe incluir el URI del nuevo recurso en el encabezado Ubicación de la respuesta.

ASP.NET Web API facilita la manipulación del mensaje de respuesta HTTP. Esta es la implementación mejorada:

public HttpResponseMessage PostProduct(Product item)
{
    item = repository.Add(item);
    var response = Request.CreateResponse<Product>(HttpStatusCode.Created, item);

    string uri = Url.Link("DefaultApi", new { id = item.Id });
    response.Headers.Location = new Uri(uri);
    return response;
}

Tenga en cuenta que el tipo de valor devuelto del método ahora es HttpResponseMessage. Al devolver un HttpResponseMessage en lugar de un Producto, podemos controlar los detalles del mensaje de respuesta HTTP, incluido el código de estado y el encabezado Ubicación.

El método CreateResponse crea un HttpResponseMessage y escribe automáticamente una representación serializada del objeto Product en el cuerpo del mensaje de respuesta.

Nota:

En este ejemplo no se valida el Product. Para obtener más información sobre la validación del modelo, consulte Validación de modelos en ASP.NET Web API.

Actualización de un recurso

Actualizar un producto con PUT es sencillo:

public void PutProduct(int id, Product product)
{
    product.Id = id;
    if (!repository.Update(product))
    {
        throw new HttpResponseException(HttpStatusCode.NotFound);
    }
}

El nombre del método comienza por "Put...", por lo que la API web lo hace coincidir con las solicitudes PUT. El método toma dos parámetros, el id. del producto y el producto actualizado. El parámetro id se toma de la ruta de acceso del URI y el parámetro producto se deserializa desde el cuerpo de la solicitud. De forma predeterminada, el marco de ASP.NET Web API toma tipos de parámetros sencillos de la ruta y tipos complejos del cuerpo de la solicitud.

Eliminación de un recurso

Para eliminar un recurso, defina un método "Delete...".

public void DeleteProduct(int id)
{
    Product item = repository.Get(id);
    if (item == null)
    {
        throw new HttpResponseException(HttpStatusCode.NotFound);
    }

    repository.Remove(id);
}

Si una solicitud DELETE se realiza correctamente, puede devolver el estado 200 (CORRECTO) con un cuerpo de entidad que describa el estado; estado 202 (Aceptado) si la eliminación sigue pendiente; o el estado 204 (sin contenido) sin cuerpo de entidad. En este caso, el método DeleteProduct tiene un tipo de valor devuelto void, por lo que ASP.NET Web API lo traduce automáticamente en el código de estado 204 (sin contenido).