[Перевод] Построение RESTful Message Based веб-сервисов на WCF09.04.2014 16:33
Введение
Я уже писал о том, как сделать SOAP Message Based веб-сервис на WCF. А сейчас хочу рассказать о проектировании и построении RESTful Message Based веб-сервисов на WCF. Для понимания данной статьи необходимы базовые знания о REST и о том, как создавать RESTful веб-сервисы на WCF. Для ознакомления с RESTful веб-сервисами вы можете ознакомиться с: A Guide to Designing and Building RESTful Web Services with WCF 3.5.В статье я постараюсь раскрыть и решить проблемы дизайна RESTful. Вы узнаете, как построить RESTful веб-сервис, который:
Обладает стабильным и универсальным интерфейсом.
Передает данные в соответствие с паттерном DTO.
Давайте спроектируем WCF веб сервис для Санты Клауса. Санта очень любит архитектурный стиль REST и совсем не любит Open Data Protocol (OData), поэтому он выдвинул следующие требования: Сервис должен иметь RESTful API
Сервис должен обладать следующим функционалом:
Сохранение запроса на подарок.
Обновление запроса на подарок.
Получение запроса на подарокпо Статусу и Стране.
Удаление запроса на подарокпо Id.
Определение основных бизнес-объектов
Наша цель — спроектировать веб-сервис в стиле RESTful, поэтому давайте оставим бизнес-объекты простыми на столько, на сколько это возможно.Рассмотрим класс Запрос на подарок (далее PresentRequest). PresentRequest — это агрегат и содержит всю необходимую информацию о желании.
PresentRequest
public class PresentRequest
{
public Address Address { get; set; }
public Guid Id { get; set; }
public PresentRequestStatus Status { get; set; }
public string Wish { get; set; }
}
Address
public class Address
{
public string Country { get; set; }
public string Recipient { get; set; }
public string StreetAddress { get; set; }
public int ZipCode { get; set; }
}
PresentRequestStatus
public enum PresentRequestStatus
{
Pending,
Accepted,
Rejected,
Completed
}
Теперь мы имеем все необходимое для начала.RESTful веб-сервис на WCF: проблема проектирования
На этом шаге мы определим интерфейс веб-сервиса. Давайте начнем с метода Save.Сохранение PresentRequest
Простейшая реализация будет выглядеть так:
public void Save (PresentRequest request)
Клиент заполняет все поля и отправляет запрос на веб-сервис. Метод Save возвращает void, т.к. мы знаем, что сервис будет высоконагруженным, поэтому генерация уникального Id ложится на плечи клиента.В соответствие со стилем проектирования RESTful, мы должны декорировать метод Save атрибутом WebInvoke и указать подходящий HTTP-метод. Вот маленькая шпаргалка по HTTP методам:
Operation
HTTP
Create
PUT / POST
Read
GET
Update
PUT / PATCH
Delete
DELETE
В результате получаем такой ServiceContract:
[ServiceContract]
public interface IPresentRequestService
{
[WebInvoke (Method = «POST», UriTemplate = «requests»)]
[OperationContract]
void Save (PresentRequest request);
}
Замечание: ServiceContract — это основная часть сервиса, которая должна обладать стабильностью и гибкостью. Все клиенты зависят от ServiceContract, поэтому мы должны быть очень аккуратными с какими-либо изменениями в контракте.Метод Save имеет как плюсы, так и минусы.Плюсы:
Метод абстрактный, поэтому мы можем легко добавлять поля в PresentRequest
Запрос отправляется как объект, а не как параметры URL
Большинство разработчиков знают из книги «Мифический человеко-месяц» о том, что первая версия ПО будет выброшена. То же самое относится и к ServiceContract, поэтому мы должны постараться сделать его гибким на столько, на сколько это возможно.Минусы: Мы должны иметь столько же методов Save, сколько разных объектов-наследников PresentRequest у нас будет. Но как насчет ООП?
Я знаю о KnownTypeAttribute, но нам прийдется создать бесполезную иерархию классов только для процесса десериализации.Операции Create, Update и Delete имеют аналогичные плюсы и минусы. Операция Get — отличается и явзяется, имхо, самым трудным в сопровождении методом.
Получение PresentRequests
Для операции Get параметры отправляются в строке запроса. В нашем случае, для получения PresentRequest по статусу и стране, нам нужно создать что-то вроде
[WebGet (UriTemplate = «requests? country={country}&status={status}»)]
[OperationContract]
List Get (string country, string status);
Плюсы: Перед перечислением недостатков давайте взглянем на метод Get. Представим, что мы используем этот метод внутри нашего приложения, без WCF.
public interface IPresentRequestService
{
List Get (string country, string status);
}
Одним из самых больших проблем этого метода — сигнатура. Мы должны будем обновлять реализацию сервиса после любых изменений в сигнатуре метода. Этот метод — хрупкий и имеет запашок. Таким образом, операция Get в стиле RESTful является трудно сопровождаемой по умолчанию.Вот более удачное решение, мы можем менять запрос без изменения интерфейса:
public interface IPresentRequestService
{
List Get (PresentRequestQuery query);
}
Все необходимые данные запроса содержит класс PresentRequestQuery:
public class PresentRequestQuery
{
public string Country { get; set; }
public string Status { get; set; }
}
Минусы: Как было сказано выше, метод Get имеет хрупкую сигнатуру, поэтому расширить функциональность без breaking changes действительно сложно. Параметры операции Get отправляются как строка запроса с простыми полями, которые также представлены в сигнатуре метода Get. Связность между параметрами отсутствует, т.к. WCF не создает объект запроса на основе параметров.Давайте взглянем на пример: URL SantaClaus.org/requests? country=sheldonopolis&status=pending для получения PresentReuqests по стране и статусу.Вот соответствующий метод в WCF-сервисе:
public List Get (string country, string status)
{
throw new NotImplementedException ();
}
Согласно сигнатуре метода связность между country и status отсутствуют. Фактически, мы не знаем, что означает country и status, мы можем лишь предполагать. По моему мнению, WCF должно уметь создать сроку запроса на основе объекта запроса (сериализовать), а также создать объект запроса на основе строки запроса (десериализация). Таким образом, для отправки следующий объект запроса:
public class PresentRequestQuery
{
public string Country { get; set; }
public string Status { get; set; }
}
должен быть сериализован в country=sheldonopolis&status=pending, а после получения строка запроса должна быть десериализована в экземпляр PresentRequestQuery и метод Get должен выглядеть так:
public List Get (PresentRequestQuery query)
{
throw new NotImplementedException ();
}
Мы должны создать столько методов Get, сколько запросов мы имеем. Вот пример кода из WCF’s Guide to Designing and Building RESTful Web Services: BookmarkService
[ServiceContract]
public partial class BookmarkService
{
[WebGet (UriTemplate = »? tag={tag}»)]
[OperationContract]
Bookmarks GetPublicBookmarks (string tag) {…}
[WebGet (UriTemplate = »{username}? tag={tag}»)]
[OperationContract]
Bookmarks GetUserPublicBookmarks (string username, string tag) {…}
[WebGet (UriTemplate = «users/{username}/bookmarks? tag={tag}»)]
[OperationContract]
Bookmarks GetUserBookmarks (string username, string tag) {…}
[WebGet (UriTemplate = «users/{username}/profile»)]
[OperationContract]
UserProfile GetUserProfile (string username) {…}
[WebGet (UriTemplate = «users/{username}»)]
[OperationContract]
User GetUser (string username) {…}
[WebGet (UriTemplate = «users/{username}/bookmarks/{bookmark_id}»)]
[OperationContract]
Bookmark GetBookmark (string username, string bookmark_id) {…}
…
}
Я не понимаю, почему WCF не поддерживает сериализацию строки запроса, то есть создание объекта из строки запроса. Этот простой трюк мог бы помочь создать более стабильную сигнатуру метода. С другой стороны, метод Get может иметь такую сигнатуру. Так вид метода повторно является повторно используемым и полиморфным.
Message Get (Message request);
Минусы операции Get: Методы трудно сопровождаемы
Необходимо создавать слишком много методов Get
Отсутствует связность между параметрами запроса
Полиморфизм отсутствует
Пожалуйста, имейте ввиду, что WCF SOAP сервис имеет полиморфизм, точнее имеет специальный полиморфизм (ad hoc polymorphism), реализуемый через KnownTypeAttribute, но, по-моему, WCF должен поддерживать параметрический полиморфизм.Заключение
WCF как RESTful фрэймворк имеет несколько архитектурных особенностей, которые усложняет создание повторно используемых и стабильных сервисов. С другой стороны, WCF имеет все необходимое для решения этих проблем.RESTful Web Service на WCF: улучшенный дизайн
Прежде всего, давайте устраним недостатки метода Get. Я думаю, подход, основанный на сообщениях с сериализацией, может нам помочь.Сериализация и десериализация URL
Мы уже видели класс PresentRequestQuery, но теперь давайте сериализуем его.
public class PresentRequestQuery
{
public string Country { get; set; }
public string Status { get; set; }
}
Как нам известно, Get отправляет параметры в виде строки запроса, поэтому наш метод сериализации должен создавать валидную строку запроса. Идеальная строка запроса, полученная в результате сериализации, должна выглядеть так: country=sheldonopolis&status=pending и мы хотим создать что-то похожее. Идеальный результат сериализации имеет один недостаток: отсутствие связи между параметрами, поэтому мы не можем десериализовать URL в объект запроса. Наш механизм сериализации должен решить и эту проблему.Вообще говоря, строка запроса — это коллекция различных пар «ключ-значение»: key1=value1&key2=value2&key3=value3 .В нашем случае, мы имеем два ключа:
Тип запроса
Данные запроса, поля объекта
Я вижу следующий алгоритм сериализации: Определить тип запроса
Сериализовать объект запроса в JSON
Закодировать JSON
Результирующая строка запроса должна соответствовать маске: type={request type}&data={request data}Вот экземпляр объекта запроса:
var query = new PresentRequestQuery
{
Country = «sheldonopolis»,
Status = «pending»
};
Результирующая строка запроса: type=PresentRequestQuery&data=%7B%22Country%22%3A%22sheldonopolis%22%2C%22Status%22%3A%22pending%22%7DЭта строка запроса может быть легко десериализована в экземпляр PresentRequestQuery. Реализация очень проста: CreateQueryParams(T value)
private static NameValueCollection CreateQueryParams(T value)
{
string data = JsonDataSerializer.ToString (value);
var result = new NameValueCollection
{
{ RestServiceMetadata.ParamName.Type, UrlEncode (typeof (T).Name) },
{ RestServiceMetadata.ParamName.Data, UrlEncode (data) }
};
return result;
}
, где UrlEncode вызывает лишь Uri.EscapeDataString и JsonDataContractSerializer — это экземпляр DataContractJsonSerializer.ToString(T value)
public static string ToString(T value)
{
using (var stream = new MemoryStream ())
{
var serializer = new DataContractJsonSerializer (typeof (T));
serializer.WriteObject (stream, value);
return Encoding.UTF8.GetString (stream.ToArray ());
}
}
Теперь мы готовы к следующему шагу — использованию подхода, основанного на сообщениях. Для SOAP сервиса мы использовали этот контракт:
ISoapService
SeriviceContract:
[ServiceContract]
public interface ISoapService
{
[OperationContract (Action = ServiceMetadata.Action.Process)]
void Process (Message message);
[OperationContract (Action = ServiceMetadata.Action.ProcessWithResponse,
ReplyAction = ServiceMetadata.Action.ProcessResponse)]
Message ProcessWithResponse (Message message);
}
Стиль RESTful требует наличия как минимум четырех методов: Get, Post, Put, Delete and ServiceContract может быть примерно таким: IJsonService
[ServiceContract]
public interface IJsonService
{
[OperationContract]
[WebInvoke (Method = OperationType.Delete,
UriTemplate = RestServiceMetadata.Path.Delete,
RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)]
void Delete (Message message);
[OperationContract]
[WebInvoke (Method = OperationType.Delete,
UriTemplate = RestServiceMetadata.Path.DeleteWithResponse,
RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)]
Message DeleteWithResponse (Message message);
[OperationContract]
[WebGet (UriTemplate = RestServiceMetadata.Path.Get,
RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)]
void Get (Message message);
[OperationContract]
[WebGet (UriTemplate = RestServiceMetadata.Path.GetWithResponse,
RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)]
Message GetWithResponse (Message message);
[OperationContract]
[WebInvoke (Method = OperationType.Post,
UriTemplate = RestServiceMetadata.Path.Post,
RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)]
void Post (Message message);
[OperationContract]
[WebInvoke (Method = OperationType.Post,
UriTemplate = RestServiceMetadata.Path.PostWithResponse,
RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)]
Message PostWithResponse (Message message);
[OperationContract]
[WebInvoke (Method = OperationType.Put,
UriTemplate = RestServiceMetadata.Path.Put,
RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)]
void Put (Message message);
[OperationContract]
[WebInvoke (Method = OperationType.Put,
UriTemplate = RestServiceMetadata.Path.PutWithResponse,
RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)]
Message PutWithResponse (Message message);
}
IJsonService обладает гибкостью, стабильностью и легкостью сопровождения. Мы можем передавать любые данные, так как сервис зависит только от класса Message, который является фундаментальным для WCF (MSDN). Еще одно преимущество — это CRUD. Используя IJsonService и сериализацию в URL мы можем создавать повторно используемые RESTful сервисы с параметрическим полиморфизмом.Реализация RESTful сервиса
Я не стану приводить здесь весь код, т.к. он уже приводился ранее. Ниже приводится пример, как Создавать, Обновлять, Получать и Удалять запросы.ClientProcessor
public sealed class ClientProcessor: IPostWithResponse,
IGetWithResponse,
IDelete,
IPutWithResponse
{
private static List _clients = new List();
public void Delete (DeleteClientRequest request)
{
_clients = _clients.Where (x => x.Id!= request.Id).ToList ();
}
public object GetWithResponse (GetClientRequest request)
{
Client client = _clients.Single (x => x.Id == request.Id);
return new ClientResponse { Id = client.Id, Email = client.Email };
}
public object PostWithResponse (CreateClientRequest request)
{
var client = new Client
{
Id = Guid.NewGuid (),
Email = request.Email
};
_clients.Add (client);
return new ClientResponse { Id = client.Id, Email = client.Email };
}
public object PutWithResponse (UpdateClientRequest request)
{
Client client = _clients.Single (x => x.Id == request.Id);
client.Email = request.Email;
return new ClientResponse { Id = client.Id, Email = client.Email };
}
}
Следующие интерфейсы представляют CRUD операции: Теперь нам необходимо связать запросы с подходящими CRUD-операциями.ServiceProcessor
public abstract class ServiceProcessor
{
internal static readonly RequestMetadataMap _requests = new RequestMetadataMap ();
protected static readonly Configuration _configuration = new Configuration ();
private static readonly RequestProcessorMap _requestProcessors = new RequestProcessorMap ();
protected static void Process (RequestMetadata requestMetaData)
{
IRequestProcessor processor = _requestProcessors.Get (requestMetaData.Type);
processor.Process (requestMetaData);
}
protected static Message ProcessWithResponse (RequestMetadata requestMetaData)
{
IRequestProcessor processor = _requestProcessors.Get (requestMetaData.Type);
return processor.ProcessWithResponse (requestMetaData);
}
protected sealed class Configuration: IConfiguration
{
public void Bind(Func creator)
where TRequest: class
where TProcessor: IRequestOperation
{
if (creator == null)
{
throw new ArgumentNullException («creator»);
}
_requestProcessors.Add(creator);
_requests.Add();
}
public void Bind()
where TRequest: class
where TProcessor: IRequestOperation, new ()
{
Bind(() => new TProcessor ());
}
}
}
Конкретный ServiceProcessor имеет только методы конфигурирования и обработки.RestServiceProcessor
public sealed class RestServiceProcessor: ServiceProcessor
{
private RestServiceProcessor ()
{
}
public static IConfiguration Configure (Action action)
{
action (_configuration);
return _configuration;
}
public static void Process (Message message)
{
RequestMetadata metadata = _requests.FromRestMessage (message);
Process (metadata);
}
public static Message ProcessWithResponse (Message message)
{
RequestMetadata metadata = _requests.FromRestMessage (message);
return ProcessWithResponse (metadata);
}
}
RequestMetadataMap используется для хранения типов запросов, которые необходимы для создания конкретных запросов из экземпляров Message.RequestMetadataMap
internal sealed class RequestMetadataMap
{
private readonly Dictionary _requestTypes =
new Dictionary();
internal void Add()
where TRequest: class
{
Type requestType = typeof (TRequest);
_requestTypes[requestType.Name] = requestType;
}
internal RequestMetadata FromRestMessage (Message message)
{
UriTemplateMatch templateMatch = WebOperationContext.Current.IncomingRequest.UriTemplateMatch;
NameValueCollection queryParams = templateMatch.QueryParameters;
string typeName = UrlSerializer.FromQueryParams (queryParams).GetTypeValue ();
Type targetType = GetRequestType (typeName);
return RequestMetadata.FromRestMessage (message, targetType);
}
internal RequestMetadata FromSoapMessage (Message message)
{
string typeName = SoapContentTypeHeader.ReadHeader (message);
Type targetType = GetRequestType (typeName);
return RequestMetadata.FromSoapMessage (message, targetType);
}
private Type GetRequestType (string typeName)
{
Type result;
if (_requestTypes.TryGetValue (typeName, out result))
{
return result;
}
string errorMessage = string.Format (
«Binding on {0} is absent. Use the Bind method on an appropriate ServiceProcessor», typeName);
throw new InvalidOperationException (errorMessage);
}
}
Посмотрим на повторно используемую реализацию IJsonService: JsonServicePerCall
[ServiceBehavior (InstanceContextMode = InstanceContextMode.PerCall)]
public sealed class JsonServicePerCall: IJsonService
{
public void Delete (Message message)
{
RestServiceProcessor.Process (message);
}
public Message DeleteWithResponse (Message message)
{
return RestServiceProcessor.ProcessWithResponse (message);
}
public void Get (Message message)
{
RestServiceProcessor.Process (message);
}
public Message GetWithResponse (Message message)
{
return RestServiceProcessor.ProcessWithResponse (message);
}
public void Post (Message message)
{
RestServiceProcessor.Process (message);
}
public Message PostWithResponse (Message message)
{
return RestServiceProcessor.ProcessWithResponse (message);
}
public void Put (Message message)
{
RestServiceProcessor.Process (message);
}
public Message PutWithResponse (Message message)
{
return RestServiceProcessor.ProcessWithResponse (message);
}
}
Как видите, можно отправлять все, что угодно и полностью в соответствии с RESTful.Самое интересное происходит в RestRequestMetadata, классе, который помогает создать конкретный запрос из URL. Перед тем, как взглянуть на реализацию RestRequestMetadata, я хочу дать некоторые пояснения. RestRequestMetadata использует WebOperationContext для получения строки запроса и создания конкретного запроса. Также он может создавать ответное сообщение на основе запроса.RestRequestMetadata
internal sealed class RestRequestMetadata: RequestMetadata
{
private readonly object _request;
private readonly WebOperationContext _webOperationContext;
internal RestRequestMetadata (Message message, Type targetType) : base (targetType)
{
_webOperationContext = WebOperationContext.Current;
OperationType = GetOperationType (message);
_request = CreateRequest (message, targetType);
}
public override string OperationType { get; protected set; }
public override Message CreateResponse (object response)
{
var serializer = new DataContractJsonSerializer (response.GetType ());
return _webOperationContext.CreateJsonResponse (response, serializer);
}
public override TRequest GetRequest()
{
return (TRequest)_request;
}
private static object CreateRequestFromContent (Message message, Type targetType)
{
using (var stream = new MemoryStream ())
{
XmlDictionaryWriter writer = JsonReaderWriterFactory.CreateJsonWriter (stream);
message.WriteMessage (writer);
writer.Flush ();
var serializer = new DataContractJsonSerializer (targetType);
stream.Position = 0;
return serializer.ReadObject (stream);
}
}
private static string GetOperationType (Message message)
{
var httpReq = (HttpRequestMessageProperty)message.Properties[HttpRequestMessageProperty.Name];
return httpReq.Method;
}
private object CraeteRequestFromUrl (Type targetType)
{
UriTemplateMatch templateMatch = _webOperationContext.IncomingRequest.UriTemplateMatch;
NameValueCollection queryParams = templateMatch.QueryParameters;
return UrlSerializer.FromQueryParams (queryParams).GetRequestValue (targetType);
}
private object CreateRequest (Message message, Type targetType)
{
if (IsRequestByUrl ())
{
return CraeteRequestFromUrl (targetType);
}
return CreateRequestFromContent (message, targetType);
}
private bool IsRequestByUrl ()
{
return OperationType == Operations.OperationType.Get ||
OperationType == Operations.OperationType.Delete;
}
}
Все конкретные запросы обрабатываются классом RequestProcessor.RequestProcessor
internal sealed class RequestProcessor: IRequestProcessor
where TRequest: class
where TProcessor: IRequestOperation
{
private readonly Func _creator;
public RequestProcessor (Func creator)
{
_creator = creator;
}
public void Process (RequestMetadata metadata)
{
switch (metadata.OperationType)
{
case OperationType.Get:
Get (metadata);
break;
case OperationType.Post:
Post (metadata);
break;
case OperationType.Put:
Put (metadata);
break;
case OperationType.Delete:
Delete (metadata);
break;
default:
string message = string.Format («Invalid operation type: {0}», metadata.OperationType);
throw new InvalidOperationException (message);
}
}
public Message ProcessWithResponse (RequestMetadata metadata)
{
switch (metadata.OperationType)
{
case OperationType.Get:
return GetWithResponse (metadata);
case OperationType.Post:
return PostWithResponse (metadata);
case OperationType.Put:
return PutWithResponse (metadata);
case OperationType.Delete:
return DeleteWithResponse (metadata);
default:
string message = string.Format («Invalid operation type: {0}», metadata.OperationType);
throw new InvalidOperationException (message);
}
}
private void Delete (RequestMetadata metadata)
{
var service = (IDelete)_creator ();
var request = metadata.GetRequest();
service.Delete (request);
}
private Message DeleteWithResponse (RequestMetadata metadata)
{
var service = (IDeleteWithResponse)_creator ();
var request = metadata.GetRequest();
object result = service.DeleteWithResponse (request);
return metadata.CreateResponse (result);
}
private void Get (RequestMetadata metadata)
{
var service = (IGet)_creator ();
var request = metadata.GetRequest();
service.Get (request);
}
private Message GetWithResponse (RequestMetadata metadata)
{
var service = (IGetWithResponse)_creator ();
var request = metadata.GetRequest();
object result = service.GetWithResponse (request);
return metadata.CreateResponse (result);
}
private void Post (RequestMetadata metadata)
{
var service = (IPost)_creator ();
var request = metadata.GetRequest();
service.Post (request);
}
private Message PostWithResponse (RequestMetadata metadata)
{
var service = (IPostWithResponse)_creator ();
var request = metadata.GetRequest();
object result = service.PostWithResponse (request);
return metadata.CreateResponse (result);
}
private void Put (RequestMetadata metadata)
{
var service = (IPut)_creator ();
var request = metadata.GetRequest();
service.Put (request);
}
private Message PutWithResponse (RequestMetadata metadata)
{
var service = (IPutWithResponse)_creator ();
var request = metadata.GetRequest();
object result = service.PutWithResponse (request);
return metadata.CreateResponse (result);
}
}
Клиент RESTful сервиса
Клиент достаточно прост, просто сериализует данные в строку запроса и отправляет сервису. Клиент основан на HttpClient. Ниже приведены методы клиента: Методы клиента
public void Delete(TRequest request)
where TRequest: class
public TResponse Delete(TRequest request)
where TRequest: class
public Task DeleteAsync(TRequest request)
where TRequest: class
public Task DeleteAsync(TRequest request)
where TRequest: class
public void Get(TRequest request)
where TRequest: class
public TResponse Get(TRequest request)
where TRequest: class
public Task GetAsync(TRequest request)
where TRequest: class
public Task GetAsync(TRequest request)
where TRequest: class
public void Post(TRequest request)
where TRequest: class
public TResponse Post(TRequest request)
where TRequest: class
public Task PostAsync(TRequest request)
where TRequest: class
public Task PostAsync(TRequest request)
where TRequest: class
public void Put(TRequest request)
where TRequest: class
public TResponse Put(TRequest request)
where TRequest: class
public Task PutAsync(TRequest request)
where TRequest: class
public Task PutAsync(TRequest request)
where TRequest: class
А теперь давайте сделаем Санту счастливым обладателем RESTful — сервиса, основанного на сообщениях.
Пример RESTful сервиса
Санта до сих пор ожидает RESTful сервиса, способного сохранять и искать запросы на подарки по фильтру.Сервис
Файл конфигурации самый обычный: Конфигурация
JsonServicePerCall и IJsonService уже упоминались выше.Ниже представдена привязка и другие настройки. Биндинг говорит, что PresentRequestProcessor будет обрабатывать PresentRequest и PresentRequestQuery.
Настройка привязки
private static void Main ()
{
RestServiceProcessor.Configure (x =>
{
x.Bind();
x.Bind();
x.Bind();
x.Bind();
});
using (var serviceHost = new WebServiceHost (typeof (JsonServicePerCall)))
{
serviceHost.Open ();
Console.WriteLine («Santa Clause Service has started»);
Console.ReadKey ();
serviceHost.Close ();
}
}
И наконец, PresentRequestProcessor показывает как Get, Post, Put and Delete запросы на подарки: PresentRequestProcessor
public sealed class PresentRequestProcessor: IPost,
IPost,
IGetWithResponse,
IDelete
{
private static List _requests = new List();
public void Delete (DeletePresentRequestsByStatus request)
{
var status = (PresentRequestStatus)Enum.Parse (typeof (PresentRequestStatus), request.Status);
_requests = _requests.Where (x => x.Status!= status).ToList ();
Console.WriteLine («Request list was updated, current count: {0}», _requests.Count);
}
public object GetWithResponse (PresentRequestQuery request)
{
Console.WriteLine («Get Present Requests by: {0}», request);
var status = (PresentRequestStatus)Enum.Parse (typeof (PresentRequestStatus), request.Status);
return _requests.Where (x => x.Status == status)
.Where (x => x.Address.Country == request.Country)
.ToList ();
}
public void Post (PresentRequest request)
{
request.Status = PresentRequestStatus.Pending;
_requests.Add (request);
Console.WriteLine («Request was added, Id: {0}», request.Id);
}
public void Post (UpdatePresentRequestStatus request)
{
Console.WriteLine («Update requests on status: {0}», request.Status);
var status = (PresentRequestStatus)Enum.Parse (typeof (PresentRequestStatus), request.Status);
_requests.ForEach (x => x.Status = status);
}
}
Клиент
Код клиента самодокументируемый: Клиент
private static void Main ()
{
var client = new JsonServiceClient («http://localhost:9090/requests»);
var presentRequest = new PresentRequest
{
Id = Guid.NewGuid (),
Address = new Address
{
Country = «sheldonopolis»,
},
Wish = «Could you please help developers to understand,» +
«WCF is awesome only with Nelibur»
};
client.Post (presentRequest);
var requestQuery = new PresentRequestQuery
{
Country = «sheldonopolis»,
Status = PresentRequestStatus.Pending.ToString ()
};
List pendingRequests = client.Get>(requestQuery);
Console.WriteLine («Pending present requests count: {0}», pendingRequests.Count);
var updatePresentRequestStatus = new UpdatePresentRequestStatus
{
Status = PresentRequestStatus.Accepted.ToString ()
};
client.Post (updatePresentRequestStatus);
var deleteByStatus = new DeletePresentRequestsByStatus
{
Status = PresentRequestStatus.Accepted.ToString ()
};
client.Delete (deleteByStatus);
Console.WriteLine («Press any key for Exit»);
Console.ReadKey ();
}
Результаты выполнения: скриншот программы Fiddler: Конец
Подход, основанный на сообщениях — это мега мощный архитектурный стиль. Он может помочь создать RESTful сервис со стабильным, обслуживаемым интерфейсом и конечно Санта сам будет доволен получить именно такой RESTful сервис в качестве подарка на Рождество.Исходники можно скачать со статьи-оригинала или с сайта проекта.Доступен также nuget package.
Интересная статья по теме: Advantages of message based web services.
© Habrahabr.ru