Я нашел способ, который может быть не самым элегантным, но выполняет свою работу. Идея состоит в том, чтобы делегировать all работу пользовательскому классу, производному от SoapExtension, и фактически ничего не делать в самом WebMethod - WebMethod как конечная точка для вызова:
[WebMethod]
public Foo GimmeFoo()
{
return null;
}
Но вот волшебство: вы пишете SoapExtension, которое перехватывает весь трафик SOAP, и, когда наступает этап SoapMessageStage.AfterSerialize, вы добавляете туда уже сериализованную полезную нагрузку:
public class SerializationPassThrough : SoapExtension
{
private Stream oldStream;
private Stream newStream;
// Other overrides...
public override void ProcessMessage(SoapMessage message)
{
switch (message.Stage)
{
case SoapMessageStage.BeforeSerialize:
// ...
break;
case SoapMessageStage.AfterSerialize:
string newOutput = ReadPreCookedResponseFromDB();
RewriteOutput(newOutput);
break;
case SoapMessageStage.BeforeDeserialize:
// ...
break;
case SoapMessageStage.AfterDeserialize:
// ...
break;
default:
throw new Exception("invalid stage");
}
}
private void RewriteOutput(string output)
{
newStream.Position = 0;
StreamWriter sw = new StreamWriter(newStream);
sw.Write(output);
sw.Flush();
newStream.Position = 0;
Copy(newStream, oldStream);
newStream.Position = 0;
}
private void Copy(Stream from, Stream to)
{
TextReader reader = new StreamReader(from);
TextWriter writer = new StreamWriter(to);
string toWrite = reader.ReadToEnd();
writer.WriteLine(toWrite);
writer.Flush();
}
}
Последний штрих: вам нужно указать среде выполнения ASP.NET использовать SoapExtension. Вы можете сделать это через атрибут в WebMethod или в web.config (что я и сделал):
<system.web>
<webServices>
<soapExtensionTypes>
<add type="SoapSerializationTest.SerializationPassThrough, SoapSerializationTest" priority="1" />
</soapExtensionTypes>
</webServices>
<system.web>
Исходные примеры кода, из которых я получил эти фрагменты, находятся по адресу http://msdn.microsoft.com/en-us/library/7w06t139(VS.85).aspx.