Just another note on using Dojo to communicate with ASP.NET JSON Services. In my last post on this topic, we did a simple GET request to a service, and that is all well and good, but suppose we want to POST some data?
Well, I ran into this today, and since it was a royal pain to dig the answer out of the internets, I'll share it with you (and Google) now.
I have created a web service that cranks out 1:100,000 scale maps as PDFs. Since these can take a while to process, the service kicks up a thread and cooks the map asynchronously, and simply returns a status message to the requesting client. When the map is done, an email is sent to the user telling them that the map is ready to be downloaded.
Here's the web service signature...
[WebMethod(Description = "Request a map")]
[ScriptMethod(ResponseFormat = ResponseFormat.Json)]
public string CreateMapSheet(string emailAddress, string serviceName, string mapSheetId, int year)
{
// Do good things and create the map
return "A message will be sent to " + emailAddress + " with details of how to download the map when it is ready.";
}
We can see that the service is marked up so that it accepts JSON ([ScriptMethod] attribute) and responds with Json ( ResponseFormat = ResponseFormat.Json). Ok - moving on...
On my test page, I have a very simple form...
The HTML is below...
<form id="form1" action="" > <ul> <li> <label for="emailaddress">Email Address:</label> <input type="text" id="emailaddress" value="dbouwman@edats.com"/> </li> <li> <label for="tileid">Tile Identifier:</label> <input type="text" id="tileid" value="73"/> </li> <li> <label for="service">Service:</label> <input type="text" id="service" value="UTM_ZN10"/> </li> <li> <label for="year">Year:</label> <input type="text" id="year" value="2007"/> </li> <input id="Button2" type=button value="Submit via POST" onclick="SubmitMapRequest();" /> </ul>
As we can see, the Submit button calls the SubmitMapRequest function, which is where we actually make the xhr request. The working final Javascript is show below.
function SubmitMapRequest(){ //debugger; var responseNode = dojo.byId("response"); var maprequest = { "emailAddress":dojo.byId("emailaddress").value , "serviceName":dojo.byId("service").value , "mapSheetId":dojo.byId("tileid").value , "year": dojo.byId("year").value }; dojo.rawXhrPost({ url: "./MapSheets.asmx/CreateMapSheet", handleAs: 'json', timeout:1000, postData: dojo.toJson(maprequest), contentType: "application/json; charset=utf-8", load: function(data,args){ responseNode.innerHTML = data.d; }, error: function(error,args){ responseNode.warn("Error!",error); } }); }
So - what's going on here...
Step one is to pack up the form data into a javascript object which can then be sent to the service. It took a while to locate a sample that actually worked, so it's worth showing below...
var maprequest = { "emailAddress":dojo.byId("emailaddress").value , "serviceName":dojo.byId("service").value , "mapSheetId":dojo.byId("tileid").value , "year": dojo.byId("year").value };
Since we are not using the MSAjax framework, we can't rely on just using the web service proxy to make the request. Thus we need to take a little more responsibility for constructing the json, and that's exactly what we are doing here. What's important to note is that the naming of the fields in the javascript must match the naming of the parameters in our web service method. So we send in 'emailAddress' not 'EmailAddress' or 'emailaddress'.
It took a lot of digging, FireBugging and messing with Fiddler to figure this out. In the end I used rawXhrPost as it does not try to get all fancy with the content - it just POSTs the it to the Url. But for the sake of completeness and Google-indexing, here's the deal with dojo.xhrPost
My original code used dojo.xhrPost, and looked like this:
function SubmitMapRequestPOST(){ var responseNode = dojo.byId("response"); var maprequest = { "emailAddress":dojo.byId("emailaddress").value , "serviceName":dojo.byId("service").value , "mapSheetId":dojo.byId("tileid").value , "year": dojo.byId("year").value }; dojo.xhrPost({ url: "./MapSheets.asmx/CreateMapSheet", handleAs: 'json', content: maprequest, contentType: "application/json; charset=utf-8", load: function(data,args){ responseNode.innerHTML = data.d; }, error: function(error,args){ responseNode.warn("Error!",error); } }); }
Using this I would get a Json error from the service...
{"Message":"Invalid JSON primitive: emailAddress.","Stack Trace":"blah blah blah..."}
When I'd look at the POST in FireBug, it was not JSON... instead it looked like this
emailAddress=me%40foo.com&serviceName=UTM_ZN10&mapSheetId=73&year=2007
Next I tried to use dojo.toJson to force the maprequest to become Json...
dojo.xhrPost({ url: "./MapSheets.asmx/CreateMapSheet", handleAs: 'json', content: dojo.toJson(maprequest), contentType: "application/json; charset=utf-8", load: function(data,args){ responseNode.innerHTML = data.d; }, error: function(error,args){ responseNode.warn("Error!",error); } });
This got even more jacked up, and this is what was posted:
0=%7B&1=%22&2=e&3=m&4=a&5=i&6=l&7=A&8=d&9=d&10=r&11=e&12=s&13=s&14=%22&15=%3A&16=%22&17=m&18=e&19=%40 &20=f&21=o&22=o&23=.&24=c&25=o&26=m&27=%22&28=%2C&29=%22&30=s&31=e&32=r&33=v&34=i&35=c&36=e&37=N&38=a &39=m&40=e&41=%22&42=%3A&43=%22&44=U&45=T&46=M&47=_&48=Z&49=N&50=1&51=0&52=%22&53=%2C&54=%22&55=m&56 =a&57=p&58=S&59=h&60=e&61=e&62=t&63=I&64=d&65=%22&66=%3A&67=%22&68=7&69=3&70=%22&71=%2C&72=%22&73=y&74 =e&75=a&76=r&77=%22&78=%3A&79=%22&80=2&81=0&82=0&83=7&84=%22&85=%7D
0=%7B&1=%22&2=e&3=m&4=a&5=i&6=l&7=A&8=d&9=d&10=r&11=e&12=s&13=s&14=%22&15=%3A&16=%22&17=m&18=e&19=%40
&20=f&21=o&22=o&23=.&24=c&25=o&26=m&27=%22&28=%2C&29=%22&30=s&31=e&32=r&33=v&34=i&35=c&36=e&37=N&38=a
&39=m&40=e&41=%22&42=%3A&43=%22&44=U&45=T&46=M&47=_&48=Z&49=N&50=1&51=0&52=%22&53=%2C&54=%22&55=m&56
=a&57=p&58=S&59=h&60=e&61=e&62=t&63=I&64=d&65=%22&66=%3A&67=%22&68=7&69=3&70=%22&71=%2C&72=%22&73=y&74
=e&75=a&76=r&77=%22&78=%3A&79=%22&80=2&81=0&82=0&83=7&84=%22&85=%7D
Ummm yeah.
After much more Googling, I stumbled upon an example at the Project Zero forums. The scenario being discussed is similar - they were having issues posting, and they were seeing that the data was being sent as HTML FORM encoding, not Json. And the solution was to use dojo.rawXhrPost.
Using this, we see that the post actually contains the Json we created...
{"emailAddress":"me@foo.com","serviceName":"UTM_ZN10","mapSheetId":"73","year":"2007"}
and everything works smoothly.
Now one might ask - "How was I supposed to know that?" Great question. The Dojo API documentation for dojo.rawXhrPost is obtuse at best. The Dojo Quick Start site only talks about xhrGet and xhrPost, and the Book of Dojo is the same.
However, the O'Reilly book "Dojo: The Definitive Guide" does actually cover this stuff - but it's still pretty slim.
If I have some time over the next few days I'll roll this into a sample (minus the mapsheet service stuff!)
I'm Dave and this is my blog. I'm usually writing about .NET Software Development, ArcGIS, or Agile Practices, but other stuff does creep in from time to time. I hope you find something of use, and feel free to contact me if you have any questions. You can also check out my profile on LinkedIn
dojo.DTSAgile.com is our technology preview / demo site. As I and my team cook up cool things we post them here.
ArcDeveloper.net is a site that hosts a set of open source projects related to ArcGIS. This includes Tile Cache for .NET (TC4N) and Feature Server for .NET (FS4N). Come over and check it out!
Assembla is a free service that provides Subversion source control, wikis and work Tracking. The ArcDeveloper project is run from here. It rocks. Check them out today.
Agilistas is a LinkedIn group focused on discussing and promoting Agile practices. Everyone is welcome to join in the conversation as we evolve the process of creating software to make it more enjoyable for all involved.