Как отправить данные в виде SoapMessage и получить ответ?

У меня есть данные, которые нужно отправить на сервер в формате SOAP. Этот сервер немедленно подтвердит получение сообщений. Через несколько часов я получаю (возможно, с другого сервера) SOAP-сообщение, содержащее информацию об обработанных данных.

Я прочитал Stackoverflow: как отправить запрос SOAP и получить ответ. Однако ответы восьмилетней давности. Хотя они все еще могут работать, возможно, есть более новые методы.

И действительно кажется: у Microsoft есть System.Web.Services.Protocols с такими классами, как МылоСообщение, SoapClientMessage, SoapServerMessage и т. д.

Глядя на классы, я нахожу множество 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?

Эти классы предназначены для этой цели? Или мне следует придерживаться метода 2011 года, когда вы создаете веб-запрос SOAP, самостоятельно форматируя данные XML в формате мыла, как предлагает вышеупомянутый вопрос Stackoverflow?

Мне очень жаль, обычно я бы написал то, что пробовал. Увы, я не вижу связи между предоставленные классы SoapMessage. Я понятия не имею, как их использовать.

Дополнение после комментариев

Я использую Windows Server/Visual Studio (новейшие версии)/.NET (новейшие версии)/С# (новейшие версии).

Связь с сервером является взаимной аутентификацией. Сертификат, который мне нужно использовать для связи с сервером, имеет формат PEM (CER/CRT). Приватный ключ RSA. Этот сертификат выдается соответствующим ЦС, сервер также будет использовать сертификаты, используемые соответствующим ЦС. Так что мне не нужно создавать новый сертификат (на самом деле он не будет принят). При необходимости я готов конвертировать сертификаты с помощью таких программ, как OpenSsl и им подобных.

Я пытался использовать Apache TomCat для связи, но мне кажется, что это слишком много для задачи отправки одного сообщения SOAP в день и ожидания одного ответа в день.

Может из-за того, что java для меня совершенно новая техника, мне было сложно увидеть содержимое полученных сообщений. Итак, вернемся к C# и .NET.

Я планировал создать DLL для использования консольным приложением. Функция будет иметь некоторые данные в потоке в качестве входных данных. Он создаст мыльное сообщение, отправит его, дождется ответа о том, что сообщение получено правильно, и подождет (возможно, несколько часов) нового мыльного сообщения, содержащего результаты обработанных данных. Чтобы сделать надлежащую отчетность и отмену возможной, я думаю, лучше всего сделать это с помощью async-await

Если отправка заказа и ожидание результата не могут быть выполнены в одном приложении, я готов создать службу Windows, которая прослушивает ввод, но я предпочитаю, чтобы это было просто.

(Виртуальный) компьютер будет использоваться только для этой задачи, поэтому никому больше не нужно будет прослушивать порт 443. Будет отправлено одно сообщение о заказе в день и одно сообщение о результате в день.

Просто ошибка предостережения, по моему опыту, использование 8-летней технологии (WCF, SOAP и т. д.) не очень хорошо сочетается с новыми версиями IIS (или Kestral, если вы используете .Net Core). Если бы вы могли использовать REST, это было бы в 10 раз проще и перспективнее. Возможно, спросите у стороннего поставщика их дорожную карту/планы.

Jeremy Thompson 16.04.2019 04:10

Пара вопросов 1. Что происходит между моментом, когда вы делаете веб-звонок, и моментом, когда вы получаете ответ? 2. Что делается с данными, которые вы отправляете в веб-запросе?

smehaffie 16.04.2019 05:58

Вы пишете клиент для существующего сервера или и клиент, и сервер?

Daniel W. 17.04.2019 11:41

Я отправляю приказ что-то сделать на существующий сервер. Сразу же получаю ответ, что мой заказ принят. Так что в этой части я клиент. Через несколько часов какой-то Клиент пришлет мне сообщение с результатами моего Заказа. В этой части я буду сервером. Мой заказ содержит достаточно информации, чтобы клиент знал, куда отправлять результаты заказа и как идентифицировать этот заказ.

Harald Coppoolse 17.04.2019 12:24

Джереми: об изменении интерфейса: это правительство использует этот процесс для общения со многими компаниями. О запросе на изменение протокола не может быть и речи.

Harald Coppoolse 17.04.2019 12:26

Почему вы не позволяете VS создавать все методы для адреса wsdl? Это действительно легко

Amirhossein Mehrvarzi 17.04.2019 21:11
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
6
6
1 257
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Лично я использую ServiceStack для создания как клиента, так и сервера

https://docs.servicestack.net/soap-поддержка

Или нужет SoapHttpClient

https://github.com/pmorelli92/SoapHttpClient

Или мой пример из далекого прошлого, когда

Возможно ли, что я могу преобразовать простую строку в сообщение SOAP и отправить его?

Ответ зависит от того, какой фреймворк или библиотеки вы планируете использовать?

Я дал больше информации в своем вопросе. Надеюсь, это ответит на ваши вопросы.

Harald Coppoolse 15.04.2019 09:48

Самый простой правильный способ сделать это — использовать Reactive eXtensions и создать наблюдаемое для обработки запроса, его сбоя, политики повторных попыток, планирования и тестов. Создайте сервис и спрячьте за ним вызовы мыльного клиента. В реальной жизни вы можете использовать задания Quartz, Polly или сотню других мелочей, но на самом деле они вам не нужны. Сертификат должен быть установлен на отладочной машине, а также на развернутой виртуальной машине. Просто помните, что «беспорядочный успех лучше идеального», начните с чего-то, заставьте его работать и улучшайте его.

Margus 15.04.2019 11:25
Ответ принят как подходящий

Вот пример кода клиента и сервера консоли C# (они находятся в одном образце, но это, конечно, только для демонстрационных целей), который использует HTTPS.

Для клиентской стороны мы повторно используем класс SoapHttpClientProtocol, но для серверной стороны, к сожалению, мы не можем ничего повторно использовать, потому что классы полностью привязаны к классу HttpContext ASP.NET (IIS)

На стороне сервера мы используем HttpListener, поэтому, в зависимости от вашей конфигурации, стороне сервера, вероятно, потребуются права администратора, чтобы иметь возможность вызывать HttpListener's Prefixes.Add(url).

Код не использует клиентский сертификат, но вы можете добавить его туда, где я разместил комментарии // TODO

Код предполагает наличие сертификата, связанного с используемым URL-адресом и портом. Если его нет (используйте netsh http show sslcert для сброса всех связанных сертификатов), вы можете использовать описанную здесь процедуру, чтобы добавить один: https://stackoverflow.com/a/11457719/403671

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

Это было именно то, что я использовал. Причина, по которой это не сработало, заключалась в netsh: во всех других ответах на stackoverflow, которые я читал о HttpListener и безопасности, вы были первым, кто упомянул об этом. Я использовал правильную команду Netsh, и это сработало! Ты гений!

Harald Coppoolse 19.04.2019 16:55

Самый простой современный ответ — объявить простой класс, определяющий структуру вашего сообщения, а затем сериализовать его с помощью HttpClient для отправки.

Тем не менее, SOAP является стандартом, созданным для обмена сообщениями на основе описаний, поэтому по-прежнему актуальной рекомендацией является создание кода клиента из описания wsdl с использованием «ссылки на службу», а затем использование сгенерированного объекта клиента.

Однако я бы порекомендовал, как и другие, вместо этого попытаться перейти на службы REST (при условии, что это возможно). Код менее сложен, система намного проще в использовании, и это глобальный стандарт.

Вот сравнение и пример обоих...

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

Другие вопросы по теме