Take a Walk on the Client Side with WebAPI and WebForms

Posted by: the telerik blogs, on 26 Apr 2012 | View original | Bookmarked: 0 time(s)

In my last blog I introduced the basics of using ASP.NET AJAX and WebAPI together in the same project.  In addition, I showed how to bind results from WebAPI to a Teleriks ASP.NET AJAX Grid. In this blog I will continue down this path and implement insert, update, and delete operations using WebAPI and RadGrid!

Note: While this post focuses on using WebAPI in conjunction with RadGrid, most of the client code can be used when working with RadGrid in client mode.

Set It Up!

To get started an additional reference needs to be added to the project:

  • System.Net.Http.Formatting

Adding this reference allows us to use HttpResponseMessage<T>, which will be used to return data to the client. 

Now on to the fun stuff!

Let There Be Customers!

Customers cant be updated or deleted if they dont exist; so I will start off by building a way to create customers. 

Creating customers requires:

A Post action method in the controller to create a customer.

A button in the UI to create a new customer.

A form to enter customer information.

A way to submit the new customers information to the server.

A way to refresh the grid to show the new customer.

 

Starting with step one, I am going to create the Post method in the CustomerController as shown here: 

public HttpResponseMessage Post(Customer newCustomer)
{
    repository.Add(newCustomer);
    repository.Save();
 
    var response = new HttpResponseMessage<Customer>(newCustomer, HttpStatusCode.Created);
    response.Headers.Location = new Uri(Request.RequestUri, "/api/customers/" + newCustomer.Id.ToString());
 return response;
}

This method adds a new customer to the repository, saves the customer, and then returns a new HttpResponseMessage<Customer>.  The response message passes the created customer in its body, and returns a status code of 201 Created. The 201 status code indicates to the client that the new resource was actually created.  We will look at how to handle that client side in a moment.

Before we can focus on client code, the UI needs to be setup.  The first thing to do is add a new command item in the grid header. This will be where a user clicks to add a new customer:

<CommandItemTemplate>
 <button onclick="addCustomer(); return false;">Add New Customer</button>
</CommandItemTemplate>

Now I need a form for users to enter the new customer information.  In this example I use Teleriks ASP.NET AJAX Window control to create a modal dialog, and add a form inside the dialog:

<telerik:RadWindow runat="server" ID="CustomerEditWindow" Modal="true">
 <ContentTemplate>
 <form id="customerInfoForm">
 <p>
                Enter Customer Information:
 </p>
 <asp:HiddenField ID="customerId" runat="server" />
 <div>
 <asp:TextBox ID="customerFirstName" runat="server" /></div>
 <div>
 <asp:TextBox ID="customerLastName" runat="server" /></div>
 <div>
<asp:Button ID="btnCancelCustomer" Text="Cancel" CssClass="secondaryAction" runat="server" OnClientClick="closeCustomerEditor(); return false;" />
 <asp:Button ID="btnSaveCustomer" Text="Save" runat="server" CssClass="primaryAction" OnClientClick="saveCustomer(); return false;" />
 </div>
 </form>
 </ContentTemplate>
</telerik:RadWindow>

I went ahead and added an extra hidden field to store the customer id.  This will be used later when implementing updates.  I also created two buttons, one to submit the form, and one to cancel customer creation. 

The last step to implementing customer creation is wiring everything up with JavaScript.  When the Add Customer button in the grid header is pressed, an addCustomer JavaScript function is called.  This JavaScript function simply shows the editor form, as shown here:

function addCustomer() {
   $find("<%=CustomerEditWindow.ClientID %>").show();
}

When the cancel button is clicked, the form needs to be cleared, and the window needs to be closed.  This is handled by the closeCustomerEditor JavaScript function:

function closeCustomerEditor() {
 //close dialog
    $find("<%=CustomerEditWindow.ClientID %>").close();
 
 //reset form
    $get("<%= customerFirstName.ClientID %>").value = ";
    $get("<%= customerLastName.ClientID %>").value = ";
    $get("<%= customerId.ClientID %>").value = ";
}

When the submit button is pressed, the saveCustomer JavaScript function is called. In the saveCustomer function a client side customer object is created using the values from the form, and then the createResource function is called passing the customer object, and a callback function.

function saveCustomer() {
 var customer = {
        FirstName: $get("<%= customerFirstName.ClientID %>").value,
        LastName: $get("<%= customerLastName.ClientID %>").value,
        Id: $get("<%= customerId.ClientID %>").value
                    };
 
    closeCustomerEditor();
    createResource(customer, refreshGrid);   
}

The createResource method sends an AJAX POST containing the new customers information to the Post action created earlier. If everything goes as expected the server will return a 204 Created result, and the callback function will be executed.

function createResource(model, success) {
    $.ajax({
        url: baseUrl,
        data: model,
        type: "POST",
        statusCode: {
            201: function (newResource) {
 if (success) {
                    success(newResource);
                }
            }
        }
    });
}

At this point customers can be created, and its time to implement editing!  

Oh No, I Miss Spelled His Name!

At this point we can move on to editing a customer.  Updating a customer is very similar to creating a new one. 

The main differences are:

The form needs to be pre-populated with the edited customers information.

When sending the updated information to the server the PUT verb should be used.

Lets dig in!

 

The first thing needed here, is a way to retrieve a customer by its id from the server.  For this I will create a new action method on the CustomerController named Get, and it will take a single parameter named id. The Get method will attempt to retrieve a customer based on the specified id, and if one is found, the server will return a new HttpResponseMessage<Customer> with a status code of 200 - OK. If no customer is found, it should throw a new HttpResponseException, and use the 404 Not Found status.

public HttpResponseMessage Get(int id)
{
  HttpResponseMessage response;
 
  var customer = repository.GetById(id);
 
 if (customer == null)
  {
 throw new HttpResponseException("Invalid Customer", HttpStatusCode.NotFound);
  }
 else
  {
      response = new HttpResponseMessage<Customer>(customer, HttpStatusCode.OK);
  }
 
 return response;
}

The next step is to create an action method in the CustomerController that will handle update operations for the customer resource.  The Put method retrieves the customer by its id, updates it, and returns a HttpResponseMessage with a 200 OK status code if it was successful:

public HttpResponseMessage Put(Customer updatedCustomer)
{
    HttpResponseMessage response;
    var customer = repository.GetById(updatedCustomer.Id);
 
 if (customer == null)
    {
 throw new HttpResponseException("Invalid Customer", HttpStatusCode.NotFound);
    }
 else
    {
        customer.FirstName = updatedCustomer.FirstName;
        customer.LastName = updatedCustomer.LastName;
 
        repository.Save();
 
        response = new HttpResponseMessage(HttpStatusCode.OK);
    }
 
 return response;
}

Note 1: By convention WebAPI will automatically use a method that starts with Put to handle requests using the PUT verb.  If you want to use a different method, simply add the [HttpPut] attribute to the method.

Note 2: If you want to add validation (everyone should! :)) please see the validation section below!

At this point the client-side needs to be wired up!

The first thing I need to do is add a new column to the grid, and then add a client side handler for grid commands.

Since the goal is to edit a customer, I added a column of type GridEditCommandColumn:

<telerik:GridEditCommandColumn ButtonType="ImageButton" ItemStyle-Width="20"/>

Next i configured the grids client events, and added a client side handler for OnCommand:

<ClientSettings ClientEvents-OnCommand="handleGridCommand">
 <DataBinding Location="~/api" ResponseType="JSON">
 <DataService TableName="Customers" Type="OData" />
 </DataBinding>
 <Scrolling AllowScroll="True" UseStaticHeaders="True" SaveScrollPosition="True" />
</ClientSettings>

Then I created a client method to handle the grid commands. The handleGridCommand function inspects the command, and if it is an edit command, the default grid action is cancelled, and the editCustomer client function is called:

function handleGridCommand(sender, args) {
 var command = args.get_commandName().toLowerCase();
 
 if (command == "edit") {
 
 //get the id for the data key for the command's row
 var customerId = getKeyValueByRowIndex(args.get_tableView(), args.get_commandArgument());
       editCustomer(customerId);
 
       args.set_cancel(true);
   }
}

The editCustomer function, queries the service for the customer by its id, sets the forms input values, and shows the edit customer modal dialog using a few additional methods:

function editCustomer(id) {
    getResource(id, loadEditScreen)
}
 
function loadEditScreen(resource) {
    $get("<%= customerFirstName.ClientID %>").value = resource.FirstName;
    $get("<%= customerLastName.ClientID %>").value = resource.LastName;
    $get("<%= customerId.ClientID %>").value = resource.Id;
    $find("<%=CustomerEditWindow.ClientID %>").show();
}
 
function getResource(id, success) {
    $.ajax({
        url: baseUrl + '/' + id,
        type: "GET",
        dataType: 'JSON',
        statusCode: {
            200: function (resource) {
 if (success) {
                    success(resource);
                }
            }
        }
    });
}

At this point the user should see the modal dialog, populated with the customers information.  Once they make some edits, and click the save button in the dialog, the saveCustomer JavaScript method is called just like when creating a customer; however, the method needs to be tweaked slightly to handle updating customers:

function saveCustomer() {
 var customer = {
        FirstName: $get("<%= customerFirstName.ClientID %>").value,
        LastName: $get("<%= customerLastName.ClientID %>").value,
        Id: $get("<%= customerId.ClientID %>").value
                    };
 
    closeCustomerEditor();
 
 if (customer.Id == '' || customer.Id == 0) {
        createResource(customer, refreshGrid);
    }
 else {
        updateResource(customer, refreshGrid);
    }
}

Now when saveCustomer is called the updateResource method will be called when saving an existing customer.

The updateResource method sends an AJAX request using the PUT Http verb. Using the PUT verb directs the controller to the Put action in the CustomersController. Once again, if everything goes as planed the server returns a status code of 200 OK, and the grid is refreshed:

function updateResource(model, success) {
  $.ajax({
      url: baseUrl + '/' + model.Id,
      data: model,
      type: "PUT",
      dataType:'JSON',
      statusCode: {
          200: function (resource) {
 if (success) {
                  success(resource);
              }
          }
      }
  });
}

Now we can fix any typo in a customers name, but what about when we need to remove a customer?!

Deleting Customers!

The final step in this journey is adding the ability to delete customers. Configuring deletes is very similar to the previous sections. 

In this case I need to add the following:

A server side action on the controller to handle the delete.

A new column in the grid to display the delete button.

Some JavaScript to handle the client side delete command.

A grid refresh once the customer is deleted.

 

Once again, I will start off with step one and setup the server side code first! In the CustomersController I created a Delete action method. The Delete method attempts to retrieve a customer by the specified id, and if a valid customer is found, the server returns a 204 No Content status. If the specified customer was not found, we will throw a new HttpResponseException, set a message and use the 404 Not Found status.

public HttpResponseMessage Delete(int id)
{
   HttpResponseMessage response;
 
   var customer = repository.GetById(id);
 
 if (customer == null)
   {
 throw new HttpResponseException("Invalid Customer", HttpStatusCode.NotFound);
   }
 else
   {
       repository.Remove(customer);
       repository.Save();
       response = new HttpResponseMessage(HttpStatusCode.NoContent);
   }
 
 return response;
}

 

Now we need to configure the UI.  First I will add a new column to the grid, except this time I will add a GridClientDeleteColumn:

<telerik:GridClientDeleteColumn ButtonType="ImageButton" ItemStyle-Width="20"/>

Now I need to update the handleGridCommand to make it handle the delete command as well:

function handleGridCommand(sender, args) {
 var command = args.get_commandName().toLowerCase();
 
 if (command == "delete" || command == "edit") {
 
 //get the id for the data key for the command's row
 var customerId = getKeyValueByRowIndex(args.get_tableView(), args.get_commandArgument());
 
 if (command == "delete") {
           deleteResource(customerId, refreshGrid);
       }
 else {
           editCustomer(customerId);
       }
 
       args.set_cancel(true);
   }
}

Here I check to see if the command is the delete command, and if it is, I cancel the default grid command, and call the deleteResource JavaScript method. The deleteResource method send an AJAX request to the server using the Http Delete verb. If the server returns a status code of 204, we know the delete was successful, and call the success callback function (if it was passed in).

function deleteResource(id, success, fail) {
    $.ajax({
        url: baseUrl + '/' + id,
        type: "DELETE",
        statusCode: {
            204: function () {
 if (success) {
                    success();
                }
            }
        }
    });
}

At this point delete operations should be working!

In Action

Now the fun part, seeing if it all works!  Go ahead and run the application, and you should see the grid populated with some customers (I like this store apparently):

image

Now open your favorite developer tools, create/update/delete some customers, and inspect the network traffic:

image

As you can see highlighted in red, everything is working as expected!

Validating the Request

In this blog I did not really cover validating the data model other than checking to see if valid ids were sent to the server. That said, the way the controller, and client code, is configured it is rather easy to return different status codes, and include the validation errors in the response.  This will allow you to leverage data annotations applied to your objects. 

As an example, if I applied an attribute to the Customer entity, and the data sent to the server was invalid, I could return ModelValidation errors like this:

if (!ModelState.IsValid)
{
 return new HttpResponseMessage<JsonValue>(ModelState.ToJson(), HttpStatusCode.BadRequest);
}

The ToJson method serializes the model errors to JSON, and the controller returns a new HttpResponseMessage<JsonValue> with a status code of 400 Bad Request.

ToJson Extension method:

public static class ModalStateHelpers
{
 public static JsonArray ToJson(this ModelStateDictionary modalState)
    {
        var errors = new JsonArray();
 foreach (var prop in modalState.Values)
        {
 if (prop.Errors.Any())
            {
                errors.Add(prop.Errors.First().ErrorMessage);
            }
        }
 
 return errors;
    }
}

The client can then handle this status code, and show the errors to these errors to the user:

function showErrors(errors) {
    var strErrors = ";
 if ($.isArray(errors)) {
        $.each(errors, function (index, err) {
            strErrors += "*" + err + "\n";
        });
    }
 else {
        strErrors = errors;
    }
    alert(strErrors);
};

While I didnt go in-depth about validation in this article, the sample code does have data attributes applied to the Customer object, and validation is handled; so make sure to check it out! :)

That's a Wrap!

Wow, this blog came out quite a bit longer than I expected! That said, hopefully it will help you get started using ASP.NET AJAX, and WebAPI!

Download Source

Happy Coding!

About the author

Joshua Holt

Joshua Holt

Joshua Holt works as a Developer Support Specialist at Telerik focusing on ASP.NET, but tends to dabble in all of the products. Part of his role at Telerik is to work closely with the team, and help developers leverage the Telerik product line up. In his free time he likes to tinker with spatial data visualization projects, and chase clouds. Prior to working at Telerik, Joshua worked as a consultant for several Fortune 500 companies in the oil and gas industry.

@jholt456

Category: Ajax | Other Posts: View all posts by this blogger | Report as irrelevant | View bloggers stats | Views: 778 | Hits: 11

Similar Posts

  • Take a Walk on the Client Side with WebAPI and WebForms Part 2 more
  • ClientIDMode in ASP.NET 4.0 more
  • Introducing the new Task type more
  • Accessing the ASP.NET Authentication, Profile and Role Service in Silverlight more
  • Silverlight 2 Essential Getting Started Facts and Guide more
  • ClientBase<T> and a Common Base Class more
  • Mapping Associations in the EDM Designer more
  • Working with Multiple Versions of the SourceGear Vault Client more
  • Sidebar + SideShow = Love more
  • WCF Client Channel Pool - Improved Client Performance more

News Categories

.NET | Agile | Ajax | Architecture | ASP.NET | BizTalk | C# | Certification | Data | DataGrid | DataSet | Debugger | DotNetNuke | Events | GridView | IIS | Indigo | JavaScript | Mobile | Mono | Patterns and Practices | Performance | Podcast | Refactor | Regex | Security | Sharepoint | Silverlight | Smart Client Applications | Software | SQL | VB.NET | Visual Studio | W3 | WCF | WinFx | WPF | WSE | XAML | XLinq | XML | XSD