Rick Strahl's Weblog  

Wind, waves, code and everything in between...
.NET • C# • Markdown • WPF • All Things Web
Contact   •   Articles   •   Products   •   Support   •   Advertise
Sponsored by:
West Wind WebSurge - Rest Client and Http Load Testing for Windows

Using jQuery to POST Form Data to an ASP.NET ASMX AJAX Web Service


:P
On this page:

The other day I got a question about how to call an ASP.NET ASMX Web Service or PageMethods with the POST data from a Web Form (or any HTML form for that matter). The idea is that you should be able to call an endpoint URL, send it regular urlencoded POST data and then use Request.Form[] to retrieve the posted data as needed. My first reaction was that you can’t do it, because ASP.NET ASMX AJAX services (as well as Page Methods and WCF REST AJAX Services) require that the content POSTed to the server is posted as JSON and sent with an application/json or application/x-javascript content type. IOW, you can’t directly call an ASP.NET AJAX service with regular urlencoded data. Note that there are other ways to accomplish this. You can use ASP.NET MVC and a custom route, an HTTP Handler or separate ASPX page, or even a WCF REST service that’s configured to use non-JSON inputs.

However if you want to use an ASP.NET AJAX service (or Page Methods) with a little bit of setup work it’s actually quite easy to capture all the form variables on the client and ship them up to the server. The basic steps needed to make this happen are:

  1. Capture form variables into an array on the client with jQuery’s .serializeArray() function
  2. Use $.ajax() or my ServiceProxy class to make an AJAX call to the server to send this array
  3. On the server create a custom type that matches the .serializeArray() name/value structure
  4. Create extension methods on NameValue[] to easily extract form variables
  5. Create a [WebMethod] that accepts this name/value type as an array (NameValue[])

This seems like a lot of work but realize that steps 3 and 4 are a one time setup step that can be reused in your entire site or multiple applications.

Let’s look at a short example that looks like this as a base form of fields to ship to the server:

InputForm

The HTML for this form looks something like this:

<div id="divMessage" class="errordisplay" style="display: none">
</div>

<div>
    <div class="label">Name:</div>
    <div><asp:TextBox runat="server" ID="txtName"  /></div> 
</div>
<div>
    <div class="label">Company:</div>
    <div><asp:TextBox runat="server" ID="txtCompany"/></div>
</div>
<div>
    <div class="label" ></div>                
    <div>                
        <asp:DropDownList runat="server" ID="lstAttending">
            <asp:ListItem Text="Attending"  Value="Attending"/>
            <asp:ListItem Text="Not Attending" Value="NotAttending" />
            <asp:ListItem Text="Maybe Attending" Value="MaybeAttending" />
            <asp:ListItem Text="Not Sure Yet" Value="NotSureYet" />
        </asp:DropDownList>
    </div>
</div>
<div>
    <div class="label">Special Needs:<br />
            <small>(check all that apply)</small></div>
    <div>
        <asp:ListBox runat="server" ID="lstSpecialNeeds" SelectionMode="Multiple">
            <asp:ListItem Text="Vegitarian" Value="Vegitarian" />
            <asp:ListItem Text="Vegan" Value="Vegan" />
            <asp:ListItem Text="Kosher" Value="Kosher" />
            <asp:ListItem Text="Special Access" Value="SpecialAccess" />
            <asp:ListItem Text="No Binder" Value="NoBinder" />
        </asp:ListBox>
    </div>
</div>
<div>
    <div class="label"></div>
    <div>        
        <asp:CheckBox ID="chkAdditionalGuests" Text="Additional Guests" runat="server" />
    </div>
</div>

<hr />

<input type="button" id="btnSubmit" value="Send Registration" />
    

The form includes a few different kinds of form fields including a multi-selection listbox to demonstrate retrieving multiple values.

Setting up the Server Side [WebMethod]

The [WebMethod] on the server we’re going to call is going to be very simple and just capture the content of these values and echo then back as a formatted HTML string. Obviously this is overly simplistic but it serves to demonstrate the simple point of capturing the POST data on the server in an AJAX callback.

public class PageMethodsService : System.Web.Services.WebService
{
    [WebMethod]
    public string SendRegistration(NameValue[] formVars)
    {
        StringBuilder sb = new StringBuilder();

        sb.AppendFormat("Thank you {0}, <br/><br/>",
                        HttpUtility.HtmlEncode(formVars.Form("txtName")));

        sb.AppendLine("You've entered the following: <hr/>");

        foreach (NameValue nv in formVars)
        {
            // strip out ASP.NET form vars like _ViewState/_EventValidation
            if (!nv.name.StartsWith("__"))
            {
                if (nv.name.StartsWith("txt") || nv.name.StartsWith("lst") || nv.name.StartsWith("chk"))
                    sb.Append(nv.name.Substring(3));
                else
                    sb.Append(nv.name);
                sb.AppendLine(": " + HttpUtility.HtmlEncode(nv.value) + "<br/>");
            }
        }
        sb.AppendLine("<hr/>");

        string[] needs = formVars.FormMultiple("lstSpecialNeeds");
        if (needs == null)
            sb.AppendLine("No Special Needs");
        else
        {
            sb.AppendLine("Special Needs: <br/>");
            foreach (string need in needs)
            {
                sb.AppendLine("&nbsp;&nbsp;" + need + "<br/>");
            }
        }

        return sb.ToString();
    }
}

The key feature of this method is that it receives a custom type called NameValue[] which is an array of NameValue objects that map the structure that the jQuery .serializeArray() function generates. There are two custom types involved in this: The actual NameValue type and a NameValueExtensions class that defines a couple of extension methods for the NameValue[] array type to allow for single (.Form()) and multiple (.FormMultiple()) value retrieval by name.

The NameValue class is as simple as this and simply maps the structure of the array elements of .serializeArray():

public class NameValue
{
    public string name { get; set; }
    public string value { get; set; }
}


The extension method class defines the .Form() and .FormMultiple() methods to allow easy retrieval of form variables from the returned array:

/// <summary>
/// Simple NameValue class that maps name and value
/// properties that can be used with jQuery's 
/// $.serializeArray() function and JSON requests
/// </summary>
public static class NameValueExtensionMethods
{
    /// <summary>
    /// Retrieves a single form variable from the list of
    /// form variables stored
    /// </summary>
    /// <param name="formVars"></param>
    /// <param name="name">formvar to retrieve</param>
    /// <returns>value or string.Empty if not found</returns>
    public static string Form(this  NameValue[] formVars, string name)
    {
        var matches = formVars.Where(nv => nv.name.ToLower() == name.ToLower()).FirstOrDefault();
        if (matches != null)
            return matches.value;
        return string.Empty;
    }

    /// <summary>
    /// Retrieves multiple selection form variables from the list of 
    /// form variables stored.
    /// </summary>
    /// <param name="formVars"></param>
    /// <param name="name">The name of the form var to retrieve</param>
    /// <returns>values as string[] or null if no match is found</returns>
    public static string[] FormMultiple(this  NameValue[] formVars, string name)
    {
        var matches = formVars.Where(nv => nv.name.ToLower() == name.ToLower()).Select(nv => nv.value).ToArray();
        if (matches.Length == 0)
            return null;
        return matches;
    }
}

Using these extension methods it’s easy to retrieve individual values from the array:

string name = formVars.Form("txtName");

or multiple values:

string[] needs = formVars.FormMultiple("lstSpecialNeeds");
if (needs != null)
{
    // do something with matches
}

Using these functions in the SendRegistration method it’s easy to retrieve a few form variables directly (txtName and the multiple selections of lstSpecialNeeds) or to iterate over the whole list of values. Of course this is an overly simple example – in typical app you’d probably want to validate the input data and save it to the database and then return some sort of confirmation or possibly an updated data list back to the client.

Since this is a full AJAX service callback realize that you don’t have to return simple string values – you can return any of the supported result types (which are most serializable types) including complex hierarchical objects and arrays that make sense to your client code.

POSTing Form Variables from the Client to the AJAX Service

To call the AJAX service method on the client is straight forward and requires only use of little native jQuery plus JSON serialization functionality. To start add jQuery and the json2.js library to your page:

    <script src="Scripts/jquery.min.js" type="text/javascript"></script>
    <script src="Scripts/json2.js" type="text/javascript"></script>

json2.js can be found here (be sure to remove the first line from the file):

http://www.json.org/json2.js

It’s required to handle JSON serialization for those browsers that don’t support it natively.

With those script references in the document let’s hookup the button click handler and call the service:

$(document).ready(function () {
    $("#btnSubmit").click(sendRegistration);
});

function sendRegistration() {
    var arForm = $("#form1").serializeArray();
$.ajax({ url: "PageMethodsService.asmx/SendRegistration", type: "POST", contentType: "application/json", data: JSON.stringify({ formVars: arForm }), dataType: "json", success: function (result) { var jEl = $("#divMessage"); jEl.html(result.d).fadeIn(1000); setTimeout(function () { jEl.fadeOut(1000) }, 5000); }, error: function (xhr, status) { alert("An error occurred: " + status); } }); }


The key feature in this code is the $("#form1").serializeArray();  call which serializes all the form fields of form1 into an array. Each form var is represented as an object with a name/value property. This array is then serialized into JSON with:

JSON.stringify({ formVars: arForm })

The format for the parameter list in AJAX service calls is an object with one property for each parameter of the method. In this case its a single parameter called formVars and we’re assigning the array of form variables to it.

The URL to call on the server is the name of the Service (or ASPX Page for Page Methods) plus the name of the method to call.

On return the success callback receives the result from the AJAX callback which in this case is the formatted string which is simply assigned to an element in the form and displayed. Remember the result type is whatever the method returns – it doesn’t have to be a string. Note that ASP.NET AJAX and WCF REST return JSON data as a wrapped object so the result has a ‘d’ property that holds the actual response:

jEl.html(result.d).fadeIn(1000);

Slightly simpler: Using ServiceProxy.js

If you want things slightly cleaner you can use the ServiceProxy.js class I’ve mentioned here before. The ServiceProxy class handles a few things for calling ASP.NET and WCF services more cleanly:

  • Automatic JSON encoding
  • Automatic fix up of ‘d’ wrapper property
  • Automatic Date conversion on the client
  • Simplified error handling
  • Reusable and abstracted

To add the service proxy add:

<script src="Scripts/ServiceProxy.js" type="text/javascript"></script>

and then change the code to this slightly simpler version:

<script type="text/javascript">
proxy = new ServiceProxy("PageMethodsService.asmx/");

$(document).ready(function () {
    $("#btnSubmit").click(sendRegistration);
});

function sendRegistration() {
    var arForm = $("#form1").serializeArray();

    proxy.invoke("SendRegistration", { formVars: arForm },
                    function (result) {
                        var jEl = $("#divMessage");
                        jEl.html(result).fadeIn(1000);
                        setTimeout(function () { jEl.fadeOut(1000) }, 5000);
                    },
                    function (error) { alert(error.message); } );
}

The code is not very different but it makes the call as simple as specifying the method to call, the parameters to pass and the actions to take on success and error. No more remembering which content type and data types to use and manually serializing to JSON. This code also removes the “d” property processing in the response and provides more consistent error handling in that the call always returns an error object regardless of a server error or a communication error unlike the native $.ajax() call.

Either approach works and both are pretty easy. The ServiceProxy really pays off if you use lots of service calls and especially if you need to deal with date values returned from the server  on the client.

Summary

Making Web Service calls and getting POST data to the server is not always the best option – ASP.NET and WCF AJAX services are meant to work with data in objects. However, in some situations it’s simply easier to POST all the captured form data to the server instead of mapping all properties from the input fields to some sort of message object first. For this approach the above POST mechanism is useful as it puts the parsing of the data on the server and leaves the client code lean and mean. It’s even easy to build a custom model binder on the server that can map the array values to properties on an object generically with some relatively simple Reflection code and without having to manually map form vars to properties and do string conversions.

Keep in mind though that other approaches also abound. ASP.NET MVC makes it pretty easy to create custom routes to data and the built in model binder makes it very easy to deal with inbound form POST data in its original urlencoded format. The West Wind West Wind Web Toolkit also includes functionality for AJAX callbacks using plain POST values. All that’s needed is a Method parameter to query/form value to specify the method to be called on the server. After that the content type is completely optional and up to the consumer. It’d be nice if the ASP.NET AJAX Service and WCF AJAX Services weren’t so tightly bound to the content type so that you could more easily create open access service endpoints that can take advantage of urlencoded data that is everywhere in existing pages. It would make it much easier to create basic REST endpoints without complicated service configuration. Ah one can dream!

In the meantime I hope this article has given you some ideas on how you can transfer POST data from the client to the server using JSON – it might be useful in other scenarios beyond ASP.NET AJAX services as well.

Additional Resources

ServiceProxy.js
A small JavaScript library that wraps $.ajax() to call ASP.NET AJAX and WCF AJAX Services. Includes date parsing extensions to the JSON object, a global dataFilter for processing dates on all jQuery JSON requests, provides cleanup for the .NET wrapped message format and handles errors in a consistent fashion.

Making jQuery Calls to WCF/ASMX with a ServiceProxy Client
More information on calling ASMX and WCF AJAX services with jQuery and some more background on ServiceProxy.js. Note the implementation has slightly changed since the article was written.

ww.jquery.js
The West Wind West Wind Web Toolkit also includes ServiceProxy.js in the West Wind jQuery extension library. This version is slightly different and includes embedded json encoding/decoding based on json2.js.

Posted in jQuery  ASP.NET  AJAX  

The Voices of Reason


 

Hemanshu Bhojak
September 08, 2010

# re: Posting Form Data to an ASP.NET ASMX AJAX Web Service with jQuery

Very informative...

Instead of <div class="label"></div> you can use <label></label> to keep your markup semantically correct :)

Ben Amada
September 09, 2010

# re: Posting Form Data to an ASP.NET ASMX AJAX Web Service with jQuery

Nice post, as always. I think one optimization that could be made would be to omit passing form values such as ViewState and EventValidation to the server -- i.e. exclude them within serializeArray().

Alistair Findlay
September 10, 2010

# re: Posting Form Data to an ASP.NET ASMX AJAX Web Service with jQuery

As Ben mentions above, you can filter out the unnecessary ViewState/EventValidation values prior to submitting to the server by using the jquery "filter" command (http://api.jquery.com/filter/), like this:

var arForm = $("#form1").serializeArray();

arForm = $(arForm).filter(function () {
return !({
"__VIEWSTATE": 1,
"__EVENTVALIDATION": 1,
"__EVENTTARGET": 1,
"__EVENTARGUMENT": 1
})[this.name];
});

Rick Strahl
September 10, 2010

# re: Posting Form Data to an ASP.NET ASMX AJAX Web Service with jQuery

@Alistair - thanks for the comment with the filter. I feel like a dumbass but I don't actually understand that filter expression (the object and the array selector) you're using. How the heck does that work?

Ben Amada
September 10, 2010

# re: Posting Form Data to an ASP.NET ASMX AJAX Web Service with jQuery

That is a nice solution by Alistair. After studying it a bit (wasn't sure at first too), an object literal is created containing 4 properties. The value of each property (1) is arbitrary. It then sees if the form "name" exists in the object as a property. That check boils down to:

return !obj[this.name]

... where "obj" is the object containing the 4 properties. If "this.name" (e.g. __VIEWSTATE, address, name, etc) is a property within obj, false is returned so the item is filtered out. Likewise, if "this.name" is not a property of obj, it evaluates to false which returns a true value so the filter function keeps the value.

Rick Strahl
September 11, 2010

# re: Using jQuery to POST Form Data to an ASP.NET ASMX AJAX Web Service

@Ben - thanks for the heads up. Yup that makes sense and is a neat trick actually to check for a value letting the JavaScript parser handle the comparisons for you without much code :-) Thanks for the idea Alistair!

Richard
September 13, 2010

# re: Using jQuery to POST Form Data to an ASP.NET ASMX AJAX Web Service

With .NET, the usual recommendation is to normalize strings to upper case, since several languages have multiple lower case representations of the same upper case letter. Alternatively, you could use the String.Equals(String, String, StringComparison) method to compare the strings.

Also, list.Where(predicate).FirstOrDefault() isn't really necessary; you can use list.FirstOrDefault(predicate) to get the same result.

public static class NameValueExtensionMethods
{
    public static string Form(this NameValue[] formVars, string name)
    {
        var matches = formVars.FirstOrDefault(nv => string.Equals(nv.name, name, StringComparison.OrdinalIgnoreCase));
        return (null != matches) ? matches.value : string.Empty;
    }
 
    public static string[] FormMultiple(this  NameValue[] formVars, string name)
    {
        var matches = formVars.Where(nv => string.Equals(nv.name, name, StringComparison.OrdinalIgnoreCase)).Select(nv => nv.value).ToArray();
        return (0 == matches.Length) ? null : matches;
    }
}

Guillermo
September 28, 2010

# re: Using jQuery to POST Form Data to an ASP.NET ASMX AJAX Web Service

Any ideas on how to prevent a dos attack on a website utilizing a webservice that hits a db? We've got a fancy 'newsletter sign up' that we've got sitewide and we'd like to use ajax to seamlessly add the user to the list w/o them leaving the page they are on.

Schmuli
December 07, 2010

# re: Using jQuery to POST Form Data to an ASP.NET ASMX AJAX Web Service

I tried using your code, and was very impressed with the ease it allowed for submitting the form, especially using the ServerProxy script, which will be useful as well in other scenarios calling the ASP.NET server.
One issue I have, which is not directly related to this but popped up after using this solution, was the value of the name attribute of the input element. Basically, the input has runat=server and appears inside a content placeholder (and a master page), and the name is now a combination of the naming containers (ex. "ctl100$ContentPlaceholder1$TextBox1"), which is basically useless when trying to extract the values on the server. Do you know of any solutions or workarounds for this issue?
If you could send any replies to my email address as well, I would really appreciate it (schmuli @ gmail).

Saeed Neamati
May 28, 2011

# re: Using jQuery to POST Form Data to an ASP.NET ASMX AJAX Web Service

Great article. I like the way jQuery is used without ScriptManager (sucky ScriptManager). It is native to the browser and JavaScript nature.
There is only one problem here, and that is, your server code (Page Method body) is now dependent on the markup, which technically means that whenever you update your markup, you have to update the body of Page Method too.

Anyway, let's support CSS3. Have a look at <a href='http://www.thoughtresults.com/css-multicol'>CSS3 Multi-Column Layout Module</a>.

Thanks

Rick Strahl
May 28, 2011

# re: Using jQuery to POST Form Data to an ASP.NET ASMX AJAX Web Service

@Schmuli - if you're posting back to the same ASP.NET WebForm page, you can use Request.Form[this.txtName.UniqueId] to avoid having to manually type in the long unique id.

If you're posting to an alternate service handler though - you'll have to use the nasty long name. Ideally though if you are using AJAX only to handle your data I'd avoid using an ASP.NET server control for those controls that post back - use a plain textbox instead and your naming will be predictable.

Thiago Silva
July 28, 2011

# re: Using jQuery to POST Form Data to an ASP.NET ASMX AJAX Web Service

In ASP.NET 3.5+, if you decorate your web service class as a [ScriptService], then you don't have to do any of that mess. The framework will deserialize form data and try to match to the parameters of the web method.

Also, if you decorate the web method as [ScriptMethod], you can optionally pass into the ScriptMethodAttribute a named parameter to specify the ResponseFormat as JSON. Again, the framework will use the JavascriptSerializer to try and convert the method return object into JSON and send it down the pipe to the client.

Angela
June 19, 2012

# re: Using jQuery to POST Form Data to an ASP.NET ASMX AJAX Web Service

I'm developing a web aplication using MVC2, jQuery, AJAX, ASMX. My web service executing some SQL query, it is invoke into script tag but the problem is that the results of these query are empty.
Why dosen't executing the querys my web service?

Is clear muy question?


This is part of the web service:

[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[System.ComponentModel.ToolboxItem(false)]
[System.Web.Script.Services.ScriptService]
    public class WebService1 : System.Web.Services.WebService
    {
 
        [WebMethod]
        public string jsGenerateTreeView(string username)
        {
            string userid = userId(username);
            List<QueryMenu> oMenuList = PopulateMenu(userid);
            string oMenu = string.Format("<ul id='ulTreeView'>{0}</ul>", ConvertToTreeClass(oMenuList, 0));
            return oMenu;
        }
 
        public string userId(string username)
        {
            string userid = "";
 
            using (SqlConnection oCon = new SqlConnection(ConfigurationManager.ConnectionStrings["MyServices"].ConnectionString))
            {
                string query = @"SELECT PKID
                            FROM Users 
                            WHERE Username = '{0}'";
 
                SqlCommand oCmd = new SqlCommand(string.Format(query, username), oCon);
                oCon.Open();
                userid = "" + oCmd.ExecuteScalar();
 
                oCon.Close();
            }
 
            return userid;
        }


And this is part of the script:

<script type="text/javascript">
    function GetMenu(usercod) {
        
        var checkSession = "";
        $.ajax({
            type: "POST",
            url: '<%= ResolveUrl("~/WebService1.asmx/CheckSession") %>',
            data: "{}",
            contentType: "application/json; charset=utf-8",
            dataType: "json",
            success: function (msg) {
                checkSession = unescape(msg.d);
                if (checkSession == "") {
                    $.ajax({
                        type: "POST",
                        url: '<%= ResolveUrl("~/WebService1.asmx/jsGenerateTreeView") %>',
                        data: "{'username':' " + usercod + "'}",
                        contentType: "application/json; charset=utf-8",
                        dataType: "json",
                        success: function (msg) {
                            //debugger;
                            $("#menucontainer").append(msg.d);
                            $("#ulTreeView").treeview(
                                {
                                    collapsed: true,
                                    animated: "fast",
                                    persist: "location"
                                });
                            var Padreid = location.href.split("#")[1];
                            ExpandTreeView(Padreid);
                        },
                        error: function (ex) {
                            alert(ex.responseText);
                        }
                    });
                }


Any idea?

lebrissou
August 07, 2012

# re: Using jQuery to POST Form Data to an ASP.NET ASMX AJAX Web Service

please can you tell me or show me how to create method with ajax because i have my SOAP webservice but i did'nt no how to consume it and my interface is with jquery mobile..

West Wind  © Rick Strahl, West Wind Technologies, 2005 - 2024