Getting Started with ASP.NET MVC (Preview 4) - Part II

Since the last post, ASP.NET MVC Preview 4 was released, you can download it here. I am using this latest version for the current post. You'll have to uninstall the older version(s) to get it to work and therefore older projects might have to be modified to work with Preview 4.

This time I'll be showing how to retrieve, display, edit, validate then save data. I'll also be using .NET 3.5 features such as anonymous types, automatic properties, object initializers and LINQ to XML.

The end result will produce the following:

image [List.aspx] http://localhost/Architecture/List

image [Edit.aspx] http://localhost/Architecture/Edit/101

image [Edit.aspx] http://localhost/Architecture/Edit/101

Solution

You can use either Visual Studio 2008 or Visual Studio 2008 Express SP1 Beta. You'll have to create a new ASP.NET MVC project (see previous post) then do the following:

- Add a new folder under Views called 'Architecture'

- Under the Views/Architecture folder, add two new pages 'List.aspx' and 'Edit.aspx' (select MVC View Content Page so you can select the master page under the Views/Shared folder)

- Under the Controllers folder, add a new controller class 'ArchitectureController.cs'

- Under the Models folder, add two new classes 'Architect.cs' & 'DataAccess.cs'

Our solution should look like this (we'll add the 'Architects.xml' file in the Model section):

image

- Before we proceed to the different sections, we need to add a menu option 'Reference List' in the Master page located under Views/Shared/Site.master, add this code:

<li><%= Html.ActionLink("Reference List", "List", "Architecture")%></li>

image http://localhost/Home

Model

In our Model, we are going to store our business entities, data calls and in this case the data itself in 'Architects.xml':

<?xml version="1.0" encoding="utf-8"?>
    <architects>
        <architect>
            <id>101</id>
            <name>Richard Meier</name>
            <building>Getty Center</building>
            <location>Los Angeles</location>
        </architect>
        <architect>
            <id>102</id>
            <name>Norman Foster</name>
            <building>HSBC Headquarters</building>
            <location>Hong Kong</location>
        </architect>
        <architect>
            <id>103</id>
            <name>Frank Gehry</name>
            <building>Guggenheim Museum</building>
            <location>Bilbao</location>
        </architect>
    </architects>

The 'Architect.cs' file will include an Architect business entity:

public class Architect
{
    public string ID { get; set; }
    public string FullName { get; set; }
    public string Building { get; set; }
    public string Location { get; set; }
}

In 'DataAccess.cs' we retrieve a list of Architects from the XML using LINQ and an object initializer:

public List<Architect> GetArchitects()
{
    // Load XML
    XElement xArchitects = XElement.Load(@"C:\MvcApplication2\Models\Architects.xml");
    List<Architect> lstResponse = new List<Architect>();

    // Query the data to get all architects
    var tempArchitects = from a in xArchitects.Descendants("architect")
    select new Architect
    {
        ID = (string)a.Element("id"),
        FullName = (string)a.Element("name"),
        Building = (string)a.Element("building"),
        Location = (string)a.Element("location")
    };

    foreach (Architect architect in tempArchitects)
    {
        lstResponse.Add(architect);
    }

    return lstResponse;
}

To retrieve a single Architect we have a similar function as GetArchitects() but with a WHERE clause to pass in the selected id:

// Query the data to get architect
var tempArchitect = from a in xArchitects.Descendants("architect")
        where (string)a.Element("id") == id
        select new Architect
        {
            ID = (string)a.Element("id"),
            FullName = (string)a.Element("name"),
            Building = (string)a.Element("building"),
            Location = (string)a.Element("location")
        };

We also need to update the XML:

public void SaveArchitect(Architect architect)
{
    XElement xArchitects = XElement.Load(@"C:\MvcApplication2\Models\Architects.xml");
    IEnumerable<XElement> xArchitect = from a in xArchitects.Elements("architect")
        where ((string)a.Element("id")).Equals(architect.ID)
        select a;
    foreach (XElement xe in xArchitect)
    {
        xe.SetElementValue("name", architect.FullName);
        xe.SetElementValue("building", architect.Building);
        xe.SetElementValue("location", architect.Location);
    }

    xArchitects.Save(@"C:\MvcApplication2\Models\Architects.xml");
}

Controller

In our controller 'ArchitectureController.cs', we're retrieving the list of architects then populating the ViewData collection which is a dictionary (key/value pattern) that is passed back to the View.

public ActionResult List()
{
    DataAccess dataAccess = new DataAccess();
    ViewData["Architects"] = dataAccess.GetArchitects();

    return View();
}

For the Edit/Save action, we need to follow the same logic as above but then the key difference is that we're using the action to both populate and save data so we first check whether this is a 'POST' or not before proceeding. Note the action signature as well, we're passing all the parameters required for both GET/POST calls. We're only using 'id' for the GET call to populate the View with data but we need the full set of parameters when we're saving data.

Next, we have our validation checks that ultimately populates the ViewData["errors"] key. If there are any errors then we skip saving the data and only display the errors. One important thing to do is to repopulate the ViewData["Architect"] key because if you don't then the textboxes that hold the values will be empty. If there are no errors then call our model layer and save the data and finally redirect to the List view using RedirectToAction().

public ActionResult Edit(string id, string fullName, string building, string location)
{
    // View Existing Data
    DataAccess dataAccess = new DataAccess();
    Architect architect = dataAccess.GetArchitect(id);
    ViewData["Architect"] = architect;
    // Non-POST requests should just display the Edit form
    if (Request.HttpMethod != "POST")
    {
        return View();
    }

    // Validate Data
    List<string> errors = new List<string>();

    if (String.IsNullOrEmpty(fullName))
    {
        errors.Add("You must specify a name.");
        architect.FullName = "";
    }
    if (String.IsNullOrEmpty(building))
    {
        errors.Add("You must specify a building.");
        architect.Building = "";
    }
    if (String.IsNullOrEmpty(location))
    {
        errors.Add("You must specify a location.");
        architect.Location = "";
    }

    // Save data
    if (errors.Count == 0)
    {
        // Populate data from the Edit view
        architect.ID = id;
        architect.FullName = fullName;
        architect.Building = building;
        architect.Location = location;

        // Save new data
        dataAccess.SaveArchitect(architect);

        // Redirect to List view
        return RedirectToAction("List");
    }

    // Display errors
    ViewData["errors"] = errors;
    ViewData["Architect"] = architect;
    return View();
}

View

There are two ways for populating the views, one is using the code-behind page and setting values to server controls and the other which I'm using here is inline code, so there's no code whatsoever in the code-behind pages. However you need to add a reference to your model classes so they can be used in the ASPX.

<%@ Import Namespace="MvcApplication2.Models" %>

In 'List.aspx' all we do is loop through our ViewData["Architects"] collection and populate the values into a table. To create a link that will take us to the Edit page we use Html.ActionLink and pass the architect ID, we also specify what View/Controller ("Edit"/"Architecture") to use. There are many html helper functions that include all the basic controls such as textboxes, dropdowns etc. You can access them using intellisense in the ASPX page by typing "Html.".

<table border="1" cellpadding="4" cellspacing="0">
    <tr>
        <td>&nbsp;</td>
        <td>Architect</td>
        <td>Building</td>
        <td>Location</td>
    </tr>
    <% foreach (var architect in (ViewData["Architects"]) as List<Architect>) { %>
    <tr>
        <td><%= Html.ActionLink(architect.ID, "Edit", "Architecture", new { id = architect.ID })%></td>
        <td><%= architect.FullName %></td>
        <td><%= architect.Building %></td>
        <td><%= architect.Location %></td>
    </tr>
    <% } %>
</table>

In 'Edit.aspx', we list the returned errors if there are any, we also populate the textboxes. You will notice that our form posts to a specific action "Edit" when it is submitted.

<% var architect = (ViewData["Architect"]) as Architect; %>

<%
IList<string> errors = ViewData["errors"] as IList<string>;
    if (errors != null) {
%>
        <ul class="error">
        <% foreach (string error in errors) { %>
            <li><%= Html.Encode(error) %></li>
        <% } %>
        </ul>
<%
}
%>

<form method="post" action="<%= Html.AttributeEncode(Url.Action("Edit")) %>">
<div>
<table>
    <tr>
        <td>Architect:</td>
        <td><%= Html.TextBox("FullName", architect.FullName)%></td>
    </tr>
    <tr>
        <td>Building:</td>
        <td><%= Html.TextBox("Building", architect.Building)%></td>
    </tr>
    <tr>
        <td>Location:</td>
        <td><%= Html.TextBox("Location", architect.Location)%></td>
    </tr>
    <tr>
        <td>&nbsp;</td>
        <td><%= Html.SubmitButton("Edit", "Save Changes")%></td>
    </tr>
</table>
</div>
</form>

Next Steps

Using this sample, you can build most of your typical web forms using all the MVC layers, the only remaining thing is building our testing scenarios/projects which I will address in the next post that will also include the source code for the entire solution.

0 comments: