简体   繁体   English

如何以SoapMessage的形式发送数据并获得回复?

[英]How to send data as a SoapMessage and get a reply?

I have some data that needs to be send in SOAP format to a server. 我有一些数据需要以SOAP格式发送到服务器。 This server will immediately acknowledge that it received the messages. 该服务器将立即确认收到了这些消息。 After a few hours I get (possibly from another server) a SOAP message that contains information about the processed data. 几个小时后,我(可能来自另一台服务器)获得一条SOAP消息,其中包含有关已处理数据的信息。

I read Stackoverflow: How to send SOAP request and receive response . 我读了Stackoverflow:如何发送SOAP请求和接收响应 However, the answers are 8 years old. 但是,答案是8岁。 Although they may still work, It may be that there are newer techniques. 虽然它们仍然可以工作,但可能有更新的技术。

And indeed it seems: Microsoft has System.Web.Services.Protocols, with classes like SoapMessage , SoapClientMessage, SoapServerMessage, etc. 确实看起来:Microsoft有System.Web.Services.Protocols,包括SoapMessage ,SoapClientMessage,SoapServerMessage等类。

Looking at the classes I find a lot of SOAP like classes (headers, extensions, client messages, server messages... Normally the provided examples give me an indication to how these classes work together and how to use them. In the MSDN documents I can only find examples of how to process already existing SOAP messages. 看一下这些类,我发现很多SOAP类(头文件,扩展名,客户端消息,服务器消息......通常,提供的示例给出了这些类如何一起工作以及如何使用它们的指示。在MSDN文档中我只能找到如何处理现有SOAP消息的示例。

Given some data that needs to be sent, how can I wrap this data somehow in one of these SOAP classes and send this message? 给定一些需要发送的数据,如何将这些数据以某种方式包装在其中一个SOAP类中并发送此消息?

Are these classes meant for this purpose? 这些课程是否用于此目的? Or should I stick to the 2011 method where you'd create a SOAP Web request by formatting the XML data in soap format yourself, as the above mentioned Stackoverflow question suggests? 或者我应该坚持2011年的方法,你可以通过自己格式化SOAP格式的XML数据来创建SOAP Web请求,正如上面提到的Stackoverflow问题所示?

I'm awfully sorry, normally I would write things I have tried. 我非常抱歉,通常我会写我尝试过的东西。 Alas I don't see the relation between the provided SoapMessage classes . 唉,我没有看到提供的SoapMessage类之间的关系。 I haven't got a clue how to use them. 我还没有弄清楚如何使用它们。

Addition after comments 评论后补充

I'm using windows server / visual studio (newest versions) / .NET (newest versions) / C# (newest versions). 我正在使用Windows服务器/ visual studio(最新版本)/ .NET(最新版本)/ C#(最新版本)。

The communication with the server is mutual authenticated. 与服务器的通信是相互认证的。 The certificate that I need to use to communicate with the server, is in PEM (CER / CRT) format. 我需要用来与服务器通信的证书是PEM(CER / CRT)格式。 The privated key is RSA. 私有密钥是RSA。 This certificate is issued by a proper CA, the server will also use certificates used by a proper CA. 此证书由适当的CA颁发,服务器也将使用由适当的CA使用的证书。 So I don't need to create a new certificate (in fact, it won't be accepted). 所以我不需要创建新证书(事实上,它不会被接受)。 If needed, I'm willing to convert the certificates using programs like OpenSsl and the like. 如果需要,我愿意使用OpenSsl等程序转换证书。

I've tried to use Apache TomCat to communicate, but I have the feeling that that's way too much for the task of sending one SOAP message per day and waiting for one answer per day. 我曾尝试使用Apache TomCat进行通信,但我觉得这对于每天发送一条SOAP消息并等待每天一个答案的任务来说太过分了。

Maybe because java is a complete new technique for me, it was difficult for me to see the contents of the received messages. 也许因为java对我来说是一种全新的技术,我很难看到收到的消息的内容。 So back to C# and .NET. 所以回到C#和.NET。

I was planning to create a DLL, to be used by a console app. 我打算创建一个DLL,供控制台应用程序使用。 The function would have some data in a stream as input. 该函数将流中的一些数据作为输入。 It would create the soap message, send it, wait for reply that the message was received correctly, and wait (possible several hours) for a new Soap message containing the results of the processed data. 它将创建soap消息,发送它,等待消息被正确接收的回复,并等待(可能几个小时)一个包含已处理数据结果的新Soap消息。 To make proper reporting, and cancellation possible, I guess it is best to do this using async-await 为了进行正确的报告和取消,我想最好使用async-await来做到这一点

If sending the order and waiting for the result can't be done in one application, I'm willing to create a windows service that that listens to the input, but I prefer to keep it simple. 如果发送订单并等待结果无法在一个应用程序中完成,我愿意创建一个监听输入的Windows服务,但我更喜欢保持简单。

The (virtual) computer will only be used for this task, so no one else will need to listen to port 443. There will be one order message send per day, and one result message per day. (虚拟)计算机将仅用于此任务,因此没有其他人需要监听端口443.每天将发送一个订单消息,每天发送一条结果消息。

Personally, I use ServiceStack to create both client and server 就个人而言,我使用ServiceStack来创建客户端和服务器

https://docs.servicestack.net/soap-support https://docs.servicestack.net/soap-support

Or SoapHttpClient nuget 或SoapHttpClient nuget

https://github.com/pmorelli92/SoapHttpClient https://github.com/pmorelli92/SoapHttpClient

Or my example from way back when 或者我的例子从回来的时候

Is it possible that I can convert simple string to SOAP Message and send it? 是否有可能将简单字符串转换为SOAP消息并发送它?

The answer depends on what framework or libraries do you plan to use? 答案取决于您计划使用的框架或库?

Here is sample C# Console client and server code (they are in the same sample but this is only for demo purpose, of course) that uses HTTPS. 以下是使用HTTPS的示例C#控制台客户端和服务器代码(它们在同一示例中,但这仅用于演示目的)。

For the client side, we reuse the SoapHttpClientProtocol class, but for the server side, unfortunately, we cannot reuse anything because classes are completely tied to ASP.NET's (IIS) HttpContext class 对于客户端,我们重用了SoapHttpClientProtocol类,但是对于服务器端,遗憾的是,我们不能重用任何东西,因为类完全依赖于ASP.NET(IIS)的HttpContext

For the server side, we use HttpListener , so, depending on your configuration, the server side will probably require admin rights to be able to call HttpListener 's Prefixes.Add(url) . 对于服务器端,我们使用HttpListener ,因此,根据您的配置,服务器端可能需要管理员权限才能调用HttpListenerPrefixes.Add(url)

The code doesn't uses client certificate, but you can add this where I placed // TODO comments 代码不使用客户端证书,但您可以在我放置// TODO注释的位置添加它

The code assumes there is a certificate associated with the url and port used. 该代码假定存在与所使用的URL和端口相关联的证书。 If there's not (use netsh http show sslcert to dump all associated certs), you can use the procedure described here to add one: https://stackoverflow.com/a/11457719/403671 如果没有(使用netsh http show sslcert转储所有关联的证书),您可以使用此处描述的过程添加一个: httpsnetsh http show sslcert

using System;
using System.IO;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using System.Web.Services;
using System.Web.Services.Protocols;
using System.Xml;

namespace SoapTests
{
    class Program
    {
        static void Main(string[] args)
        {
            // code presumes there is an sslcert associated with the url/port below
            var url = "https://127.0.0.1:443/";
            using (var server = new MyServer(url, MyClient.NamespaceUri))
            {
                server.Start(); // requests will occur on other threads
                using (var client = new MyClient())
                {
                    client.Url = url;
                    Console.WriteLine(client.SendTextAsync("hello world").Result);
                }
            }
        }
    }

    [WebServiceBinding(Namespace = NamespaceUri)]
    public class MyClient : SoapHttpClientProtocol
    {
        public const string NamespaceUri = "http://myclient.org/";

        public async Task<string> SendTextAsync(string text)
        {
            // TODO: add client certificates using this.ClientCertificates property
            var result = await InvokeAsync(nameof(SendText), new object[] { text }).ConfigureAwait(false);
            return result?[0]?.ToString();
        }

        // using this method is not recommended, as async is preferred
        // but we need it with this attribute to make underlying implementation happy
        [SoapDocumentMethod]
        public string SendText(string text) => SendTextAsync(text).Result;

        // this is the new Task-based async model (TAP) wrapping the old Async programming model (APM)
        public Task<object[]> InvokeAsync(string methodName, object[] input, object state = null)
        {
            if (methodName == null)
                throw new ArgumentNullException(nameof(methodName));

            return Task<object[]>.Factory.FromAsync(
                beginMethod: (i, c, o) => BeginInvoke(methodName, i, c, o),
                endMethod: EndInvoke,
                arg1: input,
                state: state);
        }
    }

    // server implementation
    public class MyServer : TinySoapServer
    {
        public MyServer(string url, string namespaceUri)
            : base(url)
        {
            if (namespaceUri == null)
                throw new ArgumentNullException(nameof(namespaceUri));

            NamespaceUri = namespaceUri;
        }

        // must be same as client namespace in attribute
        public override string NamespaceUri { get; }

        protected override bool HandleSoapMethod(XmlDocument outputDocument, XmlElement requestMethodElement, XmlElement responseMethodElement)
        {
            switch (requestMethodElement.LocalName)
            {
                case "SendText":
                    // get the input
                    var text = requestMethodElement["text", NamespaceUri]?.InnerText;
                    text += " from server";

                    AddSoapResult(outputDocument, requestMethodElement, responseMethodElement, text);
                    return true;
            }
            return false;
        }
    }

    // simple generic SOAP server
    public abstract class TinySoapServer : IDisposable
    {
        private readonly HttpListener _listener;

        protected TinySoapServer(string url)
        {
            if (url == null)
                throw new ArgumentNullException(nameof(url));

            _listener = new HttpListener();
            _listener.Prefixes.Add(url); // this requires some rights if not used on localhost
        }

        public abstract string NamespaceUri { get; }
        protected abstract bool HandleSoapMethod(XmlDocument outputDocument, XmlElement requestMethodElement, XmlElement responseMethodElement);

        public async void Start()
        {
            _listener.Start();
            do
            {
                var ctx = await _listener.GetContextAsync().ConfigureAwait(false);
                ProcessRequest(ctx);
            }
            while (true);
        }

        protected virtual void ProcessRequest(HttpListenerContext context)
        {
            if (context == null)
                throw new ArgumentNullException(nameof(context));

            // TODO: add a call to context.Request.GetClientCertificate() to validate client cert
            using (var stream = context.Response.OutputStream)
            {
                ProcessSoapRequest(context, stream);
            }
        }

        protected virtual void AddSoapResult(XmlDocument outputDocument, XmlElement requestMethodElement, XmlElement responseMethodElement, string innerText)
        {
            if (outputDocument == null)
                throw new ArgumentNullException(nameof(outputDocument));

            if (requestMethodElement == null)
                throw new ArgumentNullException(nameof(requestMethodElement));

            if (responseMethodElement == null)
                throw new ArgumentNullException(nameof(responseMethodElement));

            var result = outputDocument.CreateElement(requestMethodElement.LocalName + "Result", NamespaceUri);
            responseMethodElement.AppendChild(result);
            result.InnerText = innerText ?? string.Empty;
        }

        protected virtual void ProcessSoapRequest(HttpListenerContext context, Stream outputStream)
        {
            // parse input
            var input = new XmlDocument();
            input.Load(context.Request.InputStream);

            var ns = new XmlNamespaceManager(new NameTable());
            const string soapNsUri = "http://schemas.xmlsoap.org/soap/envelope/";
            ns.AddNamespace("soap", soapNsUri);
            ns.AddNamespace("x", NamespaceUri);

            // prepare output
            var output = new XmlDocument();
            output.LoadXml("<Envelope xmlns='" + soapNsUri + "'><Body/></Envelope>");
            var body = output.SelectSingleNode("//soap:Body", ns);

            // get the method name, select the first node in our custom namespace
            bool handled = false;
            if (input.SelectSingleNode("//x:*", ns) is XmlElement requestElement)
            {
                var responseElement = output.CreateElement(requestElement.LocalName + "Response", NamespaceUri);
                body.AppendChild(responseElement);

                if (HandleSoapMethod(output, requestElement, responseElement))
                {
                    context.Response.ContentType = "application/soap+xml; charset=utf-8";
                    context.Response.StatusCode = (int)HttpStatusCode.OK;
                    var writer = new XmlTextWriter(outputStream, Encoding.UTF8);
                    output.WriteTo(writer);
                    writer.Flush();
                    handled = true;
                }
            }

            if (!handled)
            {
                context.Response.StatusCode = (int)HttpStatusCode.BadRequest;
            }
        }

        public void Stop() => _listener.Stop();
        public virtual void Dispose() => _listener.Close();
    }
}

The simplest modern answer is to declare a simple class that defines the structure of your message and then serialize it using HttpClient to send it. 最简单的现代答案是声明一个定义消息结构的简单类,然后使用HttpClient将其序列化以发送它。

However, SOAP is a standard built for description based messaging so the still relevant recommendation is to generate your client code from the wsdl description using a "service reference" then use the generated client object. 但是,SOAP是为基于描述的消息传递而构建的标准,因此仍然相关的建议是使用“服务引用”从wsdl描述生成客户端代码,然后使用生成的客户端对象。

I would however recommend, like others have pointed out that you try to move to REST services instead (assuming this is possible). 但是我建议,像其他人一样指出你试图转而使用REST服务(假设这是可能的)。 The code is less complex, the system is far simpler to use and it's a global standard. 代码不那么复杂,系统使用起来更简单,而且它是一个全球标准。

Here is a comparison and example of both ... 以下是两者的比较和示例......

https://smartbear.com/blog/test-and-monitor/understanding-soap-and-rest-basics/ https://smartbear.com/blog/test-and-monitor/understanding-soap-and-rest-basics/

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM