Convention Over Configuration - 30 Oct 2009

Creating Applications Using ASP.NET MVC, jQuery, and AJAX: Part II

asp:Feature

LANGUAGES: C# | VB.NET

ASP.NET VERSIONS: 3.5

 

Convention Over Configuration

Creating Applications Using ASP.NET MVC, jQuery, and AJAX: Part II

By Emad Ibrahim

 

As is the nature of working with bleeding-edge preview technology, you are bound to bleed once in a while. A lot has changed since Part I of this article was written. Preview 4 of ASP.NET MVC was released and now we have to change some of our code. The code changes are minor, but the new project template includes views and code to log in, register, and log out. Although these pages don t use AJAX, it would be easy to AJAX-enable them. For the sake of simplicity, I am going to start with a new project using the new template (see end of article for download details).

 

Let s say we are building a classifieds Web site similar to craigslist. The first thing we ll do is enable the user to list an item for sale. A listing is made up of a title, description, and, optionally, an image. First, let s create a new controller named ListingController and add a New action. This action only needs to render the view for creating a new listing (see Figure 1).

 

_

Public Function [New]() As ActionResult

   Return View()

End Function

Figure 1: Controller action to render the new listing view.

 

Notice that we don t tell it which view to render; instead, it will automatically look for the view with the same name as the action ( new ). It will look for new.aspx or new.ascx in the Views | Listing folder, then it will look in the Views | Shared folder. Alternatively, you could call View( new ). Also note the Authorize attribute; this is new to Preview 4 and it s the quickest way to authorize an action. By adding this attribute, only a logged-in user will be allowed to run it. If you are not logged in, you ll be directed to the log-in page. All this is done automatically, starting with the Preview 4 version.

 

Let s create the view by adding a new MVC View Content Page and using the Site.Master page in the Views | Shared folder as the master. Run the project (Ctrl+F5) and navigate to http://localhost:9055/listing/new (you should get a blank content page). We need to collect a title, description, and image on this page, so let s add all the necessary controls and a Submit button. The HTML is shown in Figure 2; the form is shown in Figure 3.

 

 

Title

   id="valTitle">

 <%=Html.TextBox("txtTitle", New With {.style =

   "width:300px"})%>

 

Description

   id="valDescription">

 <%= Html.TextArea("txtDescription","",8,50) %>

 

Images

   id="valImage">

 

   name="formImage" id="formImage">

   

 


 onclick="submit();" value="Submit" />

Figure 2: HTML for the create listing view.

 


Figure 3: The create new list page.

 

When the user clicks the Submit button, we want to perform some basic validation on the client (JavaScript), submit the request using AJAX to create the listing, then upload the image.

 

Validation is pretty straightforward; title and description are required, and if an image is selected, it should have a jpg or jpeg extension. The validation method is shown in Figure 4 (the image validation method is shown in Figure 5); note the use of the jQuery methods:

  • val to get the value of the element
  • hide to hide the matching elements
  • html( some html string ) to set the html of the element
  • show to display the matching elements

 

function isValid()

{

 $("span.validation").hide();

 var isvalid = true;

 if (!imageIsValid()) {

   $("#valImage").html("Invalid image. Use jpg.").show();

   var isvalid = false;

 }

 if($("#txtTitle").val().length == 0) {

   $("#valTitle").html("* Title is required").show();

   var isvalid = false;  

 }

 if($("#txtDescription").val().length == 0) {

   $("#valDescription").html("* Description is

      required").show();

   var isvalid = false;  

 }

 return isvalid;

}

Figure 4: JavaScript to validate a page.

 

function imageIsValid()

{

 var filename = $("#fileImage").val().toLowerCase();

 if (filename.length == 0) return true;

 if ((filename.lastIndexOf(".jpg") == -1) &&

      (filename.lastIndexOf(".jpeg") == -1))

   return false;

 return true;

}

Figure 5: JavaScript to validate a file extension.

 

As discussed in Part I, we are using jQuery to select and manipulate DOM elements. We first hide all spans with a validation class, then check the title, description, and image and display the appropriate error message. If everything checks out, we return true. Note the method chaining that makes jQuery very powerful and easy to use. Chaining allows you to do things like $( aDiv ).html( hello world ).hide( slow ).show( fast ). This seemingly simple JavaScript line will select the element named aDiv , change its HTML content to hello world , then hide it slowly, then show it fast. Each method basically returns an object that the following method acts upon to create a chain of method calls.

 

Now that the form is valid, we need to submit it (see Figure 6). We ll submit the form to /listing/create, so we ll need a new action in the listing controller named Create. This action will create the record in the database and then return a JSON object, which includes the Id of the inserted record. If the creation action works, we ll then upload the file using the Id sent back to us.

 

function submit()

{

 if (!isValid()) return;

 var title = $("#txtTitle").val();

 var description = $("#txtDescription").val();

 $("#btnSubmit").attr("disabled","disabled");

 //create the listing

 $.ajax({

   type: "POST",

   dataType: "json",

   url: "/listing/create",

   data: {title : title,

       description : description

   },

   success: function(result) {

     if (result.success){

         if ($("#fileImage").val().length > 0 ) {

           $("#formImage").attr("action",

             "/listing/upload/" + result.id);

           $("#formImage").submit();

       } else {

           window.location = "/listing/list";

       }

     }else {

       alert("Failed to create listing");

     }

   },

   error: function(error) {

   }

 });

 $("#btnSubmit").removeAttr("disabled");

}

Figure 6: JavaScript to submit the form.

 

Model Interruption

Before we can write code to create the listing, let s take a quick break to create the model. We need a table for the listing and another for the image. Although we are only adding one image, it is reasonable to assume that we ll be adding the ability to post multiple images in the near future; the database diagram is shown in Figure 7.

 


Figure 7: The database diagram.

 

We ll now create a LINQ to SQL model in the Models folder and call it DB. You can simply drag the two tables created above from the Server Explorer pane into the design surface. The model is shown in Figure 8.

 


Figure 8: The LINQ to SQL class diagram.

 

This looks similar to the database diagram with one minor exception: the entity names are singular. Listing and ListingImage are now classes in your project that model an entity. Let s get back to creating the Create action using the newly created model entities (see Figure 9).

 

_

Public Function Create(ByVal title As String, ByVal description

                       As String) As JsonResult

 Try

   Dim db = New DB

   Dim listing = New Listing With { _

                  .Title = title, _

                  .Description = description, _

                  .Username = User.Identity.Name.ToLower _

                   }

   db.Listings.InsertOnSubmit(listing)

   db.SubmitChanges()

   Return Json(New With {.success = True, .id = listing.Id})

 Catch ex As Exception

   Return Json(New With {.success = False, _

                         .message = ex.Message})

 End Try

End Function

Figure 9: The controller action to create a new listing.

 

This method creates a record in the database using the model created above and then returns the Id of the record in a JSON object. There are a few things to note here. First, the return type is JsonResult, which returns a JSON object to the browser. The returned JSON object is an anonymous object, the Json method is part of the MVC Framework and knows how to serialize the anonymous object back to the browser. Also note that the action is decorated with the Authorize attribute, as previously explained.

 

JSON stands for JavaScript Object Notation. As defined on http://www.json.org, it ... is a lightweight data-interchange format. It is easy for humans to read and write.

 

Now that the record is created, the submit method shown in Figure 6 will submit the form containing the image to the /listing/upload/Id. So let s create the upload action to save the file and create the database record (see Figure 10).

 

_

Public Function Upload(ByVal id As Long) As ActionResult

 If (Request.Files.Count = 0) Then

   Return RedirectToAction("List")

 End If

 Dim db = New DB

 If (db.Listings.Where(Function(l)

     (l.Id = id)).Count = 1) Then

   Dim filename As String = UploadFile()

   Dim listingImage = New ListingImage With _

                   {.ListingId = id, _

                   .Filename = filename}

   db.ListingImages.InsertOnSubmit(listingImage)

   db.SubmitChanges()

 End If

 Return RedirectToAction("List")

End Function

Figure 10: Controller action to upload a file.

 

In a real-world application you ll probably have a service layer abstracting the model, but because this article is not about data layer design patterns, and for the sake of simplicity, we are directly using the SQL to Entity classes.

 

After a successful post, we redirect the user to the /listing/list page, which will display all the items listed by the user. We need to repeat the same steps performed above: create a List action in the controller and a list.aspx view. When all is done, we ll end up with the view shown in Figure 11. The List view (list.aspx) markup is shown in Figure 12.

 


Figure 11: A grid displaying the listings.

 

<% For Each item In ViewData.Model.Items%>

 

   

     <%=Html.ActionLink(Of ListingController)(Function(c)

      c.Edit(item.Id), "edit")%>

     delete

   <%= item.Title%>

 

<% Next%>

Figure 12: Markup to render the listings grid.

 

In the markup, we loop through the list of items and display links and a title for each one. One important difference between other views we ve created is that the ViewData.Model is strongly typed and has full IntelliSense, as shown in Figure 13. This is because we changed the code-behind file to inherit ViewPage instead of ViewPage. So the class definition is the public partial class List : ViewPage . As you can see from this code, the ListModel class is defined in the ListingController class (see Figure 14). Currently, this class only contains an Items property of type List

.

 


Figure 13: IntelliSense on a strongly typed view model.

 

Public Class ListModel

   Public Items As List(Of Listing)

End Class

Figure 14: A class representing a view model.

 

To render a view with a strongly typed model, call View( list , model) instead of View( list ). If the view name is the same as the action, you can simply call View(model). To see it how it all works together, take a look at the List action in Figure 15.

 

Public Function List() As ViewResult

 Dim model = New ListModel

 Dim db = New DB

 If User.Identity.IsAuthenticated Then

   model.Items = db.Listings.Where(Function(l) _

                                   l.Username.ToLower = _

        User.Identity.Name.ToLower).Take(20).ToList

 Else

   model.Items = db.Listings.Take(20).ToList

 End If

 Return View(model)

End Function

Figure 15: The controller action to render the list of listings view.

 

If you haven t fallen in love with jQuery yet, take a look at Figure 16 to see how we change the background of the row to yellow when hovering over it (as shown in Figure 11). As mentioned in Part I, the $ is a shorthand to the jQuery API and the $(document).ready method runs as soon as the DOM is ready, but before the content is displayed, so it s the perfect place to apply styles and add event listeners. The code in Figure 16 adds a jQuery hover event listener to any div with the listingRow class. The hover event takes two functions as arguments: one runs when the mouse is over the element and another when the mouse leaves. We set the CSS background color property to yellow when the mouse is over the row and clear it when the mouse leaves. How many lines of code would you have to write to accomplish the same task without jQuery?

 

$(document).ready(function() {

 $("div.listingRow").hover(

     function(){$(this).css("backgroundColor", "yellow");},

     function(){$(this).css("backgroundColor", "");}

   );

});

Figure 16: The JavaScript function to initialize the page.

 

Let s add some more jQuery magic to the grid. We want to delete the item when the user clicks the delete link, but we want to use AJAX and also fade out the row on a successful deletion. The first thing we need to do is prompt the user to confirm the delete action. Let s replace the Html.ActionLink line with ) >delete and create the deleteItem function (see Figure 17). The trick is in the line $( #div + id) .css( backgroundColor , red ).slideUp(1000); which changes to red the background color of the row being deleted and hides it using a slide up animation. Trying different values, experiment with slideDown, fadeOut, and hide. The 1000 tells it to perform the animation over a duration of 1000 milliseconds (1 second).

 

function deleteItem(id)

{

 if(!confirm("Are you sure you want to delete this listing?"))

     return;

 $.ajax({

   type: "POST",

   dataType: "json",

   url: "/listing/delete/" + id,

   success: function(result) {

     if (result.success){

         $("#div" + id) .css("backgroundColor", "red").slideUp(1000);

     }else {

         alert("Failed to delete listing");

     }

   },

   error: function(error) {

     alert("An error occurred while deleting the listing");

   }

 });

}

Figure 17: The JavaScript function to delete a listing.

 

User Controls

One more thing we should talk about is user controls. User controls make your code more useable and maintainable. For example, we can easily refactor the listing display into its own user control, which will allow us to reuse it in other views such as a search results page. It is very easy to create a user control in ASP.NET MVC. Right-click on the Views | Listing folder and click Add | New Item, then select Web | MVC | MVC View User Control and name it _ListingDisplay (it is a convention to prefix partial pages [user controls] with an underscore). Switch to the user code and change the class definition to inherit from the generic ViewUserControl. This means that the user control expects the data passed to it to be of type Listing, just as we did with the List view earlier. All we have to do is cut and paste the markup inside the For Each loop in Figure 12 into the user control and replace any reference to item with ViewData.Model . Remember: The ViewData.Model is strongly typed as Listing . Then replace the code inside the For Each loop in list.aspx page with one line: <%= Html.RenderUserControl( ~/views/listing/_listingDisplay.ascx , item) %>. The RenderUserControl helper method, as the name suggests, renders a user control. Note that we are passing item to the RenderUserControl method; item is of type Listing and will be mapped to the ViewData.Model of the user control exactly what it expects. The modified list.aspx page is shown in Figure 18 and the _ListingDisplay user control markup is shown in Figure 19.

 

<% For Each item In ViewData.Model.Items%>

 <%= Html.RenderUserControl("~/views/listing/

  _listingDisplay.ascx", item) %>

<% Next%>

Figure 18: Refactored list view.

 

 

   <%=Html.ActionLink(Of ListingController)(Function(c)

     c.Edit(ViewData.Model.Id), "edit")%>

   delete

 <%= ViewData.Model.Title%>

Figure 19: User control to display a listing.

 

Keep in mind that this user control code requires the page reference for the JavaScript file containing the deleteItem method otherwise, you ll get a script error. In a production application, you would refactor your scripts into logical script files that get referenced where needed.

 

Conclusion

We ve looked at strongly typed view pages and view user controls using generics. We ve seen how data is passed from the controller to the view. We talked to the server using AJAX and enhanced the usability and user experience by adding visual cues and animation.

 

jQuery is a very simple, powerful, and extensible JavaScript library that will get you up and running in no time. It s difficult to cover the entire library in one article. Hopefully this whets your appetite enough to give it a try. You can get more information at http://www.jquery.com, as well as download a ton of open source extensions that let you do a multitude of things, from creating tabs, to popup messages, to image effects, etc. We also looked at some of the new features of the MVC Framework introduced in the Preview 4 release. You can download the preview from http://www.codeplex.com/aspnet.

 

Source code accompanying this article is available for download.

 

Emad Ibrahim, MCSD, has been a professional developer for nine years and has worked with .NET since the first beta. He has held titles ranging from architect to senior developer to consultant. He quit his job with Avanade to start playing around with new stuff and catch up with technology. Since then he created an open source twitter clone at http://yonkly.com. He also is an active blogger and mostly blogs about software development and technology-related stuff. His blog is at http://www.emadibrahim.com.

 

 

 

Hide comments

Comments

  • Allowed HTML tags: <em> <strong> <blockquote> <br> <p>

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
Publish