Поделиться через


Использование источников OData в рабочем процессе

службы данных WCF — это компонент платформа .NET Framework, который позволяет создавать службы, использующие протокол Open Data Protocol (OData) для предоставления и использования данных в Интернете или интрасети с помощью семантики передачи репрезентативного состояния (REST). OData предоставляет доступ к данным в виде ресурсов, которые адресуются по URI. Со службами данных, основанными на OData, может взаимодействовать любое приложение, которое способно отправить HTTP-запрос и обработать канал OData, возвращаемый службой данных. Кроме того, службы данных WCF включают клиентские библиотеки, обеспечивающие более богатый интерфейс программирования при использовании каналов OData из приложений платформы .NET Framework. В этом разделе представлены общие сведения об использовании каналов OData в рабочем процессе как с клиентскими библиотеками, так и без них.

Использование примера службы Northwind OData

В примерах в этом разделе используется пример службы данных Northwind, расположенной по адресу https://services.odata.org/Northwind/Northwind.svc/. Эта служба предоставляется как часть пакета OData SDK и предоставляет доступ только для чтения к образцу базы данных Northwind. Если необходим доступ для записи или локальная служба данных WCF, следуйте шагам, описанным в кратком руководстве по службам данных WCF, чтобы создать локальную службу OData, обеспечивающую доступ к базе данных Northwind. При следовании инструкциям по быстрому запуску замените локальный URI на тот, который приведён в примерном коде в этой теме.

Использование канала данных OData с помощью клиентских библиотек

Службы данных WCF включают клиентские библиотеки, которые позволяют легче использовать OData-канал из платформы .NET Framework и клиентских приложений. Эти библиотеки упрощают отправку и получение сообщений HTTP. Кроме того, они преобразуют основные данные сообщений в объекты CLR, представляющие данные сущностей. Клиентские библиотеки содержат два базовых класса: DataServiceContext и DataServiceQuery<TElement>. Эти классы позволяют отправлять запросы к службе данных и работать с возвращенными данными сущностей как с объектами CLR. В этом разделе описаны два подхода к созданию действий, в которых используются клиентские библиотеки.

Добавление ссылки на службу данных WCF

Чтобы создать клиентские библиотеки Northwind, можно использовать диалоговое окно «Добавить ссылку на службу» в Visual Studio 2012, чтобы добавить ссылку на службу OData Northwind.

Снимок экрана: диалоговое окно

Обратите внимание, что в этой службе нет операций, к которым можно получить доступ, а в списке Службы имеются элементы, представляющие сущности, предоставляемые службой данных «Нортвинд». При добавлении ссылки на службу для этих сущностей будут созданы классы, которые будут использоваться в клиентском коде. В примерах в этом разделе для выполнения запросов используются эти классы, а также NorthwindEntities .

Примечание.

Дополнительные сведения см. в разделе "Создание клиентской библиотеки службы данных" (службы данных WCF).

Использование асинхронных методов

Для разрешения возможных проблем с задержками, которые могут произойти при обращении к ресурсам через Интернет, рекомендуется производить обращения к службам данных WCF асинхронно. Клиентские библиотеки службы данных WCF включают асинхронные методы для вызова запросов, а Windows Workflow Foundation (WF) предоставляет AsyncCodeActivity класс для разработки асинхронных действий. AsyncCodeActivityПроизводные действия можно записать, чтобы воспользоваться преимуществами классов платформа .NET Framework, имеющих асинхронные методы, или код, выполняемый асинхронно, можно поместить в метод и вызвать с помощью делегата. В этом разделе приводятся два примера действия, являющегося производным от AsyncCodeActivity , - одно из них использует асинхронные методы клиентских библиотек служб данных WCF, а второе - делегат.

Примечание.

Дополнительные сведения см. в разделе асинхронные операции (службы данных WCF) и создание асинхронных действий.

Использование асинхронных методов клиентской библиотеки

Класс DataServiceQuery<TElement> предусматривает методы BeginExecute и EndExecute для асинхронных запросов службы OData. Эти методы могут быть вызваны из переопределений BeginExecute и EndExecute класса, являющегося производным от AsyncCodeActivity. AsyncCodeActivity BeginExecute Когда переопределение возвращается, рабочий процесс может остаться в состоянии простоя (но не сохраняться), и когда асинхронная работа завершена, EndExecute вызывается средой выполнения.

В следующем примере действие OrdersByCustomer определено с двумя входными аргументами. Аргумент CustomerId представляет клиента, который определяет, какие заказы вернуть, а аргумент ServiceUri - URI службы OData, к которой будет направлен запрос для выполнения. Поскольку действие является производным от AsyncCodeActivity<IEnumerable<Order>> , имеется также выходной аргумент Result , который служит для возврата результатов запроса. Переопределение BeginExecute создает запрос LINQ, выбирающий все заказы указанного клиента. Запрос указывается как UserState переданного AsyncCodeActivityContext, а затем вызывается метод запроса BeginExecute . Обратите внимание, что обратный вызов и состояние, передаваемые в BeginExecute запроса, являются теми же, что и переданные методу действия BeginExecute. После завершения обработки запроса вызывается метод действия EndExecute . Запрос извлекается из UserState, а затем вызывается метод запроса EndExecute . Метод возвращает IEnumerable<T> указанного типа сущности, в данном случае - Order. Так как IEnumerable<Order> является универсальным типом для AsyncCodeActivity<TResult>, этот IEnumerable установлен как ResultOutArgument<T> действия.

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);
    }
}

В следующем примере операция OrdersByCustomer получает список заказов для указанного клиента, а затем операция ForEach<T> перебирает возвращенные заказы и записывает дату каждого заказа на консоль.

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);

При вызове этого рабочего процесса на консоли отображаются следующие данные.

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

Примечание.

Если не удается установить соединение с сервером OData, будет выброшено исключение, аналогичное следующему:

Необработанное исключение: System.InvalidOperationException: во время обработки запроса произошла ошибка. >--- System.Net.WebException: не удается подключиться к удаленному серверу ---> System.Net.Sockets.SocketException: попытка подключения не удалась, так как подключенная сторона не ответила должным образом через определенный период времени, или установленное соединение не удалось, так как подключенный узел не ответил.

Если требуется дополнительная обработка данных, возвращенных запросом, ее можно выполнить в переопределении действия EndExecute. Методы BeginExecute и EndExecute вызываются потоком рабочего процесса, при этом весь код в этих переопределениях не выполняется асинхронно. Если дополнительная обработка требует большого количества ресурсов, выполняется длительное время или результаты запроса отображаются с разбиением на страницы, то рекомендуется реализовать подход, описанный в предыдущем разделе, где для асинхронного выполнения запроса и дополнительной обработки используется делегат.

Использование делегата

Помимо вызова асинхронного метода класса .NET Framework, действие на основе AsyncCodeActivity может также определять асинхронную логику в одном из своих методов. Этот метод задается с помощью делегата в переопределении метода активности BeginExecute. Когда метод возвращает управление, среда выполнения вызывает переопределение метода действия EndExecute. При вызове службы OData из рабочего процесса этот метод можно использовать для запроса к службе и выполнения дополнительной обработки.

В следующем примере определяется активность ListCustomers. Это действие отправляет запрос к образцу службы данных «Борей» и возвращает список List<Customer> , содержащий всех клиентов в базе данных «Борей». Асинхронные операции выполняются методом GetCustomers . Этот метод запрашивает у службы всех клиентов, а затем копирует их в List<Customer>. Затем проверяется, разбиты ли результаты на страницы. Если это так, отправляется запрос к службе для получения следующей страницы результатов, они добавляются к списку, и процесс продолжается до получения всех данных о клиентах.

Примечание.

Дополнительные сведения о разбиении по страницам в службы данных WCF см. в разделе "Практическое руководство. Загрузка страницы результатов (службы данных WCF)".

После добавления всех клиентов возвращается список. Метод GetCustomers указывается в переопределении действия BeginExecute . Поскольку у метода имеется возвращаемое значение, то для его указания создается Func<string, List<Customer>> .

Примечание.

Если метод, выполняющий асинхронные операции, не имеет возвращаемого значения, то используется Action вместо Func<TResult>. Примеры создания асинхронного примера с помощью обоих подходов см. в разделе "Создание асинхронных действий".

Этот Func<TResult> назначается UserState, а затем вызывается BeginInvoke. Поскольку у вызываемого метода отсутствует доступ к среде аргументов действия, значение аргумента ServiceUri передается в качестве первого параметра вместе с обратным вызовом и состоянием, переданными в BeginExecute. Когда GetCustomers возвращается, среда выполнения вызывает EndExecute. Код в EndExecute вызывает делегат из UserState, вызывает EndInvokeи возвращает результат, который является списком клиентов, возвращаемым из метода 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;
    }
}

В следующем примере действие ListCustomers вызывает список клиентов, а затем действие ForEach<T> выполняет их перечисление и отображает на консоли название компании и имя контактного лица для всех клиентов.

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);

При вызове этого рабочего процесса на консоли отображаются следующие данные. Поскольку в результате этого запроса указывается несколько клиентов, ниже отображена только часть результатов.

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
...

Использование ленты OData без использования клиентских библиотек

OData предоставляет доступ к данным в виде ресурсов, которые адресуются по URI. При использовании клиентских библиотек эти URI создаются автоматически, но при этом клиентские библиотеки использовать не обязательно. При необходимости доступ к службам OData можно получить напрямую без использования клиентских библиотек. Если клиентские библиотеки не используются, то местоположение службы и необходимые данные указываются в URI, а получение результатов производится через HTTP-запрос. После этого необработанные данные можно обработать или использовать необходимым способом. Помимо других способов, результаты запроса OData можно вызвать с помощью класса WebClient . В данном примере извлекается имя контактного лица клиента, представленного ключом «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{data}");

При выполнении этого кода на консоль выводятся следующие данные.

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>

В рабочем процессе код из этого примера может быть включен в переопределение Execute настраиваемого действия на основе CodeActivity, но той же функциональности также можно добиться и с помощью действия InvokeMethod<TResult>. Действие InvokeMethod<TResult> позволяет создателю рабочего процесса вызывать статические методы и методы экземпляра класса. Кроме того, поддерживается асинхронный вызов указанного метода. В следующем примере действие InvokeMethod<TResult> настраивается для вызова метода DownloadString класса WebClient и возвращает список клиентов.

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> может быть как статическим, так и методом экземпляра класса. Поскольку DownloadString - метод экземпляра класса WebClient , определяется новый экземпляр класса WebClient для TargetObject. DownloadString определяется как MethodName, универсальный код ресурса (URI), содержащий запрос, определяется в коллекции Parameters , и значению Result присваивается возвращаемое значение. Значение RunAsynchronously задается равным true, и это указывает на асинхронный вызов метода относительно рабочего процесса. В следующем примере создается рабочий процесс, который с помощью действия InvokeMethod<TResult> опрашивает образец службы данных «Нордвинд» для получения списка заказов конкретного клиента, после чего полученные данные выводятся на консоль.

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);

При вызове этого рабочего процесса на консоль выводятся следующие данные. Поскольку запрос возвращает несколько заказов, ниже показана только часть результатов.

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" />
...

В этом примере представлен один метод, который разработчики приложений рабочих процессов могут использовать для работы с необработанными данными, полученными от службы OData. Дополнительные сведения о доступе к службы данных WCF с помощью URI см. в статьях о доступе к ресурсам службы данных (службы данных WCF) и соглашениям OData: URI.