Announcement: WCF Web API is now ASP.NET Web API! ASP.NET Web API released with ASP.NET MVC 4 Beta. The WCF Web API and WCF support for jQuery content on this site wll removed by the end of 2012.

Submitting HTML form data to a service

One of the most common tasks that developers on the web face is submitting data from a HTML form into a database backend. Upon form submission, we need to ensure that the data is valid before we pass it to our data tier, so we need to build some validation logic into our service. To improve the client experience we also want to pre-validate the data on the client before we submit it to the server, using the same validation rules as the service itself. This provides a nice user experience, where the user can find out if the data is invalid without making a roundtrip to the server.

Development for this scenario is currently  In Progress, so the solution described below might be incomplete. Please be sure to provide feedback in the comments or file a bug regarding your ideas for improving it.

A sample demonstrating this scenario can be found at WCFJQuery/Samples/CalendarEntry. In this particular example, we have designed a form that lets us create a new event in our calendar.

CalendarEntry

This sample relies on the following client-side libraries - jquery, jquery-ui, jquery-validate – and assumes you are familiar with them. Please refer to these links to look up any concepts we refer to that might be unfamiliar.

Serializing the form data

To send the filled out form to the server, we attach a handler to the form’s submitted event.

  1. $("form").submit(function () {
  2.     // removed for clarity
  3.     $.ajax({
  4.         type: "POST",
  5.         url: "calendar/",
  6.         // use application/form-url-encoded parameters to serialize form data
  7.         data: $(this).serialize(),
  8.         // removed for clarity
  9.         success: function (value) {
  10.             // display success message
  11.         },
  12.         error: function (xhr, status, error) {
  13.             if (status == "error") {
  14.                 // use jquery-validate to show error
  15.                 validator.showErrors($.parseJSON(xhr.responseText));
  16.             }
  17.         }
  18.     });
  19.     return false;
  20. });

A couple of points to note:

  • We use the POST verb to submit the form, although GET can also be used with minor changes on the server
  • We use jQuery’s serialize() method to take all the fields in the form and their values and submit them to the service. This uses the application/form-url-encoded format by default.
  • If the server returns a success status code (200), we display a success error message
  • If the server returns an error status code (400-500), we know there was a validation error when submitting the form. The body of the response in this case will contain a JSON object with details about the validation errors encountered. We can use jQuery’s parseJSON() method to get an object out of the JSON response, and then we can use jquery-validate’s showErrors() method to highlight the offending field in the form itself.

In our service, we write a simple method to accept the form submission.

  1. [WebInvoke(UriTemplate = "", Method = "POST")]
  2. public JsonObject Post(JsonObject contact)
  3. {
  4.     // do something with the submitted data
  5.     
  6. }

The JsonObject type will contain the name/value pairs from the form. More details on JsonObject here.

Server-side validation

Once we have the JsonObject containing the form data, we have to perform validation on that data before we insert it in our database. We also need to alert the user of any mistakes they can correct.

JsonObject provides a set of fluent validation attributes based on System.ComponentModel.DataAnnotations, which make validation very straightforward.

  1. contact.ValidateStringLength("What", 1, MaxFieldLength)
  2.         .ValidateCustomValidator("StartDate", typeof(CustomValidator), "ValidateMeetingTime")
  3.         .ValidateStringLength("Where", 1, MaxFieldLength)
  4.         .ValidateStringLength("Description", MaxFieldLength)
  5.         .ValidateCustomValidator("ReminderValue", typeof(CustomValidator), "ValidateReminder")
  6.         .ValidateEnum("ShowMeAs", typeof(ShowMeAs))
  7.         .ValidateCustomValidator("Guests", typeof(CustomValidator), "ValidateGuestEmails");

If a validation error occurs, the above code will throw a ValidationException, which the service will wrap and return to the client, where we can use jquery-validate to display the error.

Client-side validation

This example does not include client-side validation yet, but that is something that is definitely being considered as the scenario matures. One possible approach is to serialize the validation rules we already defined in our service and send them over to the jquery-validate plugin on the client to enforce. Feel free to leave your thoughts in the comments on how this could be implemented.

Last edited Feb 17, 2012 at 8:35 PM by danroth27, version 5

Comments

racielrod Jul 15, 2011 at 8:54 PM 
The problem was having chars that were breaking the url, I just base64+url enconded it, and it is working like a charm.

racielrod Jul 14, 2011 at 2:23 PM 
Does anybody know if using this same principle I can PUT a resource?

The code would look like:

[WebInvoke(Method = "PUT", UriTemplate = "{id}")]
public HttpResponseMessage Update(string id, JsonValue json)
{
//logic here.
}

However, with this approach, I'm getting a 404 response from the server.
Any thoughts?
Thanks,
Raciel

racielrod Jun 22, 2011 at 5:45 PM 
I just tried this and I'm getting the following exception:

Server Error in '/' Application.

To be XML serializable, types which inherit from IEnumerable must have an implementation of Add(System.Object) at all levels of their inheritance hierarchy. System.Json.JsonValue does not implement Add(System.Object).

Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.

Exception Details: System.InvalidOperationException: To be XML serializable, types which inherit from IEnumerable must have an implementation of Add(System.Object) at all levels of their inheritance hierarchy. System.Json.JsonValue does not implement Add(System.Object).

Source Error:

An unhandled exception was generated during the execution of the current web request. Information regarding the origin and location of the exception can be identified using the exception stack trace below.

Stack Trace:


[InvalidOperationException: To be XML serializable, types which inherit from IEnumerable must have an implementation of Add(System.Object) at all levels of their inheritance hierarchy. System.Json.JsonValue does not implement Add(System.Object).]
System.Xml.Serialization.TypeScope.GetEnumeratorElementType(Type type, TypeFlags& flags) +924412
System.Xml.Serialization.TypeScope.ImportTypeDesc(Type type, MemberInfo memberInfo, Boolean directReference) +4045552
System.Xml.Serialization.TypeScope.GetTypeDesc(Type type, MemberInfo source, Boolean directReference, Boolean throwOnError) +192
System.Xml.Serialization.TypeScope.ImportTypeDesc(Type type, MemberInfo memberInfo, Boolean directReference) +864
System.Xml.Serialization.TypeScope.GetTypeDesc(Type type, MemberInfo source, Boolean directReference, Boolean throwOnError) +192
System.Xml.Serialization.ModelScope.GetTypeModel(Type type, Boolean directReference) +88
System.Xml.Serialization.XmlReflectionImporter.ImportTypeMapping(Type type, XmlRootAttribute root, String defaultNamespace) +66
System.Xml.Serialization.XmlSerializer..ctor(Type type, String defaultNamespace) +521
System.Xml.Serialization.XmlSerializer..ctor(Type type) +6
Microsoft.ApplicationServer.Http.Description.HttpOperationHandlerFactory.SetSerializerForXmlFormatter(HttpOperationDescription operation, Type type, MediaTypeFormatterCollection formatters) +421
Microsoft.ApplicationServer.Http.Description.HttpOperationHandlerFactory.SetXmlAndJsonSerializers(HttpOperationDescription operation, HttpParameter httpParameter, MediaTypeFormatterCollection formatters) +176
Microsoft.ApplicationServer.Http.Description.HttpOperationHandlerFactory.OnCreateRequestHandlers(ServiceEndpoint endpoint, HttpOperationDescription operation) +622
Microsoft.ApplicationServer.Http.Description.DelegateOperationHandlerFactory.OnCreateRequestHandlers(ServiceEndpoint endpoint, HttpOperationDescription operation) +106
Microsoft.ApplicationServer.Http.Description.HttpOperationHandlerFactory.CreateRequestHandlers(ServiceEndpoint endpoint, HttpOperationDescription operation) +155
Microsoft.ApplicationServer.Http.Description.HttpBehavior.OnGetMessageFormatter(ServiceEndpoint endpoint, HttpOperationDescription operation) +169
Microsoft.ApplicationServer.Http.Description.HttpBehavior.OnApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher) +1918
Microsoft.ApplicationServer.Http.Description.HttpBehavior.ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher) +150
System.ServiceModel.Description.DispatcherBuilder.InitializeServiceHost(ServiceDescription description, ServiceHostBase serviceHost) +3986
System.ServiceModel.ServiceHostBase.InitializeRuntime() +60
System.ServiceModel.ServiceHostBase.OnBeginOpen() +27
System.ServiceModel.ServiceHostBase.OnOpen(TimeSpan timeout) +50
System.ServiceModel.Channels.CommunicationObject.Open(TimeSpan timeout) +318
System.ServiceModel.Channels.CommunicationObject.Open() +36
System.ServiceModel.HostingManager.ActivateService(String normalizedVirtualPath) +184
System.ServiceModel.HostingManager.EnsureServiceAvailable(String normalizedVirtualPath) +615

[ServiceActivationException: The service '/form' cannot be activated due to an exception during compilation. The exception message is: To be XML serializable, types which inherit from IEnumerable must have an implementation of Add(System.Object) at all levels of their inheritance hierarchy. System.Json.JsonValue does not implement Add(System.Object)..]
System.Runtime.AsyncResult.End(IAsyncResult result) +679246
System.ServiceModel.Activation.HostedHttpRequestAsyncResult.End(IAsyncResult result) +190
System.ServiceModel.Activation.AspNetRouteServiceHttpHandler.EndProcessRequest(IAsyncResult result) +6
System.Web.CallHandlerExecutionStep.OnAsyncHandlerCompletion(IAsyncResult ar) +96

andybooth Nov 15, 2010 at 9:21 AM 
Making JSON-Schema and jQuery Validate work together might be a bit tricky, yes. I suppose longer term, the bridge between the two might be the jQuery Data Link plugin, from Microsoft/jQuery. UI validation can be via jQuery validate, Data Link provides two-way binding to the model, and JSON schema provides model validation.

yavorg Nov 7, 2010 at 2:09 AM 
Thanks Andy... we use jquery-validate on the client in this sample. Is there a way to integrate that with JSON schema or are they alternatives?

andybooth Oct 31, 2010 at 8:37 PM 
Json Schema (http://json-schema.org/) seems like an interesting client side validation integration point ie. convert Data Annotations to Json Schema and send to client for validation with js library.