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:
- Capture form variables into an array on the client with jQuery’s .serializeArray() function
- Use $.ajax() or my ServiceProxy class to make an AJAX call to the server to send this array
- On the server create a custom type that matches the .serializeArray() name/value structure
- Create extension methods on NameValue[] to easily extract form variables
- 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:
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(" " + 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.
Other Posts you might also like