ASP.NET Core中間件(Middleware)實現WCF SOAP服務端解析

来源:http://www.cnblogs.com/linezero/archive/2016/09/22/aspnetcoresoap.html
-Advertisement-
Play Games

ASP.NET Core中間件(Middleware)進階學習實現SOAP 解析。 本篇將介紹實現ASP.NET Core SOAP服務端解析,而不是ASP.NET Core整個WCF host。 因為WCF中不僅僅只是有SOAP, 它還包含很多如消息安全性,生成WSDL,雙工通道,非HTTP傳輸等 ...


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/

 

如果你覺得本文對你有幫助,請點擊“推薦”,謝謝。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 思路: 1、查看有無安裝過mysql 或 2、查看有無安裝包 3、安裝mysql服務端 4、啟動&&停止 a、設置資料庫預設字元 在mysql配置文件/etc/my.cnf中加入default-character-set=utf8 b、設置開機自啟動 c、啟動mysql 5、登錄 a、創建root管 ...
  • 今天,開始記錄我的前端技術生涯! 今天,開始記錄我的博客園生涯! 今天,技術!產品!技術+產品=能力!!! 開啟新的生活!!! ...
  • 創建基於對話框的Win32應用程式(二) —— Button的應用、新建子窗體 可以發現上一節創建的窗體中,點擊OK和Cancel兩個按鈕是沒有任何反應的。現在我們來為他們添加退出對話框的功能。 6、首先定義命令消息響應函數(Dlg_OnCommand)如下: 其中IDOK和IDCANCEL分別是按 ...
  • 一.創建一個空項目 請查看 新建 .NET Core 項目 -- Hello World! 一節,新建一個項目: 二.添加引用並修改配置為 Web API (.NET Core 已將 MVC/Web API 底層代碼及表層編程介面合二為一) 修改 .vscode\launch.json 文件 代碼如 ...
  • 下麵介紹各種List的sort的用法與比較 首先,我們建一個People的實體,有name、age、sex的屬性,我們要排序的欄位是年齡age 新建一個實體類 新建list的數據 1. 第1種排序方法,使用 IComparer 可以看到第一種方法比價麻煩,要新建一個新的類來做 2. 第2種排序方法, ...
  • 一、併發的基本含義 在操作系統中,併發是指一個時間段中有幾個程式都處於已啟動運行到運行完畢之間,且這幾個程式都是在同一個處理機上運行,但任一個時刻點上只有一個程式在處理機上運行。 在關係資料庫中,允許多個用戶同時訪問和更改共用數據的進程。SQL Server 使用鎖定以允許多個用戶同時訪問和更改共用 ...
  • Web 應用程式使用的 Cookie 個人認為這裡設置的cookie與訪問cookie的安全性關聯大一點,配置節如下 httpOnlyCookies:預設是false,作用是是否禁用瀏覽器腳本訪問cookie。在Form認證時會頒發一個認證票寫在cookie,最開始我以為這裡設置了則可以訪問,結果並 ...
  • 背水一戰 Windows 10 之 控制項(彈出類): FlyoutBase, Flyout, MenuFlyout ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...