I am trying, from a VBA for Excel program, to query a RESTful API, in VB.NET.
However, I can't seem to deserialize the XML properly.
On the VBA side, the code is the following:
Set objHTTP = CreateObject("MSXML2.ServerXMLHTTP")
URL = "http://localhost:50261/api/values"
objHTTP.Open "POST", URL, False
objHTTP.setRequestHeader "User-Agent", "Mozilla/4.0 (compatible; MSIE 6.0;Windows NT 5.0)"
objHTTP.setRequestHeader "Content-type", "application/xml"
objHTTP.send ("<?xml version=""1.0"" encoding=""UTF-8""?><KPISheet><site>mysite</site><unit>myunit</unit></KPISheet>)")
As you can see, I am sending what I believe is a well formed and very simple XML document.
On VB.NET side, I have a class, called KPISheet:
Public Class KPISheet
Public Site As String
Public Unit As String
End Class
And a WebAPI, to get the 'POST':
Public Sub PostValue(<FromBody> oKPISheet As KPISheet)
Debug.Print("toto")
End Sub
If I set a breakpoint in the Debug line, I can see that oKPISheet is Nothing, the XML file is not deserialized.
In the Output window, I get the following Error Message:
Exception thrown: 'System.Runtime.Serialization.SerializationException' in System.Runtime.Serialization.dll
I have tried with and without the tag, but I can't get it to work.
As you can see, I am sending what I believe is a well formed and very simple XML document.
Unfortunately your assumption is wrong:
<KPISheet>site>mysite</site>
That's invalid XML. You are missing an opening < for the site tag. You also seem to have some closing ) as last character in your XML.
So you can try sending valid XML:
objHTTP.send("<?xml version=""1.0"" encoding=""UTF-8""?><KPISheet><site>mysite</site><unit>myunit</unit></KPISheet>")
Also notice that the correct content type should be text/xml and not application/xml:
objHTTP.setRequestHeader "Content-type", "text/xml"
Also bear in mind that XML is case sensitive, so you should capitalize your tag names to match your property names:
objHTTP.send("<?xml version=""1.0"" encoding=""UTF-8""?><KPISheet><Site>mysite</Site><Unit>myunit</Unit></KPISheet>")
and last but not least, Web API uses the datacontract serializer by default for working with XML, so you need to include namespaces:
objHTTP.send("<?xml version=""1.0"" encoding=""UTF-8""?><KPISheet xmlns:i=""http://www.w3.org/2001/XMLSchema-instance"" xmlns=""http://schemas.datacontract.org/2004/07/WebApplication1.ViewModels.KPISheet""><Site>mysite</Site><Unit>myunit</Unit></KPISheet>")
Don't forget to adjust the namespace to match your KPISheet class.
Alternatively if you don't want to include namespaces you could switch the XML serializer being used:
config.Formatters.XmlFormatter.UseXmlSerializer = true;
And if you want to keep using the data contract serializer you could decorate your view model with the corresponding attributes:
<DataContract(Namespace="")>
Public Class KPISheet
<DataMember>
Public Site As String
<DataMember>
Public Unit As String
End Class
Related
Given the following ASP.NET WebAPI, I am trying to send a test POST using Fiddler, but can't get it to work. Whatever I send, I always just see the No data sent to service message.
Imports System.Web.Http
Imports System.Net.Http
Imports System.Net
Namespace HelloWebApiDemo
Public Class MyApiController
Inherits ApiController
Public Function [Get]() As HttpResponseMessage
Return Request.CreateResponse(HttpStatusCode.OK, "Hello")
End Function
Public Class MyXmlData
Public Property UserName As String
Public Property Password As String
Public Property SomeData As String
End Class
Public Function Post(<FromBody> x As MyXmlData) As HttpResponseMessage
If x Is Nothing Then
Return Request.CreateResponse(HttpStatusCode.InternalServerError, "No data sent to service")
End If
Dim u As String = String.Empty
Dim p As String = String.Empty
Dim d As String = String.Empty
If Not String.IsNullOrEmpty(x.UserName) Then
u = x.UserName
End If
If Not String.IsNullOrEmpty(x.Password) Then
p = x.Password
End If
If Not String.IsNullOrEmpty(x.SomeData) Then
d = x.SomeData
End If
Return Request.CreateResponse(HttpStatusCode.OK, String.Format("You posted {0}, {1} with a string that is {2} characters in length", u, p, d.Length.ToString))
End Function
End Class
End Namespace
In Fiddler, my POST looks like this:
Can anyone please advise on what I'm doing wrong? I used Content-Type: text/xml hoping that ASP.NET would be able to decipher it correctly.
Update
New screen grab following input:
Keep the Content-Type: text/xml request header and change the XML to like this.
<MyApiController.MyXmlData
xmlns:i="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://schemas.datacontract.org/2004/07/HelloWebApiDemo.HelloWebApiDemo">
<Password>somepassword</Password>
<SomeData>somedata</SomeData>
<UserName>bob</UserName>
</MyApiController.MyXmlData>
Final XML snippet that was successfully deserialized:
<MyApiController.MyXmlData
xmlns:i="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://schemas.datacontract.org/2004/07/_WebApplication1.HelloWebApiDemo">
<Password>Password</Password>
<SomeData>Data here</SomeData>
<UserName>Some Username</UserName>
</MyApiController.MyXmlData>
_WebApplication1 is the name of the solution. That must've been what was missing.
For those who have the ability to do this when faced with the same issue, another option is to use JSON. I am a huge fan of XML, but unfortunately, it seems things are far more stacked against XML's usage in scenarios like this, because in this case, the standard WebApi parser requires all that hideous extra stuff, including intimate knowledge of backend namespaces and types, as well as a full XML spec namespace (* see rant below).
{
"SomeData": "R2D2",
"UserName": "Johny",
"Password": "password",
"Num": 1013,
"IsCool": true
}
*( Rant, please ignore if you want to: Why require this, oh ye good gentleman who made WepApi and other such frameworks? why not allow the XML to be liberated, freely receivable without all this 'ugly'? If included, it could still specify something useful, if one needs to, but why must the rest of us include this ugliness? It seems things have been (arbitrarily, though perhaps more for historical baggage reasons, sorry you poor XML creature, you're stuck with yesterday's baggage though it need not be) stacked against XML's usage, including that, even with all this, a standard parser likely would not allow you to post the XML with the values as XML attributes. )
You can mark your entity class with DataContract attribute setting namespace to an empty string:
<DataContract(Namespace := "")>
Public Class MyXmlData
End Class
After that xmlns parameters will not be necessary.
I'm calling a REST service searchFavoriteCompany that in turn calls another service A.
Service A already returns JSON. But since my return type of searchFavoriteCompany my search results are then returned with escape characters:
"{\"responseHeader\":{\"status\":0,\"QTime\":2,\"params\":{\"facet\":\"false\",\"fl\":\"id,title,friendlyurl,avatar,locpath,objectid,objecttype\",\"indent\":\"off\",\"start\":\"0\",\"q\":\"title_search:*castle*\",\"wt\":\"json\",\"fq\":\"userid:\\\"C325D42C-A777-4275-BDD2-D7810A8AB9AB\\\"\",\"rows\":\"10\",\"defType\":\"lucene\"}},\"response\":{\"numFound\":2,\"start\":0,\"docs\":[{\"title\":\"Castle A\",\"objecttype\":1,\"friendlyurl\":\"castle-a\",\"avatar\":\"6_887_castle-a.JPG\",\"objectid\":6},{\"title\":\"Castle B\",\"objecttype\":1,\"friendlyurl\":\"castle-b\",\"avatar\":\"794_360_13j-Castle-by-night.jpg\",\"objectid\":794}]}}\u000a"
I don't know how to make sure my JSON results are returned without these escape characters.
Imyservice.vb
Namespace RestService
<ServiceContract()>
Public Interface Imyservice
<OperationContract()> _
_
Function searchFavoriteCompany(ByVal q As String, ByVal uuid As String) As String
End Interface
End Namespace
iservice.svc.vb
Namespace RestService
Public Class iservice
Implements Imyservice
Public Function searchFavoriteCompany(ByVal q As String, ByVal uuid As String) As String Implements Imyservice.searchFavoriteCompany
Dim req As HttpWebRequest = HttpWebRequest.Create("http://localhost/getjson") <---- this service already returns JSON data
Dim Resp As HttpWebResponse = req.GetResponse()
Dim reader As StreamReader = New StreamReader(Resp.GetResponseStream)
Dim responseString As String = reader.ReadToEnd()
Return responseString
End Function
End Class
End Namespace
Instead of returning the responsestring I also tried:
HttpContext.Current.Response.Write(responseString)
return ""
But then I get the error 'Object reference not set to an instance of an object'
And I checked: responsestring contains a value, so that is not the problem.
If you can't pass the output directly to the response, your best bet is to go back to traditional methods, and return a type-safe object from your method. You've got the JSON that the original service passes you, and your end-goal is to return that exact object back to your caller.
Assuming you've got a complete JSON object in your example - you can first go to json2csharp to take your JSON and create classes out of it. It uses C#, but you can easily convert it to VB using something like Telerik's Code Converter.
If you follow these steps, you'll end up with several classes, including one called RootObject, which contains properties for responseHeader and response, and the other classes needed. You can rename the classes as you see fit, and put them into proper namespaces - just leave the property names alone, so they match the JSON.
Once you've got this RootObject class (or whatever you rename it to), you can deserialize the JSON you got from the original service into an object of type RootObject, then your WebAPI method would simply return a RootObject. Just like any other WCF call, the framework will take care of serializing that object back into JSON (or XML) for the caller.
I am creating a VB 2010 desktop application. I use the lines below to get a string response from an aspx page that I use to collect online data for the application.
Dim response As Byte() = myWebClient.UploadValues(myWeb & "/Default.aspx", "POST", nmv)
dim str as string = Replace(System.Text.Encoding.ASCII.GetString(response), "<(.|\n)*?>", "")
System.Text.Encoding.ASCII.GetString(response)
'nmv' is a defined NameValueCollection variable
Now I need to get this data back from the aspx page as either as nmv or a datatable. Is that possible? How could I go about doing it? I am thinking the key is in the System.'Array'... or something but I can't figure it out and I don't get any search results on the web.
Getting the data back as a string array is good for me, getting it nmv is even better; but getting it back as a DataTable would be perfect! I am able to deal with the aspx page giving back the data in any format, I just need the vb app to know how to collect it.
Thank you
You can change the content type of the aspx from text to image or pdf etc. but not a complex datatype.
I would use a web service or wcf service instead. You can just declare a method of DataTable type and you're done.
http://msdn.microsoft.com/en-us/library/ms972326.aspx
Maybe easiest way would be to use *.ashx Http Handler (generic handler) and save DataTable to XML, something like this :
Server:
public class Handler1 : IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
/// if needed request data is in context.Request
DataTable tbl = GetDataTable();
/// table must have name, if not WriteXml will fail
tbl.TableName = "TableName";
tbl.WriteXml(context.Response.OutputStream);
}
public bool IsReusable
{
get
{
return false;
}
}
Then on client you should use ReadXml DataTable method to populate DataTable on client.
I have a simple webservice that I would like to upload a file to. The problem is that I need the response in json.
Form my experience in order to get a response in Json my request has to have a content-type of 'application/json'. But ofcourse this cannot be the case with a file upload since the content type will have to be 'multipart/form-data'.
In my Json i want to return a value showing whether successful and a filename.
[WebMethod(EnableSession = true)]
[ScriptMethod(ResponseFormat = ResponseFormat.Json)]
public TyoeOfSomeObject UploadFile()
{
// Get the file from the context and do something with it
HttpPostedFile httpPostedFile = HttpContext.Current.Request.Files[0];
// Return either a string or an object to serialise with the required values
return SomeObject;
}
I was having the same issue, and resolved it by setting the response's ContentType and calling the response's Write() function:
C#
String j = jsonParser.AsJson(obj);
Context.Response.ContentType = "application/json; charset=utf-8";
Context.Response.Write(j);
VB
Dim j As String = jsonParser.AsJson(obj)
Context.Response.ContentType = "application/json; charset=utf-8"
Context.Response.Write(j)
You could set the return type of your function to a string and then use some JSON serializer to serialize your object to JSON and return it as a JSON string. For JSON serialization I use Jayrock I believe ASP .NET has its own JSON libraries now as well.
You can declare your web method with byte[] as an output parameter. Then you can set ContentType and return any data you want.
If you use WCF instead of ASMX web service you can return Stream or Message in such cases (see Returning raw json (string) in wcf. You can try also return Stream instead of byte[] in the web service if your file is very large. Probably it will also works with ASMX web service.
ASP.Net Webservice Serialization
I was unable to find a way to return a response in json. I don't think its possible without tinkering with the inner workings. The solution I used was to create an aspx that could handle the file, you could ofcourse use .ashx or WCF as described by OLEG.
Hi I am developing an application with Flex for the GUI and Restlet for the webservices. I have a strange problem. I put my XML as a property on a generic object, and send it as part of a POST request. But in the Restlet webservice, this XML is irretrievable. How do I retrieve it?
I tried initialising the received Representation object to a DomRepresentation, but thats not working. If I put the received Representation object into a Form object, then getFirstValue is returning that XML as a string!
I noticed that the contentType of the HTTPService was application/www-form-encoded so I set it to application/xml and its not helping either.
I use restlet 2.0m6 and here is the code snippet that I use -
#Post
public Representation process(Representation entity)
{
try
{
DomRepresentation dom = new DomRepresentation(entity);
Document d = dom.getDocument();
.
.
}
catch(Exception e)
{
e.printStackTrace();
}
and it throws a Null Pointer exception at the dom.getDocument() line. Which means no data actually arrived.
And my flex bit looks like this -
var service : HTTPService = new HTTPService();
service.method="POST";
service.contentType="application/xml"
service.url=url;
var token :AsyncToken = service.send(params);
where params is an XML object.
Here is the answer - http://vatsalad.wordpress.com/2010/02/08/how-to-handle-xml-received-as-part-of-a-post-request-in-restlet/