ASP.Net MVC Framework pre- Preview 3 - A Step by Step guide to create a simple web app.

Yesterday I posted a step by step guide by using the Preview 2 of the ASP.Net MVC Framework, the following is an updated version that targets the pre-release of the Preview 3 version of the ASP.Net MVC Framework.

In this post you will learn how to use most of the new features in the pre-release of the Preview 3 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 pre-release Preview 3.

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 from the "My Template" section 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.

routes.MapRoute(
               "Default",                                              // Route name
               "{controller}/{action}/{id}",                           // URL with parameters
               new { controller = "Home", action = "Index", id = "" }, // Parameter defaults
               new { controller = @"[^\.]*" }                          // Parameter constraints
           );


As you can see, there is a new method added to the RouteCollection, MapRoute. This method can be used to make the configuration of routing more cleaner. You can still use the old way with the Route object.

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 from the "My Templates" section, 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
ActionResult List() { List<Customer> customers = (from customer in northwindDataContext.Customers orderby customer.CompanyName select customer).ToList(); return RenderView("Customers", customers); }


There are some changes to the Action methods between the Preview 2 and pre-release of Preview 3 of the ASP.Net MVC Framework. An Action method now returns a ActionResult. The RenderView method will now return a RenderViewResult, which inherits the ActionResult class. The changes are made to make it easier to create unit test without needing to create a lot of Mock objects, and also to make it easier in the future to support async. programming etc. You can read more about it on Scott Guthire's blog.

Note: By default if we make a call to RenderView, without passing the name of the View, it will use the name of the Action method as the View to render.

The following example shows an example of how a unit test can look like to test the List action method:


[TestClass] public class CusomerControllerTest { [TestMethod] public void ListCustomerTest() { CustomerController custController = new CustomerController(); var result = custController.List() as RenderViewResult; Assert.IsNotNull(result, "Expected a RenderViewResult"); var customers = result.ViewData as List<Customer>; Assert.IsNotNull(result.ViewName, "Customers", "..."); Assert.IsNotNull(customers, "..."); //... } }


Note: There is a helper method of the Controller base class called Redirect, which can be used to do a redirect within a Action method. The Redirect method returns a HttpRedirectResult, which inherits from the ActionResult class. The RedirectToAction method now returns an ActionRedirectResult class.


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, use the Redirect method and navigate to "/Errors/CustomerNotFound/<customer ID>". Make sure to render a View with the name “Customer”. The View should render a detail view of a Customer.

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

    if (customer == null)
        return Redirect("/Error/CustomerNotFound/" + ID);

    return RenderView("Customer", customer);
}

Note: You will not implement a Controller or Views that will handle the error in this guide, it is used to demonstrate the Redirect method, and also how you can create a unit test that can check that the Action do the right thing when a CustomerID is passed to the Action method, and the customer can't be found.

The following example will show you for example how you can write a test that will check if the user can't be found and that the Action method will do a Redirect:


[TestMethod] public void ViewCustomerTest_Cant_Found_Customer() { CustomerController custController = new CustomerController(); var result = custController.View("Unknow Id") as HttpRedirectResult; Assert.IsNotNull(result, "Expected a http redirect"); //... }

Because the Redirect method returns a HttpRedirectResult, you can in your Unit test, see if the Action method will do a Redirect or not.


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 RouteLink, for example like this:

 


<%=Html.RouteLink(ViewData.CustomerID, "Default", new { ID = ViewData.CustomerID, Controller="Home", Action="View" }) %>
 

When you use the MapRoute method to create a route in global.asax, you give the route a name, for example the default route has the name "Default". When you use the RouteLink method, you can specify the name of the route, the RouteLink method generates a HyperLink. The example above will use the Default route. The result of the above example will be:

<a href="http://weblogs.asp.net/Home/View/AFLK">AFLK</a>

Note: In a future release of the MVC Framework, you don't need to pass the Controller and the Action to the RouteLink.


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();


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
ActionResult Remove(string ID) { Customer customer = GetCustomerByID(ID); northwindDataContext.Customers.DeleteOnSubmit(customer); try { northwindDataContext.SubmitChanges(); return 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 ActionResult Update(string customerID)
{
    Customer customer = GetCustomerByID(customerID);

    BindingHelperExtensions.UpdateFrom(customer, Request.Form);

    try
    {
        northwindDataContext.SubmitChanges();
    }
    catch (Exception e)
    {
        throw new ApplicationException(
                string.Format("Can't update customer {0}", customerID)
                , e);
    }

    return 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 can now in pre-release of the Preview 3 of the ASP.Net MVC Framework also get access to the ActionResult returned from the Action method.

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", I will later create a new post about the ActionFilter in the pre-release of Preview 3.

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

Published Thursday, April 17, 2008 12:00 PM by Fredrik N
Filed under: ,

Comments

# re: ASP.Net MVC Framework pre- Preview 3 - A Step by Step guide to create a simple web app.

Friday, April 18, 2008 2:40 PM by dario-g

Great but maybe someone write post about configuration, eg. web.config, references, etc.

# re: ASP.Net MVC Framework pre- Preview 3 - A Step by Step guide to create a simple web app.

Sunday, April 20, 2008 1:05 PM by OmegaSupreme

Great article, cheers.

# re: ASP.Net MVC Framework pre- Preview 3 - A Step by Step guide to create a simple web app.

Tuesday, April 22, 2008 7:15 AM by Chris

Thanks for that.  Very useful.

# re: ASP.Net MVC Framework pre- Preview 3 - A Step by Step guide to create a simple web app.

Tuesday, June 17, 2008 9:34 AM by Nilserik Karlsson

Hi,

the RenderView method seem to be removed in the newly released Preview 3. Have you had time to look at how to implement the RenderView functionality in the new version?

return RenderView("Customers", customers);

# re: ASP.Net MVC Framework pre- Preview 3 - A Step by Step guide to create a simple web app.

Friday, June 20, 2008 11:02 AM by Sean Kokko

Nilserik -

Use

return View("Customers",customers);

and it should work for you.

# re: ASP.Net MVC Framework pre- Preview 3 - A Step by Step guide to create a simple web app.

Sunday, June 29, 2008 4:54 PM by Cory

Using preview 3, a DataContext object was not created automatically after I created my LINQ to SQL class. Has this changed in preview 3?

Leave a Comment

(required) 
(required) 
(optional)
(required)