Is there a way to dynamically change the WebResponseFormat on a method given a parameter passed by the client? I default my WebResponseFormat to XML, but I want to give the client the opportunity to specify a format as JSON or XML and if none is specified, default to XML.
Currently I am doing the following:
[WebGet(UriTemplate = "objects", BodyStyle = WebMessageBodyStyle.Bare)]
[OperationContract]
List<SampleObject> GetObjects();
The user can call it via:
http://localhost/rest/myservice/objects
They then can specify a format by doing:
http://localhost/rest/myservice/objects?format=json
The problem is that when I try to set the response content type via:
WebOperationContext.Current.OutgoingResponse.ContentType = "application/json";
That just returns the XML but the browser attempts to process it like a JSON object instead of serializing the response as JSON.
Is this even possible with .NET 3.5 outside of using a Stream as the return value and serializing the response myself? If not, is there a better solution?
I was able to resolve this by doing the following:
[WebGet(UriTemplate = "objects", BodyStyle = WebMessageBodyStyle.Bare)]
[OperationContract]
List<SampleObject> GetObjects();
[WebGet(UriTemplate = "objects?format=json", BodyStyle = WebMessageBodyStyle.Bare, ResponseFormat = WebMessageFormat.Json)]
[OperationContract]
List<SampleObject> GetObjectsInJson();
It isn't pretty, but if format=xml is defined or left off, it will default to my operation contract, but if format=json is defined, it defaults to the second operation contract. This successfully returns the result as JSON and XML as desired.
For .NET 3.5 and WCF REST 3.5, I don't know of any way to do this elegantly.
.NET 4 and WCF REST in .NET 4 will support the "content negotiation" strategy that you use - just setting the ContentType = "application/json" will cause the service to automagically return JSON instead of XML.
So if there's any chance for you, wait for .NET 4 (should be out mid April 2010) and use that - it should offer lots of improvements in WCF anyway, especially in the WCF REST area.
Related
I am building a couple of REST web services with WCF. I have some POST services to which I pass a list of objects in the wrapped HTTP body like this:
[OperationContract]
[WebInvoke(
Method = "POST",
UriTemplate = "DoSomething/pages?term={term}",
BodyStyle = WebMessageBodyStyle.Wrapped)
]
MyResponseContainer DoSomethingFromPages(string term, List<Page> pages);
This works just fine. I can wrap the List<Page> into JSON or XML and put it into the body and it is deserialized automatically.
In a similar way I have another service where I read a binary stream (a file) from the HTTP body like this:
[OperationContract]
[WebInvoke(
Method = "POST",
UriTemplate = "DoSomething/upload?term={term}",
BodyStyle = WebMessageBodyStyle.Bare)
]
MyResponseContainer DoSomethingUploadFile(Stream httpBody, string term);
Now my question is: Can I somehow combine both ways? So basically I want to pass a list of custom objects together with a (file)stream. And I would like to avoid using multi-part forms style.
Thanks a lot!
Since updating our project to Visual Studio 2012 and a targeting the .NET framework 4.5 from 4.0, we've been intermittently experiencing serialization exception issues when attempting to return data from our service methods, though for purposes of debugging the problem, we've focused our attention on a single service method, GetPageSelectListForSite.
This methods interface declaration is the following:
[OperationContract]
[WebInvoke(Method = "GET",
ResponseFormat = WebMessageFormat.Json,
RequestFormat = WebMessageFormat.Json,
UriTemplate = "/Pages/Site/ListItems/{siteId}",
BodyStyle = WebMessageBodyStyle.Bare),
Description("")]
IEnumerable<PageInfoItem> GetPreviousPages(string siteId);
It is called with the same signature parameter, and the same IEnumerable data is returned inside of a controller; however, occassionally (once every 100 or so times) this will result in a serialization exception when the controller is trying to deserialize the REST service reply:
System.Runtime.Serialization.SerializationException: There was an error deserializing the object of type System.Collections.Generic.IEnumerable`1[[Synodine.Hkd.Models.PageInfoItem, Synodine.Hkd.Models, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]. Encountered unexpected character 'D'. ---> System.Xml.XmlException: Encountered unexpected character 'D'.
The full exception and corresponding stack trace is available here:
http://pastebin.com/jaqQDbru
PageInfoItem is a fairly simple class containing a string, and derives from ListInfoBase which contains a guid, some strings, some bools, and a DateTime.
Attempts to decorate the classes with [Serializable] AND/OR [DataContract]/[DataMember] have not had any effect. We have not been able to consistently reproduce the problem.
I should note that we've seen the serialization exception error occur on an extremely simple REST GET method, with response/request formats set to Json and BodyStyle of Bare that returns a single string. It doesn't seem to matter if the rest service method in question is a POST/GET or whether its style is set to Bare or Wrapped, though in all cases we are making use of the WebMessageFormat.Json.
We've tried serializing the data ourselves and logging it to see if the JSON was malformed in some way, but the JSON is valid and is identical across failed/succeeded requests.
This was the code we used to serialize the data:
var stream1 = new MemoryStream();
var ser = new DataContractJsonSerializer(typeof(IEnumerable<PageInfoItem>));
ser.WriteObject(stream1, PageInfoItems);
stream1.Position = 0;
var streamReader = new StreamReader(stream1);
A sample of the JSON that is being logged is:
[
{
"DateLastUpdate": "\/Date(1362438368000+0000)\/",
"DateLastUpdateString": null,
"Id": "df2544e6-71a3-4f1c-ac54-d3c85269804f",
"IsSelected": false,
"Name": "samplepage",
"Path": "\/samplepage"
},
<snip>
]
This problem only occurs in our deployed environments. All attempts to reproduce the problem on our local IIS webservers have failed.
We are using the WebChannelFactory to consume the WCF REST Services. Because of this, we don't have (and are not sure how to get) direct access to the Response so we cannot be 100% of the data being sent across the wire.
Has anyone encountered anything similar to this when upgrading their projects to Visual Studio 2012/.NET 4.5 Framework?
Any responses definitely appreciated,
-- Shaun
Can you try exposing another function which return same data that GetPreviousPage returns, but changing the ResponseFormat to say Xml? Then call it multiple times (e.g. from a browser) and see if this new function ever fails. This well help you know if the problem is with Json Serializer.
[OperationContract]
[WebInvoke(Method = "GET",
ResponseFormat = WebMessageFormat.Xml,
RequestFormat = WebMessageFormat.Json,
UriTemplate = "/Pages/Site/ListItemsNew/{siteId}",
BodyStyle = WebMessageBodyStyle.Bare),
Description("")]
IEnumerable<PageInfoItem> GetPreviousPagesNew(string siteId);
PS: Posted this as an answer as it's difficult to post code in comment.
I am sending a JSON message including filename, and a base64encoded image to a WCF service. I am not really sure on how to convert it back to the image, more specificallly deserializing the return stream.
WCF Interface
[OperationContract]
[WebInvoke(
Method = "POST",
UriTemplate = "/UploadImage", ResponseFormat = WebMessageFormat.Json,
RequestFormat = WebMessageFormat.Json)]
string UploadImage(Stream image);
And some part of the message(which I save to a file so I can view and try to understand)
--hr56lXG6Q_hKg5opmTx4xejr28dU17AC
Content-Disposition: form-data; name="entity"
{"filename":"mypicture.jpg","thebigfile":"\/9j\/4Re6RXhpZgAATU0AKgAAAAgACwEPAAIAAAAOAAAAkgEQAAIAAAAGAAAAoAESAAMAAAABAAYAAAEaAAUAAAABAAAApgEbAAUAAAABAAAArgEoAAMAAAABAAIAAAExAAIAAAATAAAAtgEyAAIAAAAUAAAAygITAAMAAAABAAEAAIdpAAQAAAABAAAA3oglAAQAAAABAAACegAAAoBTb255IEVyaWNzc29uAExUMjZpAAAAAEgAAAABAAAASAAAAAE2LjEuQS4yLjQ1XzUzX2YxMDAApDIwMTI6MTA6MDYgMDk6MzI6MTcAABiCmgAF
and lots more of the base64 encoded image....
--hr56lXG6Q_hKg5opmTx4xejr28dU17AC--
How do I deserialize this? Is Stream the way to go? I do not simply want to remove the top rows and then start deserializing the JSON array, I want to know WHY it looks like this.
To process the message you gave, the OperationContract needs to look something like:
[WebInvoke(Method="POST", UriTemplate="/UploadImage", BodyStyle=WebMessageBodyStyle.WrappedRequest, ResponseFormat=WebMessageFormat.Json, RequestFormat=WebMessageFormat.Json)]
[OperationContract]
string UploadImage(string filename, string thebigfile);
See http://msdn.microsoft.com/en-us/library/bb885100.aspx for more information
You would then need to manually Base64-decode the "thebigfile" parameter using the decoder provided by the .NET Framework. There is no built-in support for Base64 inside JSON as far as I know, see http://msdn.microsoft.com/en-us/library/bb412170.aspx for details on how various data types are supported.
I have one WCF service and one single method named GetStudentList() in the service.It's working fine when it's return single response.Something like this
[WebGet(ResponseFormat = WebMessageFormat.Xml, BodyStyle = WebMessageBodyStyle.WrappedRequest)]
List<Student> GetStudentList();
But i want to return multiple response i.e. xml and json both.something like this
[WebGet(ResponseFormat = WebMessageFormat.Json, BodyStyle = WebMessageBodyStyle.WrappedRequest)]
[WebGet(ResponseFormat = WebMessageFormat.Xml, BodyStyle = WebMessageBodyStyle.WrappedRequest)]
List<Student> GetStudentList();
Is it possible?if yes then how?
It is possible in .NET 4.0 but not in the way you specified it. .NET 4.0 adds new parameter to WebHttp behavior:
<endpointBehaviors>
<behavior name="WebHttp">
<webHttp automaticFormatSelectionEnabled="true" />
</behavior>
</endpointBehaviors>
When you use automatic format selection the format of the response is based on:
Accept header from the request
Content type of the request
Default format specified on operation
Default format specified in webHttp behavior
So if you call your REST service with JSON request you will get JSON. If you call it with POX request you will get XML. Full description of automatic format selection is in MSDN.
I do not think it is possible to return the object as both Json and XML using one call. Think of WCF like a normal method call in this regard; you call one method, you get one serialized return value. Once the service has returned one response to the caller, the call is complete.
Think carefully about why you want to use both response types; they are both informative, universal standards for object serialization, and using WCF, you would only need both if you were using the serialized response text directly. If at all possible, I would refactor the clients to work with the same response type.
The simplest workaround, if two types really are needed, would be to provide two "overloads" of this method, and make each client type smart enough to know which call it needs to make. Because the difference is not in the method signature, it's not a true overload; you'll have to separate them either by name (GetStudentListJSON vs GetStudentListXML) or by locating the methods in different service classes.
You could also always return one response type, and convert on the client side by deserializing/reserializing when you need the object serialized in the other format. This does require you to be using .NET code that you have control over on the client side of the call.
I don't know of a way where you can get 2 outputs from a service operation. You can always get the XML (serialized DataContract) and then "JSON Serialize" it. Here is how you can do it.
List<Student> studentList = GetStudent();
string jsonString = JsonSerialize(studentList.GetType(), studentList);
And then add this function to a utility class:
public static string JsonSerialize(Type type, object objectGraph)
{
MemoryStream memoryStream = new MemoryStream();
try
{
System.Runtime.Serialization.Json.DataContractJsonSerializer serializer = new System.Runtime.Serialization.Json.DataContractJsonSerializer(type);
serializer.WriteObject(memoryStream, objectGraph);
return Encoding.Default.GetString(memoryStream.ToArray());
}
catch (Exception ex)
{
throw ex;
}
finally
{
if (memoryStream != null) memoryStream.Close();
}
}
i am using the Plain old XML template from WCF REST Starter Kit Preview 2 to build a REST service and i would like to set the ResponseFormat inside the OperationContract, not outside(not with ResponseFormat = WebMessageFormat.Json) because i want to set it accordingly to the Accept Header in the Request. and ideally i would like to be able to set the RequestFormat from inside the OperationContract also, acording to the Content-Type header.
So, could anyone tell me how i can do it, or point me towards some examples.
chers,
Later Edit: so i don't want to have things like "format" in the UriTemplate, i just want one UriTemplate, no ResponseFormat (and no RequestFormat at some point) and the ResponseBody to be output according to the AcceptHeader
[WebHelp(Comment = "For Service testing purposes")]
[WebGet(UriTemplate = "Echo")]
[OperationContract]
public ResponseBody Echo()
You'll have to strip off most of the WCF REST code and rebuild it yourself. There is no support for content-type negotiation out of the box. I am lead to believe the starter kit has an example using their extensibility points, you may want to check with them.
http://damianblog.com/2008/10/31/wcf-rest-dynamic-response/ is a good place to start