Thursday, September 18, 2008
Posted on Thursday, September 18, 2008 1:47:24 PM (Mountain Daylight Time, UTC-06:00)  Comments [3] | 
Categories: .NET | ArcGIS Server | Dojo

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.

Scenario

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...

post-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...

Packing up Data as JSON

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'.

Posting the Data to the Web Service Part 1: dojo.xhrPost

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

Ummm yeah.

Posting the Data to the Web Service Part 2: dojo.rawXhrPost

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!)

Friday, September 19, 2008 6:54:09 AM (Mountain Daylight Time, UTC-06:00)
But JSON format is not working for datasets, datatable and dataviews.
sujani
Friday, September 19, 2008 7:58:29 AM (Mountain Daylight Time, UTC-06:00)
@sujani
Can you be more specific? Are you trying to return ADO.NET datasets/tables/views? Or ESRI ADF datasets/tables?

In either case, I prefer working with custom business objects instead of these heavy weight data objects. The nice thing is that business objects are usually serializable so conversion to and from Json is relatively easy, and working with them in javascript is pretty straight forward as well.

Dave
Monday, September 22, 2008 3:42:48 AM (Mountain Daylight Time, UTC-06:00)
I try to pass an array to the server with this method but I can't make it work.

I changed the web service signature: public string CreateMapSheet(string emailAddress, string serviceName, string[] mapSheetId, int year)

and the javascript code: var maprequest = { "emailAddress":dojo.byId("emailaddress").value , "serviceName":dojo.byId("service").value , "mapSheetId": [ dojo.byId("tileid_1").value , dojo.byId("tileid_2").value ] , "year": dojo.byId("year").value };

I get the following error: Type 'System.String' is not supported for deserialization of an array.

Do you have any idea?
alex
Comments are closed.