WCF + REST: Where is the request data? - wcf

I'm currently developing a WCF RESTful service. Within the validation of the POST data, I am throwing exceptions if the request XML does not conform to our business rules.
The goal is to send an e-mail to the appropriate staff if a request comes in that considered invalid. But, along with the incoming request headers, method and URI, I'd like to also send the XML that was posted.
I have not been able to find a way to access this data. Is WCF actually destroying the request body/data before I have a chance to access it or am I missing something?
Your help is appreciated as I'm confused as to why I can't access the request data.

This unfortunately isn't supported- we had a similar need, and did it by calling internal members with reflection. We just use it in an error handler (so we can dump the raw request), but it works OK. I wouldn't recommend it for a system you don't own and operate though (eg, don't ship this code to a customer), since it can change at any time with a service pack or whatever.
public static string GetRequestBody()
{
OperationContext oc = OperationContext.Current;
if (oc == null)
throw new Exception("No ambient OperationContext.");
MessageEncoder encoder = oc.IncomingMessageProperties.Encoder;
string contentType = encoder.ContentType;
Match match = re.Match(contentType);
if (!match.Success)
throw new Exception("Failed to extract character set from request content type: " + contentType);
string characterSet = match.Groups[1].Value;
object bufferedMessage = operationContextType.InvokeMember("request",
BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.GetField,
null, oc, null);
//TypeUtility.AssertType(bufferedMessageType, bufferedMessage);
object messageData = bufferedMessageType.InvokeMember("MessageData",
BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.GetProperty,
null, bufferedMessage, null);
//TypeUtility.AssertType(jsonBufferedMessageDataType, messageData);
object buffer = jsonBufferedMessageDataType.InvokeMember("Buffer",
BindingFlags.Instance | BindingFlags.Public | BindingFlags.GetProperty,
null, messageData, null);
ArraySegment<byte> arrayBuffer = (ArraySegment<byte>)buffer;
Encoding encoding = Encoding.GetEncoding(characterSet);
string requestMessage = encoding.GetString(arrayBuffer.Array, arrayBuffer.Offset, arrayBuffer.Count);
return requestMessage;
}

So, if you declare your contract something like:
[WebInvoke(Method = "POST", UriTemplate = "create", ResponseFormat=WebMessageFormat.Json)]
int CreateItem(Stream streamOfData);
(you can use XML instead)
The streamOfData should be the body of an HTTP POST. You can deserialize it using something like:
StreamReader reader = new StreamReader(streamId);
String res = reader.ReadToEnd();
NameValueCollection coll = HttpUtility.ParseQueryString(res);
It's working like that for us, at least. You may want to use a different approach to get the string into an XMLDocument or something. This works for our JSON posts. Might not be the most elegant solution, but it is working.
I hope this helps.
Glenn

Try this,
OperationContext.Current.RequestContext.RequestMessage

Here's how you do it without reflection:
using (var reader = OperationContext.Current.RequestContext.RequestMessage.GetReaderAtBodyContents ()) {
if (reader.Read ())
return new string (Encoding.ASCII.GetChars (reader.ReadContentAsBase64 ()));
return result;
}
}
If the reader is a HttpStreamXmlDictionaryReader (as it was in my case), the class's implementation of the method ReadContentAsBase64(byte[] buffer, int index, int count) simply passes these parameters to the Stream.Read method.
Once I have the byte[] I convert the bytes to a string via ASCII encoding. For a proper implementation, you could use the content type & encoding from the message's headers to do per HTTP spec.

You could arrest the HttpApplication.Request.InputStream in a custom HttpModule of the WCF Service, read the stream and again set its position to 0 in the custom HttpModule's event handler. Then store it in session and access it further in the actual OperationContract.
For example:
public class CustomModule : IHttpModule
{
public void Dispose()
{
}
public void Init(HttpApplication context)
{
context.AcquireRequestState +=context_AcquireRequestState;
}
void context_AcquireRequestState(object sender, EventArgs e)
{
HttpApplication application = sender as HttpApplication;
Stream str = application.Request.InputStream;
StreamReader sr = new StreamReader(str);
string req = sr.ReadToEnd();
str.Position = 0;
application.Session["CurrentRequest"] = req;
}
}

Related

How Dynamically change URL in a WCF Custom Behavior

Class is defined as follows:
public class BizTalkRESTTransmitHandler : IClientMessageInspector
I'm a method with this signature:
public object BeforeSendRequest(ref Message request, IClientChannel channel)
So I think I need to manipulate the channel object.
The reason is this is being using in BizTalk 2010 SendPort to support JSON.
I tried this so far:
if (channel.RemoteAddress.Uri.AbsoluteUri == "http://api-stage2.mypartner.com/rest/events/2/"
|| channel.RemoteAddress.Uri.AbsoluteUri == "http://api.mypartner.com/rest/events/2/")
{
//TODO - "boxout" will become a variable obtained by parsing the message
Uri newUri = new Uri(channel.RemoteAddress.Uri.AbsoluteUri + "boxout");
channel.RemoteAddress.Uri = newUri;
}
Above gives compile error: "System.ServiceModel.EndpointAddress.Uri" cannot be assigned to - it is ready only" RemoteAddress seems to be read only as well.
I have referenced these questions but they don't use channel object.
Assign a URL to Url.AbsoluteUri in ASP.NET, and
WCF change endpoint address at runtime
But they don't seem to be dealing with channel object.
Update 1: I tried the following:
//try create new channel to change URL
WebHttpBinding myBinding = new WebHttpBinding();
EndpointAddress myEndpoint = new EndpointAddress(newURL);
ChannelFactory<IClientChannel> myChannelFactory = new ChannelFactory<IClientChannel>(myBinding, myEndpoint); //Change to you WCF interface
IClientChannel myNewChannel = myChannelFactory.CreateChannel();
channel = myNewChannel; //replace the channel parm passed to us
but it gave this error:
System.InvalidOperationException: Attempted to get contract type for IClientChannel, but that type is not a ServiceContract, nor does it inherit a ServiceContract.
IClientMessageInspector is not the right place the manipulate the Channel, you should use IEndpointBehavior instead:
From MSDN
Implements methods that can be used to extend run-time behavior for an
endpoint in either a service or client application.
Here is a simple example:
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{
Uri endpointAddress = endpoint.Address.Uri;
string address = endpointAddress.ToString();
if (address == "http://api-stage2.mypartner.com/rest/events/2/"
|| address == "http://api.mypartner.com/rest/events/2/")
{
//TODO - "boxout" will become a variable obtained by parsing the message
Uri newUri = new Uri(address + "boxout");
ServiceHostBase host = endpointDispatcher.ChannelDispatcher.Host;
ChannelDispatcher newDispatcher = this.CreateChannelDispatcher(host, endpoint, newUri);
host.ChannelDispatchers.Add(newDispatcher);
}
}
Here you can read the excelent post of Carlos Figueira about IEndpointBehavior:
https://blogs.msdn.microsoft.com/carlosfigueira/2011/04/04/wcf-extensibility-iendpointbehavior/
Another alternative is to implement a simple Routing with WCF, here is link with an example:
WCF REST service url routing based on query parameters
Hope it helps.
Using the interface IEndpointBehavior, you'll have access to the ApplyClientBehavior method, which exposes the ServiceEndPoint instance.
Now you can change the value for the Address by defining a new EndpointAddress instance.
public class MyCustomEndpointBehavior : IEndpointBehavior
{
public void AddBindingParameters(ServiceEndpoint serviceEndpoint, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
{
}
public void ApplyClientBehavior(ServiceEndpoint serviceEndpoint, System.ServiceModel.Dispatcher.ClientRuntime behavior)
{
serviceEndpoint.Address = new System.ServiceModel.EndpointAddress("http://mynewaddress.com");
}
public void ApplyDispatchBehavior(ServiceEndpoint serviceEndpoint, System.ServiceModel.Dispatcher.EndpointDispatcher endpointDispatcher)
{
}
public void Validate(ServiceEndpoint serviceEndpoint)
{
}
}
I might be a bit too late but hoe it helps a bit.
I recently had a similar objective (also related to biztalk) where I needed to change the url based on some value sent on the message.
I tried using the ApplyDispatchBehavior method but it was never called and also, I couldn't see how to access the message from here so I started looking at method BeforeSendRequest (in the Inspector class).
Here is what i came up with:
object IClientMessageInspector.BeforeSendRequest(ref Message request, IClientChannel channel)
{
var queryDictionary = HttpUtility.ParseQueryString(request.Headers.To.Query);
string parameterValue = queryDictionary[this.BehaviourConfiguration.QueryParameter];
//Only change parameter value if it exists
if (parameterValue != null)
{
MessageBuffer buffer = request.CreateBufferedCopy(Int32.MaxValue);
request = buffer.CreateMessage();
//Necessary in order to read the message without having WCF throwing and error saying
//the messas was already read
var reqAux = buffer.CreateMessage();
//For some reason the message comes in binary inside tags <Binary>MESSAGE</Binary>
using (MemoryStream ms = new MemoryStream(Convert.FromBase64String(reqAux.ToString().Replace("<Binary>", "").Replace("</Binary>", ""))))
{
ms.Position = 0;
string val = ExtractNodeValueByXPath(ms, this.BehaviourConfiguration.FieldXpath);
queryDictionary.Set(this.BehaviourConfiguration.QueryParameter, DateTime.Now.ToString("yyyyMMddHHmmssfff") + "_" +
this.BehaviourConfiguration.Message + (string.IsNullOrWhiteSpace(val) ? string.Empty : "_" + val) + ".xml");
UriBuilder ub = new UriBuilder(request.Headers.To);
ub.Query = queryDictionary.ToString();
request.Headers.To = ub.Uri;
}
}
return null;
}
So, I discovered that, messing with the request.Headers.To I could change the endpoint.
I had several problems getting the message content and most examples on the internet (showing to use the MessageBuffer.CreateNavigator or Message.GetBody< string > which was always throwing an expcetion i couldn't get around) would not give me the biztalk message but rather the soap message?... not sure but it had a node header, body and inside the body there was some base64 string which was not my biztalk message.
Also, as you can see in Convert.FromBase64String(reqAux.ToString().Replace("<Binary>", "").Replace("</Binary>", "")), I had to do this ugly replaces. I don't don't why this comes in base64, probably some WCF configuration?, but by doing it, I could then look for my value.
NOTE: I haven't fully tested this, but so far it as worked for my examples.
By the way, any idea on what can i switch my MemoryStream with so it becomes a more streaming solution?

WCF WebInvoke with query string parameters AND a post body

I'm fairly new to web services and especially WCF so bear with me.
I'm writing an API that takes a couple of parameters like username, apikey and some options, but I also need to send it a string which can be a few thousands words, which gets manipulated and passed back as a stream. It didn't make sense to just put it in the query string, so I thought I would just have the message body POSTed to the service.
There doesn't seem to be an easy way to do this...
My operation contract looks like this
[OperationContract]
[WebInvoke(Method = "POST", BodyStyle = WebMessageBodyStyle.Bare,
UriTemplate="Method1?email={email}&apikey={apikey}"+
"&text={text}&quality={qual}", BodyStyle = WebMessageBodyStyle.Bare)]
Stream Method1(string email, string apikey, string text, string qual);
And this works. But it is the 'text' parameter I want to pull out and have in the post body. One thing I read said to have a stream as another parameter, like this:
Stream Method1(string email, string apikey, string qual, Stream text);
which I could then read in. But that throws an error saying that if I want to have a stream parameter, that it has to be the only parameter.
So how can I achieve what I am trying to do here, or is it no big deal to send up a few thousand words in the query string?
https://social.msdn.microsoft.com/Forums/vstudio/en-US/e2d074aa-c3a6-4e78-bd88-0b9d24b561d1/how-to-declare-post-parameters-in-wcf-rest-contract?forum=wcf
Best answer I could find that tackles this issue and worked for me so I could adhere to the RESTful standards correctly
A workaround is to not declare the query parameters within the method signature and just manually extract them from the raw uri.
Dictionary<string, string> queryParameters = WcfUtils.QueryParameters();
queryParameters.TryGetValue("email", out string email);
// (Inside WcfUtils):
public static Dictionary<string, string> QueryParameters()
{
// raw url including the query parameters
string uri = WebOperationContext.Current.IncomingRequest.UriTemplateMatch;
return uri.Split('?')
.Skip(1)
.SelectMany(s => s.Split('&'))
.Select(pv => pv.Split('='))
.Where(pv => pv.Length == 2)
.ToDictionary(pv => pv[0], pv => pv[1].TrimSingleQuotes());
}
// (Inside string extension methods)
public static string TrimSingleQuotes(this string s)
{
return (s != null && s.Length >= 2 && s[0] == '\'' && s[s.Length - 1] == '\'')
? s.Substring(1, s.Length - 2).Replace("''", "'")
: s;
}
Ended up solving simply by using WebServiceHostFactory

Returning Azure BLOB from WCF service as a Stream - Do we need to close it?

I have a simple WCF service that exposes a REST endpoint, and fetches files from a BLOB container. The service returns the file as a stream. i stumbled this post about closing the stream after the response has been made :
http://devdump.wordpress.com/2008/12/07/disposing-return-values/
This is my code:
public class FileService
{
[OperationContract]
[WebGet(UriTemplate = "{*url}")]
public Stream ServeHttpRequest(string url)
{
var fileDir = Path.GetDirectoryName(url);
var fileName = Path.GetFileName(url);
var blobName = Path.Combine(fileDir, fileName);
return getBlob(blobName);
}
private Stream getBlob(string blobName)
{
var account = CloudStorageAccount.FromConfigurationSetting("ConnectingString");
var client = account.CreateCloudBlobClient();
var container = client.GetContainerReference("data");
var blob = container.GetBlobReference(blobName);
MemoryStream ms = new MemoryStream();
blob.DownloadToStream(ms);
ms.Seek(0, SeekOrigin.Begin);
return ms;
}
}
So I have two question :
Should I follow the pattern mentioned in the post ?
If I change my return type to Byte[], what are Cons/Pros ?
( My client is Silverlight 4.0, just in case it has any effect )
I'd consider changing your return type to byte[]. It's tidier.
Stream implements IDisposable, so in theory the consumer of your method will need to call your code in a using block:
using (var receivedStream = new FileService().ServeHttpRequest(someUrl))
{
// do something with the stream
}
If your client definitely needs access to something that Stream provides, then by all means go ahead and return that, but by returning a byte[] you keep control of any unmanaged resources that are hidden under the covers.
OperationBehaviorAttribute.AutoDisposeParameters is set to TRUE by default which calls dispose on all the inputs/outputs that are disposable. So everything just works.
This link :
http://devdump.wordpress.com/2008/12/07/disposing-return-values/
explains how to manually control the process.

How to read the body of a Message object when implementing IDispatchMessageFormatter for custom (de)serialization of a WCF REST service?

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;

WCF 4.0 REST Upload MS-Excel File

I am trying to upload MS-Excel file through WCF-REST Service.
I used the solution given in below post:-
RESTful WCF service image upload problem
My POST Method is declared as:
[OperationContract]
[WebInvoke(Method = "POST", UriTemplate = "/RFQ")]
[WebContentType("application/octet-stream")]
void UploadRFQDoc(Stream fileContents);
When I debug, stream content is fine till the call goes, and when I attach service to debug, Stream fileContents parameter becomes null , and service returns with [Bad Request]. I am not sending large file (it is just 50 KB). I am using HttpClient to call the Post.
Here are the client code(RestClient is HttpClient).
protected void Post(string uri, Stream stream, int length)
{
var content = HttpContent.Create(output => CopyToStream(stream, output, length), "application/octet-stream", length);
Uri relativeUri = new Uri(uri, UriKind.Relative);
var resp = RestClient.Post(relativeUri, content);
ProcessResponse(resp);
}
void CopyToStream(Stream input, Stream output, int length)
{
var buffer = new byte[length];
var read = input.Read(buffer, 0, Convert.ToInt32 (length));
output.Write(buffer, 0, read);
}
Any clue what else can go wrong.
Many Thanks.
[WebContentType("application/octet-stream")] attribute was unnecessary here. I commented it out, and all worked fine :).