我在最后一天遇到了这个问题。我已经根据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;
}
}
因此,当消息进来时,我可以在BeforeDeserialization和AfterDeserialization调试时看到入站SOAP请求,这很好。然后调用我的Web服务方法。这很简单:
[WebMethod()]
[EncryptionExtension]
public string HelloWorld()
{
return "Hello world";
}
然后该过程重新进入我的SoapExtension。将断点放在BeforeSerialization和AfterSerialization上我看到出站流不包含任何内容。我并不感到惊讶它在BeforeSerialization上是空的,但我很惊讶它在AfterSerialization时是空的。这会产生问题,因为我需要获取出站流,以便我可以对其进行加密。
有人能告诉我为什么出站流是空的吗?我已经按照这篇MSDN文章说明它不应该是http://msdn.microsoft.com/en-us/library/ms972410.aspx。 我错过了一些配置或其他什么吗?
答案 0 :(得分:3)
我在“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;
}
然后,您必须处理ProcessMessage中的AfterSerialize和BeforeDeserialize案例。我让他们分别调用ProcessTransmitStream(message)和ProcessReceivedStream(message)来帮助保持流程清晰。
ProcessTransmitStream从_accessStream获取其输入(在首次将此MemoryStream的位置重置为0之后)并将其输出写入_transportStream - 这可能允许非常有限的访问(没有搜索等),所以我建议先处理到本地MemoryStream缓冲区然后将其(在将其Postion重置为0之后)复制到_transportStream中。 (或者如果你将它处理成一个字节数组或字符串,你可以直接写入_transportStream。我的用例是压缩/解压缩,所以我偏向于将它作为流处理。)
ProcessReceivedStream从_transportStream获取其输入并将其输出写入_accessStream。在这种情况下,您应该首先将_transportStream复制到本地MemoryStream缓冲区(然后将缓冲区的位置重置为0),您可以更方便地访问它。 (或者您可以直接将整个_transportStream读入字节数组或其他形式,如果您需要它。)确保在返回之前重置_accessStream.Position = 0,以便为链中的下一个链接做好准备从中读取。
这是用于更改序列化流。如果您不更改流,则不应覆盖ChainStream(从而将扩展从流处理链中取出)。相反,您将在BeforeSerialize和/或AfterDeserialize阶段进行处理。在这些阶段,您不会修改或访问流,而是处理消息对象本身,例如在BeforeSerialize阶段的message.Headers集合中添加自定义SoapHeader。
SoapMessage类本身是抽象的,所以你真正得到的是SoapClientMessage或SoapServerMessage。文档说你在客户端获得了一个SoapClientMessage,在服务器端获得了一个SoapServerMessage(在调试器中进行试验应该能够确认或纠正它)。它们在您可以访问的内容方面看起来非常相似,但您必须转向正确的方式才能正确访问它;使用错误的将失败,并且为ProcessMessage参数声明的基本SoapMessage类型不允许您访问所有内容。
我还没有查看属性内容(它不会是我编码的一部分),所以我无法帮助解决如何使用该部分。
答案 1 :(得分:2)
我在尝试编写一个SoapExtension时遇到了这个帖子,该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>
答案 2 :(得分:1)
为了能够操作输出,您需要在ChainStream
方法中执行更多操作,而不仅仅是返回相同的流。
您还需要在ProcessMessage
方法中实际执行某些操作。您提供的代码中没有任何内容发生。
这是对SOAP扩展的好读:http://hyperthink.net/blog/inside-of-chainstream/。请务必阅读有关比oldStream和NewStream更好命名的注释。就个人而言,将它们称为wireStream和appStream,让我更加清楚。
答案 3 :(得分:0)
我让SOAP扩展工作的唯一方法是从MSDN示例开始,让示例工作。只有一旦它工作,我会一点一点地改变它,测试沿途的每一步,直到它完成我想要的。
这甚至可能告诉我我做错了什么,但是对我来说,下次永远记不住了。通常与Streams有关。