Switch from soap11 to soap12 - c#

I switched the protocol that I'm using in C# from
oCode.SoapVersion = SoapProtocolVersion.Soap11;
to
oCode.SoapVersion = SoapProtocolVersion.Soap12;
I get an error "WSE005: The input was not a valid SOAP message because it has either the wrong name or the wrong namespace. The name specified follows: Envelope. The namespace it was defined under follows: http://www.w3.org/2003/05/soap-envelope."
I can't find anything about what it means, there's nothing in Google with that message. Are there other things I need to consider when switching from 11 to 12? I didn't seem to find anyone else with the same issue.
Any help is welcome, thanks.
We're not using a WCF, but the
namespace System.Web.Services.Protocols
{
[ComVisible(true)]
public class SoapHttpClientProtocol : HttpWebClientProtocol
{
public SoapHttpClientProtocol();
[ComVisible(false)]
[DefaultValue(SoapProtocolVersion.Default)]
[WebServicesDescriptionAttribute("ClientProtocolSoapVersion")]
public SoapProtocolVersion SoapVersion { get; set; }
public void Discover();
protected IAsyncResult BeginInvoke(string methodName, object[] parameters, AsyncCallback callback, object asyncState);
protected object[] EndInvoke(IAsyncResult asyncResult);
protected virtual XmlReader GetReaderForMessage(SoapClientMessage message, int bufferSize);
protected override WebRequest GetWebRequest(Uri uri);
protected virtual XmlWriter GetWriterForMessage(SoapClientMessage message, int bufferSize);
protected object[] Invoke(string methodName, object[] parameters);
protected void InvokeAsync(string methodName, object[] parameters, SendOrPostCallback callback);
protected void InvokeAsync(string methodName, object[] parameters, SendOrPostCallback callback, object userState);
}
}

SOAP version 1.2 made a number of breaking changes, including the namespace of the SOAP envelope.
You didn't specify whether it was the client or the server code you changed, but one or other of the sides of this communication are not aware of the version change — probably the server, which is responding that it doesn't recognise the SOAP 1.2 envelope. If it doesn't support SOAP 1.2, you can't use this option.

Related

WCF Custom Message Encoder uses Soap1.2 even though I explicitly specify 1.1

I have a custom encoder hooked up to a customBinding that uses a TextMessageEncoding element underwater. I specified specifically that SOAP 1.1 should be used with WS Addressing 1.0. My custom encoder, as I said, uses a text message encoder underwater; the encoder just adds some headers that the service that calls my service wants implemented.
When I add the generated WSDL (that uses SOAP 1.2, even though I specified SOAP 1.1 with WS Addressing 1.0) in SOAPUI and send a request, the content-type is different and it causes an error. I presume this is because WHILE generating the WSDL, it uses Soap 1.2, but WHILE sending a request it tries to use SOAP 1.1 even though a message is send using SOAP 1.2.
There are a few related issues:
When I FIRST use <textMessageEncoding messageVersion="Soap11WSAddressing10" writeEncoding="utf-8" />, add the WSDL to SOAPUI, then change the the encoding to my custom encoder and send a request (Which results in the WSDL being Soap 1.1 so I thought maybe this would be a very hacky workaround) , I get the following error:
HTTP/1.1 415 Cannot process the message because the content type 'text/xml;charset=UTF-8' was not the expected type 'text/xml; charset=utf-8'.
The issue here is that UTF-8 is in uppercase and that there is whitespace after the ; . I think this is rather weird and should not be an issue!
In Visual Studio custom encoder has a squiggly line that says The element 'binding' has invalid child element 'MissingWSAddressingHeadersTextEncoding'. List of possible elements expected: context, .... I have looked at this Stackoverflow post and used the UI editor, but the squiggly line stays. I have also edited my DotNetConfig.xsd I am 100% confident the address is correct because at run-time the custom encoder works fine.
Please take a look at my code. The links provided in the code are the ones I used to create the encoder.
The class that is used as the extension
namespace DigipoortConnector.Api.Digipoort_Services.Extensions
{
public class WSAddressingEncodingBindingElementExtension : BindingElementExtensionElement
{
public override Type BindingElementType => typeof(WSAddressingEncodingBindingElement);
protected override BindingElement CreateBindingElement()
{
return new WSAddressingEncodingBindingElement();
}
}
}
The element, factory and encoder
namespace DigipoortConnector.Api.Digipoort_Services.Extensions
{
//https://msdn.microsoft.com/en-us/library/system.servicemodel.channels.messageencoder(v=vs.110).aspx
//https://blogs.msdn.microsoft.com/carlosfigueira/2011/11/08/wcf-extensibility-message-encoders/
public class WSAddressingEncodingBindingElement : MessageEncodingBindingElement
{
public override MessageEncoderFactory CreateMessageEncoderFactory() => new WSAddressingEncoderFactory();
public override MessageVersion MessageVersion
{
get => MessageVersion.Soap11WSAddressing10;
set
{
if (value != MessageVersion.Soap11WSAddressing10)
{
throw new ArgumentException("Invalid message version");
}
}
}
public override BindingElement Clone() => new WSAddressingEncodingBindingElement();
public override IChannelFactory<TChannel> BuildChannelFactory<TChannel>(BindingContext context)
{
context.BindingParameters.Add(this);
return context.BuildInnerChannelFactory<TChannel>();
}
public override IChannelListener<TChannel> BuildChannelListener<TChannel>(BindingContext context)
{
context.BindingParameters.Add(this);
return context.BuildInnerChannelListener<TChannel>();
}
private class WSAddressingEncoderFactory : MessageEncoderFactory
{
private MessageEncoder _encoder;
public override MessageEncoder Encoder
{
get
{
if (_encoder == null)
{
_encoder = new WSAddressingEncoder();
}
return _encoder;
}
}
public override MessageVersion MessageVersion => MessageVersion.Soap11WSAddressing10;
}
private class WSAddressingEncoder : MessageEncoder
{
private MessageEncoder _underlyingEncoder;
private const string AddressingNamespace = "http://www.w3.org/2005/08/addressing";
public WSAddressingEncoder()
{
_underlyingEncoder = new TextMessageEncodingBindingElement(MessageVersion.Soap11WSAddressing10, Encoding.UTF8)
.CreateMessageEncoderFactory().Encoder;
}
public override string ContentType => _underlyingEncoder.ContentType;//.Replace("utf-8", "UTF-8").Replace("; ", ";"); //The replaces are used to fix the uppecase/; problem
public override string MediaType => _underlyingEncoder.MediaType;
public override MessageVersion MessageVersion => _underlyingEncoder.MessageVersion;
public override Message ReadMessage(Stream stream, int maxSizeOfHeaders, string contentType)
=> _underlyingEncoder.ReadMessage(stream, maxSizeOfHeaders, contentType);
public override Message ReadMessage(ArraySegment<byte> buffer, BufferManager bufferManager, string contentType)
=> _underlyingEncoder.ReadMessage(buffer, bufferManager, contentType);
public override void WriteMessage(Message message, Stream stream)
=> _underlyingEncoder.WriteMessage(message, stream);
public override ArraySegment<byte> WriteMessage(Message message, int maxMessageSize, BufferManager bufferManager, int messageOffset)
{
message.Headers.Add(MessageHeader.CreateHeader("To", AddressingNamespace, "http://www.w3.org/2005/08/addressing/anonymous"));
var relatesToHeaderValue = message.Headers.RelatesTo?.ToString();
message.Headers.Add(MessageHeader.CreateHeader("MessageID", AddressingNamespace, relatesToHeaderValue));
return _underlyingEncoder.WriteMessage(message, maxMessageSize, bufferManager, messageOffset);
}
}
}
}
The binding and the extension element
<customBinding>
<binding>
<security
authenticationMode="CertificateOverTransport"
messageSecurityVersion="WSSecurity10WSTrust13WSSecureConversation13WSSecurityPolicy12BasicSecurityProfile10"
enableUnsecuredResponse="false"
messageProtectionOrder="EncryptBeforeSign"
includeTimestamp="true"
defaultAlgorithmSuite="TripleDesRsa15" />
<missingWSAddressingHeadersTextEncoding />
<httpsTransport requireClientCertificate="true" />
</binding>
</customBinding>
<extensions>
<bindingElementExtensions>
<add name="missingWSAddressingHeadersTextEncoding" type="DigipoortConnector.Api.Digipoort_Services.Extensions.WSAddressingEncodingBindingElementExtension, DigipoortConnector.Api"/>
</bindingElementExtensions>
</extensions>
The only thing I can assume that causes this issue is that at BUILD TIME (When the WSDL will be generated?) my custom encoder is NOT used correctly. But then at run-time it will be used, even though the fallback encoder that was used to generate the WSDL (Textencoder with default SOAP 1.2) is expecting 1.2...?
I have tried using the WCF GUI editor (Right click on web.config and select Edit WCF configuration). When I open it, it says that the DLL of my custom encoder can't be found. I can go ahead and add it manually, but saving and restarting the editor yields the same result. When inspecting my custom binding and selecting my custom encoder, it shows no results:
Although this could also be because my encoding extension class does not have any other properties..
I am totally at a loss here! Please help me figure this out!
#gnud 's answer was the fix!
Implement the IWsdlExportExtension interface.
Follow the MS docs of the Encoder, BindingElement and Factory.
https://learn.microsoft.com/en-us/dotnet/api/system.servicemodel.channels.messageencodingbindingelement?view=netframework-4.7.1
https://learn.microsoft.com/en-us/dotnet/api/system.servicemodel.channels.messageencoderfactory?view=netframework-4.7.1
https://learn.microsoft.com/en-us/dotnet/api/system.servicemodel.channels.messageencoder?view=netframework-4.7.1

IEndpointBehavior life cycle / logging service calls

I'm trying to log all outbound requests that go to service references, including the full request and response body. I thought I had a solution using behaviorExtensions but, after deploying, it became clear that the extension was shared between multiple requests.
Here's my current code:
public class LoggingBehaviorExtender : BehaviorExtensionElement
{
public override Type BehaviorType => typeof(LoggingRequestExtender);
protected override object CreateBehavior() { return new LoggingRequestExtender(); }
}
public class LoggingRequestExtender : IClientMessageInspector, IEndpointBehavior
{
public string Request { get; private set; }
public string Response { get; private set; }
#region IClientMessageInspector
public virtual object BeforeSendRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel)
{
Request = request.ToString();
Response = null;
return null;
}
public virtual void AfterReceiveReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
{
Response = reply.ToString();
}
#endregion
#region IEndpointBehavior
public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters) { }
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
clientRuntime.MessageInspectors.Add(this);
}
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher) { }
public void Validate(ServiceEndpoint endpoint) { }
#endregion
}
Then, when I reach the point to log, I extract the behavior...
var lre = client.Endpoint.Behaviors.OfType<LoggingRequestExtender>().FirstOrDefault();
var req = lre?.Request;
var resp = lre?.Response;
Adding debugging logging to the LoggingRequestExtender, I found it was only instantiated once for multiple requests.
Is there a way to make sure this behavior class is instantiated fresh for each thread? Or is there a better way of getting the full request / response body when making service calls?
Edit / Partial answer:
Since writing this I have discovered that the value returned by BeforeSendRequest is passed into AfterReceiveReply as the correlationState so I can connect the request and response using a guid:
public virtual object BeforeSendRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel)
{
var guid = Guid.NewGuid();
WebServiceLog.LogCallStart(guid, channel.RemoteAddress.ToString(), request.ToString());
return guid;
}
public virtual void AfterReceiveReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
{
Guid guid = (Guid)correlationState;
WebServiceLog.LogCallEnd(guid, reply.ToString());
}
I see two flaws to this approach. One, which is livable, is that this requires a log insert and then update rather than a single insert.
The second is more of an issue: In the case of an exception (e.g. timeout), we never hit AfterRecieveSupply so the log doesn't know what happened. I can separately log the exception...
try
{
response = client.SomeFunction(request);
}
catch (Exception ex)
{
AppLog.Error("Some function failed", ex);
}
... but I can't see a way of accessing the guid outside of BeforeSendRequest / AfterReceiveReply so I have nothing to tie the exception log to the service request log.
There are several approaches to this.
1, The situation you have described with having to log calls separately doesn't have to be like that. If your WCF service is in a non load balanced server just add the request to a MemoryCache using the Guid as a key. When the request comes in then pull off the request and log in one go. To capture the timed out calls you could run a process on a thread that would check the MemoryCache every x minutes to pull out and log (using an adequate lock to ensure thread saftey).
If the WCF service is in a load balanced environment then all you do is the same as above but store to a no sql type data store.
2, Is the code that makes the outbound calls within your scope for change? If so, you can forgo creating a behavior extension and create a bespoke message logger instead. Using a class that implements IDisposable you can write nice code like this..
RequestMessage request = new RequestMessage();
ResponseMessage response = null;
using (_messageLogger.LogMessage(request, () => response, CallContextHelper.GetContextId(), enabled))
{
response = _outboundService.DoSomething(request);
}
This will then not need another process to capture any timed out threads which will be handled in the dispose method.
If you need more clarity then let me know, hopefully this helps you...

Using types, declared in Web Service 1, in Web Service 2. Asp.Net C#

I have WebService1. Class "MapObject" declared in WebService1's solution.
[WebMethod]
public void GetRoute(MapObject from, MapObject to)
{
// Something here
return;
}
WebService2 is using methods of WebService1. Also, WebService2 has method
[WebMethod]
public void TestMethod(MapObject obj)
{
/// something here
}
But when I try to call TestMethod I recieve
System.IndexOutOfRangeException: Индекс находился вне границ массива.
в System.Web.Services.Protocols.HttpServerType..ctor(Type type)
в System.Web.Services.Protocols.HttpServerProtocol.Initialize()
в System.Web.Services.Protocols.ServerProtocolFactory.Create(Type type, HttpContext context, HttpRequest request, HttpResponse response, Boolean& abortProcessing)
This is because MapObject can not be serialized, but I can not understand why.
I would like to know how I can solve this problem, if I need to use types of WebService1 in methods of WebService2.

How to look at the actual SOAP request/response in C#

I have added a wsdl file in my project as a service reference. The application sends SOAP messages to a particular device which then sends the response in SOAP format.
Is there a way to look at the actual SOAP message that is wrapped in XML? Having to turn on wireshark to look at the SOAP messages gets tedious.
You can use the SVCTraceViewer to trace what are the messages that are being sent to and fro for each service call. You just have to set up the config and WCF builds the log files with the .svclog extension.
More details on this tool and its associated configuration is here. This does not require any 3rd party tool or network inspectors to be run etc... This is out of the box from Microsoft.
You are probably looking for SOAP extension,
look at this post:
Get SOAP Message before sending it to the WebService in .NET
Use Fiddler to inspect the messages. Ref: Using fiddler.
In case of WCF it has a less-known way to intercept original XML - custom MessageEncoder. It works on low level, so it captures real byte content including any malformed xml.
If you want use this approach you need to wrap a standard textMessageEncoding with custom message encoder as new binding element and apply that custom binding to endpoint in your config.
Also there is an example how I did it in my project -
wrapping textMessageEncoding, logging encoder, custom binding element and config.
I just wrapped the SOAP xml writer method and then inside the method made an event when the writing is flushed:
protected override XmlWriter GetWriterForMessage(SoapClientMessage message, int bufferSize)
{
VerboseXmlWriter xmlWriter = new VerboseXmlWriter(base.GetWriterForMessage(message, bufferSize));
xmlWriter.Finished += XmlWriter_Finished;
}
The Class for the VerboseXmlWriter goes like that (just the idea):
public sealed class VerboseXmlWriter : XmlWriter
{
private readonly XmlWriter _wrappedXmlWriter;
private readonly XmlTextWriter _buffer;
private readonly System.IO.StringWriter _stringWriter;
public event EventHandler Finished;
private void OnFinished(StringPayloadEventArgs e)
{
EventHandler handler = Finished;
handler?.Invoke(this, e);
}
public VerboseXmlWriter(XmlWriter implementation)
{
_wrappedXmlWriter = implementation;
_stringWriter = new System.IO.StringWriter();
_buffer = new XmlTextWriter(_stringWriter);
_buffer.Formatting = Formatting.Indented;
}
~VerboseXmlWriter()
{
OnFinished(new StringPayloadEventArgs(_stringWriter.ToString()));
}
public override void Flush()
{
_wrappedXmlWriter.Flush();
_buffer.Flush();
_stringWriter.Flush();
}
public string Xml
{
get
{
return _stringWriter?.ToString();
}
}
public override WriteState WriteState => _wrappedXmlWriter.WriteState;
public override void Close()
{
_wrappedXmlWriter.Close();
_buffer.Close();
}
public override string LookupPrefix(string ns)
{
return _wrappedXmlWriter.LookupPrefix(ns);
}
public override void WriteBase64(byte[] buffer, int index, int count)
{
_wrappedXmlWriter.WriteBase64(buffer, index, count);
_buffer.WriteBase64(buffer, index, count);
}
public override void WriteSurrogateCharEntity(char lowChar, char highChar)
{
_wrappedXmlWriter.WriteSurrogateCharEntity(lowChar, highChar);
_buffer.WriteSurrogateCharEntity(lowChar, highChar);
}
and so on...
Implement the interface XmlWriter with the same structure as the example overrides. I also made an event-args class to transport the SOAP message out.
public class StringPayloadEventArgs : EventArgs
{
public string Payload { get; }
public StringPayloadEventArgs(string payload)
{
Payload = payload;
}
}
You can also use the same idea for the incomming SOAP messages.

Getting WCF message body before deserialization

I am implementing WCF service that exposes a method whose [OperationContract] is [XmlSerializerFormat]. I sometimes get request whose body is not valid XML. In such cases I want to log the original body, so I can know why it didn't constitute valid XML. However, I can't get it from the Message object, see my attempts (by implementing IDispatchMessageInspector interface):
public object IDispatchMessageInspector.AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
{
request.ToString(); // "... Error reading body: System.Xml.XmlException: The data at the root level is invalid. Line 1, position 1. ..."
request.WriteBody(...); // Serialization Exception, also in WriteMessage and other Write* methods
request.GetReaderAtBodyContents(...); // Same
HttpRequestMessageProperty httpRequest = (HttpRequestMessageProperty)request.Properties[HttpRequestMessageProperty.Name]; // no body in httpRequest
}
When looking in watch, request.messageData appears to contain the body - but that's a private member.
How can I get the message buffer without trying to deserialize it?
Yes, you need custom MessageEncoder, unlike message inspectors (IDispatchMessageInspector / IClientMessageInspector) it sees original byte content including any malformed XML data.
However it's not trivial how to implement this approach. You have to wrap a standard textMessageEncoding as custom binding element and adjust config file to use that custom binding.
Also you can see as example how I did it in my project - wrapping textMessageEncoding, logging encoder, custom binding element and config.
For the opposite direction (I am writing a WCF client and the server returns invalid XML), I was able to extract the raw reply message in IClientMessageInspector.AfterReceiveReply by
accessing the internal MessageData property of reply via Reflection, and then
accessing its Buffer property, which is an ArraySegment<byte>.
Something similar might be available for the request message on the server side; so it might be worth examining the request variable in the debugger.
I'm aware that this is not exactly what you are asking for (since you are on the server side, not on the client side), and I'm also aware that using reflection is error-prone and ugly. But since the correct solution is prohibitively complex (see baur's answer for details) and this "raw dump" is usually only required for debugging, I'll share my code anyways, in case it is helpful to someone in the future. It works for me on .NET Framework 4.8.
public void AfterReceiveReply(ref Message reply, object correlationState)
{
object messageData = reply.GetType()
.GetProperty("MessageData",
BindingFlags.NonPublic | BindingFlags.Instance)
.GetValue(reply, null);
var buffer = (ArraySegment<byte>)messageData.GetType()
.GetProperty("Buffer")
.GetValue(messageData, null);
byte[] rawData =
buffer.Array.Skip(buffer.Offset).Take(buffer.Count).ToArray();
// ... do something with rawData
}
And here's the full code of the EndpointBehavior:
public class WcfLogger : IEndpointBehavior
{
public byte[] RawLastResponseBytes { get; private set; }
// We don't need these IEndpointBehavior methods
public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters) { }
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher) { }
public void Validate(ServiceEndpoint endpoint) { }
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
clientRuntime.ClientMessageInspectors.Add(new MessageCaptureInspector(this));
}
internal class MessageCaptureInspector : IClientMessageInspector
{
private WcfLogger logger;
public MessageCaptureInspector(WcfLogger logger)
{
this.logger = logger;
}
public void AfterReceiveReply(ref Message reply, object correlationState)
{
// Ugly reflection magic. We need this for the case where
// the reply is not valid XML, and, thus, reply.ToString()
// only contains an error message.
object messageData = reply.GetType()
.GetProperty("MessageData",
BindingFlags.NonPublic | BindingFlags.Instance)
.GetValue(reply, null);
var buffer = (ArraySegment<byte>)messageData.GetType()
.GetProperty("Buffer")
.GetValue(messageData, null);
logger.RawLastResponseBytes =
buffer.Array.Skip(buffer.Offset).Take(buffer.Count).ToArray();
}
public object BeforeSendRequest(ref Message request, IClientChannel channel)
{
return null;
}
}
}
Usage:
var logger = new WcfLogger();
myWcfClient.Endpoint.EndpointBehaviors.Add(logger);
try
{
// ... call WCF method that returns invalid XML
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
File.SaveAllBytes(#"C:\temp\raw_response.bin", logger.RawLastResponseBytes);
// Use the exception message and examine raw_response.bin with
// a hex editor to find the problem.
UPDATE
Some others that have run into this issue appear to have created a Customer Message Encoder.
A message encoding binding element serializes an outgoing Message and
passes it to the transport, or receives the serialized form of a
message from the transport and passes it to the protocol layer if
present, or to the application, if not present.

Categories