Создание WCF сервиса с поддержкой Ajax: ч.1 – Контракты

Недавно меня попросили показать пример создания веб-сервиса для взаимодействия со страницей, использующей технологию 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)

Контракт данных определяет какие данные будут передаваться между севером и клиентом. Для его определения необходимо отметить атрибутами:

  1. [DataContract] – класс, который определяет контракт данных.
  2. [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 и добавим внутрь раздела следующе строки:

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

Что определяется в добавленных строках?

Блок указывает что дальше содержится конфигурация WCF.

Раздел служит для описания поведений. Элемент предоставляет возможность обращаться к Конечной точке со страниц ASP.NET, используя технологию Ajax.

В следующем разделе расположены указания по настройке среды. Атрибут aspNetCompatibilityEnabled задействует режим совместимости WCF c ASP.NET. При этом разрешаются запросы к WCF сервисам только по HTTP каналам через ASP.NET и блокируются любые другие. Атрибут multipleSiteBindingsEnabled разрешает использовать несколько привязок для одного узла IIS.

В разделе указываются службы , которые доступны извне. Каждая служба содержит Конечная точка (), для которой указываются поведение, связывание и поддерживаемый контракт. Связывание webHttpBinding означает, что используется HTTP протокол для обмена сообщениями (формат ответов Json установлен по умолчанию).

Во второй части займемся разработкой клиента, который будет использовать данный WCF сервис.