Creación de un punto de conexión de OData v4 mediante ASP.NET WEB API

Open Data Protocol (OData) es un protocolo de acceso a datos para la web. OData proporciona una manera uniforme de consultar y manipular conjuntos de datos mediante operaciones CRUD (crear, leer, actualizar y eliminar).

ASP.NET WEB API admite tanto v3 como v4 del protocolo. Incluso puede tener un punto de conexión v4 que se ejecute en paralelo con un punto de conexión v3.

En este tutorial se muestra cómo crear un punto de conexión de OData v4 que admita operaciones CRUD.

Versiones de software usadas en el tutorial

  • Web API 5.2
  • OData v4
  • Visual Studio 2017 (descargar Visual Studio 2017 aquí)
  • Entity Framework 6
  • .NET 4.7.2

Versiones del tutorial

Para la versión 3 de OData, consulte Creación de un punto de conexión de OData v3.

Creación del proyecto de Visual Studio

En Visual Studio, en el menú Archivo, seleccione Nuevo> Proyecto .

Expanda Instalado>Visual C#>Weby seleccione la plantilla ASP.NET Aplicación web (.NET Framework). Asigne al proyecto el nombre "ProductService".

Screenshot of the visual studio new project window, showing menu options to create an A S P dot NET Web Application with the dot NET Framework.

Seleccione Aceptar.

Screenshot of the A S P dot NET Web Application, showing available templates to create the application with a Web A P I folder and core reference.

Seleccione la plantilla Vacía. En Agregar carpetas y referencias principales para:, seleccione Web API. Seleccione Aceptar.

Instalación de los paquetes de OData

En el menú Herramientas, seleccione Administrador de paquetes NuGet>Consola del Administrador de paquetes. En la ventana Consola del Administrador de paquetes, escriba:

Install-Package Microsoft.AspNet.Odata

Este comando instala los paquetes NuGet de OData más recientes.

Incorporación de una clase de modelo

Un modelo es un objeto que representa una entidad de datos en la aplicación.

En el Explorador de soluciones, haga clic con el botón derecho en la carpeta Models. En el menú contextual, seleccione Agregar>Clase.

Screenshot of the solution explorer window, highlighting the path to add a model class object to the project.

Nota:

Por convención, las clases de modelo se colocan en la carpeta Models, pero no es necesario seguir esta convención en sus propios proyectos.

Asigne Product como nombre de la clase. En el archivo Product.cs, reemplace el código reutilizable por lo siguiente:

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

La propiedad Id es la clave de entidad. Los clientes pueden consultar entidades por clave. Por ejemplo, para obtener el producto con el identificador 5, el URI es /Products(5). La propiedad Id también será la clave principal de la base de datos back-end.

Habilitación de Entity Framework

En este tutorial, usaremos Entity Framework (EF) Code First para crear la base de datos de back-end.

Nota:

La API web OData no requiere EF. Use cualquier capa de acceso a datos que pueda traducir entidades de base de datos en modelos.

En primer lugar, instale el paquete NuGet para EF. En el menú Herramientas, seleccione Administrador de paquetes NuGet>Consola del Administrador de paquetes. En la ventana Consola del Administrador de paquetes, escriba:

Install-Package EntityFramework

Abra el archivo Web.config y agregue la siguiente sección dentro del elemento de configuración, después del elementoconfigSections.

<configuration>
  <configSections>
    <!-- ... -->
  </configSections>

  <!-- Add this: -->
  <connectionStrings>
    <add name="ProductsContext" connectionString="Data Source=(localdb)\mssqllocaldb; 
        Initial Catalog=ProductsContext; Integrated Security=True; MultipleActiveResultSets=True; 
        AttachDbFilename=|DataDirectory|ProductsContext.mdf"
      providerName="System.Data.SqlClient" />
  </connectionStrings>

Esta configuración agrega una cadena de conexión para una base de datos LocalDB. Esta base de datos se usará al ejecutar la aplicación localmente.

A continuación, agregue una clase denominada ProductsContext a la carpeta Models:

using System.Data.Entity;
namespace ProductService.Models
{
    public class ProductsContext : DbContext
    {
        public ProductsContext() 
                : base("name=ProductsContext")
        {
        }
        public DbSet<Product> Products { get; set; }
    }
}

En el constructor, "name=ProductsContext" proporciona el nombre de la cadena de conexión.

Configuración del punto de conexión de OData

Abra el archivo App_Start/WebApiConfig.cs. Agregue los siguientes mediante instrucciones:

using ProductService.Models;
using Microsoft.AspNet.OData.Builder;
using Microsoft.AspNet.OData.Extensions;

A continuación, agregue el código siguiente al método Registro :

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        // New code:
        ODataModelBuilder builder = new ODataConventionModelBuilder();
        builder.EntitySet<Product>("Products");
        config.MapODataServiceRoute(
            routeName: "ODataRoute",
            routePrefix: null,
            model: builder.GetEdmModel());
    }
}

Este código hace dos cosas:

  • Crea un modelo de datos de entidad (EDM).
  • Agrega una ruta.

Un EDM es un modelo abstracto de los datos. El EDM se usa para crear el documento de metadatos del servicio. La clase ODataConventionModelBuilder crea un EDM mediante convenciones de nomenclatura predeterminadas. Este enfoque requiere el código mínimo. Si desea tener más control sobre el EDM, puede usar la clase ODataModelBuilder para crear el EDM agregando propiedades, claves y propiedades de navegación explícitamente.

Una ruta indica a la API web cómo enrutar las solicitudes HTTP al punto de conexión. Para crear una ruta de OData v4, llame al método de extensión MapODataServiceRoute.

Si la aplicación tiene varios puntos de conexión de OData, cree una ruta independiente para cada uno. Asigne a cada ruta un nombre y prefijo de ruta únicos.

Agregar el controlador OData

Un controlador es una clase que controla las solicitudes HTTP. Cree un controlador independiente para cada conjunto de entidades en el servicio OData. En este tutorial, creará un controlador para la Product entidad.

En el Explorador de soluciones, haga clic con el botón derecho en la carpeta Controladores y seleccione Agregar>Clase. Asigne ProductsController como nombre de la clase.

Nota:

La versión de este tutorial para OData v3 usa el Agregar controladorandamiaje. Actualmente, no hay andamiaje para OData v4.

Reemplace el código reutilizable en ProductsController.cs por lo siguiente.

using ProductService.Models;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using System.Web.Http;
using System.Web.OData;
namespace ProductService.Controllers
{
    public class ProductsController : ODataController
    {
        ProductsContext db = new ProductsContext();
        private bool ProductExists(int key)
        {
            return db.Products.Any(p => p.Id == key);
        } 
        protected override void Dispose(bool disposing)
        {
            db.Dispose();
            base.Dispose(disposing);
        }
    }
}

El controlador usa la ProductsContext clase para acceder a la base de datos mediante EF. Observe que el controlador invalida el método Dispose para eliminar ProductsContext.

Este es el punto de partida del controlador. A continuación, agregaremos métodos para todas las operaciones CRUD.

Consulta del conjunto de entidades

Agregue los métodos siguientes a ProductsController.

[EnableQuery]
public IQueryable<Product> Get()
{
    return db.Products;
}
[EnableQuery]
public SingleResult<Product> Get([FromODataUri] int key)
{
    IQueryable<Product> result = db.Products.Where(p => p.Id == key);
    return SingleResult.Create(result);
}

La versión sin parámetros del método Get devuelve toda la colección Products. El método Get con una clave parámetro busca un producto por su clave (en este caso, la propiedad Id).

El atributo [EnableQuery] permite a los clientes modificar la consulta mediante opciones de consulta como $filter, $sort y $page. Para obtener más información, consulte Compatibilidad con las opciones de consulta de OData.

Adición de una entidad al conjunto de entidades

Para permitir que los clientes agreguen un nuevo producto a la base de datos, agregue el método siguiente a ProductsController.

public async Task<IHttpActionResult> Post(Product product)
{
    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }
    db.Products.Add(product);
    await db.SaveChangesAsync();
    return Created(product);
}

Actualización de una entidad

OData admite dos semánticas diferentes para actualizar una entidad, PATCH y PUT.

  • PATCH realiza una actualización parcial. El cliente especifica solo las propiedades que se van a actualizar.
  • PUT reemplaza a toda la entidad.

La desventaja de PUT es que el cliente debe enviar valores para todas las propiedades de la entidad, incluidos los valores que no cambian. La especificación de OData indica que se prefiere PATCH.

En cualquier caso, este es el código para los métodos PATCH y PUT:

public async Task<IHttpActionResult> Patch([FromODataUri] int key, Delta<Product> product)
{
    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }
    var entity = await db.Products.FindAsync(key);
    if (entity == null)
    {
        return NotFound();
    }
    product.Patch(entity);
    try
    {
        await db.SaveChangesAsync();
    }
    catch (DbUpdateConcurrencyException)
    {
        if (!ProductExists(key))
        {
            return NotFound();
        }
        else
        {
            throw;
        }
    }
    return Updated(entity);
}
public async Task<IHttpActionResult> Put([FromODataUri] int key, Product update)
{
    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }
    if (key != update.Id)
    {
        return BadRequest();
    }
    db.Entry(update).State = EntityState.Modified;
    try
    {
        await db.SaveChangesAsync();
    }
    catch (DbUpdateConcurrencyException)
    {
        if (!ProductExists(key))
        {
            return NotFound();
        }
        else
        {
            throw;
        }
    }
    return Updated(update);
}

En el caso de PATCH, el controlador usa el tipo Delta<T> para realizar un seguimiento de los cambios.

Eliminación de una entidad

Para permitir que los clientes eliminen un producto de la base de datos, agregue el método siguiente a ProductsController.

public async Task<IHttpActionResult> Delete([FromODataUri] int key)
{
    var product = await db.Products.FindAsync(key);
    if (product == null)
    {
        return NotFound();
    }
    db.Products.Remove(product);
    await db.SaveChangesAsync();
    return StatusCode(HttpStatusCode.NoContent);
}