CoverStory
LANGUAGES:C# | VB.NET
ASP.NET VERSIONS: 3.5
Convention Over Configuration
Creating Applications Using ASP.NETMVC, jQuery, and AJAX: Part I
By Emad Ibrahim
Many of the new Web 2.0 applications coming out right noware being written in Ruby on Rails (RoR) ? and for good reason (I dislike theterm Web 2.0, but for lack of a better term, I?ll go ahead and use it). RoR isa very powerful Web platform that relies heavily on the principal of conventionover configuration. This simply means there are a set of conventions that alldevelopers should abide by, and by doing so we eliminate the hours we spendconfiguring the application.
Microsoft wasn?t going to just sit back and watch Webdevelopers flock to another platform, so they came up with an answer to RoR. Thatanswer is the ASP.NET MVC Framework. This isa drastic departure from the ASP.NET Webforms applications we are used to writing. It might actually remind you ofclassic ASP; don?t worry, it is far from that. This is a very powerful frameworkthat, in my opinion, is way more powerful than the competition. For starters,you?ll be able to use the best Integrated Development Environment (IDE) on theplanet (Visual Studio 2008). This feature alone puts at your fingertips all thepowerful tools and features you are used to: IntelliSense, auto-complete, anintegrated debugger, master pages, etc.
In this article, we?ll create a ?Web 2.0? applicationusing the ASP.NET MVC Framework, and we?lladd AJAX and JavaScriptfunctionality using jQuery.
The ASP.NET MVC Framework
MVC stands for Model-View-Controller. This is a commondesign pattern that, whether you know it or not, you?ve most likely used. Thegeneral premise is that the model is the class that talks to your data store,the view is your UI (page, user control, etc.), and the controller handles theinteraction between the view and the model. So if you have a search button, thebutton itself is on the view; when you click it, the controller runs the code, thentalks to the model, which in turn queries your database. The controller then receivesthe results and sends it back to the results view.
Unlike ASP.NET Webforms, there is no postback model and click event to be handled on the server,and there is no viewstate. This creates a much cleaner separation between thepresentation layer and layers below it ? and the generated HTML is a lotcleaner and lighter, because it doesn?t include a hidden viewstate field and abunch of JavaScript to manage the postbacks and page lifecycle. To get an ideaof this look at Figures 1 and 2, which show the generated HTML for a simpleform with one textbox and a button; one was generated using regular Web forms, theother using the MVC Framework.
<!DOCTYPE html PUBLIC ?-//W3C//DTD XHTML 1.0 Transitional//EN? ?http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd?>
<html xmlns=?http://www.w3.org/1999/xhtml?>
<head><title>
</title></head>
<body>
<form name=?form1? method=?post? action=?Sample1.aspx?id=?form1?>
<div>
<input type=?hidden? name=?__VIEWSTATE? id=?__VIEWSTATE?
value=?/wEPDwUJODU2ODYyODA2ZGQk+3rXpDybjZHn9aMYdRBjbvoatQ==?/>
</div>
<div>
<input name=?SearchBox? type=?text? id=?SearchBox?/>
<input type=?submit? name=?SearchButton?value=?Search? id=?SearchButton? />
</div>
<div>
<input type=?hidden? name=?__EVENTVALIDATION?id=?__EVENTVALIDATION?
value=?/wEWAwL34uW7AwLEu5j0CgLR1viaCayW/W1agurzYOoQmR6cdijk0P/6?/>
</div></form>
</body>
</html>
Figure 1: HTMLgenerated from an ASP.NET Web form.
<!DOCTYPE html PUBLIC ?-//W3C//DTD XHTML 1.0 Transitional//EN? ?http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd?>
<html xmlns=?http://www.w3.org/1999/xhtml?>
<head><title>
</title></head>
<body>
<div>
<input type=?text? name=?SearchBox? id=?SearchBox?value=?? />
<button onclick=?Search? name=?SearchButton?id=?SearchButton? >Search</button>
</div>
</body>
</html>
Figure 2: HTMLgenerated from an MVC page.
To live by the convention over configuration mantra,the MVC project is organized as shown in Figure 3. This structure is straightforward;we can deduce the content and function of each folder based on its name.
Figure 3: MVC project structure.
One of the most loved (at least for me) features in MVC isURL routing, which allows us to create SEO-friendly and clean URLs. So insteadof /users/view.aspx?username=bob, you can do /users/view/bob. And as long as wefollow the convention, we won?t have to configure anything. In the above URL, ?users?is the controller, ?view? is the action, and ?bob? is the Id.The default route defined in the project will automatically handle the aboveURL. The route table is defined in the Global.asax.cs file and the defaultroute is shown in Figure 4.
routes.MapRoute( _
?Default?, _
?{controller}/{action}/{id}?, _
New With {.controller = ?Home?, .action = ?Index?,.id = ??} _
)
Figure 4: Defaultroute.
jQuery
One quick disclaimer: I love the things I can do withJavaScript and AJAX, but I am lazyand not proficient at JavaScript. The last thing I want to do is write hundredsof lines of code to perform simple effects or pop up a funky, Web 2.0-ishmessage. Thanks to jQuery, I don?t have to do that. jQuery is a powerful,lightweight, and extensible JavaScript library that lets us write code in oneor two lines that would otherwise take tens of lines. And the best part is, wedon?t have to worry about browser compatibility. jQuery works with IE 6.0+,Firefox 2+, Safari 2+, and Opera 9+. And for someone who is lazy and notdexterous with JavaScript, I managed to figure out jQuery in a couple of days simplyby looking at samples and reading the online documentation.
For example, if you wanted to toggle an HTML element?svisibility and also animate it, you could simply write: $(?#mydiv?).toggle(?slow?).Yes, this is a very simple example, but imagine the alternative of writing yourown JavaScript and making it work with all browsers and handle exceptionsgracefully. Note that the dollar sign ($) is shorthand for calling the jQuery APIand is interchangeable with jQuery. So $(?#mydiv?) is equivalent to jQuery(?#mydiv?).
Create the Application
Now that we have a basic understanding of what MVC and jQueryare, let?s create our application. The first thing we need to do is enable somebasic account management for users to sign up (create an account) and log in. Wewant the URLs to be /account/signup and /account/login.
We can?t log in until we?ve created an account, so let?sstart with sign up. We start by creating an Account folder under the Viewsfolder, then add a Signup page. In MVC-world, we don?t create a Page; instead,we create a ViewPage. A ViewPage is the counterpart of a Page in the Web formworld. If you right-click on the Account folder and click Add | New Item, thennavigate to Web | MVC, you?ll see the dialog box shown in Figure 5.
Figure 5: The Add New Item dialogbox.
Figure 5 shows the available MVC-related items. The MVCController class is nothing more than a class that inherits fromSystem.Web.Mvc.Controller. The other items are similar to their counterparts inthe Web form world, so we get a content page, a page, a master page, and a usercontrol. The MVC View Content Page is different than an MVC View Page in thatit requires an MVC View Master Page.
Let?s add a View Content Page and use the Site.mastermaster page located at Views\Shared. We?ll name this page signup.aspx.
Let?s create the UI for the sign-up page. For the sake ofsimplicity, all we need to collect are username, password, and e-mail. The pageshould look similar to Figure 6; the associated markup is shown in Figure 7.
Figure 6: The sign-up form.
<div id=?divSignup?>
<div class=?form?>
<h2>
Sign up</h2>
<p>
<label for=?username? class=?label?>
User Name:</label>
<%=Html.TextBox(?username?, ??, New With{.class = ?textbox?})%>
<span id=?valSignupUserName? class=?validator?style=?display: none?></span>
</p>
<p>
<label for=?password? class=?label?>
Password:</label>
<%=Html.Password(?password?)%>
<span id=?valSignupPassword? class=?validator?style=?display: none?></span>
</p>
<p>
<label for=?email? class=?label?>
Email:</label>
<%=Html.TextBox(?email?, ??, New With{.class = ?textbox?})%>
<span id=?valSignupEmail? class=?validator?style=?display: none?></span>
</p>
<span id=?signupMessage? class=?message?style=?display: none?></span>
<p>
<span class=?message? id=?errorMessage?>
<%=IIf(IsNothing(ViewData(?errorMessage?)),??,
Html.Encode(ViewData(?errorMessage?)))%></span>
<%=Html.Button(?btnSignup?, ?Sign up?,?javascript:Account.signup?)%>
</p>
</div>
</div>
Figure 7: Markupfor the sign-up form.
Basically, all we do here is add a label, a textbox, and aspan for validation for username, password, and e-mail. There are a few thingsnew to MVC, so let?s take a look.
First, take note of the embedded server code Html.TextBox,Html.Password, and Html.Button. Html is a helper class that is part of the MVC Framework;it contains a bunch of helper methods to simplify HTML generation. Each ofthese methods has multiple overloads and, don?t forget, because the MVC Frameworkis open source, you can write your own helper methods. For example, theoverloads for the TextBox method are shown in Figure 8.
Figure 8: Overloads for the Html.TextBoxmethod.
There are helper methods to create dropdown lists,checkbox lists, buttons, password fields, images, and hyperlinks, among others.But remember, all these methods are doing is generating HTML, so the line:
<%=Html.TextBox("email", "",
New With {.class ="textbox"})%>
generates the following HTML:
<input class="textbox" type="text"name="email"
id="email"value="" />
Notice that the Sign up button calls the JavaScript methodAccount.signup. I?ll explain this below.
View Logic
Now that we?ve created the view, let?s add some UI logic. Weneed to add some field validation, as well as submit the form to the server. Becausewe need to AJAX-ify the site and reduce round trips, we?ll perform this logicin JavaScript. Let?s create a JavaScript file under the Content folder and nameit Account.js. This file contains the signup method shown in Listing One that gets called from the Sign up button.
I know this looks like a lot of JavaScript, but if youread through it, you?ll realize how simple, concise, and maintainable it reallyis. We first hide all the validation spans and messages, then validate thepassword, e-mail, and username; if either one fails, we display a descriptivemessage in the appropriate span without having to post anything to the server. Ifall validations succeed, we create an AJAX POST request and post the data (i.e.,username, password, and e-mail) to the URL /account/signupsubmit. If we get asuccessful response back, we check to see if we succeeded in registering anaccount. Account sign-up could fail for several reasons, such as an invalidusername, a duplicate e-mail address, or some other unhandled server exception.Note the use of jQuery selectors and you?ll immediately understand their power.For example, the line:
$('#divSignup #btnSignup').attr("disabled","disabled");
selects the element named btnSignup inside the elementnamed divSignup and adds to it the disabled=?disabled? attribute. This willdisable the button right before the AJAXrequest begins, which will prevent erroneous double-clicks by the user. We thenenable the button upon receiving a response to the AJAXrequest by calling:
$('#divSignup #btnSignup').removeAttr("disabled");
Another example of jQuery selectors is shown in the way wehide all the validation spans with one line of code:
$("#divSignup span.validator").hide();
This basically selects all the ?span? elements with a ?validator?class inside the element ?divSignup?, then calls the ?hide? method on eachmatched element.
So, using jQuery, we are able to select the exact elementin the HTML DOM and manipulate itaccordingly. We do all that with very few lines of JavaScript code (most of thetime, just one line) that will work on all browsers.
Also notice that our AJAXrequest is of type POST, and the data being submitted is serialized in JSON format.You?ll see later that we don?t need to write any code to deserialize thisrequest and that the MVC Framework automatically knows how to handle it.
Controller
From the JavaScript we can see that we are submitting theform to the URL /account/signupsubmit and we are submitting username, email,and password as properties of a JSON object. Using the route convention of/controller/action, we can immediately see that we need an account controllerwith a SignupSubmit action. So let?s create an account controller byright-clicking the Controllers folder and adding an MVC Controller class. Theconvention is to name it AccountController. This will create the controllerclass with a default Index action that throws an exception. We need to createtwo actions inside the controller. One action, named Signup, renders the sign-uppage when the user navigates to /account/signup; the other receives the sign-upform submission named SignupSubmit. The Signup method is straightforward; allit does is render a view, as shown in Figure 9.
Public Function Signup As ViewResult
Return View(?signup?)
End Function
Figure 9: The Signupaction method.
The SignupSubmit receives the data from the form andcreates the user account, as shown in Figure 10.
Public Function SignupSubmit( _
ByVal username As String, _
ByVal password As String, _
ByVal email As String) As JsonResult
Dim jsonData As New JsonData
Try
If String.IsNullOrEmpty(username) Then
jsonData.errorMessage = _
?User name cannot be blank.?
ElseIf String.IsNullOrEmpty(password) Then
jsonData.errorMessage = _
?Password cannot be blank.?
ElseIf password.Length < _
Membership.Provider.MinRequiredPasswordLengthThen
jsonData.errorMessage = _
String.Format( _
?Password must be {0} character long.?, _
Membership.Provider.MinRequiredPasswordLength)
ElseIf String.IsNullOrEmpty(email) Then
jsonData.errorMessage = ?Email cannot beblank.?
ElseIf Not IsValidEmail(email) Then
jsonData.errorMessage = ?Invalid emailaddress.?
ElseIf Not IsValidUsername(HttpContext,username) Then
jsonData.errorMessage = ?Invalid User name.?
Else
Try
?create the user using the built-in
?Membership(APIs)
Dim status As MembershipCreateStatus
Dim user As MembershipUser = _
Membership.Provider.CreateUser( _
username, password, email, Nothing, _
Nothing, True, Nothing, status)
If IsNothing(user) Then
Throw New _
MembershipCreateUserException(status)
End If
FormsAuthentication.SetAuthCookie(username,False)
jsonData.isSuccessful = True
Catch e As MembershipCreateUserException
jsonData.errorMessage =e.Message
End Try
End If
Catch e As Exception
jsonData.errorMessage = e.Message
End Try
Return Json(jsonData)
End Function
Figure 10: The SignupSubmitmethod.
The code in Figure 10 validates the input data, creates anaccount, then returns the results as a JSON object. The thing to note here isthat the method signature matches the data submitted by the AJAXrequest, i.e., username, password, and email. If you put a breakpoint in thecode in Figure 10 you?ll see that the variables username, password, and emailmatch the values entered in the form. This automatically happens because theMVC Framework knows how to route and serialize the request automatically. Thereturned JSON object is a very simple class that has a few properties toencapsulate the response (see Figure 11).
Private Class JsonData
Private _errorMessage As String = ??
Public Property errorMessage As String
Get
Return _errorMessage
End Get
Set(ByVal value As String)
_errorMessage = value
End Set
End Property
Private _isSuccessful As Boolean = False
Public Property isSuccessful As Boolean
Get
Return _isSuccessful
End Get
Set(ByVal value As Boolean)
_isSuccessful = value
End Set
End Property
End Class
Figure 11: The JsonDataclass.
All we have to do in our SignupSubmit method is create aninstance of JsonData, set the values for isSuccessful and errorMessageaccordingly, then return the object like this:
return Json(jsonData);
You don?t have to worry about object serialization ? it?sall handled automatically for you. The JavaScript method above understands thereturned results and reacts accordingly. As an example, if we were to try to signup with an existing username, the response JSON string would be:
{?errorMessage?:?The username is already in use.?,
?isSuccessful?:false}.
The full HTTP response (header and body) is shown in Figure12.
Server: ASP.NETDevelopment Server/9.0.0.0
Date: Fri, 13 Jun 2008 14:42:45 GMT
X-AspNet-Version: 2.0.50727
Cache-Control: private
Content-Type: application/json; charset=utf-8
Content-Length: 71
Connection: Close
{?errorMessage?:?The username is already in use.?,
?isSuccessful?:false}
Figure 12: HTTP response.
That?s all. We repeat the same steps for the log-in andlog-out actions that are included in the accompanying source code (see end ofarticle for download details). You?ll also notice a minor change to thenavigation menu in the master page. The changes, shown in Figure 13, selectivelyemit the appropriate HTML according to the authentication state, i.e., displaylog-in and sign-up links when logged out and display a log-out link when loggedin.
<ul id=?menu?>
<li>
<%= Html.ActionLink(?Home?, ?Index?, ?Home?)%>
</li>
<% If (Request.IsAuthenticated) Then
%>
<li>
<%=Html.ActionLink(?Logout?, ?Logout?, ?Account?)%>
</li>
<%
Else
%>
<li>
<%= Html.ActionLink(?Login?, ?Login?, ?Account?)%>
</li>
<li>
<%= Html.ActionLink(?Signup?, ?Signup?, ?Account?)%>
</li>
<% End If
%>
</ul>
Figure 13:Navigation menu in master page.
Conclusion
We?ve created our first view, controller, and action. Ifyou look at the source code you?ll see that I used the built-in ASP.NETmembership API to create the user accounts,so there is no model per se in this example because it is abstracted by the API.
We also covered form creation, submission, and validation.We used the jQuery JavaScript library to simplify our UI logic, add validation,and submit a form using an AJAXrequest. We also saw how we pass data from the client to the server and viceversa using JSON serialization.
In Part II we?ll delve more into jQuery and use some coolanimation effects and DOM manipulationtechniques to enhance the user experience and make our site more responsive. We?llalso look at other features of the MVC Framework, such as user controls,routing customization, and ViewData, and talk about the ?model? part of theframework.
Source codeaccompanying this article is available for download to asp.netPRO subscribersat www.aspnetPRO.com.
Emad Ibrahim, MCSD,has been a professional developer for nine years and has worked with .NETsince the first beta. He has held titles ranging from architect to seniordeveloper to consultant. He quit his job with Avanade to start playing aroundwith new stuff and catch up with technology. Since then he created an opensource twitter clone at yonkly.com. He also is anactive blogger and mostly blogs about software development andtechnology-related stuff. His blog is at www.emadibrahim.com.
Begin Listing One ? The JavaScriptsignup method
signup: function {
//hide all validation spans and messages
$(?#divSignup span.validator?).hide;
$(?#signupMessage?).hide;
//get the user name text box and value
var txtUserName = $(?#divSignup #username?);
var userName = $.trim(txtUserName.val);
//validate that username exists
if (userName.length == 0) {
Account._showMessage(?#divSignup#valSignupUserName?,
?User name cannot be blank.?, true);
$(txtUserName).focus;
return;
}
//get password text box and value
var txtPassword = $(?#divSignup #password?);
var password = txtPassword.val;
//validate password length
if (password.length < 4) {
Account._showMessage(?#divSignup#valSignupPassword?,
?Password must be 4 character long.?, true);
$(txtPassword).focus;
return;
}
//get email text box and value
var txtEmail = $(?#divSignup #email?);
var email = $.trim(txtEmail.val);
//validate that email exists
if (email.length == 0) {
Account._showMessage(?#divSignup#valSignupEmail?,
?Email cannot be blank.?, true);
$(txtEmail).focus;
return;
}
//valide email format
if (!Account._isValidEmail(email)) {
Account._showMessage(?#divSignup#valSignupEmail?,
?Invalid email address.?, true);
$(txtEmail).focus;
return;
}
//display status message
Account._showMessage(?#divSignup#signupMessage?,
?Signing up...?, false);
//disable submit button to prevent doublesubmission
$(?#divSignup #btnSignup?).attr(?disabled?,
?disabled?);
//post form to server using an ajax request
$.ajax({
type: ?POST?,
dataType: ?json?,
url: ?/account/signupsubmit?,
data: { userName: userName,
password: password,
email: email
},
success: function(result) {
//returned successfully from server
//enable the submit button
$(?#divSignup #btnSignup?).removeAttr(?disabled?);
//if results were successful then redirect to/home
if (result.isSuccessful == true) {
location.href = ?/home?;
}
else {
//a server error happened, get the errormessage
var msg = result.errorMessage;
//hide the status message
$(?#divSignup #signupMessage?).hide;
//display the appropriate error message
if (msg.indexOf(?username?) > -1) {
Account._showMessage
(?#divSignup #valSignupUserName?,
msg, true);
$(txtUserName).focus;
}
else if (msg.indexOf(?E-mail?) > -1) {
Account._showMessage
(?#divSignup #valSignupEmail?,
msg, true);
$(txtEmail).focus;
}
else {
Account._showMessage
(?#divSignup #signupMessage?,
msg, true);
}
}
}
End Listing One