回答:
web.config
SOAP(リクエスト/レスポンス)エンベロープを取得するために、次の変更を行いました。これにより、すべての未加工のSOAP情報がファイルに出力されますtrace.log
。
<system.diagnostics>
<trace autoflush="true"/>
<sources>
<source name="System.Net" maxdatasize="1024">
<listeners>
<add name="TraceFile"/>
</listeners>
</source>
<source name="System.Net.Sockets" maxdatasize="1024">
<listeners>
<add name="TraceFile"/>
</listeners>
</source>
</sources>
<sharedListeners>
<add name="TraceFile" type="System.Diagnostics.TextWriterTraceListener"
initializeData="trace.log"/>
</sharedListeners>
<switches>
<add name="System.Net" value="Verbose"/>
<add name="System.Net.Sockets" value="Verbose"/>
</switches>
</system.diagnostics>
リクエストとレスポンス全体をログファイルに記録するSoapExtensionを実装できます。次に、web.configでSoapExtensionを有効にすることができます。これにより、デバッグの目的で簡単にオン/オフを切り替えることができます。これは私が自分で使用するために見つけて変更した例です。私の場合、ロギングはlog4netによって行われましたが、ログメソッドを独自のものに置き換えることができます。
public class SoapLoggerExtension : SoapExtension
{
private static readonly ILog log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
private Stream oldStream;
private Stream newStream;
public override object GetInitializer(LogicalMethodInfo methodInfo, SoapExtensionAttribute attribute)
{
return null;
}
public override object GetInitializer(Type serviceType)
{
return null;
}
public override void Initialize(object initializer)
{
}
public override System.IO.Stream ChainStream(System.IO.Stream stream)
{
oldStream = stream;
newStream = new MemoryStream();
return newStream;
}
public override void ProcessMessage(SoapMessage message)
{
switch (message.Stage)
{
case SoapMessageStage.BeforeSerialize:
break;
case SoapMessageStage.AfterSerialize:
Log(message, "AfterSerialize");
CopyStream(newStream, oldStream);
newStream.Position = 0;
break;
case SoapMessageStage.BeforeDeserialize:
CopyStream(oldStream, newStream);
Log(message, "BeforeDeserialize");
break;
case SoapMessageStage.AfterDeserialize:
break;
}
}
public void Log(SoapMessage message, string stage)
{
newStream.Position = 0;
string contents = (message is SoapServerMessage) ? "SoapRequest " : "SoapResponse ";
contents += stage + ";";
StreamReader reader = new StreamReader(newStream);
contents += reader.ReadToEnd();
newStream.Position = 0;
log.Debug(contents);
}
void ReturnStream()
{
CopyAndReverse(newStream, oldStream);
}
void ReceiveStream()
{
CopyAndReverse(newStream, oldStream);
}
public void ReverseIncomingStream()
{
ReverseStream(newStream);
}
public void ReverseOutgoingStream()
{
ReverseStream(newStream);
}
public void ReverseStream(Stream stream)
{
TextReader tr = new StreamReader(stream);
string str = tr.ReadToEnd();
char[] data = str.ToCharArray();
Array.Reverse(data);
string strReversed = new string(data);
TextWriter tw = new StreamWriter(stream);
stream.Position = 0;
tw.Write(strReversed);
tw.Flush();
}
void CopyAndReverse(Stream from, Stream to)
{
TextReader tr = new StreamReader(from);
TextWriter tw = new StreamWriter(to);
string str = tr.ReadToEnd();
char[] data = str.ToCharArray();
Array.Reverse(data);
string strReversed = new string(data);
tw.Write(strReversed);
tw.Flush();
}
private void CopyStream(Stream fromStream, Stream toStream)
{
try
{
StreamReader sr = new StreamReader(fromStream);
StreamWriter sw = new StreamWriter(toStream);
sw.WriteLine(sr.ReadToEnd());
sw.Flush();
}
catch (Exception ex)
{
string message = String.Format("CopyStream failed because: {0}", ex.Message);
log.Error(message, ex);
}
}
}
[AttributeUsage(AttributeTargets.Method)]
public class SoapLoggerExtensionAttribute : SoapExtensionAttribute
{
private int priority = 1;
public override int Priority
{
get { return priority; }
set { priority = value; }
}
public override System.Type ExtensionType
{
get { return typeof (SoapLoggerExtension); }
}
}
次に、次のセクションをweb.configに追加します。ここで、YourNamespaceおよびYourAssemblyは、SoapExtensionのクラスとアセンブリをポイントします。
<webServices>
<soapExtensionTypes>
<add type="YourNamespace.SoapLoggerExtension, YourAssembly"
priority="1" group="0" />
</soapExtensionTypes>
</webServices>
なぜweb.configやシリアライザクラスのすべての大騒ぎがわからない。以下のコードは私のために働きました:
XmlSerializer xmlSerializer = new XmlSerializer(myEnvelope.GetType());
using (StringWriter textWriter = new StringWriter())
{
xmlSerializer.Serialize(textWriter, myEnvelope);
return textWriter.ToString();
}
myEnvelope
から来たの?
試してみてくださいFiddler2をし、それはあなたが要求と応答を検査できるようになります。Fiddlerがhttpトラフィックとhttpsトラフィックの両方で動作することは注目に値するかもしれません。
Web参照への呼び出しが例外をスローした場合、Tim Carterのソリューションは機能しないようです。例外がスローされたら、エラーハンドラーでそれを(コードで)確認できるように、未加工のWeb応答を取得しようとしています。ただし、呼び出しが例外をスローすると、Timのメソッドによって書き込まれた応答ログが空白になることがわかりました。私はコードを完全には理解していませんが、.NetがすでにWeb応答を無効にして破棄した後、Timのメソッドがプロセスに割り込んでいるようです。
低レベルのコーディングを使用して手動でWebサービスを開発しているクライアントと協力しています。この時点で、SOAP形式の応答の前に、独自の内部プロセスエラーメッセージをHTML形式のメッセージとして応答に追加しています。もちろん、automagic .Net Webリファレンスはこれを爆破します。例外がスローされた後、生のHTTP応答を取得できた場合、混合戻りHTTP応答内のSOAP応答を探して解析し、データを正常に受信したかどうかを確認できます。
後で...
実行後でも機能する解決策を次に示します(私は応答の後でしかないことに注意してください-要求も取得できます)。
namespace ChuckBevitt
{
class GetRawResponseSoapExtension : SoapExtension
{
//must override these three methods
public override object GetInitializer(LogicalMethodInfo methodInfo, SoapExtensionAttribute attribute)
{
return null;
}
public override object GetInitializer(Type serviceType)
{
return null;
}
public override void Initialize(object initializer)
{
}
private bool IsResponse = false;
public override void ProcessMessage(SoapMessage message)
{
//Note that ProcessMessage gets called AFTER ChainStream.
//That's why I'm looking for AfterSerialize, rather than BeforeDeserialize
if (message.Stage == SoapMessageStage.AfterSerialize)
IsResponse = true;
else
IsResponse = false;
}
public override Stream ChainStream(Stream stream)
{
if (IsResponse)
{
StreamReader sr = new StreamReader(stream);
string response = sr.ReadToEnd();
sr.Close();
sr.Dispose();
File.WriteAllText(@"C:\test.txt", response);
byte[] ResponseBytes = Encoding.ASCII.GetBytes(response);
MemoryStream ms = new MemoryStream(ResponseBytes);
return ms;
}
else
return stream;
}
}
}
設定ファイルで設定する方法は次のとおりです。
<configuration>
...
<system.web>
<webServices>
<soapExtensionTypes>
<add type="ChuckBevitt.GetRawResponseSoapExtension, TestCallWebService"
priority="1" group="0" />
</soapExtensionTypes>
</webServices>
</system.web>
</configuration>
「TestCallWebService」は、ライブラリの名前(たまたま、私が作業していたテストコンソールアプリの名前)に置き換える必要があります。
ChainStreamに行く必要はありません。あなたはProcessMessageからもっと簡単にそれを行うことができるはずです:
public override void ProcessMessage(SoapMessage message)
{
if (message.Stage == SoapMessageStage.BeforeDeserialize)
{
StreamReader sr = new StreamReader(message.Stream);
File.WriteAllText(@"C:\test.txt", sr.ReadToEnd());
message.Stream.Position = 0; //Will blow up 'cause type of stream ("ConnectStream") doesn't alow seek so can't reset position
}
}
SoapMessage.Streamを検索すると、この時点でデータを検査するために使用できる読み取り専用ストリームであると想定されます。これはねじ込み式のエラーです。これは、ストリームを読み取った場合、データが見つからない後続の爆弾の処理エラー(ストリームが最後にあった)であり、位置を最初にリセットできないためです。
興味深いことに、ChainStreamとProcessMessageの両方の方法を実行すると、ChainStreamでストリームタイプをConnectStreamからMemoryStreamに変更し、MemoryStreamではシーク操作が許可されるため、ProcessMessageメソッドが機能します。(ConnectStreamをMemoryStreamにキャストしようとしましたが、許可されませんでした。)
したがって、Microsoftは、ChainStreamタイプでのシーク操作を許可するか、SoapMessage.Streamを本来の読み取り専用コピーにする必要があります。(下院議員などを書きなさい...)
もう1点。例外の後で未加工のHTTP応答を取得する方法を作成した後も、(HTTPスニファによって判断されるように)完全な応答が得られませんでした。これは、開発Webサービスが応答の先頭にHTMLエラーメッセージを追加したときに、Content-Lengthヘッダーを調整しなかったため、Content-Length値が実際の応答本文のサイズよりも小さかったためです。私が得たのは、Content-Length値の文字数だけでした-残りはありませんでした。明らかに、.Netが応答ストリームを読み取るときは、Content-Lengthの文字数を読み取るだけで、おそらくContent-Lengthの値が間違っていることは許されません。これはあるべき姿です。ただし、Content-Lengthヘッダー値が間違っている場合、応答本文全体を取得する唯一の方法は、HTTPスニッファー(http://www.ieinspector.com)。
フレームワークがその基になるストリームを処理するときにログを記録するロギングストリームをフックすることで、フレームワークにロギングを実行させたいと思います。次のコードは、ChainStreamメソッドで要求と応答のどちらを行うかを決定できないため、希望するほどきれいではありません。以下はその扱い方です。ストリームのアイデアを上書きしてくれたJon Hannaに感謝
public class LoggerSoapExtension : SoapExtension
{
private static readonly string LOG_DIRECTORY = ConfigurationManager.AppSettings["LOG_DIRECTORY"];
private LogStream _logger;
public override object GetInitializer(LogicalMethodInfo methodInfo, SoapExtensionAttribute attribute)
{
return null;
}
public override object GetInitializer(Type serviceType)
{
return null;
}
public override void Initialize(object initializer)
{
}
public override System.IO.Stream ChainStream(System.IO.Stream stream)
{
_logger = new LogStream(stream);
return _logger;
}
public override void ProcessMessage(SoapMessage message)
{
if (LOG_DIRECTORY != null)
{
switch (message.Stage)
{
case SoapMessageStage.BeforeSerialize:
_logger.Type = "request";
break;
case SoapMessageStage.AfterSerialize:
break;
case SoapMessageStage.BeforeDeserialize:
_logger.Type = "response";
break;
case SoapMessageStage.AfterDeserialize:
break;
}
}
}
internal class LogStream : Stream
{
private Stream _source;
private Stream _log;
private bool _logSetup;
private string _type;
public LogStream(Stream source)
{
_source = source;
}
internal string Type
{
set { _type = value; }
}
private Stream Logger
{
get
{
if (!_logSetup)
{
if (LOG_DIRECTORY != null)
{
try
{
DateTime now = DateTime.Now;
string folder = LOG_DIRECTORY + now.ToString("yyyyMMdd");
string subfolder = folder + "\\" + now.ToString("HH");
string client = System.Web.HttpContext.Current != null && System.Web.HttpContext.Current.Request != null && System.Web.HttpContext.Current.Request.UserHostAddress != null ? System.Web.HttpContext.Current.Request.UserHostAddress : string.Empty;
string ticks = now.ToString("yyyyMMdd'T'HHmmss.fffffff");
if (!Directory.Exists(folder))
Directory.CreateDirectory(folder);
if (!Directory.Exists(subfolder))
Directory.CreateDirectory(subfolder);
_log = new FileStream(new System.Text.StringBuilder(subfolder).Append('\\').Append(client).Append('_').Append(ticks).Append('_').Append(_type).Append(".xml").ToString(), FileMode.Create);
}
catch
{
_log = null;
}
}
_logSetup = true;
}
return _log;
}
}
public override bool CanRead
{
get
{
return _source.CanRead;
}
}
public override bool CanSeek
{
get
{
return _source.CanSeek;
}
}
public override bool CanWrite
{
get
{
return _source.CanWrite;
}
}
public override long Length
{
get
{
return _source.Length;
}
}
public override long Position
{
get
{
return _source.Position;
}
set
{
_source.Position = value;
}
}
public override void Flush()
{
_source.Flush();
if (Logger != null)
Logger.Flush();
}
public override long Seek(long offset, SeekOrigin origin)
{
return _source.Seek(offset, origin);
}
public override void SetLength(long value)
{
_source.SetLength(value);
}
public override int Read(byte[] buffer, int offset, int count)
{
count = _source.Read(buffer, offset, count);
if (Logger != null)
Logger.Write(buffer, offset, count);
return count;
}
public override void Write(byte[] buffer, int offset, int count)
{
_source.Write(buffer, offset, count);
if (Logger != null)
Logger.Write(buffer, offset, count);
}
public override int ReadByte()
{
int ret = _source.ReadByte();
if (ret != -1 && Logger != null)
Logger.WriteByte((byte)ret);
return ret;
}
public override void Close()
{
_source.Close();
if (Logger != null)
Logger.Close();
base.Close();
}
public override int ReadTimeout
{
get { return _source.ReadTimeout; }
set { _source.ReadTimeout = value; }
}
public override int WriteTimeout
{
get { return _source.WriteTimeout; }
set { _source.WriteTimeout = value; }
}
}
}
[AttributeUsage(AttributeTargets.Method)]
public class LoggerSoapExtensionAttribute : SoapExtensionAttribute
{
private int priority = 1;
public override int Priority
{
get
{
return priority;
}
set
{
priority = value;
}
}
public override System.Type ExtensionType
{
get
{
return typeof(LoggerSoapExtension);
}
}
}
以下は、トップアンサーの簡略版です。これをまたはファイルの<configuration>
要素に追加します。プロジェクトのフォルダにファイルが作成されます。または、属性を使用してログファイルの絶対パスを指定できます。web.config
App.config
trace.log
bin/Debug
initializeData
<system.diagnostics>
<trace autoflush="true"/>
<sources>
<source name="System.Net" maxdatasize="9999" tracemode="protocolonly">
<listeners>
<add name="TraceFile" type="System.Diagnostics.TextWriterTraceListener" initializeData="trace.log"/>
</listeners>
</source>
</sources>
<switches>
<add name="System.Net" value="Verbose"/>
</switches>
</system.diagnostics>
maxdatasize
およびtracemode
属性が許可されていないことを警告しますが、これらはログに記録できるデータの量を増やし、16進数ですべてをログに記録することを避けます。
私はパーティーにかなり遅れていることを理解しています。言語は実際には指定されていなかったため、誰かが偶然これに遭遇して解決策が必要な場合に備えて、Bimmerboundの回答に基づくVB.NETソリューションを示します。注:プロジェクトにstringbuilderクラスへの参照がまだない場合は、これを参照する必要があります。
Shared Function returnSerializedXML(ByVal obj As Object) As String
Dim xmlSerializer As New System.Xml.Serialization.XmlSerializer(obj.GetType())
Dim xmlSb As New StringBuilder
Using textWriter As New IO.StringWriter(xmlSb)
xmlSerializer.Serialize(textWriter, obj)
End Using
returnSerializedXML = xmlSb.ToString().Replace(vbCrLf, "")
End Function
関数を呼び出すだけで、Webサービスに渡そうとしているオブジェクトのシリアル化されたxmlを含む文字列が返されます(現実的には、これはスローしたいオブジェクトに対しても機能するはずです)。
補足として、xmlを返す前の関数でのreplace呼び出しは、出力からvbCrLf文字を取り除くことです。私は生成されたxml内にそれらをたくさん持っていましたが、これは明らかにシリアル化しようとしているものによって異なり、オブジェクトがWebサービスに送信されている間に削除される可能性があると思います。