Использование веб-каналов 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/. Эта служба поставляется в составе пакета SDK OData и предоставляет доступ только для чтения к образцу базы данных "Борей". Если необходим доступ для чтения или локальная служба данных WCF, то можно выполнить действия, описанные в кратком руководстве по службам данных WCF , чтобы создать локальную службу OData, обеспечивающую доступ к базе данных "Борей". При выполнении инструкций, описанных в кратком руководстве, необходимо заменить локальный 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>
является универсальным типом, этот IEnumerable параметр AsyncCodeActivity<TResult>задается как Result OutArgument<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
Примечание.
Если не удалось установить соединение с сервером, то будет вызвано исключение, аналогичное следующему:
Необработанное исключение: 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{0}", 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.