Як отримати запит XML SOAP запиту веб-служби WCF?


79

Я телефоную до цієї веб-служби в коді, і я хотів би побачити XML, але я не можу знайти властивість, яке його розкриває.


Щоб ми могли допомогти вам у вирішенні вашої проблеми, вам доведеться опублікувати код.
jon3laze

Можливий дублікат stackoverflow.com/questions/1217374/…
Кімберлі

Відповіді:


143

Я думаю, ви мали на увазі, що хочете бачити XML у клієнта, а не відстежувати його на сервері. У такому випадку ваша відповідь на запитання, на яке я посилався вище, а також на тему Як перевірити або змінити повідомлення на клієнті . Але оскільки у версії цієї статті .NET 4 відсутній C #, а в прикладі .NET 3.5 є певна плутанина (якщо не помилка), тут вона розширена для вашої мети.

Ви можете перехопити повідомлення, перш ніж воно згасне, за допомогою IClientMessageInspector :

using System.ServiceModel.Dispatcher;
public class MyMessageInspector : IClientMessageInspector
{ }

Методи в цьому інтерфейсі BeforeSendRequestі AfterReceiveReplyнадають вам доступ до запиту та відповіді. Щоб використовувати інспектор, вам потрібно додати його до IEndpointBehavior :

using System.ServiceModel.Description;
public class InspectorBehavior : IEndpointBehavior
{
    public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
    {
        clientRuntime.MessageInspectors.Add(new MyMessageInspector());
    }
}

Ви можете залишити інші методи цього інтерфейсу як порожні реалізації, якщо ви також не хочете використовувати їх функціонал. Прочитайте інструкції, щоб дізнатися більше.

Після створення екземпляра клієнта додайте поведінку до кінцевої точки. Використання імен за замовчуванням із зразка проекту WCF:

ServiceReference1.Service1Client client = new ServiceReference1.Service1Client();
client.Endpoint.Behaviors.Add(new InspectorBehavior());
client.GetData(123);

Встановити точку зупинки в MyMessageInspector.BeforeSendRequest(); request.ToString()перевантажено, щоб показати XML.

Якщо ви взагалі збираєтеся маніпулювати повідомленнями, вам доведеться попрацювати над копією повідомлення. Детальніше див. У розділі Використання класу повідомлень .

Завдяки відповіді Зака Бонема на ще одне запитання за пошук цих посилань.


Я дав і вам, і відповіді Зака ​​Бонхема голос проти! Дякуємо за просте рішення. Я підключив його, щоб написати файл на основі конфігурації
Rhyous

відкритий об'єкт BeforeSendRequest (запит на повідомлення повідомлення, канал IClientChannel) {if (ConfigurationManager.AppSettings ["SaveWCFRequestXmlFiles"]! = "true") return null; var file = Path.Combine (ConfigurationManager.AppSettings ["XmlFilePath"], "Request-" + DateTime.Now.ToString ("yyyyMMdd-Hmmssfff") + ".xml"); FileWriter.Write (request.ToString (), файл); повернути null; }
Rhyous

1
Це працює, але він робить не те, що я (і OP, я припускаю) хочу, тому що це не надає вам доступу до необробленого вхідного тіла, яке надійшло на рівень HTTP. XML перевірено ще до того, як ви його отримаєте. Коли веб-служба (як це іноді трапляється) відповідає на сторінку з помилкою HTML, мені потрібно отримати HTML цієї сторінки. І те, що я отримую, і абсолютно не потрібно, це частина XML, яка залишається поза ("... Потік ..." у Message.ToString, а також неповний XML із ReadOuterXml і ReadInnerXml із XmlReader, який ви можете отримати для тіла )
Luc VdV

Як і коментар @ LucVdV, це не дає вам фактичного запиту. Існують невеликі відмінності між тим, що це виробляє, і тим, що, як каже Fiddler, насправді перейшло межу. Досить суттєві відмінності, що копіювання результату в SoapUI або Postman видає дивні помилки під час надсилання запиту.
asontu

30

Варіант 1

Використовуйте трасування / реєстрацію повідомлень .

Подивіться тут і тут .


Варіант 2

Ви завжди можете використовувати Fiddler для перегляду запитів HTTP та відповіді.


Варіант 3

Використовуйте трасування System.Net .


1
Будьте обережні з Fiddler, коли з якихось причин використовуєте конфігурації безпеки на параноїчному рівні (наприклад, взаємні сертифікати через SSL ), наявність проксі-сервера викликає цей виняток: System.ServiceModel.Security.SecurityNegotiationException
user1778770

1
Не знаю, чому варіант 1 для мене не спрацював. Спробував кілька різних параметрів конфігурації для реєстрації повідомлень, і тіло не відображається у SvcTraceViewer.
Шив,

Варіант 1 у мене спрацював. Я зміг побачити ціле повідомлення SOAP, як вихідне, так і вхідне. Мені довелося встановити прапор рівня обслуговування, щоб побачити, що я хочу. logMessagesAtServiceLevel = "true"
Амонн

@vbguyny повний зразок за допомогою трасування Net?
PreguntonCojoneroCabrón

7
OperationContext.Current.RequestContext.RequestMessage 

цей контекст є доступним на стороні сервера під час обробки запиту. Це не працює для односторонніх операцій


2
Тільки на стороні сервера , а не на стороні клієнта
PreguntonCojoneroCabrón

7

Просто ми можемо простежити повідомлення запиту як.

OperationContext context = OperationContext.Current;

if (context != null && context.RequestContext != null)

{

Message msg = context.RequestContext.RequestMessage;

string reqXML = msg.ToString();

}

1
Я думаю, це доступно в кінцевій точці служби (сервері). Чи є спосіб отримати те саме на кінці клієнта, звідки надсилається запит?
Гіманш

5

Я просто хотів додати це до відповіді від Кімберлі. Можливо, це може заощадити трохи часу та уникнути помилок компіляції за нереалізацію всіх методів, які вимагає інтерфейс IEndpointBehaviour.

З найкращими побажаннями

Нікі

    /*
        // This is just to illustrate how it can be implemented on an imperative declarared binding, channel and client.

        string url = "SOME WCF URL";
        BasicHttpBinding wsBinding = new BasicHttpBinding();                
        EndpointAddress endpointAddress = new EndpointAddress(url);

        ChannelFactory<ISomeService> channelFactory = new ChannelFactory<ISomeService>(wsBinding, endpointAddress);
        channelFactory.Endpoint.Behaviors.Add(new InspectorBehavior());
        ISomeService client = channelFactory.CreateChannel();
    */    
        public class InspectorBehavior : IEndpointBehavior
        {
            public void AddBindingParameters(ServiceEndpoint endpoint, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
            {
                // No implementation necessary  
            }

            public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
            {
                clientRuntime.MessageInspectors.Add(new MyMessageInspector());
            }

            public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
            {
                // No implementation necessary  
            }

            public void Validate(ServiceEndpoint endpoint)
            {
                // No implementation necessary  
            }  

        }

        public class MyMessageInspector : IClientMessageInspector
        {
            public object BeforeSendRequest(ref Message request, IClientChannel channel)
            {
                // Do something with the SOAP request
                string request = request.ToString();
                return null;
            }

            public void AfterReceiveReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
            {
                // Do something with the SOAP reply
                string replySoap = reply.ToString();
            }
        }

1

Я використовую наведене нижче рішення для хостингу IIS у режимі сумісності ASP.NET. Подяки до блогу MSDN Родні Віани .

Додайте до свого web.config наступне в розділі appSettings:

<add key="LogPath" value="C:\\logpath" />
<add key="LogRequestResponse" value="true" />

Замініть ваш global.asax.cs нижче (також виправте ім'я простору імен):

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Security;
using System.Web.SessionState;

using System.Text;
using System.IO;
using System.Configuration;

namespace Yournamespace
{
    public class Global : System.Web.HttpApplication
    {
        protected static bool LogFlag;
        protected static string fileNameBase;
        protected static string ext = "log";

        // One file name per day
        protected string FileName
        {
            get
            {
                return String.Format("{0}{1}.{2}", fileNameBase, DateTime.Now.ToString("yyyy-MM-dd"), ext);
            }
        }

        protected void Application_Start(object sender, EventArgs e)
        {
            LogFlag = bool.Parse(ConfigurationManager.AppSettings["LogRequestResponse"].ToString());
            fileNameBase = ConfigurationManager.AppSettings["LogPath"].ToString() + @"\C5API-";   
        }

        protected void Session_Start(object sender, EventArgs e)
        {

        }

        protected void Application_BeginRequest(object sender, EventArgs e)
        {
            if (LogFlag) 
            {                
                // Creates a unique id to match Rquests with Responses
                string id = String.Format("Id: {0} Uri: {1}", Guid.NewGuid(), Request.Url);
                FilterSaveLog input = new FilterSaveLog(HttpContext.Current, Request.Filter, FileName, id);
                Request.Filter = input;
                input.SetFilter(false);
                FilterSaveLog output = new FilterSaveLog(HttpContext.Current, Response.Filter, FileName, id);
                output.SetFilter(true);
                Response.Filter = output;
            }
        }

        protected void Application_AuthenticateRequest(object sender, EventArgs e)
        {

        }

        protected void Application_Error(object sender, EventArgs e)
        {

        }

        protected void Session_End(object sender, EventArgs e)
        {

        }

        protected void Application_End(object sender, EventArgs e)
        {

        }
    }

    class FilterSaveLog : Stream
    {

        protected static string fileNameGlobal = null;
        protected string fileName = null;

        protected static object writeLock = null;
        protected Stream sinkStream;
        protected bool inDisk;
        protected bool isClosed;
        protected string id;
        protected bool isResponse;
        protected HttpContext context;

        public FilterSaveLog(HttpContext Context, Stream Sink, string FileName, string Id)
        {
            // One lock per file name
            if (String.IsNullOrWhiteSpace(fileNameGlobal) || fileNameGlobal.ToUpper() != fileNameGlobal.ToUpper())
            {
                fileNameGlobal = FileName;
                writeLock = new object();
            }
            context = Context;
            fileName = FileName;
            id = Id;
            sinkStream = Sink;
            inDisk = false;
            isClosed = false;
        }

        public void SetFilter(bool IsResponse)
        {


            isResponse = IsResponse;
            id = (isResponse ? "Reponse " : "Request ") + id;

            //
            // For Request only read the incoming stream and log it as it will not be "filtered" for a WCF request
            //
            if (!IsResponse)
            {
                AppendToFile(String.Format("at {0} --------------------------------------------", DateTime.Now));
                AppendToFile(id);

                if (context.Request.InputStream.Length > 0)
                {
                    context.Request.InputStream.Position = 0;
                    byte[] rawBytes = new byte[context.Request.InputStream.Length];
                    context.Request.InputStream.Read(rawBytes, 0, rawBytes.Length);
                    context.Request.InputStream.Position = 0;

                    AppendToFile(rawBytes);
                }
                else
                {
                    AppendToFile("(no body)");
                }
            }

        }

        public void AppendToFile(string Text)
        {
            byte[] strArray = Encoding.UTF8.GetBytes(Text);
            AppendToFile(strArray);

        }

        public void AppendToFile(byte[] RawBytes)
        {
            bool myLock = System.Threading.Monitor.TryEnter(writeLock, 100);


            if (myLock)
            {
                try
                {

                    using (FileStream stream = new FileStream(fileName, FileMode.OpenOrCreate, FileAccess.ReadWrite))
                    {
                        stream.Position = stream.Length;
                        stream.Write(RawBytes, 0, RawBytes.Length);
                        stream.WriteByte(13);
                        stream.WriteByte(10);

                    }

                }
                catch (Exception ex)
                {
                    string str = string.Format("Unable to create log. Type: {0} Message: {1}\nStack:{2}", ex, ex.Message, ex.StackTrace);
                    System.Diagnostics.Debug.WriteLine(str);
                    System.Diagnostics.Debug.Flush();


                }
                finally
                {
                    System.Threading.Monitor.Exit(writeLock);


                }
            }


        }


        public override bool CanRead
        {
            get { return sinkStream.CanRead; }
        }

        public override bool CanSeek
        {
            get { return sinkStream.CanSeek; }
        }

        public override bool CanWrite
        {
            get { return sinkStream.CanWrite; }
        }

        public override long Length
        {
            get
            {
                return sinkStream.Length;
            }
        }

        public override long Position
        {
            get { return sinkStream.Position; }
            set { sinkStream.Position = value; }
        }

        //
        // For WCF this code will never be reached
        //
        public override int Read(byte[] buffer, int offset, int count)
        {
            int c = sinkStream.Read(buffer, offset, count);
            return c;
        }

        public override long Seek(long offset, System.IO.SeekOrigin direction)
        {
            return sinkStream.Seek(offset, direction);
        }

        public override void SetLength(long length)
        {
            sinkStream.SetLength(length);
        }

        public override void Close()
        {

            sinkStream.Close();
            isClosed = true;
        }

        public override void Flush()
        {

            sinkStream.Flush();
        }

        // For streamed responses (i.e. not buffered) there will be more than one Response (but the id will match the Request)
        public override void Write(byte[] buffer, int offset, int count)
        {
            sinkStream.Write(buffer, offset, count);
            AppendToFile(String.Format("at {0} --------------------------------------------", DateTime.Now));
            AppendToFile(id);
            AppendToFile(buffer);
        }

    }
}

Він повинен створити файл журналу в папці LogPath із запитом і відповіддю XML.


Тільки для сторони сервера ?
PreguntonCojoneroCabrón

0

Існує ще один спосіб побачити XML SOAP - власний MessageEncoder . Основна відмінність від IClientMessageInspector полягає в тому, що він працює на нижчому рівні, тому він фіксує вихідний вміст байтів, включаючи будь-який неправильний XML-файл.

Для того, щоб реалізувати трасування за допомогою цього підходу, вам потрібно обернути стандартне кодування textMessageEnc із користувацьким кодером повідомлень як новий елемент прив'язки та застосувати це спеціальне прив'язку до кінцевої точки у вашій конфігурації .

Також ви можете побачити як приклад, як я це зробив у своєму проекті - обтікання textMessageEncoding, кодування журналу , спеціальний елемент прив'язки та конфігурацію .

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.