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

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

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

添加完成后下面来新建一个中间件 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();
        }
    }

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

服务端

在服务端使用,这里你也可以新建一个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/

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

时间: 2024-08-04 14:48:08

ASP.NET Core中间件(Middleware)实现WCF SOAP服务端解析的相关文章

在ASP.NET Core使用Middleware模拟Custom Error Page功能

一.使用场景 在传统的ASP.NET MVC中,我们可以使用HandleErrorAttribute特性来具体指定如何处理Action抛出的异常.只要某个Action设置了HandleErrorAttribute特性,那么默认的,当这个Action抛出了异常时MVC将会显示Error视图,该视图位于~/Views/Shared目录下. 自定义错误页面的目的,就是为了能让程序在出现错误/异常的时候,能够有较好的显示体验.有时候在Error视图中也会发生错误,这时ASP.NET/MVC将会显示其默认

ASP.NETCore学习记录(二) —— ASP.NET Core 中间件

ASP.NET Core 中间件 目录: IApplicationBuilder 什么是中间件 ? 使用 IApplicationBuilder 创建中间件 Run.Map 与 Use 方法 实战中间件 参考原文 我们知道在 ASP.NET 中,有一个面向切面的请求管道,由22个主要的事件构成,能够让我们在往预定的执行顺序里面添加自己的处理逻辑.一般采取两种方式:一种是直接在 Global.asax 中对应的方法中直接添加代码.一种是是在 web.config 中通过注册 HttpModule

ASP.NET Core 中间件基本用法

ASP.NET Core 中间件 ASP.NET Core的处理流程是一个管道,而中间件是装配到管道中的用于处理请求和响应的组件.中间件按照装配的先后顺序执行,并决定是否进入下一个组件.中间件管道的处理流程如下图(图片来源于官网): 管道式的处理方式,更加方便我们对程序进行扩展. 使用中间件 ASP.NET Core中间件模型是我们能够快捷的开发自己的中间件,完成对应用的扩展,我们先从一个简单的例子了解一下中间件的开发. Run 首先,我们创建一个ASP.NET Core 应用,在Startup

12.ASP.NET Core 中间件组件

这篇文章中,我将带领大家一起详细学习:ASP.NET Core Middleware Components.这篇文章中,我将详细讨论下面几个问题: 什么是ASP.NET Core 中的中间件组件? ASP.NET Core应用程序中,在哪里来使用中间件组件? 怎样来配置ASP.NET Core 应用程序中的中间件组件? 使用中间件组件的例子有哪些? ASP.NET Core应用程序中,中间件组件执行的顺序是? 什么是ASP.NET Core中间件组件? ASP.NET Core中间件组件就是组装

Asp.net core 中间件简单应用

Asp.net core中间件 ,处理http请求和响应的中间组件,对比起asp.net ,asp.net core 管道机制,可以说是帅气十足,简单直接.下面是通过中间件对一个请求的url 指定路由 新建webapi 项目 Startup类中Configure方法中添加处理中间件代码如下 public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app

ASP.NET Core中Middleware的使用

ASP.NET 5中Middleware的基本用法 在ASP.NET 5里面引入了OWIN的概念,大致意思是将网站部署.服务器.中间组件以及应用分离开,这里提到的Middleware就是中间组件. 这里引用asp.net网站的介绍图 Middleware的作用有点类似于httpmodule,服务器接收到的请求都会传递到各个Middleware,多个Middleware形成一个处理管道. 由于不针对于特定的请求,所以Middleware的执行范围是在Application之外,这种模式很适合处理日

在ASP.NET Core Web API中为RESTful服务增加对HAL的支持

HAL(Hypertext Application Language,超文本应用语言)是一种RESTful API的数据格式风格,为RESTful API的设计提供了接口规范,同时也降低了客户端与服务端接口的耦合度.很多当今流行的RESTful API开发框架,包括Spring REST,也都默认支持HAL规范,当RESTful API被调用后,服务端就会返回ContentType为application/hal+json的JSON内容,例如: { "_links": { "

在 asp.net core 中使用类似 Application 的服务

在 asp.net core 中使用类似 Application 的服务 Intro 在 asp.net 中,我们可以借助 Application 来保存一些服务器端全局变量,比如说服务器端同时在线的人数计数,比如一些网站的配置信息. 在 ASP.NET 应用中,之前开发的活动室预约系统把网站的 keyword 以及 Title 等信息,在网站启动的时候会从数据库加载配置并保存到 Application 中,在需要的地方直接使用 Application 来获取,后台更新配置之后,更新 Appli

ASP.NET Core Web程序托管到Windows 服务

原文:ASP.NET Core Web程序托管到Windows 服务 前言 在 .NET Core 3.1和WorkerServices构建Windows服务 我们也看到了,如何将workerservices构建成服务,那么本篇文章我们再来看看如何将web应用程序托管到我们的服务中. 将WEB应用作为服务运行 我们需要将我们的WEB应用程序编译成exe文件,在ASP.NETCore中其实这是一个很简单的过程,我们只需要修改.csproj即可.正如下面代码片段 <Project Sdk="M