代码之家  ›  专栏  ›  技术社区  ›  Michael Edwards

序列化后SOAP扩展流为空

  •  2
  • Michael Edwards  · 技术社区  · 15 年前

    我这一天一直有这个问题。我在msdn文章和大量博客文章之后创建了一个SOAP扩展,但我就是无法让它工作。好一些代码:

    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请求,这非常好。然后调用我的Web服务方法。简单来说:

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

    然后该过程跳回到我的soapextension中。在BeforeSerialization和AfterSerialization处放置断点,我看到出站流不包含任何内容。在序列化之前它是空的,我并不感到惊讶,但是在序列化之后它是空的,我感到惊讶。这会造成一个问题,因为我需要获得出站流的控制权,以便对其进行加密。

    有人能告诉我为什么出站流是空的吗?我跟踪了这篇文章,指出它不应该是 http://msdn.microsoft.com/en-us/library/ms972410.aspx . 我是缺少配置还是其他配置?

    4 回复  |  直到 13 年前
        1
  •  3
  •   Rob Parker    14 年前

    我在“soapextension msdn”上的谷歌搜索中发现了这个问题(它还发现了示例代码为top hit的文档),所以这里有一些有用的建议,可以帮助其他人理解在编写soap extensions时有时令人困惑或矛盾的文档。

    如果要修改序列化消息(作为流),则需要创建并返回一个不同于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;
    }
    

    然后,您必须在processmessage中处理afterserialize和beforereserialize案例。我让他们分别调用processTransmitstream(message)和processReceivedstream(message),以帮助保持流程清晰。

    processTransmitstream从accessstream获取其输入(首先将此memoryStream的position重置为0之后),并将其输出写入到transportStream,这可能会允许非常有限的访问(无搜索等),因此我建议先将处理过程复制到本地memoryStream缓冲区,然后将其复制(将其position重置为0之后)到\u trans端口流。(或者,如果您将其处理成字节数组或字符串,则可以直接将其写入到cTransportStream。我的用例是压缩/解压,所以我倾向于将其全部作为流处理。)

    processReceivedStream从TransportStream获取其输入并将其输出写入accessStream。在这种情况下,您应该首先将_transportstream复制到本地memorystream缓冲区(然后将缓冲区的位置重置为0),这样您可以更方便地访问它。(或者,如果您需要的话,您可以直接将整个TransportStream读取到字节数组或其他形式中。)请确保在返回之前重置accessStream.position=0,以便链中的下一个链接可以读取它。

    这是为了更改序列化流。如果不更改流,则不应重写chain stream(从而将扩展从流处理链中去掉)。相反,您将在BeforeSerialize和/或AfterDeserialize阶段进行处理。在这些阶段中,您不修改或访问流,而是处理消息对象本身,例如在BeforeSerialize阶段向Message.Headers集合添加自定义SoapHeader。

    SoapMessage类本身是抽象的,所以您真正得到的是SoapClientMessage或SoapServerMessage。文档说您在客户端得到了一个soapclientmessage,在服务器端得到了一个soapservermsage(在调试器中进行的实验应该能够确认或更正这一点)。在您可以访问的内容方面,它们看起来非常相似,但是您必须强制转换到正确的类型才能正确访问它;使用错误的类型将失败,并且为processmessage参数声明的基本soapmessage类型不会授予您访问所有内容的权限。

    我还没有研究属性的东西(它不会是我正在编码的内容的一部分),所以我无法帮助使用这个部分。

        2
  •  2
  •   Ben Gripka Javed Akram    13 年前

    我在尝试写一个soapextension来记录我在soap级别的Web服务活动时遇到了这篇文章。此脚本经过测试,并在服务器端使用时将活动记录到文本文件中。不支持客户端。

    只需将“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>
    
        3
  •  1
  •   chyne    15 年前

    为了能够操作输出,您需要在 ChainStream 方法,而不仅仅是返回同一个流。

    你还必须在 ProcessMessage 方法。在您提供的代码中没有发生任何事情。

    这是关于SOAP扩展的一个很好的理解: http://hyperthink.net/blog/inside-of-chainstream/ . 一定要阅读关于比OldStream和Newstream更好命名的评论。就我个人而言,称他们为wireleam和appstream,使事情对我更清楚。

        4
  •  0
  •   John Saunders    15 年前

    我获得SOAP扩展的唯一方法是从msdn示例开始,然后 让这个例子发挥作用 . 只有当它开始工作时,我才会一点一点地改变它,测试一路上的每一步,直到它完成我想要的。

    这甚至可以告诉我我做错了什么,但对我来说,这永远都不足以让我记住下一次。不过,通常和溪流有关。