Skip to content

Instantly share code, notes, and snippets.

@mjrousos
Last active April 13, 2021 21:47
Show Gist options
  • Save mjrousos/fc6eed8ba87fe33d86a3cc19002d6c89 to your computer and use it in GitHub Desktop.
Save mjrousos/fc6eed8ba87fe33d86a3cc19002d6c89 to your computer and use it in GitHub Desktop.
CustomMiddleware
// The middleware delegate to call after this one finishes processing
private readonly RequestDelegate _next;
public SOAPEndpointMiddleware(RequestDelegate next)
{
_next = next;
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
app.UseMiddleware<SOAPEndpointMiddleware>();
// This call to use MVC middleware from the project template is not necessary, but
// doesn't hurt anything so long as it comes after our UseMiddleware call.
app.UseMvc();
}
public class ContractDescription
{
public ServiceDescription Service { get; private set; }
public string Name { get; private set; }
public string Namespace { get; private set; }
public Type ContractType { get; private set; }
public IEnumerable<OperationDescription> Operations { get; private set; }
public ContractDescription(ServiceDescription service, Type contractType, ServiceContractAttribute attribute)
{
Service = service;
ContractType = contractType;
Namespace = attribute.Namespace ?? "http://tempuri.org/"; // Namespace defaults to http://tempuri.org/
Name = attribute.Name ?? ContractType.Name; // Name defaults to the type name
var operations = new List<OperationDescription>();
foreach (var operationMethodInfo in ContractType.GetTypeInfo().DeclaredMethods)
{
foreach (var operationContract in operationMethodInfo.GetCustomAttributes<OperationContractAttribute>())
{
operations.Add(new OperationDescription(this, operationMethodInfo, operationContract));
}
}
Operations = operations;
}
}
private object[] GetRequestArguments(Message requestMessage, OperationDescription operation)
{
var parameters = operation.DispatchMethod.GetParameters();
var arguments = new List<object>();
// Deserialize request wrapper and object
using (var xmlReader = requestMessage.GetReaderAtBodyContents())
{
// Find the element for the operation's data
xmlReader.ReadStartElement(operation.Name, operation.Contract.Namespace);
for (int i = 0; i < parameters.Length; i++)
{
var parameterName = parameters[i].GetCustomAttribute<MessageParameterAttribute>()?.Name ?? parameters[i].Name;
xmlReader.MoveToStartElement(parameterName, operation.Contract.Namespace);
if (xmlReader.IsStartElement(parameterName, operation.Contract.Namespace))
{
var serializer = new DataContractSerializer(parameters[i].ParameterType, parameterName, operation.Contract.Namespace);
arguments.Add(serializer.ReadObject(xmlReader, verifyObjectName: true));
}
}
}
return arguments.ToArray();
}
public async Task Invoke(HttpContext httpContext)
{
Console.WriteLine($"Request for {httpContext.Request.Path} received ({httpContext.Request.ContentLength ?? 0} bytes)");
// Call the next middleware delegate in the pipeline
await _next.Invoke(httpContext);
}
using CustomMiddleware;
namespace Microsoft.AspNetCore.Builder
{
// Extension method used to add the middleware to the HTTP request pipeline.
public static class SOAPEndpointExtensions
{
public static IApplicationBuilder UseSOAPEndpoint(this IApplicationBuilder builder)
{
return builder.UseMiddleware<SOAPEndpointMiddleware>();
}
}
}
public class OperationDescription
{
public ContractDescription Contract { get; private set; }
public string SoapAction { get; private set; }
public string ReplyAction { get; private set; }
public string Name { get; private set; }
public MethodInfo DispatchMethod { get; private set; }
public bool IsOneWay { get; private set; }
public OperationDescription(ContractDescription contract, MethodInfo operationMethod, OperationContractAttribute contractAttribute)
{
Contract = contract;
Name = contractAttribute.Name ?? operationMethod.Name;
SoapAction = contractAttribute.Action ?? $"{contract.Namespace.TrimEnd('/')}/{contract.Name}/{Name}";
IsOneWay = contractAttribute.IsOneWay;
ReplyAction = contractAttribute.ReplyAction;
DispatchMethod = operationMethod;
}
}
POST http://localhost:5000/CalculatorService.svc HTTP/1.1
Cache-Control: no-cache, max-age=0
Content-Type: text/xml; charset=utf-8
Accept-Encoding: gzip, deflate
SOAPAction: "http://tempuri.org/ICalculatorService/Add"
Connection: Keep-Alive
Content-Length: 183
Host: localhost:5000
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body>
<Add xmlns="http://tempuri.org/">
<x>17.078485198821166</x>
<y>12.667884180633298</y>
</Add>
</s:Body>
</s:Envelope>
HTTP/1.1 200 OK
Date: Tue, 15 Mar 2016 19:39:05 GMT
Content-Type: text/xml; charset=utf-8
Server: Kestrel
Content-Length: 231
<?xml version="1.0" encoding="utf-8"?>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body>
<AddResponse xmlns="http://tempuri.org/">
<AddResult>30.589212379692686</AddResult>
</AddResponse>
</s:Body>
</s:Envelope>
public class ServiceBodyWriter : BodyWriter
{
string ServiceNamespace;
string EnvelopeName;
string ResultName;
object Result;
public ServiceBodyWriter(string serviceNamespace, string envelopeName, string resultName, object result) : base(isBuffered: true)
{
ServiceNamespace = serviceNamespace;
EnvelopeName = envelopeName;
ResultName = resultName;
Result = result;
}
protected override void OnWriteBodyContents(XmlDictionaryWriter writer)
{
writer.WriteStartElement(EnvelopeName, ServiceNamespace);
var serializer = new DataContractSerializer(Result.GetType(), ResultName, ServiceNamespace);
serializer.WriteObject(writer, Result);
writer.WriteEndElement();
}
}
public class ServiceDescription
{
public Type ServiceType { get; private set; }
public IEnumerable<ContractDescription> Contracts { get; private set; }
public IEnumerable<OperationDescription> Operations => Contracts.SelectMany(c => c.Operations);
public ServiceDescription(Type serviceType)
{
ServiceType = serviceType;
var contracts = new List<ContractDescription>();
foreach (var contractType in ServiceType.GetInterfaces())
{
foreach (var serviceContract in contractType.GetTypeInfo().GetCustomAttributes<ServiceContractAttribute>())
{
contracts.Add(new ContractDescription(this, contractType, serviceContract));
}
}
Contracts = contracts;
}
}
// The middleware delegate to call after this one finishes processing
private readonly RequestDelegate _next;
private readonly Type _serviceType;
private readonly string _endpointPath;
private readonly MessageEncoder _messageEncoder;
public SOAPEndpointMiddleware(RequestDelegate next, Type serviceType, string path, MessageEncoder encoder)
{
_next = next;
_serviceType = serviceType;
_endpointPath = path;
_messageEncoder = encoder;
}
private readonly ServiceDescription _service;
public SOAPEndpointMiddleware(RequestDelegate next, Type serviceType, string path, MessageEncoder encoder)
{
_next = next;
_endpointPath = path;
_messageEncoder = encoder;
_service = new ServiceDescription(serviceType);
}
public static IApplicationBuilder UseSOAPEndpoint<T>(this IApplicationBuilder builder, string path, MessageEncoder encoder)
{
return builder.UseMiddleware<SOAPEndpointMiddleware>(typeof(T), path, encoder);
}
public async Task Invoke(HttpContext httpContext)
{
if (httpContext.Request.Path.Equals(_endpointPath, StringComparison.Ordinal))
{
// TODO : Reading message goes here
}
else
{
await _next(httpContext);
}
}
Message responseMessage;
// Read request message
var requestMessage = _messageEncoder.ReadMessage(httpContext.Request.Body, 0x10000, httpContext.Request.ContentType);
// TODO : Get requested action and invoke
var soapAction = httpContext.Request.Headers["SOAPAction"].ToString().Trim('\"');
if (!string.IsNullOrEmpty(soapAction))
{
requestMessage.Headers.Action = soapAction;
}
// TODO : Lookup operation and invoke
var operation = _service.Operations.Where(o => o.SoapAction.Equals(requestMessage.Headers.Action, StringComparison.Ordinal)).FirstOrDefault();
if (operation == null)
{
throw new InvalidOperationException($"No operation found for specified action: {requestMessage.Headers.Action}");
}
// TODO : Invoking the operation goes here
// Get service type
var serviceInstance = serviceProvider.GetService(_service.ServiceType);
// Get operation arguments from message
var arguments = GetRequestArguments(requestMessage, operation);
// Invoke Operation method
var responseObject = operation.DispatchMethod.Invoke(serviceInstance, arguments.ToArray());
// TODO : Encode responseObject into the response message
// Create response message
var resultName = operation.DispatchMethod.ReturnParameter.GetCustomAttribute<MessageParameterAttribute>()?.Name ?? operation.Name + "Result";
var bodyWriter = new ServiceBodyWriter(operation.Contract.Namespace, operation.Name + "Response", resultName, responseObject);
responseMessage = Message.CreateMessage(_messageEncoder.MessageVersion, operation.ReplyAction, bodyWriter);
httpContext.Response.ContentType = httpContext.Request.ContentType; // _messageEncoder.ContentType;
httpContext.Response.Headers["SOAPAction"] = responseMessage.Headers.Action;
_messageEncoder.WriteMessage(responseMessage, httpContext.Response.Body);
using System.ServiceModel;
namespace TestApp
{
public class CalculatorService : ICalculatorService
{
public double Add(double x, double y) => x + y;
public double Divide(double x, double y) => x / y;
public double Multiply(double x, double y) => x * y;
public double Subtract(double x, double y) => x - y;
}
[ServiceContract]
public interface ICalculatorService
{
[OperationContract] double Add(double x, double y);
[OperationContract] double Subtract(double x, double y);
[OperationContract] double Multiply(double x, double y);
[OperationContract] double Divide(double x, double y);
}
}
using System;
using System.ServiceModel;
using System.ServiceModel.Channels;
namespace TestClient
{
public class Program
{
public static void Main(string[] args)
{
if (args.Length < 1)
{
Console.WriteLine("Please provide the remote URL of the calculator service");
return;
}
// Create random inputs
Random numGen = new Random();
double x = numGen.NextDouble() * 20;
double y = numGen.NextDouble() * 20;
var serviceAddress = $"{args[0]}/CalculatorService.svc";
var client = new CalculatorServiceClient(new BasicHttpBinding(), new EndpointAddress(serviceAddress));
Console.WriteLine($"{x} + {y} == {client.Add(x, y)}");
Console.WriteLine($"{x} - {y} == {client.Subtract(x, y)}");
Console.WriteLine($"{x} * {y} == {client.Multiply(x, y)}");
Console.WriteLine($"{x} / {y} == {client.Divide(x, y)}");
}
}
class CalculatorServiceClient : ClientBase<ICalculatorService>
{
public CalculatorServiceClient(Binding binding, EndpointAddress remoteAddress) : base(binding, remoteAddress) { }
public double Add(double x, double y) => Channel.Add(x, y);
public double Subtract(double x, double y) => Channel.Subtract(x, y);
public double Multiply(double x, double y) => Channel.Multiply(x, y);
public double Divide(double x, double y) => Channel.Divide(x, y);
}
[ServiceContract]
public interface ICalculatorService
{
[OperationContract]
double Add(double x, double y);
[OperationContract]
double Subtract(double x, double y);
[OperationContract]
double Multiply(double x, double y);
[OperationContract]
double Divide(double x, double y);
}
}
public static IApplicationBuilder UseSOAPEndpoint<T>(this IApplicationBuilder builder, string path, Binding binding)
{
var encoder = binding.CreateBindingElements().Find<MessageEncodingBindingElement>()?.CreateMessageEncoderFactory().Encoder;
return builder.UseMiddleware<SOAPEndpointMiddleware>(typeof(T), path, encoder);
}
@ShaomingCode
Copy link

very good

@duszakpawel
Copy link

does it work with complex types?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment