MVC Form Helper

by Seth 23. July 2008 20:50

Moving away from ASP 3.0 Request.Form(" ...

As mentioned in my previous post, I wanted to find a way to auto-populate and entity object from the Request.Form collection. I also wanted (as an aside) to see if the grid still worked on MVC Preview 4 (it does). Here is the code:

public static T Request<T>(this Controller controller) where T : class, new()
{
    T o = new T();
    var request = controller.Request;
    foreach (var property in typeof(T).GetProperties())
    {
        // Check if the object has the appropriate property
        var q = (request.Form.AllKeys
            .Where<string>(s => s.ToLower() == property.Name.ToLower()))
            .ToList<string>();

        // if more than one... ignore
        if(q.Count == 1)
        {
             string datum = request.Form[q[0]];
             if (property.PropertyType == typeof(string))
                 property.SetValue(o, datum, null);
             else
                 property.SetValue(o,
                    Convert.ChangeType(datum, property.PropertyType),
                    null);
         }
    }

    return o;
}

Notice that this is not an extension on the HtmlHelper since this will only be used inside the controller (hence a controller extension method). Here is how it is used:

Student s = this.Request<Student>();

Now for the explanation! The constraints on T (line 1, the where part) say that T must be a reference type and have a parameter-less constructor. I guess it doesn't have to have a parameter-less constructor but I thought it would be a pain to find out which request key matched up with which parameter in the constructor. I also thought that Entity classes should be POCO objects so they would naturally have an empty constructor. Some screenshots:

Before:

image

After:

image

The Grid

I am continuing to work on the grid (this was a part of it). I just haven't gotten around to implementing the next thing all the way. The goal again was to have more control over how the grid should be rendered. I am borrowing some of the ideas from the MVCContrib grid but simplifying it a whole lot (for simple users like myself)

MVC Preview 4

I uninstalled preview 3 and then installed preview 4 and shortly after found that VS could not open my preview 3 stuff. Instead of rigging it, I just re-created the MVC part. The Controls assembly (the one with the grid) worked perfectly after correcting the references to the MVC dll's.

Tags: ,

ASP.NET | MVC

Ajax HTML Grid Control for ASP.NET MVC (Part 3)

by Seth 10. July 2008 20:24

Moving to an HTML Helper

As I was looking around at the various HTML helpers out there, I realized that I should probably conform and stick to what is being done. Having said that, I refactored the Grid class to be a new HTML helper. Here is how it is used (similar to an MvcForm<T> actually):

<% using (Html.GridControl<StudentController, StudentEntity, int>(
   s => s.EditStudent(), 
   s => s.DeleteStudent(), 
   ViewData.Model, 
   s => s.StudentId, 
   "Student",
   null)) {  %>
Special text here!
<% } %>

The GridControl has now been attached as an extension method to the standard HTML Helper. Doing that required a bit of re-arranging on the parameters that needed to be passed in to the helper. The first are the types being used:

  1. The first type is the Controller being used for updates
  2. The second is the type of item in the collection
  3. The last is the data type of the key of each item

For the parameters:

  1. The first specifies the controller action when an item is edited
  2. The second specifies the controller action when a item is deleted
  3. The third is the actual Collection object
  4. The fourth tells the GridControl how to generate the key for each item
  5. The fifth is the name to use for the control
  6. The last specifies any additional html attributes to use (not implemented currently)

I really liked using the action expressions since it allows a better granularity in handling the actual Ajax requests to the controller. I used the this code to generate the appropriate URLS (strings):

_editAction = helper.BuildUrlFromExpression(editAction);
_deleteAction = helper.BuildUrlFromExpression(deleteAction);

An important thing to note is that the grid is actually printed out when the helper is disposed. This means that anything that is placed inside of the using brackets will be shown before the grid is displayed.

grid

Getting Data to the Controller

Now for the actual work! The first thing I wanted to ensure was that I sent the data back to the controller via a POST and not a GET method. The $.getJSON function in JQuery performs a GET (see post) so I needed to change that. Here is what the grid produced for the save and delete actions:

else if(action == 'save')
{
    var arr = $('#Student_Form').serializeArray();
    $.ajax({
       type: 'POST',
       contentType: 'application/x-www-form-urlencoded',
       url: '/Student/EditStudent',
       data: arr,
       dataType: 'json',
       success: function(msg){
         alert( 'Data Saved: ' + msg );
       }
     });
}
else if(action == 'delete')
{
    var dat = 'DataId=' + id;
    $.ajax({
       type: 'POST',
       contentType: 'application/x-www-form-urlencoded',
       url: '/Student/DeleteStudent',
       data: dat,
       dataType: 'json',
       success: function(msg){
         alert( 'Delete: ' + msg );
       }
     });
}

Notice that this is a continuation of the JavaScript from the previous post. The fundamental JQuery call for AJAX is $.ajax(properties) where the properties are the specifications on how the AJAX call should be made. The first and most important thing was to use POST rather than GET. A GET http call is supposed to be idempotent while a POST call can actually change the state of the server. Both the delete and save actions should change the state of the server. Also, from a security standpoint, it is always easier to craft some form of URL to cause an action to execute on a controller when using GET. This can open things up to malicious attacks. Imagine anyone simply typing http://yoursite/Student/DeleteStudent?DataId=1232 into their browser and the controller deleting that record. That would be bad. With a POST this can be controlled better.

My favorite part of the JQuery code is the .serializeArray() part. This takes each of the valid form members and automagically places the data in a JSON array structure to be passed to the controller.

Getting the Data in the Controller

Here is a nice screenshot of what happens when the controller save action is fired:

postback

The AJAX postback, in conjunction with the .serializeArray() call placed all of the items of the StudentEntity in the Request.Form collection. Now for the ugly code (reminiscent of ASP 3.0)

public JsonResult EditStudent()
{
	StudentEntity student = new StudentEntity();
	student.StudentId = int.Parse(Request.Form["StudentId"]);
	student.Address = Request.Form["Address"];
	
	int age;
	if (int.TryParse(Request.Form["Age"], out age)) student.Age = age;
	student.FirstName = Request.Form["FirstName"];
	student.LastName = Request.Form["LastName"];
	student.Phone = Request.Form["Phone"];
	student.State = Request.Form["State"];
	student.Zip = Request.Form["Zip"];
	
	StudentService.SaveStudent(student);
	
	return new JsonResult
	{
		 Data = "Edited student " + student.StudentId.ToString() + "!"
	};
}

The result sent back is a simple message. More could be done here, but I have not thought about it too much. I also dislike the whole Request.Form["val"] stuff. I think this could be automated using the grid somehow so we can get rid of the tedious code. Actually I think adding a generic static method to the Grid class should do the trick. I will probably add it next time.

Things to do

So far things seem to be going well. Here are some things I would like to add:

  1. Better entity population from the Request.Form collection
  2. An "Add New" feature to the grid
  3. On delete of a row, remove the TR element from the table (the database thinks it is gone, but the HTML says otherwise)
  4. CSS Grid customization

If you can think of any other things drop me a line.

Code

The Code

Tags: , , ,

Ajax | ASP.NET | JQuery | MVC

Ajax HTML Grid Control for ASP.NET MVC (Part 2)

by Seth 8. July 2008 00:11

Preamble

Over the long weekend I thought a lot about where I wanted to go with the grid "control" for ASP.NET MVC. One of the things that weighed heavily on my mind was the ability to have the control fully customizable. As I thought about this, I decided that first thing's first: I need to get the functionality working and then worry about the prettiness factor. So for those of you concerned about the customizability - it's coming. For now, I really want to focus on the ability of the grid to get work done.

Code Bloat

As I began to dive again into the previous code I realized that having everything in one extension method was going to be... well ugly. The first order of business was to abstract everything in to a separate DLL. In order to do so, I created a new Grid class that handles all of the grid drawing.

JavaScript

I am partial to using JQuery. So here is the general idea of what the JavaScript code needs to do:

  1. Detect that the user would like to edit a particular row
  2. Detect any rows previously being edited (to move the edit focus to the new row being edited)
  3. Display text boxes for the user to have the ability to edit the values
  4. Post back an edit or a delete

In order to do each of the items above, I needed to change what the grid was outputting a little. First I needed to wrap the table in a <form> tag in order to retrieve any valid values being edited. Also, I needed to add the following links: Edit, Delete, Cancel, Save. The first two (Edit, Delete) should be shown when a row was not in edit mode. The second two (Cancel, Save) should only be shown when a row is in edit mode. I also wanted to make sure that only one row was being edited at a time. Here is a fragment of the HTML the control produces:

<form name="Student_Form" id="Student_Form">
<table id="Student" style="border: solid 1px black;width:100%;">
<tr id="Student_6">
    <td class="edit">
    <span class="editor">
       <a id="6_Student_edit" href="#">Edit</a>  <a id="6_Student_delete" href="#">Delete</a>
    </span>
    <span class="editing">
       <a id="6_Student_cancel" href="#">Cancel</a>  <a id="6_Student_save" href="#">Save</a>
    </span>6</td>
    <td class="FirstName">Frances</td>
    <td class="LastName">Adams</td>
    <td class="Age">&nbsp;</td>
    <td class="Address">&nbsp;</td>
    <td class="City">&nbsp;</td>
    <td class="State">&nbsp;</td>
    <td class="Zip">&nbsp;</td>
    <td class="Phone">&nbsp;</td>
</tr>
...

Once I decided on the DOM elements the form would produce, it was time to put some JavaScript to each row. The first bit of code is designed to attach events to each of the links in the grid as well as hide the "editing" spans since each row by default starts in non-edit mode:

$('document').ready(
    function(){
        $('#Student span.editing').hide();
        $('#Student a').click(
            function(event){
                event.preventDefault();
                handleEditClick(this.id);
            }
        );
    }
);

This code ensures that the editing spans are hidden and each click event on the anchor tags are redirected to the handleEditClick function. Notice that I pass in the id of the actual anchor tag. These I defined as id_Grid_action (see above). Whenever a link is pushed I get those three pieces of information to proceed with processing. Now for the handleClick function (it is long):

function handleEditClick(itm) {
    var o = itm.split('_');
    var id = o[0];
    var grid = o[1];
    var action = o[2];
    var name = '#' + grid + '_' + id + ' td';

    if(action == 'edit')
    {
        // un-edit any others that might be in editmode
        $('#Student span.editor:hidden a:first').each(
            function() {
                var cl = this.id.split('_');
                $('#' + cl[0] + '_' + cl[1] + '_cancel').click();
            }
        );

        $('.editor', name).hide();
        $('.editing', name).show();

        $(name).each(
            function() {
                if($(this).hasClass('edit')) return;
                var data = $(this).text() == ' ' ? '' : $(this).text();
                $(this).html('<input type="text" name="' + 
					$(this).attr('class') + 
					'" value="' + 
					data + 
					'" size="10" />');
            }
        );
    }
    else if(action == 'cancel')
    {
        $('.editor', name).show();
        $('.editing', name).hide();
        $(name).each(
            function() {
                if($(this).hasClass('edit')) return;
                var data = $('input', this).val();
                $(this).html(data == '' ? 'nbsp;' : data);
            }
        );
    }
    else if(action == 'save')
    {
        //alert('Save ' + itm);
        var arr = $('#Student_Form').serializeArray();
        $.each(arr, 
            function(i, field) {
                alert(field.name + ': ' + field.value);
            }
        );
    }
    else if(action == 'delete')
        alert('Delete ' + itm);

As advertised, I first break up the id_gird_action pair into variables that will be useful. Also notice that ALL actions come into this function. First lets focus on the edit action. Now this is why I love JQuery:

$('#Student span.editor:hidden a:first').each(...

This particular line of code selects the first anchor tag under a span with class editor that is hidden from the Student grid. Why would I want to do that? From the id of the anchor tag I can reconstruct the id of the cancel anchor tag and then "click" it in order to cancel the update. To do it any other way would be difficult (at least I think so). The elegance of JQuery allows for those kinds of things. Once we cancel any other edit, we proceed to go through each TD in the row in question (with the exception of the edit anchors) and push the data into text boxes. Also, we set the appropriate edit spans to visible and hidden in order to have the correct actions displayed. Next, the Cancel action. The job of the cancel action is to take the data out of the text boxes and stuff them back into the TD tag. I realize that I should probably add a "Do you want to save this?" confirm box, but I will leave that for later. Notice again the elegance of JQuery:

var data = $('input', this).val();

The $(INPUT, TD) functions as a selection within a previous selection. In other words, within the current TD, find me an INPUT HTML element and retrieve the value. Once we retrieve the value, we can put it back into the TD tag without the INPUT element.

The Grid Class

The first thing I did was write the JavaScript code with the output from the Grid class. In other words, I ran the flat table (without the JavaScript) and cut and pasted the table to a standard HTML file. Once I had the new HTML file I worked on the JavaScript until it worked as I expected. Now the only problem left was creating  a RenderJavaScript() that emitted the Grid specific JavaScript we needed. Doing that seemed a bit tedious so I downloaded a nifty little Add In that did it for me.

Outcome

Some screens:

image

Usage

Now that I've abstracted the Grid out to a completely separate project, I changed the helper class to this:

public static string ToAjaxGrid<T, TKey>(this IEnumerable<T> list, 
                                 Func<T, TKey> key, 
                                 string name)
{
   Grid<T, TKey> grid = new Grid(list, key, name);
   return grid.Render();
}

This change allowed me to leave the code in the View the same:

<%= ViewData.Model.ToAjaxGrid(s => s.StudentId, "Student") %>

Next Time

I think the client side functionality is (mostly) done. For the next installment I will try to actually submit the requested actions to the MVC controller (save, delete).

Code

Download

Tags: , , ,

Ajax | ASP.NET | JQuery | MVC

About the author

356044 My name is Seth Juarez. I currently reside in Salt Lake City and develop web applications for my church.

I received my Bachelors Degree in Computer Science at UNLV with a Minor in Mathematics. I recently completed my Masters Degree at the University of Utah and am continuing on to a PhD in the field of Computer Science. I currently am interested in Artificial Intelligence specifically in the realm of Machine Learning. I currently am working on a .NET library meant to simplify the usage of the common machine learning algorithms.

I've been married now for 8 years to a fabulously beautiful girl and have two wonderful daughters and a son.

RecentComments

Comment RSS