使用 WCF 构建 RESTful 基于消息的 Web 服务






4.97/5 (37投票s)
如何使用 WCF 构建 RESTful 基于消息的 Web 服务。
引言
我之前写过关于如何使用 Windows Communication Foundation (WCF) 构建基于 SOAP 消息的 Web 服务的文章。我想谈谈使用 WCF 设计和构建 RESTful 基于消息的 Web 服务。这不是入门教程,所以您需要具备关于REST是什么以及如何使用 WCF 创建 RESTful Web 服务的基本知识。您可以从《使用 WCF 3.5 设计和构建 RESTful Web 服务指南》中找到更多关于 RESTful 服务的信息。
在本文中,我将尝试揭示和解决 RESTful 设计问题。您将了解如何使用 WCF 构建 RESTful Web 服务
- 稳定且通用的接口
- 根据 DTO 模式传输数据
让我们为圣诞老人设计一个 WCF Web 服务。圣诞老人非常喜欢 REST 作为一种架构风格,并且不喜欢开放数据协议 (OData),因此他提出了以下要求:
- RESTful API
- 服务应能够执行
- 保存礼物请求
- 更新礼物请求
- 按
Status
和Country
获取礼物请求 - 按
Id
删除礼物请求
定义基本业务对象
我们的目标是设计一个 RESTful 风格的 Web 服务,因此让我们尽可能地使业务对象保持简单。这可能有点不正确,但很简单。
这是一个礼物请求,这是一个领域驱动设计的聚合,包含了关于愿望的所有必要信息。
我没有为该类添加注释,我希望它不言自明。
public class PresentRequest
{
public Address Address { get; set; }
public Guid Id { get; set; }
public PresentRequestStatus Status { get; set; }
public string Wish { get; set; }
}
这是 Address
和 PresentRequestStatus:
public class Address
{
public string Country { get; set; }
public string Recipient { get; set; }
public string StreetAddress { get; set; }
public int ZipCode { get; set; }
}
public enum PresentRequestStatus
{
Pending,
Accepted,
Rejected,
Completed
}
现在,我们已经拥有了开始所需的一切。
WCF RESTful Web 服务:设计问题
在此步骤中,我们将定义 Web 服务接口。让我们从 Save
方法开始。
保存礼物请求
最简单的实现将如下所示:
public void Save(PresentRequest request)
客户端填写所有字段并将请求发送到 Web 服务。Save
方法返回 void
,因为我们知道,该服务应该高负载运行,因此客户端应自行设置唯一的 Id
。
根据 RESTful 设计风格,我们必须使用 WebInvoke
属性标记 Save
方法并设置相应的 HTTP 方法。
以下是 HTTP 方法备忘单:
操作 (Operation) |
HTTP |
Create |
PUT / POST |
读取 |
GET |
更新 |
PUT / PATCH |
删除 |
删除 |
结果是,Save
方法,ServiceContract
可能如下所示:
[ServiceContract]
public interface IPresentRequestService
{
[WebInvoke(Method = "POST", UriTemplate = "requests")]
[OperationContract]
void Save(PresentRequest request);
}
Save
方法既有优点也有缺点。
注意:ServiceContract
是服务的主要部分,它应该是稳定且灵活的。所有客户端都依赖于 ServiceContract。我们应该非常小心任何对 ServiceContract
的更改。
优点
- 方法签名是
abstract
,因此我们可以轻松地向PersentRequest
添加任何字段,而不会导致破坏性更改。 - 请求作为对象发送,而不是作为 URL 参数。
大多数开发人员从《人月神话》这本书中了解到,您将获得软件的第一个版本。ServiceContract
也是如此,因此我们必须尝试并尽可能地创建灵活的 ServiceContract
。
缺点
- 我们必须拥有尽可能多的
Save
方法来处理不同的对象。但是面向对象编程呢?
我知道 KnownTypeAttribute
,但为什么我们要创建无用的类层次结构来仅仅进行反序列化过程?
Create
、Update
和 Delete
操作是相似的,它们具有相同的优点和通常相同的缺点。Get
操作不同,对我而言,它是最难维护的方法。
获取 PresentRequests
对于 Get
操作,参数通过查询字符串发送。在我们的例子中,要按 Status
和 Country
获取礼物请求,我们需要创建类似这样的内容:
[WebGet(UriTemplate = "requests?country={country}&status={status}")]
[OperationContract]
List<PresentRequest> Get(string country, string status);
优点
- 可读的 URL,例如 http://SantaClaus.org/requests?country=sheldonopolis&status=pending
在列出缺点之前,让我们只看一下 Get
方法。
例如,我们在应用程序内部也使用相同的方法,而无需任何 WCF。
public interface IPresentRequestService
{
List<PresentRequest> Get(string country, string status);
}
此方法最大的问题之一是方法签名。在方法签名发生任何更改后,我们都必须更新服务实现。此方法易碎且脆弱,带有糟糕设计的痕迹。因此,RESTful 风格的 Get
操作默认情况下是难以维护的。
这是一个更好的解决方案,我们可以更改查询而不进行任何 interface
更改:
public interface IPresentRequestService
{
List<PresentRequest> Get(PresentRequestQuery query);
}
其中 PresentRequestQuery
类包含所有必需的字段:
public class PresentRequestQuery
{
public string Country { get; set; }
public string Status { get; set; }
}
缺点
正如我们已经看到的,Get
方法具有难以维护的签名,因此在不破坏现有功能的情况下扩展功能非常困难。
Get
操作参数通过简单的字段查询字符串发送,这些字段也存在于 Get
方法的签名中。由于 WCF 无法从参数创建请求对象,因此参数之间缺乏内聚性。
让我们看一个例子:
URL http://SantaClaus.org/requests?country=sheldonopolis&status=pending
用于获取按国家和状态的 PresentRequest
。
这是相应的 WCF 方法:
public List<PresentRequest> 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<PresentRequest> Get(PresentRequestQuery query)
{
throw new NotImplementedException();
}
我们必须为每个 get 请求创建尽可能多的 Get
方法,这是 WCF 《设计和构建 RESTful Web 服务指南》中的代码示例。
[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 服务具有多态性,实际上它通过 KnownTypeAttribute
具有临时多态性,但就我而言,它应该是参数多态性。
结论
WCF 作为 RESTful 框架,存在一些架构问题,这些问题使创建可重用和稳定的服务变得复杂。另一方面,它拥有解决这些问题所需的一切。
WCF RESTful Web 服务:更好的设计
首先,让我们解决 Get
方法缺点的问题,我认为基于消息的方法和 URL 序列化可以帮助我们。
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
等。
在我们的例子中,我们有两个键:
- 请求类型
- 请求数据,对象字段
我看到了以下序列化算法:
- 通过
DynamicMethod
获取属性名称。 - 创建查询字符串,格式为:Property1=Value1&Property2=Value2&etc
这是一个请求对象的实例:
var query = new PresentRequestQuery
{
Country = "sheldonopolis",
Status = "pending"
};
结果查询字符串:type=PresentRequestQuery&Country=sheldonopolis&Status=Pending
此查询字符串应该可以轻松地反序列化为 PresentRequestQuery
的实例对象。
这是方法:
- 创建
DynamicMethod
的实例对象。 - 通过编译的 Expression 设置属性值。
public static ObjectActivator CreateCtor(Type type)
{
ConstructorInfo emptyConstructor = type.GetConstructor(Type.EmptyTypes);
var dynamicMethod = new DynamicMethod("CreateInstance", type, Type.EmptyTypes, true);
ILGenerator ilGenerator = dynamicMethod.GetILGenerator();
ilGenerator.Emit(OpCodes.Nop);
ilGenerator.Emit(OpCodes.Newobj, emptyConstructor);
ilGenerator.Emit(OpCodes.Ret);
return (ObjectActivator)dynamicMethod.CreateDelegate(typeof(ObjectActivator));
}
其中 ObjectActivator
是一个返回对象的委托。因此,我们有一个实例对象,并且需要设置属性。
public static PropertySetter CreatePropertySetter(PropertyInfo property)
{
ParameterExpression target = Expression.Parameter(typeof(object), "target");
ParameterExpression valueParameter = Expression.Parameter(typeof(object), "value");
MemberExpression member = Expression.Property(Expression.Convert(target, property.DeclaringType), property);
MethodInfo convertTo = typeof(DelegateFactory).GetMethod("ConvertTo", BindingFlags.NonPublic | BindingFlags.Static);
MethodInfo genericConvertTo = convertTo.MakeGenericMethod(property.PropertyType);
BinaryExpression assignExpression = Expression.Assign(member, Expression.Call(genericConvertTo, valueParameter));
Expression<PropertySetter> lambda = Expression.Lambda<PropertySetter>(assignExpression, target, valueParameter);
return lambda.Compile();
}
private static T ConvertTo<T>(object value)
{
TypeConverter converter = TypeDescriptor.GetConverter(typeof(T));
return (T)converter.ConvertFrom(value);
}
其中 PropertySetter
是一个具有以下签名的委托:
public delegate void PropertySetter(object target, object value);
现在我们准备进入下一步了,我已经写了关于基于消息的方法的文章,对于 SOAP 服务,我们使用了这个 ServiceContract
:
[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
,ServiceContract
可以是这样的:
[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
是灵活、稳定且易于维护的,我们可以传输任何数据,因为服务合同仅依赖于 WCF 的 Message 类。
"Message 类是 Windows Communication Foundation (WCF) 的基础。客户端和服务之间的所有通信最终都会导致 Message 实例的发送和接收。" (MSDN)
另一个优点是CRUD。基于 IJsonService
和 URL 序列化,我们可以创建具有参数多态性的可重用 RESTful 服务。
RESTful 服务实现
我不会在这里展示所有代码,因为我使用了与创建SOAP基于消息的Web 服务相同的方法。
此示例演示了如何 Create
、Update
、Get
和 Delete
请求。
public sealed class ClientProcessor : IPostWithResponse<CreateClientRequest>,
IGetWithResponse<GetClientRequest>,
IDelete<DeleteClientRequest>,
IPutWithResponse<UpdateClientRequest>
{
private static List<Client> _clients = new List<Client>();
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 操作:
与 SOAP 服务一样,我们必须将请求对象绑定到适当的 CRUD 操作。
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<TRequest, TProcessor>(Func<TProcessor> creator)
where TRequest : class
where TProcessor : IRequestOperation
{
if (creator == null)
{
throw new ArgumentNullException("creator");
}
_requestProcessors.Add<TRequest, TProcessor>(creator);
_requests.Add<TRequest>();
}
public void Bind<TRequest, TProcessor>()
where TRequest : class
where TProcessor : IRequestOperation, new()
{
Bind<TRequest, TProcessor>(() => new TProcessor());
}
}
}
具体的 ServiceProcessor
只有处理和配置方法。
public sealed class RestServiceProcessor : ServiceProcessor
{
private RestServiceProcessor()
{
}
public static IConfiguration Configure(Action<IConfiguration> 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
用于存储 Request
的类型,这是从 Message
创建具体 Request
所必需的。
internal sealed class RequestMetadataMap
{
private readonly Dictionary<string, Type> _requestTypes =
new Dictionary<string, Type>();
internal void Add<TRequest>()
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);
}
}
正如您所见,RequestMetadataMap
支持 SOAP 和 REST 服务,并且使用相同的 RequestProcessorMap
来将 Request
的类型与请求处理器绑定。
这是可重用的实现:
[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 的。
最有趣的事情发生在
中,这个类有助于从 URL 创建具体的请求。在查看 RestRequestMetadata
RestRequestMetadata
实现之前,我想做一些解释。RestRequestMetadata
使用 WebOperationContext
来获取查询字符串并创建具体的请求。此外,该类还可以从响应创建响应消息。
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<TRequest>()
{
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
处理,这是用于 SOAP 请求的同一个类。
internal sealed class RequestProcessor<TRequest, TProcessor> : IRequestProcessor
where TRequest : class
where TProcessor : IRequestOperation
{
private readonly Func<TProcessor> _creator;
public RequestProcessor(Func<TProcessor> 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<TRequest>)_creator();
var request = metadata.GetRequest<TRequest>();
service.Delete(request);
}
private Message DeleteWithResponse(RequestMetadata metadata)
{
var service = (IDeleteWithResponse<TRequest>)_creator();
var request = metadata.GetRequest<TRequest>();
object result = service.DeleteWithResponse(request);
return metadata.CreateResponse(result);
}
private void Get(RequestMetadata metadata)
{
var service = (IGet<TRequest>)_creator();
var request = metadata.GetRequest<TRequest>();
service.Get(request);
}
private Message GetWithResponse(RequestMetadata metadata)
{
var service = (IGetWithResponse<TRequest>)_creator();
var request = metadata.GetRequest<TRequest>();
object result = service.GetWithResponse(request);
return metadata.CreateResponse(result);
}
private void Post(RequestMetadata metadata)
{
var service = (IPost<TRequest>)_creator();
var request = metadata.GetRequest<TRequest>();
service.Post(request);
}
private Message PostWithResponse(RequestMetadata metadata)
{
var service = (IPostWithResponse<TRequest>)_creator();
var request = metadata.GetRequest<TRequest>();
object result = service.PostWithResponse(request);
return metadata.CreateResponse(result);
}
private void Put(RequestMetadata metadata)
{
var service = (IPut<TRequest>)_creator();
var request = metadata.GetRequest<TRequest>();
service.Put(request);
}
private Message PutWithResponse(RequestMetadata metadata)
{
var service = (IPutWithResponse<TRequest>)_creator();
var request = metadata.GetRequest<TRequest>();
object result = service.PutWithResponse(request);
return metadata.CreateResponse(result);
}
}
正如您所注意到的,SOAP 基于消息的服务和 RESTful 基于消息的服务的大部分代码是相同的,唯一的区别在于序列化过程,但好处是相同的。
RESTful 服务客户端
客户端非常简单,只需将数据序列化为适当的查询字符串并将其发送到服务。客户端基于 HttpClient。以下是客户端的所有方法:
public void Delete(object request)
public TResponse Delete<TResponse>(object request)
public Task DeleteAsync(object request)
public Task<TResponse> DeleteAsync<TResponse>(object request)
public void Get(object request)
public TResponse Get<TResponse>(object request)
public Task GetAsync(object request)
public Task<TResponse> GetAsync<TResponse>(object request)
public void Post(object request)
public TResponse Post<TResponse>(object request)
public Task<TResponse> PostAsync<TResponse>(object request)
public Task PostAsync(object request)
public void Put(object request)
public TResponse Put<TResponse>(object request)
public Task PutAsync(object request)
public Task<TResponse> PutAsync<TResponse>(object request)
现在,让我们让圣诞老人成为最快乐的 RESTful 基于消息的服务的所有者。
RESTful 服务示例
圣诞老人仍在等待一个能够按过滤器保存和查找礼物请求的 RESTful 服务。
服务
配置文件与往常一样。
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<system.serviceModel>
<services>
<service name="Nelibur.ServiceModel.Services.JsonServicePerCall">
<host>
<baseAddresses>
<add baseAddress="https://:9090/requests" />
</baseAddresses>
</host>
<endpoint binding="webHttpBinding"
contract="Nelibur.ServiceModel.Contracts.IJsonService" />
</service>
</services>
</system.serviceModel>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
</configuration>
JsonServicePerCall
和 IJsonService
已在上面提及。
这里是绑定和其他配置。绑定表示,PresentRequestProcessor
将处理 PresentRequest
和 PresentRequestQuery
。
private static void Main()
{
RestServiceProcessor.Configure(x =>
{
x.Bind<PresentRequest, PresentRequestProcessor>();
x.Bind<PresentRequestQuery, PresentRequestProcessor>();
x.Bind<UpdatePresentRequestStatus, PresentRequestProcessor>();
x.Bind<DeletePresentRequestsByStatus, PresentRequestProcessor>();
});
using (var serviceHost = new WebServiceHost(typeof(JsonServicePerCall)))
{
serviceHost.Open();
Console.WriteLine("Santa Clause Service has started");
Console.ReadKey();
serviceHost.Close();
}
}
最后 PresentRequestProcessor
显示如何 Get, Post, Put
和 Delete
礼物请求。
public sealed class PresentRequestProcessor : IPost<PresentRequest>,
IPost<UpdatePresentRequestStatus>,
IGetWithResponse<PresentRequestQuery>,
IDelete<DeletePresentRequestsByStatus>
{
private static List<PresentRequest> _requests = new List<PresentRequest>();
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("https://: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<PresentRequest> pendingRequests = client.Get<List<PresentRequest>>(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 Web 服务,当然,圣诞老人也会很高兴在圣诞节收到这种 RESTful 服务。
希望您喜欢,请花点时间发表评论。感谢阅读本文。
历史
- 2014 年 2 月 3 日:初始版本
- 2014 年 4 月 22 日
- 更改了 URL 序列化。URL 可读。
- 2014 年 5 月 10 日
- 更改了客户端的方法签名。 例如:
client.Get<GetClientRequest, ClientResponse>(request)
=>client.Get<ClientResponse>(request)