• ASP.NET Core中间件(Middleware)实现WCF SOAP服务端解析


    ASP.NET Core中间件(Middleware)进阶学习实现SOAP 解析。

    本篇将介绍实现ASP.NET Core SOAP服务端解析,而不是ASP.NET Core整个WCF host。

    因为WCF中不仅仅只是有SOAP, 它还包含很多如消息安全性,生成WSDL,双工信道,非HTTP传输等。

    ASP.NET Core 官方推荐大家使用RESTful Web API的解决方案提供网络服务。

    SOAP 即 Simple Object AccessProtocol 也就是简单对象访问协议。

    SOAP 呢,其指导理念是“唯一一个没有发明任何新技术的技术”,

    是一种用于访问 Web 服务的协议。

    因为 SOAP 基于XML 和 HTTP ,其通过XML 来实现消息描述,然后再通过 HTTP 实现消息传输。

    SOAP 是用于在应用程序之间进行通信的一种通信协议。

    因为是基于 XML 和HTTP 的,所以其独立于语言,独立于平台,并且因为 XML 的扩展性很好,所以基于 XML 的 SOAP 自然扩展性也不差。

    通过 SOAP 可以非常方便的解决互联网中消息互联互通的需求,其和其他的 Web 服务协议构建起 SOA 应用的技术基础。

    下面来正式开始 ASP.NET Core 实现SOAP 服务端解析。

    新建项目

    首先新建一个ASP.NET Core Web Application -》 SOAPService 然后再模板里选择 Web API。

    然后我们再添加一个Class Library -》 CustomMiddleware

    实现

    下面我们来实现功能,首先在类库项目中添加以下引用

    Install-Package Microsoft.AspNetCore.Http.Abstractions
    
    Install-Package System.ServiceModel.Primitives
    
    Install-Package System.Reflection.TypeExtensions
    
    Install-Package System.ComponentModel

    首先新建一个 ServiceDescription、ContractDescription和OperationDescription 类,这里需要注意的是ServiceDescription,ContractDescription和OperationDescription这里使用的是不能使用 System.ServiceModel.Description命名空间中的类型。它们是示例中简单的新类型。

    ServiceDescription.cs

        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;
            }
        }
    View Code

    ContractDescription.cs

        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;
            }
        }
    View Code

    OperationDescription.cs

        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;
            }
        }
    View Code

    添加完成后下面来新建一个中间件 SOAPMiddleware ,对于新建中间件可以参考我之前的文章:http://www.cnblogs.com/linezero/p/5529767.html

    SOAPMiddleware.cs 代码如下:

        public class SOAPMiddleware
        {
            private readonly RequestDelegate _next;
            private readonly Type _serviceType;
            private readonly string _endpointPath;
            private readonly MessageEncoder _messageEncoder;
            private readonly ServiceDescription _service;
            private IServiceProvider serviceProvider;
    
            public SOAPMiddleware(RequestDelegate next, Type serviceType, string path, MessageEncoder encoder,IServiceProvider _serviceProvider)
            {
                _next = next;
                _serviceType = serviceType;
                _endpointPath = path;
                _messageEncoder = encoder;
                _service = new ServiceDescription(serviceType);
                serviceProvider = _serviceProvider;
            }
    
            public async Task Invoke(HttpContext httpContext)
            {
                if (httpContext.Request.Path.Equals(_endpointPath, StringComparison.Ordinal))
                {
                    Message responseMessage;
    
                    //读取Request请求信息
                    var requestMessage = _messageEncoder.ReadMessage(httpContext.Request.Body, 0x10000, httpContext.Request.ContentType);
                    var soapAction = httpContext.Request.Headers["SOAPAction"].ToString().Trim('"');
                    if (!string.IsNullOrEmpty(soapAction))
                    {
                        requestMessage.Headers.Action = soapAction;
                    }
                    //获取操作
                    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}");
                    }
                    //获取注入的服务
                    var serviceInstance = serviceProvider.GetService(_service.ServiceType);
    
                    //获取操作的参数信息
                    var arguments = GetRequestArguments(requestMessage, operation);
    
                    // 执行操作方法
                    var responseObject = operation.DispatchMethod.Invoke(serviceInstance, arguments.ToArray());
    
                    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;
                    httpContext.Response.Headers["SOAPAction"] = responseMessage.Headers.Action;
    
                    _messageEncoder.WriteMessage(responseMessage, httpContext.Response.Body);
                }
                else
                {
                    await _next(httpContext);
                }
            }
    
            private object[] GetRequestArguments(Message requestMessage, OperationDescription operation)
            {
                var parameters = operation.DispatchMethod.GetParameters();
                var arguments = new List<object>();
    
                // 反序列化请求包和对象
                using (var xmlReader = requestMessage.GetReaderAtBodyContents())
                {
                    // 查找的操作数据的元素
                    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 static class SOAPMiddlewareExtensions
        {
            public static IApplicationBuilder UseSOAPMiddleware<T>(this IApplicationBuilder builder, string path, MessageEncoder encoder)
            {
                return builder.UseMiddleware<SOAPMiddleware>(typeof(T), path, encoder);
            }
            public static IApplicationBuilder UseSOAPMiddleware<T>(this IApplicationBuilder builder, string path, Binding binding)
            {
                var encoder = binding.CreateBindingElements().Find<MessageEncodingBindingElement>()?.CreateMessageEncoderFactory().Encoder;
                return builder.UseMiddleware<SOAPMiddleware>(typeof(T), path, encoder);
            }
        }

    这里对于输出的消息做了一个封装,以输出具有正确的元素名称的消息的主体。

    添加一个 ServiceBodyWriter 类。

        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();
            }
        }
    View Code

    这里对于中间件整个就完成了。

    服务端

    在服务端使用,这里你也可以新建一个Web 项目。

    因为刚刚我们已经新建好了一个Web API项目,我们就直接拿来使用。

    首先添加 CustomMiddleware 引用

    在 SOAPService 中添加一个 CalculatorService 类

        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;
            public string Get(string str) => $"{str} Hello World!";
        }
    
        [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);
            [OperationContract]
            string Get(string str);
        }

    这里我为了方便将接口契约也放在CalculatorService 中,你也可以新建一个接口。

    然后在 Startup.cs  的 ConfigureServices 中注入 CalculatorService

            public void ConfigureServices(IServiceCollection services)
            {
                // Add framework services.
                services.AddMvc();
                services.AddScoped<CalculatorService>();
            }

    在Configure 方法中加入中间件

            public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
            {
                loggerFactory.AddConsole(Configuration.GetSection("Logging"));
                loggerFactory.AddDebug();
                //加入一个/CalculatorService.svc 地址,绑定Http
                app.UseSOAPMiddleware<CalculatorService>("/CalculatorService.svc", new BasicHttpBinding());
    
                app.UseMvc();
            }

    这样就完成了服务端编写。

    客户端

    新建一个 Console Application -》SOAPClient

    添加如下引用:

    Install-Package System.ServiceModel.Primitives
    
    Install-Package System.Private.ServiceModel
    
    Install-Package System.ServiceModel.Http

    Program代码如下:

        public class Program
        {
            public static void Main(string[] args)
            {
                Random numGen = new Random();
                double x = numGen.NextDouble() * 20;
                double y = numGen.NextDouble() * 20;
    
                var serviceAddress = "http://localhost:5000/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)}");
                client.Get("Client");
            }
        }
        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);
    
            public void Get(string str)
            {
                Console.WriteLine(Channel.Get(str));
            }
        }
    
        [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);
            [OperationContract]
            string Get(string str);
        }

    编写好以后,分别对应到目录使用dotnet run执行程序。

    成功建立了连接,也有返回。也就实现SOAP 的解析。

    示例代码GitHub:https://github.com/linezero/Blog/tree/master/SOAPService

    参考文档:https://blogs.msdn.microsoft.com/dotnet/2016/09/19/custom-asp-net-core-middleware-example/

    如果你觉得本文对你有帮助,请点击“推荐”,谢谢。

  • 相关阅读:
    Java 端口转发
    Tomcat笔记
    RocketMQ开启ACL后客户端连接报Algorithm HmacSHA1 not available的解决方式
    RSA签名与验签
    小米9升级MIUI11
    【转】linux awk命令详解
    进程和线程
    Jenkins笔记
    【转】Jenkins启动、停止脚本
    UiAutomator源码分析
  • 原文地址:https://www.cnblogs.com/linezero/p/aspnetcoresoap.html
Copyright © 2020-2023  润新知