Недавно меня попросили показать пример создания веб-сервиса для взаимодействия со страницей, использующей технологию Ajax. Давайте посмотрим, как можно решить данную задачу с применением Windows Communication Foundation (WCF).
WCF это часть .NET Framework предназначенная для разработки сервисноориентированных приложений и организации обмена данными. Она поддерживает различные способы взаимодействий. В качестве сервера могут выступать как обычные, так и веб-приложения. При этом клиентом может быть приложение, построенное практически на любой платформе.
Немного теории
Сервис WCF это приложение, предоставляющее для связи набор Конечных точек (Endpoints). Их можно представить как точки связи в внешним миром.
Конечная точка определяется следующими характеристиками:
- Адрес (Address) – задает её расположение (например: http://site.com/endpoint.svc).
- Связывание (Binding) – указывает параметры ее взаимодействия с внешним миром (протокол, кодирование и безопасность).
- Контракт (Contract) – определяет какие операции и данные она предоставляет клиентам.
Клиент, зная указанные характеристики, обращается к Конечной точке для выполнения нужных ему операций. Все это лишь вершина айсберга описания WCF, но для разработки первого приложения этого вполне достаточно.
Разработка веб-приложения с использованием WCF
Давайте запустим Visual Studio 2010 и создадим новое пустое веб-приложение (ASP.NET Empty Web Application) под названием WebsitesCatalog. После завершения разработки оно будет отображать список сайтов, показывая дополнительную информацию о выбранном сайте в отдельном блоке.
Чтобы лучше понять процесс создания WCF сервиса не будем использовать помощника для создания классов. Поэтому необходимо самостоятельно добавить в Reference следующие сборки:
- System.Runtime.Serialization
- System.ServiceModel
Кроме того, если необходимо поддерживать HTTP GET запросы и ответы в XML формате, то может потребоваться System.ServiceModel.Web. В примере будет использоваться формат Json, поэтому необходимости в данной сборке нет.
Чтобы не сваливать все файлы в кучу, создадим две папки:
- Code для хранения классов, работающих с данными;
- Service для WCF файлов сервиса.
При добавлении класса в папку, Visual Studio уточняет его пространство имен имением самой папки. Откажемся от этого и будем для всех файлов использовать namespace WebsitesCatalog. Разумеется в реальном проекте структура может быть намного сложнее, но для демонстрационного примера такого решения достаточно.
Создание WCF сервиса начнем с описания контрактов.
Контракты данных (Data сontracts)
Контракт данных определяет какие данные будут передаваться между севером и клиентом. Для его определения необходимо отметить атрибутами:
- [DataContract] – класс, который определяет контракт данных.
- [DataMember] – каждое поле этого класса, которое будет участвовать в обмене данными.
Обратите внимание, что не обязательно все поля должны быть отмечены как [DataMember]. Это дает возможность исключать их из списка доступных клиенту.
Контракты для стандартных типов, таких как int или string, уже определены в WCF. Поэтому необходимо описывать контракты только для пользовательских типов.
Добавим в папку Code новый класс WebsiteInfo. В нем укажем свойства, содержащие описание сайта: уникальный код, название, ссылку и описание. Отметим их все атрибутом [DataMember], а сам класс – [DataContract]:
namespace WebsitesCatalog
{
using System;
using System.Runtime.Serialization;
[DataContract]
public class WebsiteInfo
{
[DataMember]
public string Id { get; set; }
[DataMember]
public string Title { get; set; }
[DataMember]
public string Url { get; set; }
[DataMember]
public string Description { get; set; }
}
}
Контракт данных определен.
Для получения данных добавим новый класс – источник данных. Для упрощения не будем использовать в проекте базу данных, а воспользуемся экземпляром ConcurrentDictionary в качестве хранилища.
Так же в папке Code создадим класс WebsiteDataSource. Реализация очень простая и не нуждается в дополнительных комментариях:
namespace WebsitesCatalog
{
using System.Collections.Concurrent;
using System.Collections.Generic;
public class WebsiteDataSource
{
private static readonly ConcurrentDictionary<string, WebsiteInfo> _dataSource =
new ConcurrentDictionary<string, WebsiteInfo>();
public IEnumerable<WebsiteInfo> Items
{
get { return WebsiteDataSource._dataSource.Values; }
}
public void AddOrUpdate(WebsiteInfo websiteInfo)
{
WebsiteDataSource._dataSource.AddOrUpdate(
websiteInfo.Id, websiteInfo, (key, oldValue) => websiteInfo);
}
public WebsiteInfo Get(string id)
{
WebsiteInfo websiteInfo;
if (WebsiteDataSource._dataSource.TryGetValue(id, out websiteInfo)) {
return websiteInfo;
}
websiteInfo = new WebsiteInfo() { Id = string.Empty };
return websiteInfo;
}
public void Delete(string id)
{
WebsiteInfo websiteInfo;
WebsiteDataSource._dataSource.TryRemove(id, out websiteInfo);
}
}
}
Теперь, когда определен контракт для данных и есть источник данных, можно перейти к описанию взаимодействия сервиса с клиентами.
Сервисные контракты (Service Contracts)
Сервисные контракты определяют операции, которые может доступны клиенту. Для его определения так же, как и в случае с данными, используются атрибуты:
- [ServiceContract] – указывает на класс или интерфейс, определяющий методы контракта.
- [OperationContract] – задает методы, которые будет доступны клиентам.
Кроме того, при указании контракта можно задать пространство имен, которому он будет принадлежать.
Описание может быть как сразу в классе, так и задано в виде интерфейса. Последний вариант является более предпочтительной практикой, т.к. не привязывает контракт к реализации. Поэтому добавим в папку Service файл IWebsiteDataService.cs с описанием интерфейса IWebsiteDataService. Обратите внимание на указание своего пространства имен WebsiteCatalogService для контракта в атрибуте ServiceContract.
namespace WebsitesCatalog
{
using System.ServiceModel;
[ServiceContract(Namespace = "WebsiteCatalogService")]
public interface IWebsiteDataService
{
[OperationContract]
void Insert(WebsiteInfo websiteInfo);
[OperationContract]
void Delete(string id);
[OperationContract]
WebsiteInfo Get(string id);
}
}
Перейдем к реализации интерфейса. В дальнейшем, можно использовать помощника для добавления "AJAX-enabled WCF service". Он автоматически создаст файл разметки, реализации и добавит нужные записи в Web.сonfig. Но, как и было сказано, в данном примере создадим всё необходимое самостоятельно.
Добавим в папку Service файл WebsiteDataService.svc.cs (наличие .svc обязательно, т.к. в дальнейшем это будет CodeBehind файл) и создадим в нем класс WebsiteDataService, реализующий интерфейс IWebsiteDataService. Нужно отметить, что для поддержки ASP.NET Ajax необходимо у класса реализации установить атрибут [AspNetCompatibilityRequirements]:
namespace WebsitesCatalog
{
using System.ServiceModel.Activation;
[AspNetCompatibilityRequirements(
RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class WebsiteDataService : IWebsiteDataService
{
private readonly WebsiteDataSource _dataSource = new WebsiteDataSource();
public void Insert(WebsiteInfo websiteInfo)
{
if (string.IsNullOrWhiteSpace(websiteInfo.Id)) {
websiteInfo.Id = Guid.NewGuid().ToString();
}
this._dataSource.AddOrUpdate(websiteInfo);
}
public void Delete(string id)
{
this._dataSource.Delete(id);
}
public WebsiteInfo Get(string id)
{
return this._dataSource.Get(id);
}
}
}
Так же надо создать файл разметки, к которому и будет обращаться клиент. Добавим в папку Service файл WebsiteDataService.svc с следующим содержимым:
<%@ ServiceHost Language="C#" Debug="true" Service="WebsitesCatalog.WebsiteDataService"
CodeBehind="WebsiteDataService.svc.cs" %>
Для полноценной работы осталось определить параметры взаимодействия.
Связываение (Binding)
Как уже было сказано, Связывание определяет параметры общения сервиса с внешним миром. Откроем файл Web.config и добавим внутрь раздела <configuration> следующе строки:
<system.serviceModel>
<behaviors>
<endpointBehaviors>
<behavior name="WebsitesCatalog.WebsiteDataServiceBehavior">
<enableWebScript />
</behavior>
</endpointBehaviors>
</behaviors>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true"
multipleSiteBindingsEnabled="true" />
<services>
<service name="WebsitesCatalog.WebsiteDataService">
<endpoint address=""
behaviorConfiguration="WebsitesCatalog.WebsiteDataServiceBehavior"
binding="webHttpBinding"
contract="WebsitesCatalog.IWebsiteDataService" />
</service>
</services>
</system.serviceModel>
Что определяется в добавленных строках?
Блок <system.serviceModel> указывает что дальше содержится конфигурация WCF.
Раздел <behaviors> служит для описания поведений. Элемент <enableWebScript /> предоставляет возможность обращаться к Конечной точке со страниц ASP.NET, используя технологию Ajax.
В следующем разделе <serviceHostingEnvironment> расположены указания по настройке среды. Атрибут aspNetCompatibilityEnabled задействует режим совместимости WCF c ASP.NET. При этом разрешаются запросы к WCF сервисам только по HTTP каналам через ASP.NET и блокируются любые другие. Атрибут multipleSiteBindingsEnabled разрешает использовать несколько привязок для одного узла IIS.
В разделе <services> указываются службы <service>, которые доступны извне. Каждая служба содержит Конечная точка (<endpoint>), для которой указываются поведение, связывание и поддерживаемый контракт. Связывание webHttpBinding означает, что используется HTTP протокол для обмена сообщениями (формат ответов Json установлен по умолчанию).
Во второй части займемся разработкой клиента, который будет использовать данный WCF сервис.