Поток расширения SOAP после сериализации пуст - PullRequest
2 голосов
/ 31 июля 2009

У меня была эта проблема в последний день. Я создал расширение SOAP, следуя статьям MSDN и множеству постов в блоге, но просто не могу заставить его работать. Хорошо, некоторый код:

public class EncryptionExtension : SoapExtension
{

    Stream _stream;
    public override object GetInitializer(Type serviceType)
    {
        return typeof(EncryptionExtension);
    }

    public override object GetInitializer(LogicalMethodInfo methodInfo, SoapExtensionAttribute attribute)
    {
        return attribute;
    }

    public override void Initialize(object initializer)
    {
    }

    public override void ProcessMessage(SoapMessage message)
    {

        switch (message.Stage)
        {

            case SoapMessageStage.BeforeSerialize:
                break;

            case SoapMessageStage.AfterSerialize:
                break;

            case SoapMessageStage.BeforeDeserialize:
                break;

            case SoapMessageStage.AfterDeserialize:
                break;
            default:
                throw new Exception("invalid stage");
        }

    }
    public override Stream ChainStream(Stream stream)
    {
        _stream = stream;
        return stream;
    }
}

Существует также класс атрибута:

[AttributeUsage(AttributeTargets.Method)]
public class EncryptionExtensionAttribute : SoapExtensionAttribute
{

    public override Type ExtensionType
    {
        get { return typeof(EncryptionExtension); }
    }

    public override int Priority
    {
        get;
        set;
    }
}

Поэтому, когда приходит сообщение, я вижу входящий SOAP-запрос при отладке в BeforeDeserialization и AfterDeserialization, что замечательно. Затем вызывается мой метод веб-службы. Что просто:

[WebMethod()]
[EncryptionExtension]
public string HelloWorld()
{
    return "Hello world";
}

Затем процесс возвращается в мой SoapExtension. Помещая точки останова в BeforeSerialization и AfterSerialization, я вижу, что исходящий поток не содержит ничего. Я не удивлен, что он пуст в PreSerialization, но я удивлен, что он пуст в AfterSerialization. Это создает проблему, потому что мне нужно получить исходящий поток, чтобы я мог зашифровать его.

Может кто-нибудь сказать мне, почему исходящий поток пуст? Я следовал за этой статьей MSDN, которая указывает, что это не должно быть http://msdn.microsoft.com/en-us/library/ms972410.aspx. Я пропустил какую-то конфигурацию или что-то еще?

Ответы [ 4 ]

3 голосов
/ 15 октября 2010

Я нашел этот вопрос в числе самых популярных запросов в поиске Google по "SoapExtension MSDN" (который также находит документ с примером кода в качестве главного запроса), поэтому вот несколько полезных советов для всех, кто пытается разобраться в иногда запутанные или противоречивые документы по кодированию расширений Soap.

Если вы модифицируете сериализованное сообщение (как поток), вам нужно создать и вернуть другой поток из переопределения ChainStream. В противном случае вы говорите, что ваше расширение не изменяет поток, а просто пропускает его. В примере используется MemoryStream, и это, вероятно, то, что вы должны использовать из-за странного дизайна: когда вызывается ChainStream, вы не знаете, отправляете ли вы или получаете, поэтому вы должны быть готовы обработать его в любом случае. Я думаю, что даже если вы обрабатываете его только в одном направлении, вам все равно придется обрабатывать другое направление и в любом случае копировать данные из одного потока в другой, потому что вы вставляете себя в цепочку, не зная, как это.

private Stream _transportStream; // The stream closer to the network transport.
private MemoryStream _accessStream; // The stream closer to the message access.

public override Stream ChainStream(Stream stream)
{
    // You have to save these streams for later.
    _transportStream = stream;
    _accessStream = new MemoryStream();
    return _accessStream;
}

Затем вам нужно обработать случаи AfterSerialize и BeforeDeserialize в ProcessMessage. У меня есть они, вызывающие ProcessTransmitStream (сообщение) и ProcessReceivedStream (сообщение) соответственно, чтобы помочь сохранить процесс ясным.

ProcessTransmitStream берет свой ввод из _accessStream (после первого сброса Положения этого MemoryStream в 0) и записывает свой вывод в _transportStream - что может позволить очень ограниченный доступ (без поиска и т. Д.), Поэтому я предлагаю сначала обработать в локальный Буфер MemoryStream, а затем скопировать его (после сброса его Postion в 0) в _transportStream. (Или, если вы обрабатываете его в байтовом массиве или строке, вы можете просто записать его непосредственно в _transportStream. Мой вариант использования был сжатие / декомпрессия, поэтому я склонен к обработке всего этого как потоков.)

ProcessReceivedStream берет свои входные данные из _transportStream и записывает свои выходные данные в _accessStream. В этом случае вам, вероятно, следует сначала скопировать _transportStream в локальный буфер MemoryStream (а затем сбросить позицию буфера на 0), к которой вы можете получить более удобный доступ. (Или вы можете просто прочитать весь _transportStream непосредственно в байтовый массив или другую форму, если вам это нужно.) Убедитесь, что вы сбросили _accessStream.Position = 0, прежде чем вернуться, чтобы он был готов для следующей ссылки в цепочке, чтобы читать из него.

Это для изменения сериализованного потока. Если вы не изменяете поток, вам не следует переопределять ChainStream (таким образом, вынимая свое расширение из цепочки обработки потока). Вместо этого вы выполняете обработку на этапах BeforeSerialize и / или AfterDeserialize. На этих этапах вы не изменяете потоки и не обращаетесь к ним, а вместо этого работаете с самим объектом сообщения, например, добавляете собственный SoapHeader в коллекцию message.Headers на этапе BeforeSerialize.

Сам класс SoapMessage является абстрактным, поэтому на самом деле вы получаете либо SoapClientMessage, либо SoapServerMessage. В документах говорится, что вы получаете SoapClientMessage на стороне клиента и SoapServerMessage на стороне сервера (эксперименты в отладчике должны быть в состоянии подтвердить или исправить это). Они кажутся довольно схожими с точки зрения того, к чему вы можете получить доступ, но вы должны привести к нужному, чтобы получить к нему доступ; неправильное использование не удастся, и базовый тип SoapMessage, объявленный для параметра ProcessMessage, не дает вам доступа ко всему.

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

2 голосов
/ 16 мая 2012

Я столкнулся с этим постом, пытаясь написать SoapExtension, которое бы регистрировало мою активность веб-сервиса на уровне мыла. Этот скрипт протестирован и работает для записи активности в текстовый файл при использовании на стороне сервера. Клиентская сторона не поддерживается.

Чтобы использовать, просто замените 'C: \ Your Destination Directory' реальным каталогом, который вы хотите использовать для записи в файл журнала.

Эта работа стоила мне целый день, поэтому я публикую ее в надежде, что другим не придется делать то же самое.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.Services;
using System.Web.Services.Protocols;
using System.IO;
using System.Net;
using System.Reflection;

    public class WebServiceActivityLogger : SoapExtension
    {
        string fileName = null;

        public override object GetInitializer(Type serviceType)
        {
            return Path.Combine(@"C:\Your Destination Directory", serviceType.Name + " - " + DateTime.Now.ToString("yyyy-MM-dd HH.mm") + ".txt");
        }

        public override object GetInitializer(LogicalMethodInfo methodInfo, SoapExtensionAttribute attribute)
        {
            return Path.Combine(@"C:\Your Destination Directory", methodInfo.DeclaringType.Name + " - " + DateTime.Now.ToString("yyyy-MM-dd HH.mm") + ".txt");
        }

        public override void Initialize(object initializer)
        {
            fileName = initializer as string;
        }

        Dictionary<int, ActivityLogData> logDataDictionary = new Dictionary<int, ActivityLogData>();
        private ActivityLogData LogData
        {
            get
            {
                ActivityLogData rtn;
                if (!logDataDictionary.TryGetValue(System.Threading.Thread.CurrentThread.ManagedThreadId, out rtn))
                    return null;
                else
                    return rtn;
            }
            set
            {
                int threadId = System.Threading.Thread.CurrentThread.ManagedThreadId;
                if(logDataDictionary.ContainsKey(threadId))
                {
                    if (value != null)
                        logDataDictionary[threadId] = value;
                    else
                        logDataDictionary.Remove(threadId);
                }
                else if(value != null)
                    logDataDictionary.Add(threadId, value);
            }
        }

        private class ActivityLogData
        {
            public string methodName;
            public DateTime startTime;
            public DateTime endTime;
            public Stream transportStream;
            public Stream accessStream;
            public string inputSoap;
            public string outputSoap;
            public bool endedInError;
        }

        public override Stream ChainStream(Stream stream)
        {
            if (LogData == null)
                LogData = new ActivityLogData();
            var logData = LogData;

            logData.transportStream = stream;
            logData.accessStream = new MemoryStream();
            return logData.accessStream;
        }

        public override void ProcessMessage(SoapMessage message)
        {
            if (LogData == null)
                LogData = new ActivityLogData();
            var logData = LogData;

            if (message is SoapServerMessage)
            {
                switch (message.Stage)
                {
                    case SoapMessageStage.BeforeDeserialize:
                        //Take the data from the transport stream coming in from the client
                        //and copy it into inputSoap log.  Then reset the transport to the beginning
                        //copy it to the access stream that the server will use to read the incoming message.
                        logData.startTime = DateTime.Now;
                        logData.inputSoap = GetSoapMessage(logData.transportStream);
                        Copy(logData.transportStream, logData.accessStream);
                        logData.accessStream.Position = 0;
                        break;
                    case SoapMessageStage.AfterDeserialize:
                        //Capture the method name after deserialization and it is now known. (was buried in the incoming soap)
                        logData.methodName = GetMethodName(message);
                        break;
                    case SoapMessageStage.BeforeSerialize:
                        //Do nothing here because we are not modifying the soap
                        break;
                    case SoapMessageStage.AfterSerialize:
                        //Take the serialized soap data captured by the access stream and
                        //write it into the log file.  But if an error has occurred write the exception details.
                        logData.endTime = DateTime.Now;
                        logData.accessStream.Position = 0;
                        if (message.Exception != null)
                        {
                            logData.endedInError = true;
                            if (message.Exception.InnerException != null && message.Exception is System.Web.Services.Protocols.SoapException)
                                logData.outputSoap = GetFullExceptionMessage(message.Exception.InnerException);
                            else
                                logData.outputSoap = GetFullExceptionMessage(message.Exception);
                        }
                        else
                            logData.outputSoap = GetSoapMessage(logData.accessStream);

                        //Transfer the soap data as it was created by the service
                        //to the transport stream so it is received the client unmodified.
                        Copy(logData.accessStream, logData.transportStream);
                        LogRequest(logData);
                        break;
                }
            }
            else if (message is SoapClientMessage)
            {
                throw new NotSupportedException("This extension must be ran on the server side");
            }

        }

        private void LogRequest(ActivityLogData logData)
        {
            try
            {
                //Create the directory if it doesn't exist
                var directoryName = Path.GetDirectoryName(fileName);
                if (!Directory.Exists(directoryName))
                    Directory.CreateDirectory(directoryName);

                using (var fs = new FileStream(fileName, FileMode.Append, FileAccess.Write))
                {
                    var sw = new StreamWriter(fs);

                    sw.WriteLine("--------------------------------------------------------------");
                    sw.WriteLine("- " + logData.methodName + " executed in " + (logData.endTime - logData.startTime).TotalMilliseconds.ToString("#,###,##0") + " ms");
                    sw.WriteLine("--------------------------------------------------------------");
                    sw.WriteLine("* Input received at " + logData.startTime.ToString("HH:mm:ss.fff"));
                    sw.WriteLine();
                    sw.WriteLine("\t" + logData.inputSoap.Replace("\r\n", "\r\n\t"));
                    sw.WriteLine();
                    if (!logData.endedInError)
                        sw.WriteLine("* Output sent at " + logData.endTime.ToString("HH:mm:ss.fff"));
                    else
                        sw.WriteLine("* Output ended in Error at " + logData.endTime.ToString("HH:mm:ss.fff"));
                    sw.WriteLine();
                    sw.WriteLine("\t" + logData.outputSoap.Replace("\r\n", "\r\n\t"));
                    sw.WriteLine();
                    sw.Flush();
                    sw.Close();
                }
            }
            finally
            {
                LogData = null;
            }
        }

        private void Copy(Stream from, Stream to)
        {
            TextReader reader = new StreamReader(from);
            TextWriter writer = new StreamWriter(to);
            writer.WriteLine(reader.ReadToEnd());
            writer.Flush();
        }

        private string GetMethodName(SoapMessage message)
        {
            try
            {
                return message.MethodInfo.Name;
            }
            catch 
            {
                return "[Method Name Unavilable]";
            }
        }

        private string GetSoapMessage(Stream message)
        {
            if(message == null || message.CanRead == false)
                return "[Message Soap was Unreadable]";
            var rtn = new StreamReader(message).ReadToEnd();
            message.Position = 0;
            return rtn;
        }

        private string GetFullExceptionMessage(System.Exception ex)
        {
            Assembly entryAssembly = System.Reflection.Assembly.GetEntryAssembly();
            string Rtn = ex.Message.Trim() + "\r\n\r\n" +
                "Exception Type: " + ex.GetType().ToString().Trim() + "\r\n\r\n" +
                ex.StackTrace.TrimEnd() + "\r\n\r\n";
            if (ex.InnerException != null)
                Rtn += "Inner Exception\r\n\r\n" + GetFullExceptionMessage(ex.InnerException);
            return Rtn.Trim();
        }
    }

Добавьте это в web.config вашего сервера.

   <system.web>
      <webServices>
        <soapExtensionTypes>
          <add type="[Your Namespace].WebServiceActivityLogger, [Assembly Namespace], Version=1.0.0.0, Culture=neutral" priority="1" group="0" />
        </soapExtensionTypes>
      </webServices>
   </system.web>
1 голос
/ 31 июля 2009

Чтобы иметь возможность манипулировать выводом, вам нужно сделать больше в методе ChainStream, чем просто возвращать тот же поток.

Вам также придется что-то делать в методе ProcessMessage. В предоставленном вами коде ничего не происходит.

Это хорошее чтение по расширениям SOAP: http://hyperthink.net/blog/inside-of-chainstream/. Обязательно прочитайте также комментарии о лучшем именовании, чем oldStream и NewStream. Лично, называя их wireStream и appStream, я все проясню.

0 голосов
/ 31 июля 2009

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...