Explain read a message object once with WCF? - wcf

I have a MessageInspector which logs the messages that come through? What is the reason why you can only read a Message once and must create a copy? I have seen the documentation from MSDN that I need to create a buffered copy, but I don't know why it is implemented this way? Can someone explain it to me?
private static void SendRequest(string request)
{
var req = (HttpWebRequest) WebRequest.Create("http://urltoservice.svc/MethodToCall");
req.ContentType = "text/xml";
req.Method = "POST";
using (var stm = req.GetRequestStream())
{
using (var stmw = new StreamWriter(stm))
{
stmw.Write(request);
}
}
byte[] myData;
using (var webResponse = req.GetResponse())
{
var responseStream = webResponse.GetResponseStream();
myData = ReadFully(responseStream);
}
// Do whatever you need with the response
string responseString = Encoding.ASCII.GetString(myData);
}
If I don't have access to the server part or the ability to change the MessageInspector to use a buffered copy of the message, can I modify the message above to make a copy of a stream? If so, how would I go about doing that?

You can copy your message into a buffer and play with it.
More details about working with messages you can find on the following link: http://msdn.microsoft.com/en-us/library/ms734675.aspx

Related

How to directly set response body to a file stream in ASP.NET Core middleware?

Sample code below to write a file stream to Response.Body in an ASP.NET Core middleware doesn't work (emits empty response):
public Task Invoke(HttpContext context)
{
context.Response.ContentType = "text/plain";
using (var fs = new FileStream("/valid-path-to-file-on-server.txt", FileMode.Open)
using (var sr = new StreamReader(fs))
{
context.Response.Body = sr.BaseStream;
}
return Task.CompletedTask;
}
Any ideas what could be wrong with this approach of directly setting the context.Response.Body?
Note: any next middleware in the pipeline is skipped for no further processing.
Update (another example): a simple MemoryStream assignment doesn't work either (empty response):
context.Response.Body = new MemoryStream(Encoding.UTF8.GetBytes(DateTime.Now.ToString()));
No. You can never do that directly.
Note that context.Response.Body is a reference to an object (HttpResponseStream) that is initialized before it becomes available in HttpContext. It is assumed that all bytes are written into this original Stream. If you change the Body to reference (point to) a new stream object by context.Response.Body = a_new_Stream, the original Stream is not changed at all.
Also, if you look into the source code of ASP.NET Core, you'll find the Team always copy the wrapper stream to the original body stream at the end rather than with a simple replacement(unless they're unit-testing with a mocked stream). For example, the SPA Prerendering middleware source code:
finally
{
context.Response.Body = originalResponseStream;
...
And the ResponseCachingMiddleware source code:
public async Task Invoke(HttpContext httpContext)
{
...
finally
{
UnshimResponseStream(context);
}
...
}
internal static void UnshimResponseStream(ResponseCachingContext context)
{
// Unshim response stream
context.HttpContext.Response.Body = context.OriginalResponseStream;
// Remove IResponseCachingFeature
RemoveResponseCachingFeature(context.HttpContext);
}
As a walkaround, you can copy the bytes to the raw stream as below:
public async Task Invoke(HttpContext context)
{
context.Response.ContentType = "text/plain";
using (var fs = new FileStream("valid-path-to-file-on-server.txt", FileMode.Open))
{
await fs.CopyToAsync(context.Response.Body);
}
}
Or if you like to hijack the raw HttpResponseStream with your own stream wrapper:
var originalBody = HttpContext.Response.Body;
var ms = new MemoryStream();
HttpContext.Response.Body = ms;
try
{
await next();
HttpContext.Response.Body = originalBody;
ms.Seek(0, SeekOrigin.Begin);
await ms.CopyToAsync(HttpContext.Response.Body);
}
finally
{
response.Body = originalBody;
}
The using statements in the question causes your stream and stream reader to be rather ephemeral, so they will both be disposed. The extra reference to the steam in "body" wont prevent the dispose.
The framework disposes of the stream after sending the response. (The medium is the message).
In net 6 I found I was getting console errors when I tried to do this e.g.:
System.InvalidOperationException: Response Content-Length mismatch: too many bytes written (25247 of 8863).
The solution was to remove the relevant header:
context.Response.Headers.Remove("Content-Length");
await context.Response.SendFileAsync(filename);

Modify middleware response

My requirement: write a middleware that filters all "bad words" out of a response that comes from another subsequent middleware (e.g. Mvc).
The problem: streaming of the response. So when we come back to our FilterBadWordsMiddleware from a subsequent middleware, which already wrote to the response, we are too late to the party... because response started already sending, which yields to the wellknown error response has already started...
So since this is a requirement in many various situations -- how to deal with it?
Replace a response stream to MemoryStream to prevent its sending. Return the original stream after the response is modified:
public async Task Invoke(HttpContext context)
{
bool modifyResponse = true;
Stream originBody = null;
if (modifyResponse)
{
//uncomment this line only if you need to read context.Request.Body stream
//context.Request.EnableRewind();
originBody = ReplaceBody(context.Response);
}
await _next(context);
if (modifyResponse)
{
//as we replaced the Response.Body with a MemoryStream instance before,
//here we can read/write Response.Body
//containing the data written by middlewares down the pipeline
//finally, write modified data to originBody and set it back as Response.Body value
ReturnBody(context.Response, originBody);
}
}
private Stream ReplaceBody(HttpResponse response)
{
var originBody = response.Body;
response.Body = new MemoryStream();
return originBody;
}
private void ReturnBody(HttpResponse response, Stream originBody)
{
response.Body.Seek(0, SeekOrigin.Begin);
response.Body.CopyTo(originBody);
response.Body = originBody;
}
It's a workaround and it can cause performance problems. I hope to see a better solution here.
A simpler version based on the code I used:
/// <summary>
/// The middleware Invoke method.
/// </summary>
/// <param name="httpContext">The current <see cref="HttpContext"/>.</param>
/// <returns>A Task to support async calls.</returns>
public async Task Invoke(HttpContext httpContext)
{
var originBody = httpContext.Response.Body;
try
{
var memStream = new MemoryStream();
httpContext.Response.Body = memStream;
await _next(httpContext).ConfigureAwait(false);
memStream.Position = 0;
var responseBody = new StreamReader(memStream).ReadToEnd();
//Custom logic to modify response
responseBody = responseBody.Replace("hello", "hi", StringComparison.InvariantCultureIgnoreCase);
var memoryStreamModified = new MemoryStream();
var sw = new StreamWriter(memoryStreamModified);
sw.Write(responseBody);
sw.Flush();
memoryStreamModified.Position = 0;
await memoryStreamModified.CopyToAsync(originBody).ConfigureAwait(false);
}
finally
{
httpContext.Response.Body = originBody;
}
}
Unfortunately I'm not allowed to comment since my score is too low.
So just wanted to post my extension of the excellent top solution, and a modification for .NET Core 3.0+
First of all
context.Request.EnableRewind();
has been changed to
context.Request.EnableBuffering();
in .NET Core 3.0+
And here's how I read/write the body content:
First a filter, so we just modify the content types we're interested in
private static readonly IEnumerable<string> validContentTypes = new HashSet<string>() { "text/html", "application/json", "application/javascript" };
It's a solution for transforming nuggeted texts like [[[Translate me]]] into its translation. This way I can just mark up everything that needs to be translated, read the po-file we've gotten from the translator, and then do the translation replacement in the output stream - regardless if the nuggeted texts is in a razor view, javascript or ... whatever.
Kind of like the TurquoiseOwl i18n package does, but in .NET Core, which that excellent package unfortunately doesn't support.
...
if (modifyResponse)
{
//as we replaced the Response.Body with a MemoryStream instance before,
//here we can read/write Response.Body
//containing the data written by middlewares down the pipeline
var contentType = context.Response.ContentType?.ToLower();
contentType = contentType?.Split(';', StringSplitOptions.RemoveEmptyEntries).FirstOrDefault(); // Filter out text/html from "text/html; charset=utf-8"
if (validContentTypes.Contains(contentType))
{
using (var streamReader = new StreamReader(context.Response.Body))
{
// Read the body
context.Response.Body.Seek(0, SeekOrigin.Begin);
var responseBody = await streamReader.ReadToEndAsync();
// Replace [[[Bananas]]] with translated texts - or Bananas if a translation is missing
responseBody = NuggetReplacer.ReplaceNuggets(poCatalog, responseBody);
// Create a new stream with the modified body, and reset the content length to match the new stream
var requestContent = new StringContent(responseBody, Encoding.UTF8, contentType);
context.Response.Body = await requestContent.ReadAsStreamAsync();//modified stream
context.Response.ContentLength = context.Response.Body.Length;
}
}
//finally, write modified data to originBody and set it back as Response.Body value
await ReturnBody(context.Response, originBody);
}
...
private Task ReturnBody(HttpResponse response, Stream originBody)
{
response.Body.Seek(0, SeekOrigin.Begin);
await response.Body.CopyToAsync(originBody);
response.Body = originBody;
}
A "real" production scenario may be found here: tethys logging middeware
If you follow the logic presented in the link, do not forget to addhttpContext.Request.EnableRewind() prior calling _next(httpContext) (extension method of Microsoft.AspNetCore.Http.Internal namespace).

Xamarin Android - Can a WCF SOAP webservice use HttpClient for TLS 1.2?

I have a customer who requires TLS 1.2 for PCI compliance. Xamarin Android does not support TLS 1.2 very well. According to this
Native HttpClientHandler and this Transport Layer Security, you can either use HttpClient with their special mechanism to access the native Java support on Android 5 and higher, or you can use the ModernHttpClient.
However, WCF SOAP Webservice proxies generated with SvcUtil appear to use HttpWebRequest, and not HttpClient.
What's the recommended way to call WCF SOAP services using HttpClient (or ModernHttpClient)? Will I have to manually write my own interfaces or can I use the proxy classes and serialize/deserialize them myself? I'd rather not have to completely start from scratch, especially since it looks like TLS 1.2 is currently being added to Mono.
i have used that type of service and it is working here i have share relevant code please try it.
static void TryByWebRequest(string soapMethod)
{
XmlDocument soapEnvelopeXml = new XmlDocument();
soapEnvelopeXml.LoadXml(#"
<s:Envelope xmlns:s=""http://schemas.xmlsoap.org/soap/envelope/"">
<s:Body>
<" + soapMethod + #"
xmlns=""your URI""
xmlns:i=""http://www.w3.org/2001/XMLSchema-instance"">
<InputXMLString>
" +
System.Security.SecurityElement.Escape(inputXML)
+ #"
</InputXMLString>
<OutputXMLString/>
</" + soapMethod + #">
</s:Body>
</s:Envelope>");
using (Stream stream = request.GetRequestStream())
{
soapEnvelopeXml.Save(stream);
}
using (WebResponse response = request.GetResponse())
{
using (StreamReader rd = new StreamReader(response.GetResponseStream()))
{
string soapResult = rd.ReadToEnd();
Console.WriteLine(soapResult);
}
}
}
static HttpWebRequest CreateWebRequest(string soapMethod)
{
HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(#"Your .asmx URL ");
webRequest.Headers.Add(#"SOAPAction", "your URI \" + soapMethod);
webRequest.ContentType = "text/xml;charset=\"utf-8\"";
webRequest.Accept = "text/xml";
webRequest.Method = "POST";
return webRequest;
}
I got this working. Since this is a (hopefully) temporary solution, my goal was to create drop-in replacements for the Proxy-generated classes, and I got pretty close. The key was to figure out how to use the DataContractSerializer to create a SOAP envelope to send, and deserialize the results.
I was successful with everything except for the serialization of the SOAP envelope that gets sent to the web service. I ended up manually wrapping the XML in the <envelope> and <body> tags. Nothing that I did could get the DataContractSerializer to create these correctly, although the Body contents were ok. The Deserializer was able to handle the response from the web service without any problem though. WCF services are very picky about the format of the SOAP envelope, and getting the classes annotated just right was a challenge.
For each function call, I had to create a Request object that wraps the parameters being sent to the web service, and a Response object that wraps the out parameters and the return code.
These look something like this, where the FunctionName is the name of the WCF function call that the proxys generated.
// request envelope
[System.Runtime.Serialization.DataContractAttribute(Name = "FunctionName", Namespace = "http://tempuri.org/")]
public class FunctionName_Request
{
[System.Runtime.Serialization.DataMemberAttribute()]
public NameSpaceFunctionNameObject1 CallingObject1;
[System.Runtime.Serialization.DataMemberAttribute()]
public NameSpaceFunctionNameObject2 CallingObject2;
}
// response envelope
[System.Runtime.Serialization.DataContractAttribute(Name = "Envelope", Namespace = "http://schemas.xmlsoap.org/soap/envelope/")]
public class FunctionName_ResponseEnvelope
{
[System.Runtime.Serialization.DataContractAttribute(Name = "Body", Namespace = "http://tempuri.org/")]
public class FunctionName_ResponseBody
{
[System.Runtime.Serialization.DataContractAttribute(Name = "FunctionNameResponse", Namespace = "http://tempuri.org/")]
public class FunctionName_Response
{
[System.Runtime.Serialization.DataMemberAttribute()]
public FunctionNameReturnCodes Result;
[System.Runtime.Serialization.DataMemberAttribute()]
public FunctionNameResponseObject Response;
}
[System.Runtime.Serialization.DataMemberAttribute()]
public FunctionName_Response FunctionNameResponse;
}
[System.Runtime.Serialization.DataMemberAttribute()]
public FunctionName_ResponseBody Body;
}
Then, I can write a replacement function that my client code can call, which has exactly the same signature as the original Proxy-generated function.
// FunctionName
public FunctionNameReturnCodes FunctionName(out FunctionNameResponseObject Response, NameSpaceFunctionNameObject1 CallingObject1, NameSpaceFunctionNameObject2 CallingObject2)
{
// create the request envelope
FunctionName_Request req = new FunctionName_Request();
req.CallingObject1 = CallingObject1;
req.CallingObject2 = CallingObject2;
// make the call
FunctionName_ResponseEnvelope resp = MakeWCFCall<FunctionName_ResponseEnvelope>(_EndpointAddress, _ServerName, req);
// get the response object
Response = resp.Body.FunctionName_Response.Response;
// result
return resp.Body.FunctionName_Response.Result;
}
Finally, this is the function that actually serializes and deserializes the object into the HttpClient. In my case, these are synchronous, but you could easily adapt this to work in the standard async case. It's template so it works with any of the proxy-generated types.
/////////////////////////////////////////////////////////////////////
// make a WCF call using an HttpClient object
// uses the DataContractSerializer to serialze/deserialze the messages from the objects
//
// We manually add the <s:Envelope> and <s:Body> tags. There should be a way to get
// the DataContractSerializer to write these too, but everything I tried gave a message
// that was not able to be procesed by the service. This includes the Message object.
// Deserializing works fine, but serializing did not.
private T MakeWCFCall<T>(string strEndpoint, string strServerName, object SourceObject)
{
T Response = default(T);
string strSoapMessage = "";
string strSoapAction = "";
// get the Soap Action by using the DataContractAttribute's name
// start by getting the list of custom attributes.
// there should only be the one
object[] oaAttr = SourceObject.GetType().GetCustomAttributes(false);
if (oaAttr.Length > 0)
{
// iterate just in case there are more
foreach (DataContractAttribute oAttr in oaAttr)
{
// make sure we got one
if (oAttr != null)
{
// this is the action!
strSoapAction = oAttr.Name;
break;
}
}
}
// serialize the request into a string
// use a memory stream as the source
using (MemoryStream ms = new MemoryStream())
{
// create the DataContractSerializer
DataContractSerializer ser = new DataContractSerializer(SourceObject.GetType());
// serialize the object into the memory stream
ser.WriteObject(ms, SourceObject);
// seek to the beginning so we can read back out of the stream
ms.Seek(0, SeekOrigin.Begin);
// create the stream reader
using (var streamReader = new StreamReader(ms))
{
// read the message back out, adding the Envelope and Body wrapper
strSoapMessage = #"<s:Envelope xmlns:s = ""http://schemas.xmlsoap.org/soap/envelope/""><s:Body>" + streamReader.ReadToEnd() + #"</s:Body></s:Envelope>";
}
}
// now create the HttpClient connection
using (var client = new HttpClient(new NativeMessageHandler()))
{
//specify to use TLS 1.2 as default connection
System.Net.ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls;
// add the Soap Action header
client.DefaultRequestHeaders.Add("SOAPAction", "http://tempuri.org/" + strServerName + "/" + strSoapAction);
// encode the saop message
var content = new StringContent(strSoapMessage, Encoding.UTF8, "text/xml");
// post to the server
using (var response = client.PostAsync(new Uri(strEndpoint), content).Result)
{
// get the response back
var soapResponse = response.Content.ReadAsStringAsync().Result;
// create a MemoryStream to use for serialization
using (MemoryStream memoryStream = new MemoryStream(Encoding.UTF8.GetBytes(soapResponse)))
{
// create the reader
// set the quotas
XmlDictionaryReader reader = XmlDictionaryReader.CreateTextReader(
memoryStream,
Encoding.UTF8,
new XmlDictionaryReaderQuotas() { MaxArrayLength = 5000000, MaxBytesPerRead = 5000000, MaxStringContentLength = 5000000 },
null);
// create the Data Contract Serializer
DataContractSerializer serializer = new DataContractSerializer(typeof(T));
// deserialize the response
Response = (T)serializer.ReadObject(reader);
}
}
}
// return the response
return Response;
}
This approach allowed me to quickly write wrappers for all of my WCF service functions, and it's working well so far.

Posting GZip content using RestSharp

How do I post GZip data using RestSharp. I have the following code but it isn't working as I would expect:
var restRequest = new RestRequest(url, Method.POST)
{
Timeout = Constants.DefaultTimeoutMilliseconds
};
var dataStream = new MemoryStream();
using (var zipStream = new GZipStream(dataStream, CompressionMode.Compress))
{
using (var writer = new StreamWriter(zipStream))
{
writer.Write(new DotNetXmlSerializer().Serialize(content));
}
}
var compressedBytes = dataStream.ToArray();
restRequest.AddParameter("application/x-gzip", compressedBytes, ParameterType.RequestBody);
return _restClient.Execute<TResponseData>(restRequest);
When I run this and check the wireshark trace, the compressedBytes variable is posted as
'System.Byte[]' - as if ToString() has been called on it despite the parameter being a system.object.
If I pass the compressed byte array through as a string using both Convert.ToBase64String() and Encoding.Utf8.GetString() then I am unable to decompress the GZip at the server. I simply get 'System.IO.InvalidDataException: The magic number in GZip header is not correct. Make sure you are passing in a GZip'.
Is there any way of posting Gzipped data using RestSharp?
Make sure you've updated to the latest version of RestSharp (like 104.4.0) as this was a bug in a previous version.
I think this was fixed in 104.2 where the PUT or POST of binary data ended up with the System.Byte[] being represented as the string.
Update your NuGet reference and try it again. Good luck!
var body = "some string";
var dataStream = new MemoryStream();
byte[] dataToCompress = Encoding.UTF8.GetBytes(body);
using (var memoryStream = new MemoryStream())
{
using (var gzipStream = new GZipStream(memoryStream, CompressionLevel.Optimal))
{
gzipStream.Write(dataToCompress, 0, dataToCompress.Length);
}
dataStream = memoryStream;
}
var client = new RestClient("url");
var request = new RestRequest("", Method.POST);
var compressedBytes = dataStream.ToArray();
request.AddHeader("Content-Encoding", "gzip");
request.AddParameter("application/x-gzip", compressedBytes, ParameterType.RequestBody);
//client.RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true;
IRestResponse response = await client.ExecuteAsync(request);
Console.WriteLine(response.Content);

WCF message body showing <s:Body>... stream ...</s:Body> after modification

Trying to use MessageInspector to modify the message before the wcf service through the proxy. However while debugging the message body does not gets copied and body shows
<s:Body>... stream ...</s:Body>
What is the problem with the code?
public class CustomWCFMessageInspector : IClientMessageInspector
{
public object BeforeSendRequest(ref Message request, IClientChannel channel)
{
request = ModifyMessage(request);
return null;
}
private Message ModifyMessage(Message oldMessage)
{
Message newMessage = null;
MessageBuffer msgbuf = oldMessage.CreateBufferedCopy(int.MaxValue);
Message tmpMessage = msgbuf.CreateMessage();
XmlDictionaryReader xdr = tmpMessage.GetReaderAtBodyContents();
XDocument xd = ConvertToXDocument(xdr);
EmitTags(xd);
var ms = new MemoryStream();
var xw = XmlWriter.Create(ms);
xd.Save(xw);
xw.Flush();
xw.Close();
ms.Position = 0;
XmlReader xr = XmlReader.Create(ms);
newMessage = Message.CreateMessage(tmpMessage.Version, null, xr);
newMessage.Headers.CopyHeadersFrom(tmpMessage);
newMessage.Properties.CopyProperties(tmpMessage.Properties);
return newMessage;
}
}
Here is solution:
if you call Message.ToString() you will get
..stream..
Instead use System.Xml.XmlWriter. Here is a sample:
MessageBuffer buffer = reply.CreateBufferedCopy(Int32.MaxValue);
Message msg = buffer.CreateMessage();
StringBuilder sb = new StringBuilder();
using (System.Xml.XmlWriter xw = System.Xml.XmlWriter.Create(sb))
{
msg.WriteMessage(xw);
xw.Close();
}
Console.WriteLine("Message Received:\n{0}", sb.ToString());
The problem was that the newMessage body was not shown in the watch window after doing ToString()
Created the buffered copy of the message to be shown in the debugger.
MessageBuffer messageBuffer = newMessage.CreateBufferedCopy(int.MaxValue);
Message message = messageBuffer.CreateMessage();
So there is No problem in the code. It is just that the debugger is not showing the message body as mentioned in the link below
http://msdn.microsoft.com/en-us/library/ms734675(v=VS.90).aspx
in the Accessing the Message Body for Debugging section.
I suspect ToString will return what you are getting. ToString is often used for debugging, and hence only shows you basic information about the object. You need to do something like this in ConvertToXDocument:
XDocument x = XDocument.Load(xdr);