Поток расширения SOAP после сериализации пуст

2

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

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 Я вижу, что исходящий поток не содержит ничего. Я не удивлен, что он пуст в BeforeSerialization, но я удивлен, что в AfterSerialization он пуст. Это создает проблему, потому что мне нужно получить исходящий поток, чтобы я мог его зашифровать.

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

Теги:
soap
web-services

4 ответа

3
Лучший ответ

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

Если вы изменяете сериализованное сообщение (как поток), вам нужно создать и вернуть другой поток из переопределения 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 (message) и ProcessReceivedStream (сообщение) соответственно, чтобы помочь очистить процесс.

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

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

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

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

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

2

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

Чтобы просто заменить "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>
  • 0
    Можно ли написать журнал с некоторыми условиями. Когда я добавляю любое условие, запрос становится пустым. void Copy (Stream from, Stream to) {TextReader reader = new StreamReader (from); TextWriter writer = new StreamWriter (to); string data = reader.ReadToEnd (); if (! data.Contains ("код ошибки") &&! data.Contains ("<SOAP: Fault>")) {writer.WriteLine (data); writer.Flush (); }} Как я могу справиться с этим?
  • 0
    Вы должны работать с потоком после того, как он скопирован. Проверьте сообщение в ProcessMessage после вызова GetSoapMessage.
1

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

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

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

  • 0
    Я попробовал этот пример перед публикацией. Поведение по умолчанию метода классов ChainStream SOAPExtension - просто возвращать поток. Что я и сделал, единственная причина, по которой я поместил его в переменную, - чтобы я мог видеть содержимое потока при отладке.
0

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

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

  • 0
    Да, я заметил, что пример в обзорном документе класса SoapExtension кажется правильным (AFAICT), но более полный документ, описывающий весь процесс, противоречит использованию потоков, передаваемых и возвращаемых ChainStream. Мое тестирование показало, что в примере они были правильными, а не в общем документе.
  • 0
    @Rob, если вы нашли ошибку в документации, надеюсь, вы сообщили об этом в Microsoft.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню