Consumo de fuentes de OData en un flujo de trabajo

Los Servicios de datos de WCF son un componente de .NET Framework que le permite crear servicios que usan Open Data Protocol (OData) para exponer y usar datos en la Web o una intranet mediante la semántica de la transferencia de estado representacional (REST). OData expone los datos como recursos direccionables a través de identificadores uniformes de recursos (URI). Cualquier aplicación puede interactuar con un servicio de datos basado en OData si puede enviar una solicitud HTTP y procesar la fuente de OData que devuelve un servicio de datos. Además, los Servicios de datos de WCF incluyen bibliotecas cliente que proporcionan una experiencia de programación más enriquecida cuando se usan fuentes de OData desde aplicaciones de .NET Framework. En este tema se proporciona una información general del uso de una fuente de OData en un flujo de trabajo con y sin utilizar las bibliotecas de clientes.

Uso del servicio de OData de ejemplo Northwind

Los ejemplos de este tema usan el servicio de datos de ejemplo Northwind ubicado en https://services.odata.org/Northwind/Northwind.svc/. Este servicio se proporciona como parte del SDK de OData y proporciona acceso de solo lectura a la base de datos Northwind de ejemplo. Si se quiere acceso de escritura, o un Servicio de datos de WCF local, puede seguir los pasos del Tutorial rápido (WCF Data Services) para crear un servicio OData local que proporcione acceso a la base de datos Northwind. Si sigue el tutorial rápido, sustituya el URI local por el que se proporciona en el código de ejemplo de este tema.

Consumo de una fuente de OData mediante las bibliotecas cliente

Los Servicios de datos de WCF incluyen bibliotecas cliente que permiten consumir más fácilmente una fuente de OData desde .NET Framework y las aplicaciones cliente. Estas bibliotecas simplifican el envío y recepción de los mensajes HTTP. También traducen la carga del mensaje en objetos CLR que representan los datos de entidad. Las bibliotecas cliente representan las dos clases principales DataServiceContext y DataServiceQuery<TElement>. Estas clases le permiten consultar un servicio de datos y, a continuación, trabajar con los datos de entidad devueltos como objetos CLR. En esta sección se incluyen dos enfoques para crear actividades que utilizan bibliotecas de clientes.

Adición de una referencia de servicio al servicio de datos de WCF

Para generar las bibliotecas cliente de Northwind, puede utilizar el cuadro de diálogo Agregar referencia de servicio de Visual Studio 2012 para agregar una referencia al servicio de OData de Northwind.

Screenshot that shows the Add Service Reference dialog.

Observe que no hay ninguna operación del servicio expuesta por el servicio y que en la lista Servicios hay elementos que representan las entidades expuestas por el servicio de datos Northwind. Cuando se agregue la referencia de servicio, se generarán las clases para estas entidades y se podrán utilizar en el código del cliente. Los ejemplos de este tema usan estas clases y la clase NorthwindEntities para realizar las consultas.

Uso de métodos asincrónicos

Para resolver los posibles problemas de latencia que se pueden producir al tener acceso a los recursos en Web, recomendamos el acceso asincrónico a Servicios de datos de WCF. Las bibliotecas cliente de los Servicios de datos de WCF incluyen métodos asincrónicos para invocar consultas y Windows Workflow Foundation (WF) proporciona la clase AsyncCodeActivity para crear actividades asincrónicas. Se pueden escribir actividades derivadas de AsyncCodeActivity para aprovechar las ventajas de las clases de .NET Framework que tienen métodos asincrónicos, o el código que se va a ejecutar de forma asincrónica se puede incluir en un método e invocar mediante un delegado. Esta sección proporciona dos ejemplos de una actividad derivada de AsyncCodeActivity ; uno que utiliza los métodos asincrónicos de las bibliotecas cliente de los Servicios de datos de WCF y otro que usa un delegado.

Uso de los métodos asincrónicos de la biblioteca cliente

La clase DataServiceQuery<TElement> proporciona los métodos BeginExecute y EndExecute para consultar asincrónicamente un servicio OData. Se puede llamar a estos métodos desde BeginExecute y EndExecute invalida de una clase derivada de AsyncCodeActivity . Cuando el método BeginExecute de AsyncCodeActivity invalida las devoluciones, el flujo de trabajo puede quedar inactivo (pero no se conserva) y cuando se completa el trabajo asincrónico, el entorno de ejecución invoca a EndExecute.

En el ejemplo siguiente, se define una actividad OrdersByCustomer con dos argumentos de entrada. El argumento CustomerId representa el cliente que identifica qué pedidos se va a devolver, mientras que el argumento ServiceUri representa el URI del servicio de OData que se va a consultar. Dado que la actividad deriva de AsyncCodeActivity<IEnumerable<Order>> hay también un parámetro de salida Result que se utiliza para devolver los resultados de la consulta. La BeginExecute reemplazada crea un consulta LINQ que selecciona todos los pedidos del cliente especificado. Esta consulta se especifica como la UserState de la AsyncCodeActivityContextpasada y, a continuación, se llama al método de la consulta BeginExecute . Observe que la devolución de llamada y el estado que se pasan a la BeginExecute de la consulta son las que se pasan al método BeginExecute de la actividad. Cuando la consulta se ha terminado de ejecutar, se invoca el método EndExecute de la actividad. La consulta se recupera desde UserStatey, a continuación, se llama al método EndExecute de la consulta. Este método devuelve las IEnumerable<T> del tipo de entidad especificado; en este caso Order. Puesto que IEnumerable<Order> es el tipo genérico de AsyncCodeActivity<TResult>, IEnumerable se establece como el elemento ResultOutArgument<T> de la actividad.

class OrdersByCustomer : AsyncCodeActivity<IEnumerable<Order>>
{
    [RequiredArgument]
    public InArgument<string> CustomerId { get; set; }

    [RequiredArgument]
    public InArgument<string> ServiceUri { get; set; }

    protected override IAsyncResult BeginExecute(AsyncCodeActivityContext context, AsyncCallback callback, object state)
    {
        NorthwindEntities dataContext = new NorthwindEntities(new Uri(ServiceUri.Get(context)));

        // Define a LINQ query that returns Orders and
        // Order_Details for a specific customer.
        DataServiceQuery<Order> ordersQuery = (DataServiceQuery<Order>)
            from o in dataContext.Orders.Expand("Order_Details")
            where o.Customer.CustomerID == CustomerId.Get(context)
            select o;

        // Specify the query as the UserState for the AsyncCodeActivityContext
        context.UserState = ordersQuery;

        // The callback and state used here are the ones passed into
        // the BeginExecute of this activity.
        return ordersQuery.BeginExecute(callback, state);
    }

    protected override IEnumerable<Order> EndExecute(AsyncCodeActivityContext context, IAsyncResult result)
    {
        // Get the DataServiceQuery from the context.UserState
        DataServiceQuery<Order> ordersQuery = context.UserState as DataServiceQuery<Order>;

        // Return an IEnumerable of the query results.
        return ordersQuery.EndExecute(result);
    }
}

En el siguiente ejemplo, la actividad OrdersByCustomer recupera una lista de pedidos para el cliente especificado y a continuación una actividad ForEach<T> enumera los pedidos devueltos y escribe la fecha de cada pedido en la consola.

Variable<IEnumerable<Order>> orders = new Variable<IEnumerable<Order>>();
DelegateInArgument<Order> order = new DelegateInArgument<Order>();

Activity wf = new Sequence
{
    Variables = { orders },
    Activities =
    {
        new WriteLine
        {
            Text = "Calling WCF Data Service..."
        },
        new OrdersByCustomer
        {
            ServiceUri = "http://services.odata.org/Northwind/Northwind.svc/",
            CustomerId = "ALFKI",
            Result = orders
        },
        new ForEach<Order>
        {
            Values = orders,
            Body = new ActivityAction<Order>
            {
                Argument = order,
                Handler = new WriteLine
                {
                    Text = new InArgument<string>((env) => string.Format("{0:d}", order.Get(env).OrderDate))
                }
            }
        }
    }
};

WorkflowInvoker.Invoke(wf);

Cuando se invoca este flujo de trabajo, se escriben los siguientes datos en la consola:

Calling WCF Data Service...
8/25/1997
10/3/1997
10/13/1997
1/15/1998
3/16/1998
4/9/1998

Nota:

Si no se puede establecer una conexión al servidor de OData, obtendrá una excepción similar a la siguiente:

Excepción no controlada: System.InvalidOperationException: Error al procesar esta solicitud. ---> System.Net.WebException: no es posible conectar con el servidor remoto ---> System.Net.Sockets.SocketException: error de intento de conexión porque la parte conectada no respondió correctamente después de un período de tiempo, o error de la conexión establecida porque el host conectado no respondió.

Si se requiere cualquier procesamiento adicional de los datos devueltos por la consulta, se puede hacer al reemplazar EndExecute en la actividad. Se invocan BeginExecute y EndExecute al usar el subproceso del flujo de trabajo y cualquier código de estos reemplazos no se ejecuta de forma asincrónica. Si el procesamiento adicional es extenso o de ejecución prolongada, o se paginan los resultados de la consulta, debería considerar el enfoque descrito en la sección siguiente, que utiliza un delegado para ejecutar la consulta y realizar el procesamiento adicional de forma asincrónica.

Uso de un delegado

Además de invocar el método asincrónico de una clase de .NET Framework, una actividad basada en AsyncCodeActivity también puede definir la lógica asincrónica en uno de sus métodos. Este método se especifica al usar un delegado en el reemplazo de BeginExecute de la actividad. Cuando el método devuelve un resultado, el runtime invoca el reemplazo de EndExecute de la actividad. Al llamar a un servicio de OData desde un flujo de trabajo, este método se puede utilizar para consultar el servicio y proporcionar cualquier procesamiento adicional.

En el ejemplo siguiente, se define una actividad ListCustomers . Esta actividad consulta el servicio de datos de Northwind de ejemplo y devuelve una List<Customer> que contiene todos los clientes de la base de datos Northwind. El método GetCustomers realiza el trabajo asincrónico. Este método consulta todos los clientes del servicio y, a continuación, los copia en una List<Customer>. Después, comprueba si los resultados están paginados. En ese caso, consulta en el servicio la siguiente página de resultados, los agrega a la lista y continúa hasta que se hayan recuperado todos los datos del cliente.

Nota:

Para obtener más información sobre la paginación en los servicios de datos de WCF, consulte Procedimientos: Carga de resultados paginados (Servicios de datos de WCF).

Una vez agregados todos los clientes, se devuelve la lista. El método GetCustomers se especifica en el reemplazo de BeginExecute de la actividad. Puesto que el método tiene un valor devuelto, se crea Func<string, List<Customer>> para especificar el método.

Nota:

Si el método que realiza el trabajo asincrónico no tiene un valor devuelto, se utiliza Action en lugar de Func<TResult>. Para obtener ejemplos de creación de un ejemplo asincrónico mediante ambos enfoques, consulte Creación de actividades asincrónicas.

Func<TResult> está asignado a UserState y, a continuación, se llama a BeginInvoke. Puesto que el método que se va a invocar no tiene el acceso al entorno de argumentos de la actividad, el valor del argumento ServiceUri se pasa como el primer parámetro, junto con la devolución de llamada y estado por los que se pasaron en BeginExecute. Cuando GetCustomers devuelve un resultado, el runtime invoca a EndExecute. El código de EndExecute recupera el delegado desde la propiedad UserState, llama a EndInvokey devuelve el resultado, que es la lista de clientes devuelta desde el método GetCustomers .

class ListCustomers : AsyncCodeActivity<List<Customer>>
{
    [RequiredArgument]
    public InArgument<string> ServiceUri { get; set; }

    protected override IAsyncResult BeginExecute(AsyncCodeActivityContext context, AsyncCallback callback, object state)
    {
        // Create a delegate that references the method that implements
        // the asynchronous work. Assign the delegate to the UserState,
        // invoke the delegate, and return the resulting IAsyncResult.
        Func<string, List<Customer>> GetCustomersDelegate = new Func<string, List<Customer>>(GetCustomers);
        context.UserState = GetCustomersDelegate;
        return GetCustomersDelegate.BeginInvoke(ServiceUri.Get(context), callback, state);
    }

    protected override List<Customer> EndExecute(AsyncCodeActivityContext context, IAsyncResult result)
    {
        // Get the delegate from the UserState and call EndInvoke
        Func<string, List<Customer>> GetCustomersDelegate = (Func<string, List<Customer>>)context.UserState;
        return (List<Customer>)GetCustomersDelegate.EndInvoke(result);
    }

    List<Customer> GetCustomers(string serviceUri)
    {
        // Get all customers here and add them to a list. This method doesn't have access to the
        // activity's environment of arguments, so the Service Uri is passed in.

        // Create the DataServiceContext using the service URI.
        NorthwindEntities context = new NorthwindEntities(new Uri(serviceUri));

        // Return all customers.
        QueryOperationResponse<Customer> response =
            context.Customers.Execute() as QueryOperationResponse<Customer>;

        // Add them to the list.
        List<Customer> customers = new List<Customer>(response);

        // Is this the complete list or are the results paged?
        DataServiceQueryContinuation<Customer> token;
        while ((token = response.GetContinuation()) != null)
        {
            // Load the next page of results.
            response = context.Execute<Customer>(token) as QueryOperationResponse<Customer>;

            // Add the next page of customers to the list.
            customers.AddRange(response);
        }

        // Return the list of customers
        return customers;
    }
}

En el siguiente ejemplo, la actividad ListCustomers recupera una lista de clientes y, a continuación, una actividad ForEach<T> los enumera y escribe el nombre de la compañía y nombre de contacto de cada cliente en la consola.

Variable<List<Customer>> customers = new Variable<List<Customer>>();
DelegateInArgument<Customer> customer = new DelegateInArgument<Customer>();

Activity wf = new Sequence
{
    Variables = { customers },
    Activities =
    {
        new WriteLine
        {
            Text = "Calling WCF Data Service..."
        },
        new ListCustomers
        {
            ServiceUri = "http://services.odata.org/Northwind/Northwind.svc/",
            Result = customers
        },
        new ForEach<Customer>
        {
            Values = customers,
            Body = new ActivityAction<Customer>
            {
                Argument = customer,
                Handler = new WriteLine
                {
                    Text = new InArgument<string>((env) => string.Format("{0}, Contact: {1}",
                        customer.Get(env).CompanyName, customer.Get(env).ContactName))
                }
            }
        }
    }
};

WorkflowInvoker.Invoke(wf);

Cuando se invoca este flujo de trabajo, se escriben los siguientes datos en la consola. Puesto que esta consulta devuelve muchos clientes, solo se mostró aquí una parte del resultado.

Calling WCF Data Service...
Alfreds Futterkiste, Contact: Maria Anders
Ana Trujillo Emparedados y helados, Contact: Ana Trujillo
Antonio Moreno Taquería, Contact: Antonio Moreno
Around the Horn, Contact: Thomas Hardy
Berglunds snabbköp, Contact: Christina Berglund
...

Consumo de una fuente de OData sin usar las bibliotecas cliente

OData expone los datos como recursos direccionables a través de identificadores uniformes de recursos (URI). Al utilizar las bibliotecas de clientes, se crean estos URI para usted, pero no tiene que utilizar las bibliotecas de clientes. Si se desea, se puede tener acceso directo a los servicios OData sin utilizar las bibliotecas de clientes. Cuando no se usan las bibliotecas de clientes, la ubicación del servicio y los datos deseados son especificados por el URI y los resultados se devuelven en respuesta a la solicitud HTTP. Entonces, estos datos sin procesar se pueden procesar o manipular del modo deseado. Una manera de recuperar los resultados de una consulta de OData es utilizando la clase WebClient . En este ejemplo, se recupera el nombre de contacto para el cliente representado por la clave ALFKI.

string uri = "http://services.odata.org/Northwind/Northwind.svc/Customers('ALFKI')/ContactName";
WebClient client = new WebClient();
string data = client.DownloadString(uri);
Console.WriteLine("Raw data returned:\n{0}", data);

Cuando se ejecuta este código, en la consola se muestra el siguiente resultado:

Raw data returned:
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<ContactName xmlns="http://schemas.microsoft.com/ado/2007/08/dataservices">Maria Anders</ContactName>

En un flujo de trabajo, el código de este ejemplo podría incorporarse al reemplazo Execute de una actividad personalizada basada en CodeActivity, pero también se puede lograr la misma funcionalidad utilizando la actividad InvokeMethod<TResult>. La actividad InvokeMethod<TResult> permite a los autores del flujo de trabajo invocarlos métodos estáticos y de instancia de una clase, pero también tiene una opción para invocar el método especificado de forma asincrónica. En el siguiente ejemplo, se configura una actividad InvokeMethod<TResult> para llamar al método DownloadString de la clase WebClient y devolver una lista de clientes.

new InvokeMethod<string>
{
    TargetObject = new InArgument<WebClient>(new VisualBasicValue<WebClient>("New System.Net.WebClient()")),
    MethodName = "DownloadString",
    Parameters =
    {
        new InArgument<string>("http://services.odata.org/Northwind/Northwind.svc/Customers('ALFKI')/Orders")
    },
    Result = data,
    RunAsynchronously = true
},

InvokeMethod<TResult> puede llamar métodos estáticos y de instancia de una clase. Puesto que DownloadString es un método de instancia de la clase WebClient , una nueva instancia de la clase WebClient se especifica para TargetObject. DownloadString se especifica como MethodName, el URI que contiene la consulta se especifica en la colección Parameters y el valor devuelto se asigna al valor de Result . El valor RunAsynchronously se establece en true, lo que significa que la invocación del método se ejecutará de forma asincrónica con respecto al flujo de trabajo. En el siguiente ejemplo, se construye un flujo de trabajo que utiliza la actividad InvokeMethod<TResult> para consultar en el servicio de datos de Northwind dl ejemplo una lista de pedidos para un cliente concreto y, a continuación, los datos devueltos se escriben en la consola.

Variable<string> data = new Variable<string>();

Activity wf = new Sequence
{
    Variables = { data },
    Activities =
    {
        new WriteLine
        {
            Text = "Calling WCF Data Service..."
        },
        new InvokeMethod<string>
        {
            TargetObject = new InArgument<WebClient>(new VisualBasicValue<WebClient>("New System.Net.WebClient()")),
            MethodName = "DownloadString",
            Parameters =
            {
                new InArgument<string>("http://services.odata.org/Northwind/Northwind.svc/Customers('ALFKI')/Orders")
            },
            Result = data,
            RunAsynchronously = true
        },
        new WriteLine
        {
            Text = new InArgument<string>(env => string.Format("Raw data returned:\n{0}", data.Get(env)))
        }
    }
};

WorkflowInvoker.Invoke(wf);

Cuando se invoca este flujo de trabajo, en la consola se muestra el siguiente resultado. Puesto que esta consulta devuelve varios pedidos, solo se muestra aquí una parte del resultado.

Calling WCF Data Service...
Raw data returned:

<?xml version="1.0" encoding="utf-8" standalone="yes"?>*
<feed
xml:base="http://services.odata.org/Northwind/Northwind.svc/"
xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices"
xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"
xmlns="http://www.w3.org/2005/Atom">
<title type="text">Orders\</title>
<id>http://services.odata.org/Northwind/Northwind.svc/Customers('ALFKI')/Orders\</id>
<updated>2010-05-19T19:37:07Z\</updated>
<link rel="self" title="Orders" href="Orders" />
<entry>
<id>http://services.odata.org/Northwind/Northwind.svc/Orders(10643)\</id>
<title type="text">\</title>
<updated>2010-05-19T19:37:07Z\</updated>
<author>
<name />
</author>
<link rel="edit" title="Order" href="Orders(10643)" />
<link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/Customer" type="application/atom+xml;type=entry" title="Customer" href="Orders(10643)/Customer" />
...

Este ejemplo proporciona un método que los autores de aplicación de flujo de trabajo pueden usar para consumir los datos sin formato devueltos de un servicio de OData. Para más información sobre el acceso a los servicios de datos de WCF mediante identificadores URI, consulte Acceso a los recursos del servicio de datos (Servicios de datos de WCF) y OData: convenciones sobre identificadores URI.