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

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;

Related

WCF Change message encoding from Utf-16 to Utf-8

I have a WCF connected service in a .net core application. I'm using the code that is autogenerated taken the wsdl definition.
Currently at the top of the request xml is including this line:
<?xml version="1.0" encoding="utf-16"?>
I can't find a simple way to change this encoding to UTF-8 when sending the request.
Since I could find a configuration option a the request/client objects, I've tried to change the message with following code at IClientMessageInspector.BeforeSendRequest
public object BeforeSendRequest(ref Message request, IClientChannel channel)
{
// Load a new xml document from current request
var xmlDocument = new XmlDocument();
xmlDocument.LoadXml(request.ToString());
((XmlDeclaration)xmlDocument.FirstChild).Encoding = Encoding.UTF8.HeaderName;
// Create streams to copy
var memoryStream = new MemoryStream();
var xmlWriter = XmlWriter.Create(memoryStream);
xmlDocument.Save(xmlWriter);
xmlWriter.Flush();
xmlWriter.Close();
memoryStream.Position = 0;
var xmlReader = XmlReader.Create(memoryStream);
// Create a new message
var newMessage = Message.CreateMessage(request.Version, null, xmlReader);
newMessage.Headers.CopyHeadersFrom(request);
newMessage.Properties.CopyProperties(request.Properties);
return null;
}
But the newMessage object still writes the xml declaration using utf-16. I can see it while debugging at the watch window since.
Any idea on how to accomplish this (should be) simple change will be very apreciated.
Which binding do you use to create the communication channel? The textmessageencoding element which has been contained in the CustomBinding generally contains TextEncoding property.
https://learn.microsoft.com/en-us/dotnet/framework/configure-apps/file-schema/wcf/textmessageencoding
WriteEncoding property specifies the character set encoding to be used for emitting messages on the binding. Valid values are
UnicodeFffeTextEncoding: Unicode BigEndian encoding
Utf16TextEncoding: Unicode encoding
Utf8TextEncoding: 8-bit encoding
The default is Utf8TextEncoding. This attribute is of type Encoding.
As for the specific binding, it contains the textEncoding property too.
https://learn.microsoft.com/en-us/dotnet/api/system.servicemodel.basichttpbinding.textencoding?view=netframework-4.0
Feel free to let me know if there is anything I can help with.

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?

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.

Exposing object of unknown type from a WCF service

[OperationContract]
public object GetDeserializedObject(int partyContactID)
{
PartyContact partyContact = GetPartyContactById(partyContactID);
ContactTermResultQueue resultQueue = GetContactTermResultQueueByID(partyContact.TemplateQueueID);
byte[] contactDataSetArray = resultQueue.QueryResult;
//Getting DataSet from the byte array
BinaryFormatter binaryFormatter = new BinaryFormatter();
Stream mStreamtoRead = new MemoryStream(contactDataSetArray);
object o = binaryFormatter.Deserialize(mStreamtoRead);
mStreamtoRead.Close();
object returnData=null;
if (o.GetType().IsArray)
{
object[] os = o as object[];
var value = from vs in os where (int) (vs.GetType().GetProperty("PartyID").GetValue(vs, null)) == partyContact.PartyID select vs;
if (value.Count() > 0)
{
returnData = value.First();
}
}
return returnData;
}
As I don't know what type of data we are going to have in the database, so wanted to return the object from this service, but it is giving me an exception.
Please let me know how can I achieve this?
Thanks in advance
You can't return object and expect it will work. The reason is that behind this code WCF engine uses serialization. When client receives message it must be able to deserialize it back to some object but to be able to do that it must know what type of object it received.
If you want to send "unknown" data use XElement. Client will receive just XML and it will be its responsibility to deal with it (parse it, deserialize it or whatever).
You can do certain things with the "raw" Message data type - but it's really not pretty programming...
Read about it here:
How to pass arbitrary data in a Message object using WCF
WCF : Untyped messages on WCF operations.

WCF + REST: Where is the request data?

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;
}
}