[ServiceContract] public interface IRpcService { [OperationContract] void RegisterClient(Client client); [OperationContract] Client GetClientByName(string clientName); [OperationContract] List<Client> GetAllClients(); }
https://ec2.amazonaws.com/?Action=AllocateAddress Domain=vpc &AUTHPARAMS
<AllocateAddressResponse xmlns="http://ec2.amazonaws.com/doc/2013-02-01/"> <requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId> <publicIp>198.51.100.1</publicIp> <domain>vpc</domain> <allocationId>eipalloc-5723d13e</allocationId> </AllocateAddressResponse>
public interface IMessageBasedService { Response Execute(Request request); }
where Request
and Response
can be any DTO, that is, with one method we can replace any RPC service contract, but WCF uses the RPC style.Request
and Response
objects to pass any DTO. But WCF does not support this design. All communication internals in WCF are based on the use of the Message class. That is, WCF converts any DTO into a Message
instance and sends Message
from the client to the server. Therefore, we must use the Message
class for Request
and Response
objects.Response
object. [ServiceContract] public interface ISoapService { [OperationContract(Action = ServiceMetadata.Action.ProcessOneWay)] void ProcessOneWay(Message message); [OperationContract(Action = ServiceMetadata.Action.Process, ReplyAction = ServiceMetadata.Action.ProcessResponse)] Message Process(Message message); }
ISoapService
allows us to transfer any data, but this is not enough. We want to create, delete objects and execute methods on it. As for me, the best choice is CRUD operations at the facility, so we can implement any operation. First of all, let's create a SoapServiceClient
that can send and receive any DTO.SoapServiceClient
will show how to create a Message
from any DTO. SoapServiceClient
is a wrapper that converts any DTO to a Message
and sends it to the service. The message to be sent contains the following data: var client = new SoapServiceClient("NeliburSoapService"); ClientResponse response = client.Post<ClientResponse>(createRequest); response = client.Put<ClientResponse>(updateRequest);
Post
method of the SoapServiceClient
class. public TResponse Post<TResponse>(object request) { return Send<TResponse>(request, OperationTypeHeader.Post); } private TResponse Send<TResponse>(object request, MessageHeader operationType) { using (var factory = new ChannelFactory<ISoapService>(_endpointConfigurationName)) { MessageVersion messageVersion = factory.Endpoint.Binding.MessageVersion; Message message = CreateMessage(request, operationType, messageVersion); ISoapService channel = factory.CreateChannel(); Message result = channel.Process(message); return result.GetBody<TResponse>(); } } private static Message CreateMessage( object request, MessageHeader actionHeader, MessageVersion messageVersion) { Message message = Message.CreateMessage( messageVersion, ServiceMetadata.Operations.Process, request); var contentTypeHeader = new ContentTypeHeader(request.GetType()); message.Headers.Add(contentTypeHeader); message.Headers.Add(actionHeader); return message; }
Please pay attention to the CreateMessage
method and how the DTO type and the method being called are added via the contentTypeHeader
and actionHeader
.SoapContentTypeHeader
and SoapOperationTypeHeader
almost identical. The SoapContentTypeHeader
used to transmit the type of DTO, and SoapOperationTypeHeader
is used to pass the target operation. Less words, more code: internal sealed class SoapContentTypeHeader : MessageHeader { private const string NameValue = "nelibur-content-type"; private const string NamespaceValue = "http://nelibur.org/" + NameValue; private readonly string _contentType; public SoapContentTypeHeader(Type contentType) { _contentType = contentType.Name; } public override string Name { get { return NameValue; } } public override string Namespace { get { return NamespaceValue; } } public static string ReadHeader(Message request) { int headerPosition = request.Headers.FindHeader(NameValue, NamespaceValue); if (headerPosition == -1) { return null; } var content = request.Headers.GetHeader<string>(headerPosition); return content; } protected override void OnWriteHeaderContents(XmlDictionaryWriter writer, MessageVersion messageVersion) { writer.WriteString(_contentType); } }
SoapServiceClient
methods: public static TResponse Get<TResponse>(object request) public static Task<TResponse> GetAsync<TResponse>(object request) public static void Post(object request) public static Task PostAsync(object request) public static TResponse Post<TResponse>(object request) public static Task<TResponse> PostAsync<TResponse>(object request) public static void Put(object request) public static Task PutAsync(object request) public static TResponse Put<TResponse>(object request) public static Task<TResponse> PutAsync<TResponse>(object request) public static void Delete(object request) public static Task DeleteAsync(object request)
Request
. The example below shows how to add and get a Client
object (client). public sealed class ClientProcessor : IPut<CreateClientRequest>, IGet<GetClientRequest> { private readonly List<Client> _clients = new List<Client>(); public object Get(GetClientRequest request) { Client client = _clients.Single(x => x.Id == request.Id); return new ClientResponse {Id = client.Id, Name = client.Name}; } public object Put(CreateClientRequest request) { var client = new Client { Id = Guid.NewGuid(), Name = request.Name }; _clients.Add(client); return new ClientResponse {Id = client.Id}; } }
IGet
and IPost
. They represent CRUD operations. Take a look at the class diagram:Request
with the appropriate CRUD operation. The easiest way is to associate a Request
with a request Processor
. For this functionality distinguishes NeliburService
. Let's take a look at it. public abstract class NeliburService { internal static readonly RequestMetadataMap _requests = new RequestMetadataMap(); protected static readonly Configuration _configuration = new Configuration(); private static readonly RequestProcessorMap _requestProcessors = new RequestProcessorMap(); protected static void ProcessOneWay(RequestMetadata requestMetaData) { IRequestProcessor processor = _requestProcessors.Get(requestMetaData.Type); processor.ProcessOneWay(requestMetaData); } protected static Message Process(RequestMetadata requestMetaData) { IRequestProcessor processor = _requestProcessors.Get(requestMetaData.Type); return processor.Process(requestMetaData); } protected sealed class Configuration : IConfiguration { public void Bind<TRequest, TProcessor>(Func<TProcessor> creator) where TRequest : class where TProcessor : IRequestOperation { if (creator == null) { throw Error.ArgumentNull("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()); } } }
RequestMetadataMap
used to store the type of Request
object that is required to create a specific Request
from a Message
. 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 NeliburService", typeName); throw Error.InvalidOperation(errorMessage); } }
RequestProcessorMap
c associates the type of the Request
object with the handler. internal sealed class RequestProcessorMap { private readonly Dictionary<Type, IRequestProcessor> _repository = new Dictionary<Type, IRequestProcessor>(); public void Add<TRequest, TProcessor>(Func<TProcessor> creator) where TRequest : class where TProcessor : IRequestOperation { Type requestType = typeof(TRequest); IRequestProcessor context = new RequestProcessor<TRequest, TProcessor>(creator); _repository[requestType] = context; } public IRequestProcessor Get(Type requestType) { return _repository[requestType]; } }
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)] public sealed class SoapService : ISoapService { public Message Process(Message message) { return NeliburSoapService.Process(message); } public void ProcessOneWay(Message message) { NeliburSoapService.ProcessOneWay(message); } }
NeliburSoapService
just executes another code, take a look at it. public sealed class NeliburSoapService : NeliburService { private NeliburSoapService() { } public static IConfiguration Configure(Action<IConfiguration> action) { action(_configuration); return _configuration; } public static Message Process(Message message) { RequestMetadata metadata = _requests.FromSoapMessage(message); return Process(metadata); } public static void ProcessOneWay(Message message) { RequestMetadata metadata = _requests.FromSoapMessage(message); ProcessOneWay(metadata); } }
NeliburSoapService
simply decorates the RequestMetadataMap
, that is, it calls the appropriate method to create the RequestMetadata
for the SOAP Message
. RequestMetadata requestMetaData = _requests.FromSoapMessage(message)
context.Process(requestMetaData).
internal sealed class SoapRequestMetadata : RequestMetadata { private readonly MessageVersion _messageVersion; private readonly object _request; internal SoapRequestMetadata(Message message, Type targetType) : base(targetType) { _messageVersion = message.Version; _request = CreateRequest(message, targetType); OperationType = SoapOperationTypeHeader.ReadHeader(message); } public override string OperationType { get; protected set; } public override Message CreateResponse(object response) { return Message.CreateMessage(_messageVersion, SoapServiceMetadata.Action.ProcessResponse, response); } public override TRequest GetRequest<TRequest>() { return (TRequest)_request; } private static object CreateRequest(Message message, Type targetType) { using (XmlDictionaryReader reader = message.GetReaderAtBodyContents()) { var serializer = new DataContractSerializer(targetType); return serializer.ReadObject(reader); } } }
RequestProcessor
. RequestProcessor
uses RequestMetadata
to define an operation and calls it when it returns the result to the SoapServiceClient
class. 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 Message Process(RequestMetadata metadata) { switch (metadata.OperationType) { case OperationType.Get: return Get(metadata); case OperationType.Post: return Post(metadata); case OperationType.Put: return Put(metadata); case OperationType.Delete: return Delete(metadata); default: string message = string.Format("Invalid operation type: {0}", metadata.OperationType); throw Error.InvalidOperation(message); } } public void ProcessOneWay(RequestMetadata metadata) { switch (metadata.OperationType) { case OperationType.Get: GetOneWay(metadata); break; case OperationType.Post: PostOneWay(metadata); break; case OperationType.Put: PutOneWay(metadata); break; case OperationType.Delete: DeleteOneWay(metadata); break; default: string message = string.Format("Invalid operation type: {0}", metadata.OperationType); throw Error.InvalidOperation(message); } } private Message Delete(RequestMetadata metadata) { var service = (IDelete<TRequest>)_creator(); var request = metadata.GetRequest<TRequest>(); object result = service.Delete(request); return metadata.CreateResponse(result); } private void DeleteOneWay(RequestMetadata metadata) { var service = (IDeleteOneWay<TRequest>)_creator(); var request = metadata.GetRequest<TRequest>(); service.DeleteOneWay(request); } private Message Get(RequestMetadata metadata) { var service = (IGet<TRequest>)_creator(); var request = metadata.GetRequest<TRequest>(); object result = service.Get(request); return metadata.CreateResponse(result); } private void GetOneWay(RequestMetadata metadata) { var service = (IGetOneWay<TRequest>)_creator(); var request = metadata.GetRequest<TRequest>(); service.GetOneWay(request); } private Message Post(RequestMetadata metadata) { var service = (IPost<TRequest>)_creator(); var request = metadata.GetRequest<TRequest>(); object result = service.Post(request); return metadata.CreateResponse(result); } private void PostOneWay(RequestMetadata metadata) { var service = (IPostOneWay<TRequest>)_creator(); var request = metadata.GetRequest<TRequest>(); service.PostOneWay(request); } private Message Put(RequestMetadata metadata) { var service = (IPut<TRequest>)_creator(); var request = metadata.GetRequest<TRequest>(); object result = service.Put(request); return metadata.CreateResponse(result); } private void PutOneWay(RequestMetadata metadata) { var service = (IPutOneWay<TRequest>)_creator(); var request = metadata.GetRequest<TRequest>(); service.PutOneWay(request); } }
CreateClientRequest
- request to create a new clientUpdateClientRequest
- update email client requestGetClientRequest
- request for a client by idClientResponse
- information about the clientRemoveClientRequest
- client removal request <configuration> <!--WCF--> <system.serviceModel> <services> <service name="Nelibur.ServiceModel.Services.Default.SoapServicePerCall"> <endpoint address="http://localhost:5060/service" binding="basicHttpBinding" bindingConfiguration="ServiceBinding" contract="Nelibur.ServiceModel.Contracts.ISoapService" /> </service> </services> <bindings> <basicHttpBinding> <binding name="ServiceBinding"> <security mode="None"> <transport clientCredentialType="None" /> </security> </binding> </basicHttpBinding> </bindings> </system.serviceModel> <startup> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" /> </startup> </configuration>
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)] public sealed class SoapServicePerCall : ISoapService { /// <summary> /// Process message with response. /// </summary> /// <param name="message">Request message.</param> /// <returns>Response message.</returns> public Message Process(Message message) { return NeliburSoapService.Process(message); } /// <summary> /// Process message without response. /// </summary> /// <param name="message">Request message.</param> public void ProcessOneWay(Message message) { NeliburSoapService.ProcessOneWay(message); } }
private static void BindRequestToProcessors() { NeliburSoapService.Configure(x => { x.Bind<CreateClientRequest, ClientProcessor>(); x.Bind<UpdateClientRequest, ClientProcessor>(); x.Bind<DeleteClientRequest, ClientProcessor>(); x.Bind<GetClientRequest, ClientProcessor>(); }); }
ClientProcessor
: public sealed class ClientProcessor : IPost<CreateClientRequest>, IGet<GetClientRequest>, IDeleteOneWay<DeleteClientRequest>, IPut<UpdateClientRequest> { private static List<Client> _clients = new List<Client>(); public void DeleteOneWay(DeleteClientRequest request) { Console.WriteLine("Delete Request: {0}\n", request); _clients = _clients.Where(x => x.Id != request.Id).ToList(); } public object Get(GetClientRequest request) { Console.WriteLine("Get Request: {0}", request); Client client = _clients.Single(x => x.Id == request.Id); return new ClientResponse { Id = client.Id, Email = client.Email }; } public object Post(CreateClientRequest request) { Console.WriteLine("Post Request: {0}", 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 Put(UpdateClientRequest request) { Console.WriteLine("Put Request: {0}", request); Client client = _clients.Single(x => x.Id == request.Id); client.Email = request.Email; return new ClientResponse { Id = client.Id, Email = client.Email }; } }
private static void Main() { var client = new SoapServiceClient("NeliburSoapService"); var createRequest = new CreateClientRequest { Email = "email@email.com" }; Console.WriteLine("POST Request: {0}", createRequest); ClientResponse response = client.Post<ClientResponse>(createRequest); Console.WriteLine("POST Response: {0}\n", response); var updateRequest = new UpdateClientRequest { Email = "new@email.com", Id = response.Id }; Console.WriteLine("PUT Request: {0}", updateRequest); response = client.Put<ClientResponse>(updateRequest); Console.WriteLine("PUT Response: {0}\n", response); var getClientRequest = new GetClientRequest { Id = response.Id }; Console.WriteLine("GET Request: {0}", getClientRequest); response = client.Get<ClientResponse>(getClientRequest); Console.WriteLine("GET Response: {0}\n", response); var deleteRequest = new DeleteClientRequest { Id = response.Id }; Console.WriteLine("DELETE Request: {0}", deleteRequest); client.Delete(deleteRequest); Console.ReadKey(); }
Source: https://habr.com/ru/post/223685/
All Articles