Datos 2.0
Exponer y consumir datos en un mundo de servicios web
Elisa Flasko y Mike Flasko
Este artículo se basa en una versión preliminar de ASP.NET 3.5 y de Microsoft AJAX Library. Por lo tanto, toda la información contenida en este documento está sujeta a cambios.
En este artículo se describen los siguientes temas:
- El significado de los servicios de datos
- Exponer y consumir datos
- Describir datos con el modelo de datos de entidad
- Seguridad de datos
|
En este artículo se usan las siguientes tecnologías:
ADO.NET Data Services, LINQ, Modelo de datos de entidad
|

Contenido
Recuerde la última aplicación enriquecida para Internet (RIA) que creó. ¿Cómo obtuvo los datos? ¿Cómo separó los datos de la presentación y la información de IU enviada al explorador? ¿Qué pasaría si existiera una manera más sencilla de hacerlo?
La separación de la presentación y de los datos no es una idea nueva, pero con la popularidad creciente de tecnologías de RIA como AJAX y Silverlight™, se ha convertido en algo más frecuente. Estas tecnologías se basan en la idea de separar la presentación y los datos para permitir una aplicación más interactiva y con mayor capacidad de respuesta.
Por ejemplo, las aplicaciones RIA basadas en Silverlight recopilan previamente el código que controla la presentación y que se implementa en el cliente a través del servidor web. A continuación, tras alcanzar el explorador web, el código devuelve la llamada a un servidor web para recuperar los datos que se van a mostrar en la interfaz de usuario. Este tipo de tecnologías normalmente eliminan la opción de un proceso de representación por parte del servidor, que mezclaría los datos y el código de presentación.
Además de separar la presentación y los datos para obtener experiencias web más interactivas y enriquecidas, existe una tendencia en la Web para exponer y consumir datos independientes de cualquier interfaz de usuario. La proliferación de aplicaciones controladas por datos, tales como las aplicaciones web híbridas, indica que la amplia disponibilidad de datos interesantes y fáciles de adoptar permite nuevos escenarios de aplicación.
Basándose en la observación de estas tendencias, ADO.NET Data Services Framework empezó como una manera de ayudar a los desarrolladores a exponer y consumir los datos a través de servicios desde sus aplicaciones RIA. Al explorar el espacio, surgieron dos ideas claves: la creación de herramientas y bibliotecas cliente de uso general con los enfoques actuales de servicios centrados en datos es un concepto difícil en sí mismo, y la creación y el mantenimiento de dichos servicios requieren una inversión considerable de los desarrolladores. En este artículo, nos centraremos en lo que son los servicios de datos y trataremos algunas de las características clave. Si desea profundizar en alguna de las ideas relacionadas con los servicios de datos, consulte el artículo titulado "Why ADO.NET Data Services?" en el blog del equipo (
go.microsoft.com/fwlink/?LinkId=120530).
En general, el objetivo de ADO.NET Data Services Framework es crear un marco sencillo basado en Transferencia de estado representacional (REST) para exponer y publicar servicios centrados en datos. Dichos servicios exponen los datos mediante una interfaz uniforme que consumirán los clientes web a través de una intranet corporativa o a través de Internet. El marco consta de una biblioteca de servidor para exponer los datos de forma segura como un servicio, y un conjunto de bibliotecas cliente creado para varias aplicaciones y tecnologías de Microsoft (Microsoft® .NET Framework, Silverlight, etc.) para consumir los servicios. La figura 1 ilustra la arquitectura.
Figura 1 La arquitectura de ADO.NET Data Services Framework (haga clic en la imagen para ampliarla)
Describir datos con el modelo de Datos de Entidad
Cada servicio de datos de ADO.NET Data Services está descrito en términos del modelo de datos de entidad (EDM) usando el lenguaje de definición de esquemas conceptuales (CSDL). El EDM usa dos conceptos principales: las entidades y las asociaciones (o relaciones). Las entidades son instancias de tipos de entidad (como Customer o Employee) que son registros estructurados con una clave. Una clave de entidad está formada a partir de un subconjunto de propiedades del tipo de entidad. La clave (CustomerId y OrderId respectivamente) identifica y permite actualizaciones de forma exclusiva en instancias de entidad, así como participar en las relaciones. Las entidades están agrupadas por EntitySets (Customers es un conjunto de instancias de Customer). Las asociaciones forman vínculos entre dos o más tipos de entidad (como Employee WorksFor Department).
¿Por qué se eligió el modelo EDM como lenguaje de descripción de datos para ADO.NET Data Services? Por lo que se ha podido comprobar, el EDM se asigna muy bien a los conceptos web principales de recursos y vínculos, convirtiéndolo en un candidato ideal (entidades para recursos y asociaciones para vínculos).
El EDM se desarrolló con el objetivo de convertirse en el modelo principal de datos para una serie de tecnologías de desarrollador y servidor. El mantenimiento de aplicaciones se simplificaría si se usase un único modelo de datos en numerosas aplicaciones. El EDM podría usarse para definir un modelo, no sólo para aplicaciones personalizadas basadas en ADO.NET Data Services, sino también como la entrada en aplicaciones de informes y visualización, aplicaciones de portal de intranet o aplicaciones de flujo de trabajo. ADO.NET Data Services es la segunda tecnología de Microsoft que se basa en el concepto de EDM (la primera, por supuesto, fue ADO.NET Entity Framework). Para obtener información adicional sobre EDM y ADO.NET Entity Framework, consulte msdn.microsoft.com/data y "ADO.NET: Moldeado flexible de datos mediante Entity Framework" en el número de julio de 2008 de MSDN
® Magazine en
msdn.microsoft.com/magazine/700331.
Datos relacionales
De forma predeterminada, ADO.NET Data Services funciona junto con ADO.NET Entity Framework para simplificar el proceso de conexión a un modelo de datos y exposición mediante datos almacenados en SQL Server® o en bases de datos de terceros (Oracle, DB2, MySQL, entre otros).
Entity Framework genera el nivel de abstracción en el que los desarrolladores trabajan con datos, lo que significa que en lugar de codificar con filas y columnas, se define un modelo conceptual de nivel superior (como un modelo de datos de Entidad) sobre los datos relacionales y, a continuación, se programa la aplicación en función del modelo. La aplicación sólo debe entender los datos en las formas que tienen sentido para la aplicación y esas formas se expresan usando un vocabulario enriquecido que incluye conceptos como herencia, tipos complejos y relaciones explícitas.
En general, ADO.NET Data Services funciona convirtiendo una solicitud que sirve para realizar una operación sobre un recurso (como una operación de solicitud HTTP sobre un URI) en la operación equivalente en el modelo de datos que el recurso representa. En este caso, puesto que el modelo de datos está respaldado por una base de datos relacional, los URI se convierten en llamadas a métodos de servicios de objeto Entity Framework.
Un modelo de datos existente, creado mediante Entity Framework, se puede exponer simplemente con unos cuantos pasos en un proyecto de aplicación web de ASP.NET de Visual Studio® 2008 SP1. También se admiten otros tipos de proyecto y mecanismos de hospedaje como, por ejemplo, Windows® Communication Foundation, o WCF (WebServiceHost). Primero se crea o se importa un modelo de datos de Entidad de ADO.NET en una aplicación web de ASP.NET. Con el modelo disponible, podemos agregar un nuevo servicio de datos de ADO.NET. El asistente para agregar un nuevo elemento generará un servicio básico y lo hará visible en el Explorador de soluciones. A continuación, podemos asociar el servicio al modelo cambiando la definición de la clase, tal como se muestra aquí:
using NorthwindModel;
public class NorthwindService : DataService <NorthwindEntities>
{
public static void InitializeService(IDataServiceConfiguration config)
{
}
}
Además, puesto que el servicio está bloqueado inicialmente de forma predeterminada, debemos preocuparnos de establecer la directiva de seguridad para servicios de modo que se permita usar el servicio. El siguiente código establece la seguridad en el nivel de EntitySet al inicializar el servicio:
public static void InitializeService(IDataServiceConfiguration config)
{
config.SetEntitySetAccessRule("*", EntitySetRights.All);
}
Como se puede observar, se trata de un ejemplo muy sencillo de configuración de la seguridad para un servicio ADO.NET Data Services. Más adelante se tratarán otros métodos de seguridad y otras consideraciones.
En este momento tenemos un servicio ADO.NET Data Services que funciona y que podemos crear y ejecutar.
Una manera sencilla de probar su servicio sin una aplicación cliente consiste en señalar un explorador web en el punto de entrada del servicio, por ejemplo <host>/<vdir>/service.svc. El punto de entrada del servicio devolverá la respuesta como XML que contiene la lista de EntitySets expuestos por el servicio de datos. Para ver Atom (el formato predeterminado devuelto por un servicio ADO.NET Data Services) en Internet Explorer®, primero debe asegurarse de que la vista de lectura de fuentes en Internet Explorer esté desactivada. Esto puede se puede hacer desde la ficha Contenido de Herramientas | Opciones de Internet.
¿Qué hay de los datos no relacionales?
Para los demás orígenes de datos o para usar tecnologías adicionales de acceso a bases de datos (como LINQ to SQL), se proporciona un mecanismo que permite que cualquier origen de datos se exponga como un servicio ADO.NET Data Services mediante un modelo de complemento.
En este caso, los URI se convierten en consultas LINQ. Data Services permite este enfoque asignando objetos que implementan la interfaz IQueryable en EntitySets. Esto significa que ADO.NET Data Services puede exponer cualquier origen de datos que tenga un proveedor IQueryable escrito para ello. Para permitir esto, ADO.NET Data Services define una asignación entre objetos CLR y artefactos del modelo de datos basado en el EDM.
En breve, trataremos la creación de un servicio ADO.NET Data Service basado en un gráfico sencillo de objetos CLR. Los pasos que ilustraremos producen un servicio de datos creado en una recopilación de objetos en memoria. Sin embargo, en una implementación típica de la producción, las propiedades IQueryable mostradas que representan EntitySets no expondrán datos en memoria, sino que más bien traducirán árboles de expresión IQueryable a consultas específicas del origen de datos. Por ejemplo, LINQ to Entities traduce árboles de expresión IQueryable a instrucciones SQL.
Al crear un servicio de datos según un almacén no relacional, el servicio de datos se vuelve a crear dentro de un proyecto de aplicación web de ASP.NET. Sin embargo, tenga en cuenta que esta vez nos hemos saltado el paso que sirve para crear el modelo de datos de entidad de ADO.NET y empezamos agregando un nuevo servicio ADO.NET Data Services. El asistente para agregar un nuevo elemento generará un servicio básico, visible en el Explorador de soluciones. Para continuar con este ejemplo sencillo, a continuación debemos crear los datos en memoria que pretendemos exponer a través de nuestro servicio. Para ello, creamos tres clases, dos de las cuales son User y Contact, donde cada User tendrá un conjunto de Contacts. La tercera clase es una clase MyDataService que expone dos propiedades públicas (Contacts y Users) como IQueryable<Contact> e IQueryable<User>, tal como se observa en la figura 2.

Figura 2 Creación de orígenes de datos IQueryable en memoria
public class User
{
public int ID { get; set; }
public string Name { get; set; }
public IList<Contact> Contacts { get; set; }
}
public class Contact
{
public int ID { get; set; }
public string Name { get; set; }
public string Email { get; set; }
}
public class MyDataService
{
static User[] _users;
static Contact[] _contacts;
static MyDataService()
{
_users = new User[]{ new User{ID=1, Name="Mike"},
new User{ID=2, Name="Elisa"} };
_contacts = new Contact[]{ new Contact{ID=1, Name="Joe",
Email="Joe@contoso.com"},
new Contact{ID=2, Name="Bob", Email="Bob@contoso.com"},
new Contact{ID=3, Name="Sam", Email="Sam@contoso.com"},
new Contact{ID=4, Name="Carl", Email="Carl@contoso.com"},
new Contact{ID=5, Name="Abby", Email="Abby@contoso.com"},
new Contact{ID=6, Name="Annie", Email="Annie@contoso.com"},
};
_users[0].Contacts = new List<Contact>();
_users[0].Contacts.Add(_contacts[0]);
_users[0].Contacts.Add(_contacts[1]);
_users[0].Contacts.Add(_contacts[4]);
_users[1].Contacts = new List<Contact>();
_users[1].Contacts.Add(_contacts[2]);
_users[1].Contacts.Add(_contacts[3]);
_users[1].Contacts.Add(_contacts[5]);
}
public IQueryable<User> Users
{
get { return _users.AsQueryable<User>();}
}
public IQueryable<Contact> Contacts
{
get { return _contacts.AsQueryable<Contact>(); }
}
}
Ahora que hemos determinado un origen de datos, volvemos a un terreno conocido, cambiando la definición de clase para señalar a nuestro origen de datos, como se observa a continuación:
public class contacts :
DataService<ADONETDataServiceNonRelSample.MyDataService>
{
public static void InitializeService(IDataServiceConfiguration config)
{
config.SetEntitySetAccessRule("*", EntitySetRights.All);
}
}
De nuevo, debemos realizar unos ajustes iniciales para establecer la directiva de seguridad, ya que el servicio inicial está bloqueado de forma predeterminada. El código muestra la misma directiva de seguridad establecida en el nivel de EntitySet, como vimos anteriormente. En este momento, volvemos a tener un servicio ADO.NET Data Services que funciona y que podemos crear y ejecutar. Para probar el servicio basta con usar Internet Explorer para navegar al extremo de servicio.
Formato URI uniforme
El identificador uniforme de recursos (URI) es uno de los conceptos principales que constituyen ADO.NET Data Services, que define un formato URI relativamente sencillo, aunque expresivo, que permite a las aplicaciones realizar consultas sobre conjuntos de entidades o entidades independientes, así como recorrer las relaciones que existen entre dichas entidades. La estructura de este URI permite a los agentes y controles comprender fácilmente cómo desplazarse por los datos presentados por un servicio.
Cuando comenzamos a probar nuestros servicios iniciales, vimos que el formato básico de URI señalaba al propio servicio de datos. Basándonos en este URI básico, podemos consultar el servicio mediante el siguiente formato:
http://<host>/<vdir>/<service.svc>/<EntitySet>[(<Key>)
[/<NavigationProperty>[(<Key>)/...]]]
Por ejemplo, siguiendo con el ejemplo anterior de Northwind, si agregamos /Customers al final del servicio de datos URI, <host>/<vdir>/NorthwindService.svc/Customers, devolvemos todos los clientes del EntitySet Customers, en este caso, todos los clientes de la base de datos Northwind. Si, por otro lado, queremos devolver una sola entidad de cliente desde el servicio Northwind, podríamos usar el valor clave de la entidad, ya que Customer es una entidad de una sola clave. Solicitando el URI <host>/<vdir>/NorthwindService.svc/Customers('ALFKI'), devolvemos el cliente único con la clave "ALFKI". La propiedad de navegación está anexada a un solo URI de consulta de entidad de manera similar. Al anexar /Customers('ALFKI')/Orders, navegamos por la relación en nuestro modelo entre Customers y Orders, devolviendo todas las pedidos asociados al cliente identificado por la clave "ALFKI".
El formato URI descrito anteriormente permite realizar la consulta y el cruce básicos de las entidades en nuestro almacén, pero no justifica la necesidad de controlar aún más la salida de consultas ni aplicar restricciones sobre éstas. Para lograr esos objetivos, usaremos un conjunto de parámetros de cadena de consulta opcionales admitidos por ADO.NET Data Services que incluyen $filter, $expand, $orderby, $skip y $top. Los parámetros de cadena de consulta se anexan al URI después del carácter ?.
Por ejemplo, para consultar todos clientes de Londres, anexaríamos el URI del servicio con /Customers?$filter=City "Londres". Para consultar un cliente específico (con clave "ALFKI") y recuperar los pedidos de ventas relacionados, anexaríamos el URI del servicio con /Customers('ALFKI')?$expand=Orders. Para clasificar los resultados por ciudad en orden ascendente (asc), anexaríamos el URI del servicio con /Customers?$orderby=City asc (o desc para orden descendente).
Para obtener una lista completa de los formatos URI admitidos por ADO.NET Data Services, consulte la documentación de Data Services, accesible desde
go.microsoft.com/fwlink/?LinkId=120539.
Seguridad del servicio de datos
La seguridad es siempre la primera preocupación al tratar una tecnología basada en la Web, especialmente un tipo de tecnología que ofrece el acceso a datos críticos y su la manipulación. Por lo tanto, la seguridad ha sido una de las principales consideraciones en el ciclo de desarrollo de los servicios de datos.
Para proporcionar autenticación, ADO.NET Data Services aprovecha gran parte de la infraestructura de autenticación existente de ASP.NET y de WCF para permitir una experiencia integrada de autenticación en las aplicaciones y en el servicio. Si tiene un sitio ASP.NET que use autenticación (ya sea uno de los proveedores de autenticación integrados o bien un proveedor personalizado que establezca la entidad principal de seguridad del contexto HTTP de forma apropiada), ADO.NET Data Services puede aprovechar el mecanismo elegido para establecer la identidad actual (principal) para cualquier solicitud dada. De forma similar, si el servicio de datos está hospedado fuera de ASP.NET (WCF o a través de un host personalizado), es posible que el host también decida usar cualquier mecanismo de autenticación, siempre y cuando ofrezca API para un autor de servicio de datos para obtener acceso a la parte principal de la solicitud.
De forma predeterminada, cualquier servicio nuevo ADO.NET Data Services está totalmente bloqueado; eso quiere decir que no se puede obtener acceso a todas las entidades, a las operaciones del servicio ni a los metadatos (no disponen de acceso de lectura ni de escritura). Dicho esto, existen varios mecanismos para controlar la directiva de autorización y realizar las validaciones previas a la solicitud con el servicio de datos.
Uno de los primeros pasos que debe dar un desarrollador de servicios de datos es abrir el acceso a recursos expuestos por el servicio de datos. En nuestros ejemplos, hemos mostrado un caso sencillo de configuración de directiva de acceso de lectura/escritura para el servicio completo. Esto se aplica a cada servicio mediante el método InitializeService. Las directivas establecidas son directivas EntitySet que permiten o restringen el acceso a los conjuntos de entidades en el modelo y establecen qué conjuntos de entidades y asociaciones relacionadas están disponibles desde el extremo de metadatos del servicio. Aunque las dos directivas que se muestran en los ejemplos abrieron el acceso al modelo completo para lectura y escritura, se podría especificar mucho más, establecer directivas diferentes para EntitySets diferentes, e incluir directivas compuestas cuando fuera necesario, tal como se muestra en la figura 3.

Figura 3 Configuración de la directiva de acceso a todo el servicio
public class MyService : DataService<NorthwindEntities>
{
public static void InitializeService(
IDataServiceConfiguration config)
{
// '/Customers' entities are enabled for all read and write
// operations
config.SetEntitySetAccessRule("Customers",
EntitySetRights.All);
// URI '/Orders' is disabled, but '/Orders(1)' is enabled for read
// only
config.SetEntitySetAccessRule("Orders",
EntitySetRights.ReadSingle);
// Can insert and update, but not delete Products
config.SetEntitySetAccessRule("Products",
EntitySetRights.WriteInsert |
EntitySetRights.WriteUpdate);
}
}
En muchos escenarios, los servicios de datos también deberán ejecutar la lógica de validación cuando las entidades especifican el servicio de datos (para inserciones, actualizaciones o eliminaciones) y restringen el acceso a entidades por solicitud previa. Para estos escenarios, puede usar interceptores, que permiten que un desarrollador de servicio de datos conecte la lógica de directiva de acceso o de validación personalizada a la canalización de procesamiento de solicitud/respuesta de un servicio de datos. Por ejemplo, si queremos permitir que los clientes recuperen sólo sus pedidos y no los pedidos formalizados por otros clientes, será necesario implementar un interceptor de consultas, como se observa en la figura 4. El interceptor de consultas devuelve un predicado que debe anexarse a la consulta y esta se envía a continuación al almacén de datos, eliminando la necesidad de acudir más veces al almacén de datos para recuperar información de control de acceso.

Figura 4 Método de interceptor de consultas
public class nw : DataService<NorthwindModel.NorthwindEntities>
{
public static void InitializeService(IDataServiceConfiguration config)
{
config.SetEntitySetAccessRule("Orders", EntitySetRights.All);
}
[QueryInterceptor("Orders")]
public Expression<Func<Orders,bool>> OnQueryOrders()
{
return o => o.Customer.ContactName ==
HttpContext.Current.User.Identity.Name
}
}
De forma similar, los interceptores también se pueden agregar para inserciones, actualizaciones y eliminaciones como interceptores de cambio. Los interceptores de cambio no tienen valor de devolución y toman dos argumentos, un objeto del tipo contenido en el EntitySet y una enumeración UpdateOperations que define la acción (actualización, inserción o eliminación) que se solicita en el recurso. El interceptor puede alterar el objeto que se le ha pasado o establecer la referencia en una instancia totalmente diferente. Si el método genera una excepción, la operación se anula, no se realiza ningún cambio en la base de datos y se devuelve un error al agente cliente.
Cliente: acceso al servicio
En general, hemos tratado el HTTP como la API para servicios de datos y, por tanto, hemos dedicado mucho tiempo a asegurarnos de que resulta sencillo usarlo directamente en el nivel de HTTP. Esto significa que no sólo es fácil interpretar las cargas del servicio, sino que además cualquier plataforma con una pila HTTP puede usar fácilmente servicios de datos. No obstante, si usa una plataforma de Microsoft para tener acceso a servicios de datos, el desarrollo en el cliente se simplifica aún más al usar las bibliotecas de cliente ADO.NET Data Services. Esto representa un modelo de programación más natural para aplicaciones escritas mediante .NET Framework, Silverlight y ASP.NET AJAX para orientarse a servicios de datos, manipular los resultados en función de objetos y administrar aspectos como el cruce de asociaciones. Las bibliotecas cliente resumen mucho los detalles de solicitudes y respuestas HTTP y garantizan una experiencia más uniforme en cada una de las pilas de cliente.
Las bibliotecas de cliente .NET Framework y Silverlight constan de dos tipos principales: la clase DataServiceContext y la clase DataServiceQuery. DataServiceContext representa el contexto del tiempo de ejecución para un servicio de datos determinado. Los servicios de datos en sí no tienen estado; sin embargo, el contexto en el que interactúa el desarrollador sí que tiene, y el estado del cliente se mantiene entre interacciones para admitir características como la resolución de identidades y la concurrencia optimista. El objeto DataServiceQuery representa una consulta específica en función del almacén definido mediante la sintaxis de URI. Para ejecutar una consulta y obtener los resultados en forma de objetos .NET, basta con enumerar el objeto de la consulta. Puede hacerlo usando, por ejemplo, la construcción estándar "foreach" en C# o "For Each" en Visual Basic®.
Para usar las entidades definidas en un servicio de datos como objetos .NET en el cliente, deben definirse las clases correspondientes para la aplicación cliente. Existen tres métodos principales para lograrlo. Una opción es definirlos manualmente. La figura 5 muestra una definición sencilla escrita a mano para la clase Region y un fragmento breve de código que ejecuta una consulta en las regiones e imprime sus identificadores y descripciones en la salida.

Figura 5 Acceso a un servicio ADO.NET Data Service desde una aplicación .NET
namespace TestApplication
{
public class Region
{
public int RegionID { get; set; }
public string RegionDescription { get; set; }
}
class Program
{
static void Main(string[] args)
{
DataServiceContext ctx = new
DataServiceContext("http://localhost:25115/NorthwindService.svc");
DataServiceQuery<Region> regions =
ctx.CreateQuery<Region>("/Region?$orderby=RegionID");
foreach (Region r in regions)
{
Console.WriteLine(r.RegionID + ", " + r.RegionDescription);
}
}
}
}
Un enfoque más habitual consiste en usar código generado por Visual Studio. Aunque escribir clases manualmente funciona bien cuando el servicio tiene sólo pocos tipos, a medida que el esquema de servicio de datos se hace más complejo, el número y el tamaño de las clases que se deben crear y mantener manualmente también crece considerablemente.
La manera más habitual de generar las clases necesarias consiste en usar el asistente de Visual Studio que ayuda a agregar una referencia de servicio. Igual que al usar Agregar referencia de servicio para un servicio WCF, basta con hacer clic con el botón secundario en el proyecto y elegir Agregar referencia de servicio (consulte la figura 6).
Figura 6 Agregar referencia de servicio (haga clic en la imagen para ampliarla)
En el cuadro de diálogo Agregar referencia de servicio, escriba en el campo Dirección el URI para el punto de entrada del servicio. En nuestro ejemplo, especificaríamos <host>/<vdir>/NorthwindService.svc. Esto generará clases basadas en la definición de servicio de datos y las agregará al proyecto.
Una opción alternativa es usar la herramienta de línea de comandos "datasvcutil.exe" para generar las clases basadas en la definición de servicio de datos. Esta herramienta se suministra con la versión de ADO.NET Data Services y se ubica en el directorio \Windows\Microsoft.Net\Framework\V3.5. La herramienta toma como argumento el URI para el punto de entrada del servicio de datos, así como el nombre y la ubicación del archivo de salida que debe generarse, de este modo:
"\Windows\Microsoft.Net\Framework\V3.5\datasvcutil.exe"
/out:C:\NorthwindSample\northwind.cs /uri:"http://<host>/<vdir>/
NorthwindService.svc"
El resultado predeterminado de la herramienta de línea de comandos es un archivo C# que contiene una clase para cada uno de los tipos de entidad descritos en el servicio de datos (pueden generarse tipos de Visual Basic mediante el modificador /language:VB). Cada clase generada tiene miembros que representan las asociaciones y los valores primitivos descritos en el servicio. Esto permite a los desarrolladores navegar por un gráfico de entidades asociadas que usan el modelo de objetos directamente.
Biblioteca de cliente .NET
La biblioteca de cliente .NET de ADO.NET Data Services presenta un modelo de programación con el que están familiarizados los desarrolladores que escriben aplicaciones mediante .NET Framework y servicios de datos. En el fondo, la biblioteca cliente usa HTTP y el formato AtomPub, así que funciona de manera natural tanto en redes corporativas como en entornos de Internet, y necesita únicamente conectividad sencilla de nivel HTTP al servicio de datos, ya sea directa o indirecta (por ejemplo, a través de proxy).
La biblioteca cliente se puede usar desde cualquier tipo de proyecto, incluidos Windows Forms, Windows Presentation Foundation (WPF) y proyectos web. Para usar la biblioteca cliente anterior, deberá agregar una referencia al ensamblado System.Data.Services.Client.dll.
Biblioteca de cliente Silverlight
El cliente Silverlight no se suministra como parte de ADO.NET Data Services, sino que se suministra como parte del SDK de Silverlight 2, lo que permite una experiencia más integrada al desarrollar aplicaciones de Silverlight. Una diferencia entre la biblioteca de cliente Silverlight y las bibliotecas de cliente .NET y AJAX es que Silverlight 2 no admite el desarrollo sincrónico. Por lo tanto, el desarrollo con la biblioteca de cliente Silverlight debe usar las API asincrónicas, siguiendo el patrón asincrónico común de inicio/final. Esto también significa que algunas de las API que quizás use en las otras dos bibliotecas cliente se han habilitado en la biblioteca de cliente Silverlight.
Biblioteca de cliente AJAX
La biblioteca de AJAX de ASP.NET está actualmente disponible en
codeplex.com/aspnet/Release/ProjectReleases.aspx?ReleaseId=13357. Parecida a la biblioteca de cliente .NET comentada anteriormente, la biblioteca de cliente AJAX resume los detalles de HTTP para que el desarrollador de aplicaciones pueda trabajar directamente con objetos JavaScript en vez de analizar y crear manualmente solicitudes y respuestas HTTP. Dispone también de varias páginas web en el sitio de CodePlex en las que se explica cómo usar la biblioteca de servicios de datos para aplicaciones de AJAX (consulte
codeplex.com).
Consultar un servicio de datos
En la figura 5, las consultas al servicio se generaron llamando al método DataServiceQuery.CreateQuery y pasando una consulta de URI. Como alternativa, la biblioteca también le permite formular consultas de servicio de datos mediante LINQ. La biblioteca cliente asigna la instrucción LINQ a un URI en el servicio de datos de destino y recupera los recursos especificados como objetos .NET. El siguiente código muestra cómo recuperar todos los clientes en la ciudad de Londres con los resultados ordenados por nombre de compañía:
var q = from c in ctx.Customers
where c.City == "London"
orderby c.CompanyName
select c;
foreach (var cust in q)
{
Console.WriteLine(cust.CompanyName);
}
Al usar LINQ to ADO.NET Data Services, el conjunto de consultas que se pueden expresar en la sintaxis de LINQ es más amplio que las habilitadas por la sintaxis de URI basada en REST de servicios de datos. Si una consulta no se puede asignar a un URI válido en el servicio de datos de destino, se generará una excepción.
Una forma de comprender los tipos de consultas que se pueden asignar a los URI es pensar en el recorrido jerárquico. Las consultas que requieran dos o más tablas dinámicas, (por ejemplo, uniones o subconsultas que usan cláusulas como Any), no pueden asignarse actualmente a un URI. Sin embargo, las consultas con una sola tabla dinámica y recorridos de asociación suelen funcionar bien.
Hasta ahora hemos consultado entidades sencillas. Las asociaciones entre objetos también están sometidas al seguimiento y la administración de DataServiceContext, y los objetos asociados pueden cargarse concienzudamente o cuando sea necesario mediante los formatos de URL tratados en la sección Formato URI uniforme. Para cargar entidades asociadas según sea necesario (carga retrasada), use el método LoadProperty en la clase DataServiceContext, tal como se muestra en la figura 7.

Figura 7 Carga retrasada de entidades relacionadas mediante LoadProperty
// get a single category
DataServiceQuery<Categories> categories =
context.CreateQuery<Categories>("/Categories(1)");
foreach (Categories c in categories)
{
Console.WriteLine(c.CategoryName);
context.LoadProperty(c, "Products");
foreach (Products p in c.Products)
{
Console.WriteLine("\t" + p.ProductName);
}
}
En algunos escenarios, quizá convenga evitar el viaje de ida y vuelta adicional en la conexión para obtener las entidades, y puede que sea preferible cargarlas con la consulta original. En este caso, la opción de ampliar se especifica en el URI. La biblioteca cliente reconoce que el resultado incluye entidades de nivel superior y entidades asociadas, y materializará todas las entidades como un solo gráfico de objeto. En la figura 8 se cargan concienzudamente los productos relacionados en un recorrido de ida y vuelta al servicio de datos.

Figura 8 Uso del comando expand para cargar concienzudamente entidades relacionadas
public IList<Products> GetProducts(string productName,
Categories category)
{
int categoryId = category.CategoryID;
string uri = serviceUri + "/Categories(" + categoryId + ")/Products?";
if (!String.IsNullOrEmpty(productName))
{
uri = uri + "$filter=ProductName eq '" + productName + "'&";
}
uri = uri + "$expand=Categories";
Uri queryUri = new Uri(uri);
try
{
IEnumerable<Products> products =
context.Execute<Products>(queryUri);
return products.ToList();
}
catch (Exception)
{
return null;
}
}
Asignación de comandos
Con consultas en ADO.NET Data Services asignadas a través de solicitudes GET de HTTP, ¿cómo se ejecutan las solicitudes Create, Update y Delete? Cada una de las cuatro operaciones CRUD (Create, Retrieve, Update, Delete) se asigna a un verbo HTTP diferente con Retrieve asignada a GET, Create asignada a POST, Update asignada a PUT y Delete asignada a DELETE. Igual que sucede con las consultas, la biblioteca cliente usada (en este caso, la biblioteca de cliente .NET) abstraerá los detalles y usará el verbo HTTP apropiado, lo que permitirá al desarrollador usar las API proporcionadas con bastante facilidad.
Para crear una instancia de una entidad en el servicio de datos, simplemente cree el objeto .NET, rellénelo con los datos deseados y, a continuación, llame a AddObject en el objeto DataServiceContext, pasando el objeto nuevo y el nombre del EntitySet al que se agregará el objeto, de la siguiente manera:
public void AddProduct(Products product)
{
context.AddObject("Products", product);
context.AttachTo("Categories", product.Categories);
context.SetLink(product, "Categories", product.Categories);
DataServiceResponse r = context.SaveChanges();
}
También es posible agregar o cambiar las asociaciones entre objetos .NET y hacer que la biblioteca cliente refleje esos cambios como operaciones de creación/eliminación de asociaciones. Por ejemplo, el código anterior crea un producto en la base de datos Northwind y lo asocia a una categoría existente. Las categorías y los productos participan en una asociación de uno a muchos; de este modo, un producto determinado tiene una categoría específica.
Tras crear una entidad, el servicio de datos devolverá una copia nueva de la misma, que incluirá valores que han sido actualizados a instancia de desencadenadores de la base de datos, claves generadas automáticamente, etc. A continuación, la biblioteca cliente actualizará automáticamente el objeto .NET con los valores nuevos.
Para modificar una instancia existente de la entidad, el cliente debe primero obtener el objeto y DataServiceContext debe realizar el seguimiento. El desarrollador consultará el objeto o lo asociará al contexto, realizará los cambios deseados en sus propiedades y, a continuación, llamará al método UpdateObject. El método UpdateObject indica a la biblioteca cliente que debe enviar una actualización para dicho objeto:
public void UpdateProduct(Products product)
{
Categories newCategory = product.Categories;
context.AttachTo("Products", product);
context.AttachTo("Categories", newCategory);
context.UpdateObject(product);
context.SetLink(product, "Categories", newCategory);
context.SaveChanges();
}
Para eliminar una instancia de la entidad, el cliente debe volver a obtener el objeto y DataServiceContext debe hacer su seguimiento. Una vez que se ha realizado su seguimiento, podemos hacer una llamada sencilla al método DeleteObject en la instancia del contexto para marcar el objeto que debe eliminarse:
public string DeleteProduct(Products product)
{
context.AttachTo("Products", product);
context.DeleteObject(product);
context.SaveChanges();
}
En todos los ejemplos anteriores, la última llamada a método hecha en el contexto ha sido una llamada al método SaveChanges. El seguimiento de los cambios lo lleva a cabo la instancia de DataServiceContext, pero dichos cambios no se envían al servidor inmediatamente. Cuando se hayan completado todos cambios necesarios para una actividad determinada, una llamada a SaveChanges enviará los cambios al servicio de datos.
Hasta ahora, cada uno de los ejemplos ha tenido como resultado un solo HTTP y una única solicitud de servidor para cada operación de cliente. Este enfoque (una única operación por cada solicitud HTTP) funciona bien en algunos escenarios, pero normalmente es mejor procesar por lotes varias operaciones y enviarlas al servicio de datos en una sola solicitud HTTP. Esto reducirá el número de viajes de ida y vuelta al servicio de datos y permitirá un ámbito lógico de atomicidad para recopilaciones de operaciones. Para admitir estos requisitos, el cliente admite el envío de grupos de operaciones CUD (Create, Update, Delete) y grupos de operaciones de consulta al servicio de datos en una sola solicitud HTTP. El siguiente código muestra como se pueden enviar dos o más consultas al servicio de datos en un solo lote:
var q = (DataServiceRequest)from o in context.SalesOrder
select o;
// send two queries in one batch request to the data service
DataServiceResponse r = service.ExecuteBatch(
new DataServiceRequest<Customer>(new
Uri("http://localhost:25115/NorthwindService.svc/Customers")),
q);
El envío de un grupo de operaciones CUD como lote al servicio de datos se lleva a cabo mediante una llamada sencilla al método SaveChanges y pasando SaveChangesOptions.Batch como el único parámetro. Este valor de parámetro indica al cliente que debe procesar por lotes todas las operaciones de cambio pendientes en un lote y enviarlas al servicio de datos como un grupo atómico. Al enviar las operaciones de cambio como lote mediante SaveChangesOptions.Batch, todos los cambios se completarán correctamente o no se aplicará ningún cambio.
Más información
Aunque este artículo explica lo básico sobre ADO.NET Data Services, hay todavía una serie de temas que quizás le interesen. Para obtener más información, vaya al blog del equipo de Data Services en
blogs.msdn.com/astoriateam y en el centro del Desarrollo de datos de MSDN en
msdn.microsoft.com/data puede consultar el tema bajo el encabezado ADO.NET Data Services.
Elisa Flasko es Directora de programas en el equipo de programabilidad de datos de Microsoft, incluidas las tecnologías de ADO.NET, las tecnologías XML y las tecnologías de conectividad de SQL Server. Para ponerse en contacto con ella, visite
blogs.msdn.com/elisaj.
Mike Flasko es Director de programas en el grupo de Programabilidad de datos SQL en Microsoft. Puede ponerse en contacto con Mike a través de su blog en
blogs.msdn.com/mflasko.