ASP.Net MVC Framework Preview 2 - A step by step guide to create a simple Web Application

UPDATE: When I cerated a new post on my blog about the pre-release of Preview 3, I replaced this one with the new post. I did my best to recover the content. Sorry!

In this post you will learn how to use most of the new features in the Preview 2 version of the ASP.NET Framework, you will also get a basic understanding about how to use LINQ to SQL. This step by step guide shouldn't be used as a "best way to create and design" a web applications by using the ASP.NET MVC Framework, this guide should only be used to get started with the MVC Framework in a fast and easy way and learn different ways to use some of the features shipped with the ASP.Net MVC Framework Preview 2.

To get the pre-release version, you can download it from here.

In this step by step guide you are going to create a web based application where you will be able to list and modify customers. This step by step guide will use C# 3.0 and the Northwind database.


1. Open Visual Studio 2008


2. Create a new Project and select the ”ASP.NET MVC Web Project” project and name it to “CustomerApp”.


3. Open global.asax and walk through the code. In Global.asax you can specify your routes. In this step by step guide you will use the default settings.


The first thing to do is to create your business objects (your Model) which your Controllers will interact with. In this step by step guide you will use LINQ to SQL, even if it’s not advisable to generate domain models out from a data source you will learn how to do it, only to get up and running fast.

Note: The business objects belongs to the Model in the MVC pattern.


4. Right click on the Models folder in the Solution Explorer and select Add > New Item.

Select the “LINQ to SQL Classes” template and name the file to “Northwind.dbml” and press the Add button.

To create your Model out from a data source you need to add a database connection to Visual Studio. This can be done by using the Server Explorer. In the Solution Explorer, right click on the Data Connections node and select Add Connection. Enter the following information to connect to the Northwind database:

Server Name: (local)

Select the Northwind database, test the connection and press OK if you were able to connect to the database.

A connection to the Northwind database will now be added under the “Data Connections” node in the Server Explorer. Expand the database and the Tables nodes; drag out the Customer table into the design view of the Northwind.dbml file. Save the file. When you save the file, LINQ to SQL will generate a Customer object for you where the columns from the Customers table will be added as properties to the Customer object. LINQ to SQL will also generate a DataContext object (NorthwindDataContext) which you will use later to access your information from the data source. Expand the Northwind.dbml file in the Solution Explorer, double click on the Northwind.designer.cs file and walk through the code. LINQ to SQL uses attributes to map classes to a table.

Now it’s time to create the Controller which you are going to use in this application.


5. Right click on the Controllers folder in the Solution Explorer and add a new item and select the “MVC Controller Class” template,  name it to “CustomerController”.


6. Add a private field of the type NorhwindDataContext to the Controller and name the field to “northwindDataContext”. Set the field to an instance of the NorthwindDataContext object.

Note: The NorthwindDataContext class will exist in the CustomerApp.Models namespace.


using
CustomerApp.Models; namespace CustomerApp.Controllers { public class CustomerController : Controller, IDisposable { private NorthwindDataContext northwindDataContext = new NorthwindDataContext(); //... } }


7. Make sure the CustomerController implements the IDisposable interface, and implement the Dispose method and make sure it will make a call to the northwindDataContext’s Dispose method. This will make sure the NorthwindDataContext’s Dispose method will be called when the Controller have been used.


public class
CustomerController : Controller, IDisposable { private NorthwindDataContext northwindDataContext = new NorthwindDataContext(); //... public void Dispose() { this.northwindDataContext.Dispose(); } }


8. Add an Action method and name it to “List”. The List action will retrieve all Customers from the Customers table of the Northwind database. LINQ to SQL will be used to get all the Customers, order the Customers by CompanyName. When all customers are retrieved make sure to render a View with the name “Customers” (The View will be added later to the project), the View should render a generic list of Customer objects.


public void
List() { List<Customer> customers = (from customer in northwindDataContext.Customers orderby customer.CompanyName select customer).ToList(); RenderView("Customers", customers); }


9. Create a View with the name “Customers” which will render the generic list of Customer objects. To add a View, right click on the Views folder and add a new folder with the name “Customer”. Right click on the Customer folder and add a new item, select the “MVC View Page” template from the "My Template" section and name the View to “Customers”. Make sure the View data inherits from ViewPage<List<CustomerApp.Models.Customer>>. This will make sure the Model which is passed as an argument to the RenderView method is a known and typed for the View. Open the file Customers.aspx.cs file, replace so the page inherits from ViewPage<List<CustomerApp.Models.Customer>> instead of ViewPage.


public partial class
Customers : ViewPage<List<CustomerApp.Models.Customer>> { }


10. Add code to render a list of Customers to the View, make sure the CustomerID and ContactName are listed. Open the Customer.aspx file, add an inline-code block and use foreach to iterate through the Customers, use the ViewData property to get access to the generic list of Customer objects.


<%
@ Page Language="C#" AutoEventWireup="true" CodeBehind="Customers.aspx.cs" Inherits="CustomerApp.Views.Customer.Customers" %> <!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 runat="server"> <title></title> </head> <body> <div> <table> <% foreach ( var customer in ViewData) { %> <tr> <td><%=Html.Encode(customer.CustomerID) %></td> <td><%=Html.Encode(customer.ContactName) %></td> </tr> <% } %> </table> </div> </body> </html>


Make sure you have saved all files, compile and run the application. To the end of the URL add “Customer/List” and press enter. The URL will look something like this: http://localhost:<port number>/Customer/List.

Your application will now make sure the Controller “CustomerController” will be used and also the Action method “List” will be invoked. If everything works, you will now see a list of CustomerID and ContactName.

Note: The route that was defined in global.asax looks like this:


{controller}/{action}/{id}


When you enter a URL like “Customer/List”, the Customer will be the name of the Controller to be used, and the List the name of the Action method to be executed, the id in the route will be empty and will be ignored.


11. Add a new Action method to the CustomerController and name it to “View”. The View method should have one argument with the name ID and the type of String. Make sure the method will use LINQ to SQL to get a Customer from the Customers table which matches the CustomerID passed as an argument. If a customer is not found throw an exception. Make sure to render a View with the name “Customer”. The View should ender a detail view of a Customer.

public void View(string ID)
{
    Customer customer = (from c in northwindDataContext.Customers
                         where c.CustomerID == ID
                         select c).FirstOrDefault();

    if (customer == null)
        throw new ApplicationException(
                    String.Format("Can't find a Customer with the CustomerID {0}", ID));

    RenderView("Customer", customer);
}


12. Add a new View to the Views/Customer folder and name it “Customer”. Make sure the Customer View will inherit form the ViewPage<CustomerApp.Models.Customer> class.


public partial class
Customer : ViewPage<CustomerApp.Models.Customer> { }


13. Open the Customer.aspx page and add inline-code block to show detail information about a Customer, make sure input text elements is used to display the content of the Customer (you will later in the lab update the Customer). Set the name attribute of the input text element to the same name as the property of the Customer object it should display. The CustomerID should be displayed as a simple text, and also be a value of an input hidden field with the name “CustomerID”. The reason why you will use a hidden field with the CustomerID, is because you will later use the MVC Frameworks “databinding” feature.

Note: The Customer object has several properties, and to make this lab simple you can only use some of them, CustomerID, CompanyName, ContactName and Phone.

Leave the <Form> elements action attribute empty at the moment.

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Customer.aspx.cs" Inherits="CustomerApp.Views.Customer.Customer" %>

<!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 runat="server">
    <title></title>
</head>
<body>
    <form method="post" action="">
        <table>
            <tr>
                <td>Customer ID:</td>
                <td><%= Html.Encode(ViewData.CustomerID) %></td>
            </tr>
            <tr>
                <td>Company name:</td>
                <td><input type="text" name="CompanyName" value="<%= Html.Encode(ViewData.CompanyName) %>" /></td>
            </tr>
            <tr>
                <td>Contact Name:</td>
                <td><input type="text" name="ContactName" value="<%= Html.Encode(ViewData.ContactName) %>" /></td>
            </tr>
            <tr>
                <td>Phone:</td>
                <td><input type="text" name="Phone" value="<%= Html.Encode(ViewData.Phone) %>" /></td>
            </tr>

        </table>

        <input type="hidden" name="CustomerID" value="<%= Html.Encode(ViewData.CustomerID) %>" />
        
    </form>
</body>
</html>


Note: Instead of writing the whole <input> element, you can also use the Html help method:


<%
= Html.TextBox("CompanyName", ViewData.CompanyName) %>


This method will render the following output:


<input type=”text” size=”20” name=”CompanyName” id=”CompanyName” value=”My Company”/>


Writing HTML will give you more control over the HTML which will be sent to the client.

Save your View and build your solution. Start the application and to the end of the URL enter “Customer/View/ALFKI”. The URL will look something like this: http://localhost:<port number>/Customer/View/ALFKI. The Customer controller will be used and its Action method View will be called based on the route in global.asax the value after the specified Action method will be passed as an argument to the specified Action method. The argument is mapped to the argument with the name “ID”.

If everything works, you will now see a detail view over the Customer with the CustomerID “ALFKI”.


14. Open the Customers.aspx View and make sure the listed CustomerID instead will be a HyperLink, which will have the following URL pattern: “/Customer/View/<CustomerID>”.


<%
@ Page Language="C#" AutoEventWireup="true" CodeBehind="Customers.aspx.cs" Inherits="CustomerApp.Views.Customer.Customers" %> <!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 runat="server"> <title></title> </head> <body> <div> <table> <% foreach ( var customer in ViewData) { %> <tr> <td> <a href="/Customer/View/<%=customer.CustomerID %>"><%=customer.CustomerID %></a> </td> <td><%=Html.Encode(customer.ContactName) %></td> </tr> <% } %> </table> </div> </body> </html>


Instead of writing the <a> element you can use the Html helper method to let it create an ActionLink, for example like this:


<%
= Html.ActionLink<CustomerApp.Controllers.CustomerController>(a => a.View(customer.CustomerID), customer.CustomerID) %>


Make sure you have saved all files, compile and run the application. To the end of the URL add “Customer/List” and press enter. The URL will look something like this: http://localhost:<port number>/Customer/List.

If everything works correct, you will now see a list of Customers and the CustomerID will not be a HyperLink. Press on a CustomerID and make sure it will navigate to the Customer View and display detail information about the selected Customer.

Note: The URL of the HyperLink’s href attribute does not point to a specific file, instead if will point to the Controller and the Action method to be executed when it’s pressed.


15. Add a new Action method to the CustomerController and name it “Remove”. Make sure it takes an ID of type string as an argument. Use LINQ to SQL to remove a Customer with the CustomerID matches the value of the ID argument. To Remove a Customer you first need to retrieve the Customer object, and then use the method DeleteOnSubmit method of the Customers property of the NorthwindDataContext object. The DeleteOnSubmit takes a Customer object as an argument. To execute the Delete, you need to call the SubmitChanges method of the NorthwindDataContext object.

Note: Both the View method and the Remove method will get a specific Customer by CustomerID, so you can do some refactoring here and use Extract method to extract code which will get the Customer by CustomerID and put it into a new method, and reuse that method both in the View and Remove Action method. To use the Refactoring feature within Visual Studio, mark the code you want to extract to a method and right click on the selected code block. Select Refactoring and Extract method option. Give the new method a name and press the OK button.

If you decide to use the Refactoring feature you can mark the following code in the View method:


Customer customer = (from c in northwindDataContext.Customers
                     where c.CustomerID == ID
                     select c).FirstOrDefault();
if (customer == null)
    throw new ApplicationException(
                String.Format("Can't find a Customer with the CustomerID {0}", ID));

Righ click on the selected code and select Refactoring and Extract method. Give it the name “GetCustomerByID” and press the OK button. If you get an alert box, just press “Yes” to ignore it.

You should now have a new method with the name GetCustomerByID which the View method will make a call to.

In the Remove method, make a call to the GetCustomerByID to get the Customer object to delete. If the application fails to remove a Customer, throw an exception. If the remove was successfully, make a call to RedirectToAction to redirect to the Action with the name “List”. This will make sure the List method of the CustomerController will be called.


public void Remove(string ID)
{
    Customer customer = GetCustomerByID(ID);

    northwindDataContext.Customers.DeleteOnSubmit(customer);

    try
    {
        northwindDataContext.SubmitChanges();
        RedirectToAction("List");
    }
    catch (Exception e)
    {
        throw new ApplicationException(string.Format("Can't remove the Customer {0}", ID), e);
    }
}


16. Next step is to add a HyperLink to the Customers View, and set the URL to “/Customer/Remove/<CustomerID>” for each Customer. The name of the HyperLink should be “Remove” and can be placed before the CustomerID HyperLink. When you press the HyperLink, the Remove method of the CustomerController will be called and it will remove the Customer.


<%
@ Page Language="C#" AutoEventWireup="true" CodeBehind="Customers.aspx.cs" Inherits="CustomerApp.Views.Customer.Customers" %> <!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 runat="server"> <title></title> </head> <body> <div> <table> <% foreach ( var customer in ViewData) { %> <tr> <td> <a href="/Customer/Remove/<%=customer.CustomerID %>">Remove</a>&nbsp; <a href="/Customer/View/<%=customer.CustomerID %>"><%=customer.CustomerID %></a> </td> <td><%=Html.Encode(customer.ContactName) %></td> </tr> <% } %> </table> </div> </body> </html>


Build the project and run the application, enter the URL to list Customers (By now you should know the URL). Press one of the Remove HyperLink. You will probably get a SQL Exception about reference problems. You aren’t going to resolve that exception. It will indicate that the remove was executed and in this lab you can be satisfied with that.


17. Add an Update method to the CustomerController, make sure it takes a argument with the name customerID and of type String. Use the customerID to call the GetCustomerByID method to retrieve the Customer from the database. Update the Customer with the new information and use the RedirectToAction method to redirect to the “List” action.

Note: When using LINQ to SQL we need to get a Customer which is attached to a DataContext, which is the reason why you need to get the Customer. Another reason is also to make it easy to handle concurrency issues etc.

Use the BindingHelperExtension class’s UpdateFrom method to set the Customer objects properties out from the Reuqest.Form collection.

Note: The BindingHelperExtension’s UpdateFrom method will get the value out from an input field with the same name as the property of the object passed to the UpdateFrom method and set the value to the object. In the Customer View, there were input fields with the same name as the properties of the Customer object, so those values will be “mapped” to the Customer object you will pass into the UpdateFrom method.


public void Update(string customerID)
{
    Customer customer = GetCustomerByID(customerID);
    
    BindingHelperExtensions.UpdateFrom(customer, Request.Form);

    northwindDataContext.SubmitChanges();

    RedirectToAction("List");
}

 

Note: Values of input fields on a View which has the same name as the arguments of the Update action method will be passed as a value to the respective argument. So in this case you have an input hidden field with the name CustomerID on the View, the value of that field, will be passed as a value to the Update method. So instead of using the UpdateFrom method of the BindingHelperExtensions, you could use arguments for all input fields.

If you want to learn more about the BindingHeperExtension class, you can read my post "ASP.Net MVC Framework 2 - The BindingHelperClass (UI-mapper)".


18. Open the Customer View and change the <form> element’s action argument to call the Update method of the CustomerController “/Customer/Update”. Add a Submit button to the View and give it the text value “Update”.

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Customer.aspx.cs" Inherits="CustomerApp.Views.Customer.Customer" %>

<!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 runat="server">
    <title></title>
</head>
<body>
    <form method="post" action="/Customer/Update">
        <table>
            <tr>
                <td>Customer ID:</td>
                <td><%= Html.Encode(ViewData.CustomerID) %></td>
            </tr>
            <tr>
                <td>Company name:</td>
                <td><input type="text" name="CompanyName" value="<%= Html.Encode(ViewData.CompanyName) %>" /></td>
            </tr>
            <tr>
                <td>Contact Name:</td>
                <td><input type="text" name="ContactName" value="<%= Html.Encode(ViewData.ContactName) %>" /></td>
            </tr>
            <tr>
                <td>Phone:</td>
                <td><input type="text" name="Phone" value="<%= Html.Encode(ViewData.Phone) %>" /></td>
            </tr>

        </table>

        <input type="hidden" name="CustomerID" value="<%= Html.Encode(ViewData.CustomerID) %>" />
        
        <input type="submit" value="Update" />
    </form>
</body>
</html>


Build the solution and run the application. Enter the URL to list all Customers “Customer/List” and select a Customer from the list. Update the Customer and press the Update button. When the Update button is pressed, the customer will be updated and you will be redirected to the List action so the List of Customer will be displayed.

Note: Instead of using an input field with the name CustomerID, you could also set the action attribute to “/Customer/Update/<the customerID>”.

The last thing to do now is to create an ActionFilter for the Remove action method. You only want to make sure the user with the name “student” will make a call to the Remove action method.


19. Add a new class to the Model folder. Name it “AuthorizedFilterAttribute”. Make sure it inherits from the base class ActionFilterAttribute. The ActionFilterAttribute is located in the namespace “System.Web.Mvc”.

The ActionFilter can be used to perform pre- and post action. You can use FilterAction to cancel an execution of an Action method, or you can use it to log information etc. 

The ActionFilter AuthorizedFilter will be used to only make sure the user “student” has access to the Remove method, if not a SecurityException will be thrown.

To implement code for a pre action, override the OnActionExecuting method of the ActionFilterAttribute class. To perform a post action, override the OnActionExecuted method. To get the current user you can use the filterContext arugment’s HttpContext property and its User.Identity.name property. You don’t need to cancel the action of the AuthorizedFilter when you throw an exception. To cancel, you can set the filterContext Cancel property to true.


using
System.Security; namespace CustomerApp.Models { public class AuthorizedFilter : ActionFilterAttribute { public override void OnActionExecuting(FilterExecutingContext filterContext) { if (filterContext.HttpContext.User.Identity.Name != "student") throw new SecurityException(
"You aren't allowed to call this action!"); } } }

 

Apply the AuthorizedFilter to the Remove method.

[AuthorizedFilter]
public void Remove(string ID)


Note: If you want to make sure the filter will be used for every Action method of a Controller, you can add the attribute to the Controller’s class.

If you want to test if the filter works, add a breakpoint to the OnActionExecuting method and run the application in debug mode. List the Customers and press the Remove HyperLink, or you can simply run the application, list the customers and press the Remove Hyperink, it should probably give you a SecurityException. If you want to learn more about ActionFilter, you can read my post "ASP.Net MVC Framework 2 - Interception and creating a Role Action Filter".

In this step by step guide you have learned how to create a simple application by using the ASP.Net MVC Framework Preview 2. You have learned how to create a Controller, Views and also ActionFilter to intercept pre actions to a Action method.

5 Comments

  • WHAT, NO TESTS?!!!

    Bad programmer. No biscuit.


  • Chris:
    No test at all Chris, isn’t that wonderful ;)
    The reason why I didn’t put unit test or use TDD in the guide is because of a bug in the current preview. We need to create too much Mock objects only to create one single unit test, to bypass the bug. And to be honest, I don’t really even like the way to write unit test against this current preview version. I want it to be much better and simpler, less mocking etc. Can’t say too much, but wait and see, maybe it will be better in a future release ;)
    The MVC pattern is not all about testing by the way. It’s more about separation of concern, but it will make it possible for use to build web based ASP.net apps by using TDD and also create unit test for complex UI logic, which is difficult when creating Web Forms.

  • Less mocking could be a good thing. I've had to teach mocking to a lot of people. The concept of dynamically creating classes, at run-time, is just really hard for many to get their heads around. Then if you add the extra complication that Rhino Mocks adds (mock.Record and mock.Playback), people just start to wonder if it is worth it. (Moq is really cool though).

    Anyway, good article. I'll look forward to the next release -- hopefully it doesn't break my existing code too badly. :)

  • I am new to MVC and followed your step-by-step to the letter. Unfortunately...it doesn't work. I keep getting the error that "the name RenderView does not exist in the current project"

  • What is RenderView?!

Comments have been disabled for this content.