I'm having a bit of trouble retrieving body from a wcf message. I am trying to implement WCF message inspector to validate messages against XSD schema.
The soap body looks like following:
<s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Header xmlns="http://www.test1.com">
<applicationID>1234</applicationID>
</Header>
<GetMatchRequest xmlns="http://www.tempuri.org">test</GetMatchRequest>
</s:Body>
The problem is when I try to get body it only gets partial body message. Gets only header element, ignores GetMatchRequest element(may be because of multiple namespaces…)
I am using following to get message body:
XmlDocument bodyDoc = new XmlDocument();
bodyDoc.Load( message.GetReaderAtBodyContents().ReadSubtree());
I have also tried following:
bodyDoc.Load( message.GetReaderAtBodyContents());
The code above results in error - This document already has a 'DocumentElement' node.
Can anyone please help in extracting body from a WCF message?
Thanks
Message.GetReaderAtBodyContents return the reader positioned not at the element, but at its first child. Usually the message body contains only a single root element, so you can load it directly. But in your message it contains multiple root elements (Header and GetMatchRequest), so if you want to load the whole body in a XmlDocument, you need to provide a wrapping element (the XmlDocument can have only one root element). In the example below I use <s:Body> as the wrapping element, but you could use anything you want. The code simply reads the body until it finds the end element (</s:Body>).
public class Post_a866abd2_bdc2_4d30_8bbc_2ce46df38dc4
{
public static void Test()
{
string xml = #"<s:Envelope xmlns:s=""http://schemas.xmlsoap.org/soap/envelope/"">
<s:Body xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance"" xmlns:xsd=""http://www.w3.org/2001/XMLSchema"">
<Header xmlns=""http://www.test1.com"">
<applicationID>1234</applicationID>
</Header>
<GetMatchRequest xmlns=""http://www.tempuri.org"">test</GetMatchRequest>
</s:Body>
</s:Envelope>";
Message message = Message.CreateMessage(XmlReader.Create(new StringReader(xml)), int.MaxValue, MessageVersion.Soap11);
Console.WriteLine(message);
XmlDocument bodyDoc = new XmlDocument();
MemoryStream ms = new MemoryStream();
XmlWriter w = XmlWriter.Create(ms, new XmlWriterSettings { Indent = true, IndentChars = " ", OmitXmlDeclaration = true });
XmlDictionaryReader bodyReader = message.GetReaderAtBodyContents();
w.WriteStartElement("s", "Body", "http://schemas.xmlsoap.org/soap/envelope/");
while (bodyReader.NodeType != XmlNodeType.EndElement && bodyReader.LocalName != "Body" && bodyReader.NamespaceURI != "http://schemas.xmlsoap.org/soap/envelope/")
{
if (bodyReader.NodeType != XmlNodeType.Whitespace)
{
w.WriteNode(bodyReader, true);
}
else
{
bodyReader.Read(); // ignore whitespace; maintain if you want
}
}
w.WriteEndElement();
w.Flush();
Console.WriteLine(Encoding.UTF8.GetString(ms.ToArray()));
ms.Position = 0;
XmlDocument doc = new XmlDocument();
doc.Load(ms);
Console.WriteLine(doc.DocumentElement.OuterXml);
}
}
Related
I am in a situation where when I get an HTTP 400 code from the server, it is a completely legal way of the server telling me what was wrong with my request (using a message in the HTTP response content)
However, the .NET HttpWebRequest raises an exception when the status code is 400.
How do I handle this? For me a 400 is completely legal, and rather helpful. The HTTP content has some important information but the exception throws me off my path.
It would be nice if there were some way of turning off "throw on non-success code" but if you catch WebException you can at least use the response:
using System;
using System.IO;
using System.Web;
using System.Net;
public class Test
{
static void Main()
{
WebRequest request = WebRequest.Create("http://csharpindepth.com/asd");
try
{
using (WebResponse response = request.GetResponse())
{
Console.WriteLine("Won't get here");
}
}
catch (WebException e)
{
using (WebResponse response = e.Response)
{
HttpWebResponse httpResponse = (HttpWebResponse) response;
Console.WriteLine("Error code: {0}", httpResponse.StatusCode);
using (Stream data = response.GetResponseStream())
using (var reader = new StreamReader(data))
{
string text = reader.ReadToEnd();
Console.WriteLine(text);
}
}
}
}
}
You might like to encapsulate the "get me a response even if it's not a success code" bit in a separate method. (I'd suggest you still throw if there isn't a response, e.g. if you couldn't connect.)
If the error response may be large (which is unusual) you may want to tweak HttpWebRequest.DefaultMaximumErrorResponseLength to make sure you get the whole error.
I know this has already been answered a long time ago, but I made an extension method to hopefully help other people that come to this question.
Code:
public static class WebRequestExtensions
{
public static WebResponse GetResponseWithoutException(this WebRequest request)
{
if (request == null)
{
throw new ArgumentNullException("request");
}
try
{
return request.GetResponse();
}
catch (WebException e)
{
if (e.Response == null)
{
throw;
}
return e.Response;
}
}
}
Usage:
var request = (HttpWebRequest)WebRequest.CreateHttp("http://invalidurl.com");
//... (initialize more fields)
using (var response = (HttpWebResponse)request.GetResponseWithoutException())
{
Console.WriteLine("I got Http Status Code: {0}", response.StatusCode);
}
Interestingly, the HttpWebResponse.GetResponseStream() that you get from the WebException.Response is not the same as the response stream that you would have received from server. In our environment, we're losing actual server responses when a 400 HTTP status code is returned back to the client using the HttpWebRequest/HttpWebResponse objects. From what we've seen, the response stream associated with the WebException's HttpWebResponse is generated at the client and does not include any of the response body from the server. Very frustrating, as we want to message back to the client the reason for the bad request.
I had similar issues when trying to connect to Google's OAuth2 service.
I ended up writing the POST manually, not using WebRequest, like this:
TcpClient client = new TcpClient("accounts.google.com", 443);
Stream netStream = client.GetStream();
SslStream sslStream = new SslStream(netStream);
sslStream.AuthenticateAsClient("accounts.google.com");
{
byte[] contentAsBytes = Encoding.ASCII.GetBytes(content.ToString());
StringBuilder msg = new StringBuilder();
msg.AppendLine("POST /o/oauth2/token HTTP/1.1");
msg.AppendLine("Host: accounts.google.com");
msg.AppendLine("Content-Type: application/x-www-form-urlencoded");
msg.AppendLine("Content-Length: " + contentAsBytes.Length.ToString());
msg.AppendLine("");
Debug.WriteLine("Request");
Debug.WriteLine(msg.ToString());
Debug.WriteLine(content.ToString());
byte[] headerAsBytes = Encoding.ASCII.GetBytes(msg.ToString());
sslStream.Write(headerAsBytes);
sslStream.Write(contentAsBytes);
}
Debug.WriteLine("Response");
StreamReader reader = new StreamReader(sslStream);
while (true)
{ // Print the response line by line to the debug stream for inspection.
string line = reader.ReadLine();
if (line == null) break;
Debug.WriteLine(line);
}
The response that gets written to the response stream contains the specific error text that you're after.
In particular, my problem was that I was putting endlines between url-encoded data pieces. When I took them out, everything worked. You might be able to use a similar technique to connect to your service and read the actual response error text.
Try this (it's VB-Code :-):
Try
Catch exp As WebException
Dim sResponse As String = New StreamReader(exp.Response.GetResponseStream()).ReadToEnd
End Try
An asynchronous version of extension function:
public static async Task<WebResponse> GetResponseAsyncNoEx(this WebRequest request)
{
try
{
return await request.GetResponseAsync();
}
catch(WebException ex)
{
return ex.Response;
}
}
This solved it for me:
https://gist.github.com/beccasaurus/929007/a8f820b153a1cfdee3d06a9c0a1d7ebfced8bb77
TL;DR:
Problem:
localhost returns expected content, remote IP alters 400 content to "Bad Request"
Solution:
Adding <httpErrors existingResponse="PassThrough"></httpErrors> to web.config/configuration/system.webServer solved this for me; now all servers (local & remote) return the exact same content (generated by me) regardless of the IP address and/or HTTP code I return.
I'm quite new to the Windows Phone dev and I have to do an application to communicate with a Restful API. Everything works fine to get the informations back from the API but my problem occurs when I try to update the content. For example, I have a profile and I try to update the user's information (change the city let's say). On the server side I can see that my update worked properly but when I go back to my profile in my WP app nothing has changed, the city is still the same as the old one. This is my code :
public MainPage()
{
InitializeComponent();
this.ApplicationBar = this.Resources["HomeBar"] as ApplicationBar;
Requester requester = new Requester();
requester.initGetRequest("/me/", GetResponseCallback, true);
}
private void GetResponseCallback(IAsyncResult asynchronousResult)
{
try
{
HttpWebRequest request = (HttpWebRequest)asynchronousResult.AsyncState;
HttpWebResponse response = (HttpWebResponse)request.EndGetResponse(asynchronousResult);
Stream streamResponse = response.GetResponseStream();
StreamReader streamRead = new StreamReader(streamResponse);
string read = streamRead.ReadToEnd();
GlobalData.GetInstance().user = JsonConvert.DeserializeObject<MeClass>(read);
Dispatcher.BeginInvoke(() =>
{
MessageBox.Show(read);
});
//Create the profile and stuff
streamResponse.Close();
streamRead.Close();
response.Close();
}
catch (WebException webException)
{
HttpStatusCode status = ((HttpWebResponse)webException.Response).StatusCode;
Dispatcher.BeginInvoke(() =>
{
MessageBox.Show(status.ToString());
});
}
}
I figured out that the string 'read' is always equal to the old one, even after the update so this is why the content is not updated but how can the response be exactly the same as before, even if the update worked fine on the server side (if I check in Postman after my update, I can see that my city is the new one). If I restart my app I can see the update.
I can also show you my initGetRequest() :
public void initGetRequest(String endPoint, Action<IAsyncResult> callback, Boolean header)
{
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url + endPoint);
if (header == true)
request.Headers["Authorization"] = GlobalData.GetInstance().Header;
request.BeginGetResponse(new AsyncCallback(callback), request);
}
Thank you for your help !
I finally found why my request was still the same even after the update. The HttpWebRequest uses a cache by default. I only added a small bit of code before calling my request :
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url + endPoint);
if (header == true)
request.Headers["Authorization"] = GlobalData.GetInstance().Header;
request.Headers[HttpRequestHeader.IfModifiedSince] = DateTime.UtcNow.ToString();
request.BeginGetResponse(new AsyncCallback(callback), request);
I had no idea about that cache so I hope this answer will help someone having the same issue !
I have several pdf files saved in ...WebContent/Manuals/filename.pdf that I am trying to display on my page. I am getting "Failed to Load PDF document" message in Chrome.
My Jsf:
<p:media value="#{reviewBean.manual}" player="pdf" height="600px" width="1000px" />
My #SessionScoped Bean:
public StreamedContent getManual() throws IOException {
String type = "application/pdf";
String path = "";
FacesContext context = FacesContext.getCurrentInstance();
if (context.getCurrentPhaseId() == PhaseId.RENDER_RESPONSE) {
return new DefaultStreamedContent();
} else {
path = "C:\\.....\\WebContent\\Manuals\\filename.pdf";
InputStream is = new ByteArrayInputStream(path.getBytes());
return new DefaultStreamedContent(is, type);
}
}
There is additional logic that i have left out for clarity which decides which pdf is displayed.
I have also tried the file path of /Manuals/filename.pdf as path
I tried following the below example:
How to bind dynamic content using <p:media>?
In my case I do not need to retrieve a value using <f:param
Is my file path incorrect to display the image? Or am I building the Stream incorrectly? Any guidance is much appreciated.
I solved this by merely returning the url as a String.
public String getManual() {
return user.getManuals().get(user.getLData().getDepart());
}
Where the returned value is the file path of the pdf: Manuals/filename.pdf
I have been trying to understand WCF internals. I tried to create custom formatters and message inspectors (both at the client and the server by following some online links) but have been facing issues.
My operationcontract method at the server simply reads a file (which has "testing" text in it) and returns a filestream. A custom dispatch formatter then tries to serialize the the stream received as a message. Following is the definition of custom serializer method at server -
public System.ServiceModel.Channels.Message SerializeReply(System.ServiceModel.Channels.MessageVersion messageVersion, object[] parameters, object result)
{
string content;
using(var reader = new StreamReader(((FileStream)result), Encoding.ASCII))
{ content = reader.ReadToEnd(); }
Message message = Message.CreateMessage(messageVersion, "http://tempuri.org/IService1/GetStreamVideoResponse", new CustomBodyWriter(content));
return message;
}
CustomBodyWriter is implemented as -
class CustomBodyWriter : BodyWriter
{
string content;
public RawBodyWriter(string content)
: base(true)
{
this.content = content;
}
protected override void OnWriteBodyContents(XmlDictionaryWriter writer)
{
writer.WriteStartElement("GetStreamVideoResponse");
writer.WriteAttributeString("xmlns", "http://tempuri.org/");
writer.WriteStartElement("GetStreamVideoResult");
writer.WriteString(content);
writer.WriteEndElement();
}
}
I think the xmldictionarywriter overrides the envelop when i write xml in OnWriteBodyContents method in CustomBodyWriter. But then how can i insert the response elements properly..
When I hook in to custom Inspector just to print the reply sent in BeforeSendReply method, I see following output at the console (which has an ending envelop element missing)-
<s:Envelope xmlns:a="http://www.w3.org/2005/08/addressing" xmlns:s="http://www.w
3.org/2003/05/soap-envelope">
<s:Header>
<a:Action s:mustUnderstand="1">http://tempuri.org/IService1/GetStreamVideoRe
sponse</a:Action>
<ActivityId CorrelationId="0e0a6aae-91fa-4f9d-94b2-8c390cb5493d" xmlns="http
://schemas.microsoft.com/2004/09/ServiceModel/Diagnostics">e452a645-e8c5-44bf-81
06-d821522b7883</ActivityId>
<a:RelatesTo>urn:uuid:72b789cb-cc0b-48ac-b73a-434040434ce1</a:RelatesTo>
</s:Header>
<s:Body>
<GetStreamVideoResponse xmlns="http://tempuri.org/">
<GetStreamVideoResult>**testing**</GetStreamVideoResult>
</GetStreamVideoResponse>
</s:Body>
At the client, i have created custom inspector which tries to deserialize message by simply reading the body part of message in a byte[] and creating a filestream over it which is then returned -
public object DeserializeReply(System.ServiceModel.Channels.Message message, object[] parameters)
{
var getBody = message.GetReaderAtBodyContents();
byte[] buffer = getBody.ReadContentAsBase64();
FileStream fs = new FileStream("c:\\test.txt", FileMode.OpenOrCreate);//not ideal but need for testing at present
fs.Write(buffer, 0, buffer.Length);
return fs;
}
how can i get the complete SOAP message that culminates with Envelop element.
Even the byte[] created at client has no value in it. What is wrong that I have been doing?
Thanks in anticipation..
I am extending WebHttpBehavior to expose a WCF REST service with customized serialization and deserialization (plus a certain number of other features that are not relevant to the problem).
The new behavior uses an implementation of IDispatchMessageFormatter to perform custom serialization and deserialization of POCOs served by the service and sent to it thanks to the SerializeReply and DeserializeRequest methods.
I can serve XML and JSON exactly how I need them in SerializeReply.
I can deserialize XML without a problem, however I can't seem to find the way to deserialize a JSON message because I can't obtain the plain text contained in the Message parameter of DeserializeRequest.
This is what the code in DeserializeRequest looks like right now:
if (format == System.ServiceModel.Web.WebMessageFormat.Json)
{
var data = ""; // TODO obtain plain text from Message object
var json = JsonConvert.DeserializeObject(data, paramType, new IsoDateTimeConverter(), new StringEnumConverter());
parameters[paramIndex] = json;
}
else
{
var serializer = new System.Xml.Serialization.XmlSerializer(paramType, string.Empty);
var reader = message.GetReaderAtBodyContents();
parameters[paramIndex] = serializer.Deserialize(reader);
}
I'm using Json.NET for JSON (de)serialization.
Any suggestions on how to obtain plain text from the Message object in order to deserialize it would be greatly appreciated.
If you think there's something wrong in my approach I'd also like to hear of it in the comments.
You need to make sure that the WebMessageFormat of the Message is set to Raw, otherwise WCF will use a the JsonMessageEncoder in order to create the Message which in turn won't allow you to get to the raw message content.
You do that by implementing a custom WebContentTypeMapper:
public class RawMapper : WebContentTypeMapper
{
public override WebContentFormat GetMessageFormatForContentType(string contentType)
{
return WebContentFormat.Raw;
}
}
...which needs to be applied to the WebHttpBinding:
webHttpBinding.ContentTypeMapper = new RawMapper();
..or via configuration:
<bindings>
<webHttpBinding>
<binding contentTypeMapper="Samples.RawMapper, MyContentTypeMapperAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
</webHttpBinding>
</bindings>
With that in place, you can then retrieve the request body as a String like that:
public void DeserializeRequest(Message message, object[] parameters)
{
var bodyReader = message.GetReaderAtBodyContents();
bodyReader.ReadStartElement("Binary");
byte[] rawBody = bodyReader.ReadContentAsBase64();
string messageAsString;
using (var reader = new StreamReader(new MemoryStream(rawBody)))
messageAsString = reader.ReadToEnd();
object jsonObj = JsonConvert.DeserializeObject(messageAsString);
parameters[0] = jsonObj;
}
Even if you are using WebMessageFormat.Json you can convert your message to string and then deserialize it to object inside DeserializeRequest method.
MemoryStream mss = new MemoryStream();
XmlDictionaryWriter writer = JsonReaderWriterFactory.CreateJsonWriter(mss);
message.WriteMessage(writer);
writer.Flush();
string messageBody = Encoding.UTF8.GetString(mss.ToArray());
var obj = JsonConvert.DeserializeObject(messageBody, operation.Messages[0].Body.Parts[0].Type);
parameters[0] = obj;