Creación de un punto de conexión de OData v3 con Web API 2

por Mike Wasson

Descargar el proyecto completado

El Open Data Protocol (OData) es un protocolo de acceso a datos para la web. OData proporciona una manera uniforme de estructurar los datos, consultar los datos y manipular el conjunto de datos a través de operaciones CRUD (crear, leer, actualizar y eliminar). OData admite los formatos AtomPub (XML) y JSON. OData también define una manera de exponer metadatos sobre los datos. Los clientes pueden usar los metadatos para detectar la información de tipo y las relaciones del conjunto de datos.

ASP.NET Web API facilita la creación de un punto de conexión de OData para un conjunto de datos. Puede controlar exactamente qué operaciones de OData admite el punto de conexión. Puede hospedar varios puntos de conexión de OData, junto con puntos de conexión que no son de OData. Tiene control total sobre el modelo de datos, la lógica de negocios de back-end y la capa de datos.

Versiones de software usadas en el tutorial

En la actualización de ASP.NET y Web Tools 2012.2 se ha añadido compatibilidad con OData de Web API. Sin embargo, en este tutorial se usa un scaffolding que se agregó en Visual Studio 2013.

En este tutorial, creará un punto de conexión de OData simple que los clientes pueden consultar. También creará un cliente de C# para el punto de conexión. Después de completar este tutorial, el siguiente conjunto de tutoriales muestra cómo agregar más funcionalidad, incluidas las relaciones de entidad, las acciones y $expand/$select.

Creación del proyecto de Visual Studio

En este tutorial, creará un punto de conexión de OData que admita operaciones CRUD básicas. El punto de conexión expondrá un único recurso, una lista de productos. Los tutoriales posteriores agregarán más características.

Inicie Visual Studio y seleccione Nuevo proyecto en la página Inicio. 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. Seleccione la plantilla Aplicación Web ASP.NET .

Screenshot of the new project window, showing the path to the template pane, and displaying highlighted directions to select the A S P dot NET Web Application option.

En el cuadro de diálogo Nuevo proyecto ASP.NET, seleccione la plantilla Vacía. En "Agregar carpetas y referencias principales para...", compruebe Web API. Haga clic en OK.

Screenshot of the A S P dot NET project dialog box, showing the template options boxes and highlighting the 'empty' option.

Adición de un modelo de entidad

Un modelo es un objeto que representa los datos de la aplicación. Para este tutorial, necesitamos un modelo que represente un producto. El modelo corresponde al tipo de entidad OData.

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

Screenshot of the solution explorer dialog box, showing the menu list for each selection as it highlights each, leading the the class option.

En el cuadro de diálogo Agregar nuevo elemento, asigne al nombre la clase "Product".

Screenshot of the 'add new project' window, displaying default sort and showing the class option, and the words 'product dot c s' in the empty field below.

Nota:

Por convención, las clases de modelo se colocan en la carpeta Models. No tiene que seguir esta convención en sus propios proyectos, pero la usaremos para este tutorial.

En el archivo Product.cs, agregue la siguiente definición de clase:

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 será la clave de entidad. Los clientes pueden consultar productos por identificador. Este campo también sería la clave principal de la base de datos back-end.

Compile el proyecto ahora. En el paso siguiente, usaremos un scaffolding de Visual Studio que usa la reflexión para buscar el tipo de producto.

Adición de un controlador OData

Un controlador es una clase que controla las solicitudes HTTP. Defina un controlador independiente para cada conjunto de entidades en el servicio OData. En este tutorial, crearemos un único controlador.

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 window, which highlights the controller option that then displays the menus to add an O Data controller.

En el cuadro de diálogo Agregar scaffold, seleccione "Controlador de OData de API Web 2 con acciones, mediante Entity Framework".

Screenshot of the 'add scaffold' screen, showing the controller options menu, and highlighting the Web A P I 2 O Data controller.

En el cuadro de diálogo Agregar controlador, asigne al controlador el nombre "ProductsController". Active la casilla "Usar acciones del controlador asincrónico". En la lista desplegable Modelo, seleccione la clase Product.

Screenshot of the add controller dialog box, displaying fields for controller name, model class drop down list, and data context class.

Haga clic en el botón Nuevo contexto de datos.... Deje el nombre predeterminado para el tipo de contexto de datos y haga clic en Agregar.

Screenshot of the new data context window, showing a field for 'new data context type' and showing the default name for the data context type.

Haga clic en Agregar en el cuadro de diálogo Agregar controlador para agregar el controlador.

Screenshot of the add controller dialog box, showing the different field requirements, with a checkbox to 'use async controller actions'.

Nota: si recibe un mensaje de error que indica "Error al obtener el tipo...", asegúrese de compilar el proyecto de Visual Studio después de agregar la clase Product. El scaffolding usa la reflexión para buscar la clase.

Screenshot of the Microsoft Visual Studio, displaying a red circled 'X' followed by the word 'error' and a detailed message of the error.

El scaffolding agrega dos archivos de código al proyecto:

  • Products.cs define el controlador de Web API que implementa el punto de conexión de OData.
  • ProductServiceContext.cs proporciona métodos para consultar la base de datos subyacente mediante Entity Framework.

Screenshot of the project window, showing the product service menu and circling the two newly added files under controllers and under models.

Adición de EDM y Route

En el Explorador de soluciones, expanda la carpeta App_Start y abra el archivo denominado WebApiConfig.cs. Esta clase contiene código de configuración para Web API. Reemplace este código por lo siguiente:

using ProductService.Models;
using System.Web.Http;
using System.Web.Http.OData.Builder;

namespace ProductService
{
    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
            builder.EntitySet<Product>("Products");
            config.Routes.MapODataRoute("odata", "odata", builder.GetEdmModel());
        }
    }
}

Este código hace dos cosas:

  • Crea un modelo de datos de entidad (EDM) para el punto de conexión de OData.
  • Agrega una ruta para el punto de conexión.

Un EDM es un modelo abstracto de los datos. El EDM se usa para crear el documento de metadatos y definir los URI para el servicio. El ODataConventionModelBuilder crea un EDM mediante un conjunto de convenciones de nomenclatura predeterminadas EDM. 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.

El método EntitySet agrega un conjunto de entidades al EDM:

modelBuilder.EntitySet<Product>("Products");

La cadena "Products" define el nombre del conjunto de entidades. El nombre del controlador debe coincidir con el nombre del conjunto de entidades. En este tutorial, el conjunto de entidades se denomina "Products" y el controlador se denomina ProductsController. Si llamó al conjunto de entidades "ProductSet", asignaría el nombre al controlador ProductSetController. Tenga en cuenta que un punto de conexión puede tener varios conjuntos de entidades. Llame a EntitySet<T> para cada conjunto de entidades y, a continuación, defina un controlador correspondiente.

El método MapODataRoute agrega una ruta para el punto de conexión de OData.

config.Routes.MapODataRoute("ODataRoute", "odata", model);

El primer parámetro es un nombre descriptivo para la ruta. Los clientes del servicio no ven este nombre. El segundo parámetro es el prefijo URI para el punto de conexión. Dado este código, el URI del conjunto de entidades Products es http://hostname/odata/Products. La aplicación puede tener más de un punto de conexión de OData. Para cada punto de conexión, llame a MapODataRoute y proporcione un nombre de ruta único y un prefijo de URI único.

Inicialización de la base de datos (opcional)

En este paso, usará Entity Framework para inicializar la base de datos con algunos datos de prueba. Este paso es opcional, pero le permite probar el punto de conexión de OData inmediatamente.

En el menú Herramientas, seleccione Administrador de paquetes NuGet y, a continuación, Consola del administrador de paquetes. En la ventana Consola del Administrador de paquetas , escriba el siguiente comando:

Enable-Migrations

Esto agrega una carpeta denominada Migrations y un archivo de código denominado Configuration.cs.

Screenshot of the solution explorer's product service menu, circling the newly added folder called migrations, and showing the file within it.

Abra este archivo y agregue el código siguiente al método Configuration.Seed.

protected override void Seed(ProductService.Models.ProductServiceContext context)
{
    // New code 
    context.Products.AddOrUpdate(new Product[] {
        new Product() { ID = 1, Name = "Hat", Price = 15, Category = "Apparel" },
        new Product() { ID = 2, Name = "Socks", Price = 5, Category = "Apparel" },
        new Product() { ID = 3, Name = "Scarf", Price = 12, Category = "Apparel" },
        new Product() { ID = 4, Name = "Yo-yo", Price = 4.95M, Category = "Toys" },
        new Product() { ID = 5, Name = "Puzzle", Price = 8, Category = "Toys" },
    });
}

En la ventana Consola del Administrador de paquetes, escriba los siguientes comandos:

Add-Migration Initial
Update-Database

Estos comandos generan código que crea la base de datos y, a continuación, ejecutan ese código.

Exploración del punto de conexión de OData

En esta sección, usaremos el proxy de depuración web de Fiddler para enviar solicitudes al punto de conexión y examinar los mensajes de respuesta. Esto le ayudará a comprender las funcionalidades de un punto de conexión de OData.

En Visual Studio, presione F5 para iniciar la depuración. De forma predeterminada, Visual Studio abre el explorador para http://localhost:*port*, donde puerto es el número de puerto configurado en la configuración del proyecto.

Puede cambiar el número de puerto en la configuración del proyecto. En el Explorador de soluciones, haga clic con el botón derecho en el proyecto y seleccione Propiedades. En la ventana de propiedades, seleccione Web. Escriba el número de puerto en Dirección URL del proyecto.

Documento de servicio

El documento de servicio contiene una lista de los conjuntos de entidades para el punto de conexión de OData. Para obtener el documento de servicio, envíe una solicitud GET al URI raíz del servicio.

Con Fiddler, escriba el siguiente URI en la pestaña Composer: http://localhost:port/odata/, donde puerto es el número de puerto.

Screenshot of the service document window, displaying the different tabs with the 'parsed' tab chosen and showing the U R L data in the composer field.

Haga clic en el botón Ejecutar . Fiddler envía una solicitud HTTP GET a la aplicación. Debería ver la respuesta en la lista Sesiones web. Si todo funciona, el código de estado será 200.

Screenshot of the web sessions list, showing the H T T P protocol with the result number 200, and the U R L address and host.

Haga doble clic en la respuesta de la lista Sesiones web para ver los detalles del mensaje de respuesta en la pestaña Inspectores.

Screenshot of the web sessions list's inspector tab, displaying the Request Headers response and the X M L information.

El mensaje de respuesta HTTP sin formato debe ser similar al siguiente:

HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Type: application/atomsvc+xml; charset=utf-8
Expires: -1
Server: Microsoft-IIS/8.0
DataServiceVersion: 3.0
Date: Mon, 23 Sep 2013 17:51:01 GMT
Content-Length: 364

<?xml version="1.0" encoding="utf-8"?>
<service xml:base="http://localhost:60868/odata" 
    xmlns="http://www.w3.org/2007/app" xmlns:atom="http://www.w3.org/2005/Atom">
  <workspace>
    <atom:title type="text">Default</atom:title>
    <collection href="Products">
        <atom:title type="text">Products</atom:title>
    </collection>
    </workspace>
</service></pre>

De forma predeterminada, Web API devuelve el documento de servicio en formato AtomPub. Para solicitar JSON, agregue el siguiente encabezado a la solicitud HTTP:

Accept: application/json

Screenshot of the web sessions window, showing the response from the A P I in the Request Headers section, and circling where to write the j son request.

Ahora, la respuesta HTTP contiene una carga JSON:

HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Type: application/json; charset=utf-8
Expires: -1
Server: Microsoft-IIS/8.0
DataServiceVersion: 3.0
Date: Mon, 23 Sep 2013 22:59:28 GMT
Content-Length: 136

{
  "odata.metadata":"http://localhost:60868/odata/$metadata","value":[
    {
      "name":"Products","url":"Products"
    }
  ]
}

Documento de metadatos de servicio

El documento de metadatos de servicio describe el modelo de datos del servicio mediante un lenguaje XML denominado lenguaje de definición de esquema conceptual (CSDL). El documento de metadatos muestra la estructura de los datos del servicio y se puede usar para generar código de cliente.

Para obtener el documento de metadatos, envíe una solicitud GET a http://localhost:port/odata/$metadata. Estos son los metadatos del punto de conexión que se muestra en este tutorial.

HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Type: application/xml; charset=utf-8
Expires: -1
Server: Microsoft-IIS/8.0
DataServiceVersion: 3.0
Date: Mon, 23 Sep 2013 23:05:52 GMT
Content-Length: 1086

<?xml version="1.0" encoding="utf-8"?>
<edmx:Edmx Version="1.0" xmlns:edmx="http://schemas.microsoft.com/ado/2007/06/edmx">
  <edmx:DataServices m:DataServiceVersion="3.0" m:MaxDataServiceVersion="3.0" 
    xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata">
    <Schema Namespace="ProductService.Models" xmlns="http://schemas.microsoft.com/ado/2009/11/edm">
      <EntityType Name="Product">
        <Key>
          <PropertyRef Name="ID" />
        </Key>
        <Property Name="ID" Type="Edm.Int32" Nullable="false" />
        <Property Name="Name" Type="Edm.String" />
        <Property Name="Price" Type="Edm.Decimal" Nullable="false" />
        <Property Name="Category" Type="Edm.String" />
      </EntityType>
    </Schema>
    <Schema Namespace="Default" xmlns="http://schemas.microsoft.com/ado/2009/11/edm">
      <EntityContainer Name="Container" m:IsDefaultEntityContainer="true">
        <EntitySet Name="Products" EntityType="ProductService.Models.Product" />
      </EntityContainer>
    </Schema>
  </edmx:DataServices>
</edmx:Edmx>

Conjunto de entidades

Para obtener el conjunto de entidades Products, envíe una solicitud GET a http://localhost:port/odata/Products.

HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Type: application/json; charset=utf-8
Expires: -1
Server: Microsoft-IIS/8.0
DataServiceVersion: 3.0
Date: Mon, 23 Sep 2013 23:01:31 GMT
Content-Length: 459

{
  "odata.metadata":"http://localhost:60868/odata/$metadata#Products","value":[
    {
      "ID":1,"Name":"Hat","Price":"15.00","Category":"Apparel"
    },{
      "ID":2,"Name":"Socks","Price":"5.00","Category":"Apparel"
    },{
      "ID":3,"Name":"Scarf","Price":"12.00","Category":"Apparel"
    },{
      "ID":4,"Name":"Yo-yo","Price":"4.95","Category":"Toys"
    },{
      "ID":5,"Name":"Puzzle","Price":"8.00","Category":"Toys"
    }
  ]
}

Entity

Para obtener un producto individual, envíe una solicitud GET a http://localhost:port/odata/Products(1), donde "1" es el identificador del producto.

HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Type: application/json; charset=utf-8
Expires: -1
Server: Microsoft-IIS/8.0
DataServiceVersion: 3.0
Date: Mon, 23 Sep 2013 23:04:29 GMT
Content-Length: 140

{
  "odata.metadata":"http://localhost:60868/odata/$metadata#Products/@Element","ID":1,
      "Name":"Hat","Price":"15.00","Category":"Apparel"
}

Formatos de serialización de OData

OData admite varios formatos de serialización:

  • Atom Pub (XML)
  • JSON "light" (introducido en OData v3)
  • JSON "verbose" (OData v2)

De forma predeterminada, Web API usa el formato "light" de AtomPubJSON.

Para obtener el formato AtomPub, establezca el encabezado Accept en "application/atom+xml". A continuación se proporciona un cuerpo de respuesta de ejemplo:

<?xml version="1.0" encoding="utf-8"?>
<entry xml:base="http://localhost:60868/odata" xmlns="http://www.w3.org/2005/Atom" xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns:georss="http://www.georss.org/georss" xmlns:gml="http://www.opengis.net/gml">
  <id>http://localhost:60868/odata/Products(1)</id>
  <category term="ProductService.Models.Product" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
  <link rel="edit" href="http://localhost:60868/odata/Products(1)" />
  <link rel="self" href="http://localhost:60868/odata/Products(1)" />
  <title />
  <updated>2013-09-23T23:42:11Z</updated>
  <author>
    <name />
  </author>
  <content type="application/xml">
    <m:properties>
      <d:ID m:type="Edm.Int32">1</d:ID>
      <d:Name>Hat</d:Name>
      <d:Price m:type="Edm.Decimal">15.00</d:Price>
      <d:Category>Apparel</d:Category>
    </m:properties>
  </content>
</entry>

Puede ver una desventaja obvia del formato Atom: es mucho más detallada que la versión light de JSON. Sin embargo, si tiene un cliente que entiende AtomPub, es posible que el cliente prefiera ese formato sobre JSON.

Esta es la versión light de JSON de la misma entidad:

{
  "odata.metadata":"http://localhost:60868/odata/$metadata#Products/@Element",
  "ID":1,
  "Name":"Hat",
  "Price":"15.00",
  "Category":"Apparel"
}

El formato de JSON light se introdujo en la versión 3 del protocolo OData. Por motivos de compatibilidad con versiones anteriores, un cliente puede solicitar el formato JSON "verbose" anterior. Para solicitar JSON verbose, establezca el encabezado Accept en application/json;odata=verbose. Esta es la versión detallada:

{
  "d":{
    "__metadata":{
      "id":"http://localhost:18285/odata/Products(1)",
      "uri":"http://localhost:18285/odata/Products(1)",
      "type":"ProductService.Models.Product"
    },"ID":1,"Name":"Hat","Price":"15.00","Category":"Apparel"
  }
}

Este formato transmite más metadatos en el cuerpo de la respuesta, lo que puede agregar una sobrecarga considerable en toda una sesión. Además, agrega un nivel de direccionamiento indirecto ajustando el objeto en una propiedad denominada "d".

Pasos siguientes