Use Basic Scaffolding in ASP .NET MVC 1.0

One of the most tedious tasks for the web developer is the manufacturing of interfaces for handling basic CRUD operations. As a solution for this Rails brought about scaffolding, which is the idea of using the entity objects as a means to drive the creation of a basic form. This basic form is capable of allowing developers to have a starting point as well as a means add basic test data for the application. In many of the early betas for ASP .NET MVC this feature was sorely lacking, though it was clear the development team would be adding it. After taking a long break from the MVC framework I finally got a chance to return to it this week. I downloaded version 1.0 and set about to playing with it. I was pleased to see that basic scaffolding is now included (I say basic cause I do not feel it is on par with Rails, yet).

The intent of this entry is to walk through using scaffolding to build a small piece of my much large Anime Manager application. We will use scaffolding to get all the pages needed to perform CRUD operations against Series, we will then tweak things to make the pages a bit more friendly and demonstrate the use of the HTML helper in MVC.

To begin we will select the MVC Web Application from Visual Studio 2008:
2009-05-22_1028
 Note: Make sure your version is 1.0 otherwise scaffolding will not be available

Once the Visual Studio loads up the project there are three folders to take notice of:

  • Controllers
  • Models
  • Views

From within the Controllers folder delete the HomeController.cs folder and its corresponding folder from Views, this will ensure a clean starting point.

Now right click on Controllers and hover over the ‘Add’ option, you should see ‘Controller…’ available in the submenu that appears, Select this and you will be presented with the following dialog:
2009-05-22_1040 

I am calling my Controller ‘Main’ as follows with my standard convention, you can name yours whatever you liked, just know that I will be referring to Main in my code examples as well as throughout this entry.

Once Visual Studio completes the process of setting up your controller, you will find several methods:

  • Index – represents the main page of this controller, we will have our selection list on this page
  • Details(int) – represents the page in this controller used to have the Read case
  • Create() – represents the page in this controller used to Create new items
  • Create(FormCollection) – represents the action responsible for actually creating a new item
  • Edit(int) – represents the page in this controller responsible for displaying information that can be updated to the end user based on a selection
  • Edit(int, FormCollection) – represents the action responsible for actually updating an item

For the uninitiated, MVC stands for Model View Controller, and its a principle of organization to enhance maintainability and clearly define responsibilities. You have models which hold data and are responsible for updating and maintaining it. You have controllers which enforce rules and prepare data before it is displayed to the user in the view.

Each of the “actions” in the controller basically represent a view page which we will create in a bit.  These actions intercept the web request and serve a similar purpose to a .NET code behind, although with more structure. This is the reason each of these calls have a reference to the View() method.  The exception are the Edit and Create overloads, which are decorated to only accept POST data. The purpose of these actions to to carry out a process and then provide some sort of feedback to the user that something happened, whether that feedback is returning to a list and seeing your new entry in the list or displaying a message.

Our next step is to provide a means to bring data into our application, for that we will use the Entity Framework to create our classes, I am assuming you have Service Pack 1 installed, so right click on models and select ‘Add’ -> ‘New Item…’
2009-05-22_1055
Note: I am not going to cover the whole setup process for Entity Framework, I will only say that I defined my Model Namespace as ‘Models’.

Following the options my EDMX file looks like this:
2009-05-22_1059 

Honestly, pretty boring – but its enough to get started. So now return to you MainController.cs class file.  The first thing we will do is add a reference to our Entity Manager so we can request data from our data layer, like so:

private Models.AnimeManagerEntities _entities =
     new MvcApplication1.Models.AnimeManagerEntities();

So the first page we are going to address is the Index page. Our end goal for this page will be to provide a list of stored Series. .NET supports strongly typed Views which allow us to tell the View what type of data it will be representing. To make this easier right click on the method name. From the context menu select ‘Add View…’, you will be presented with the following dialog:

2009-05-22_1339 

Most of the fields in this dialog should be pretty self explanatory. Since we are creating a List view we select the List template; by providing our model class Visual Studio will create grid columns based on the public properties of the class. Note: if you do not see your model in the drop down, run a build and then look again.  Once you press ‘Add’ you template view is created, the only thing left is to supply data to the view.  That is easily done using our Entity Manager like so:

public ActionResult Index()
{
     return View(_entities.Series.ToList());
}

Couldn’t be easier right, now if you run your application you should get a very nice grid for the Index page. So in just a few short steps you have a working web application that can list your data.

Now lets do some touch up work. We probably don’t want to display the StudioId to our users, surely the Studio name is more valuable. So lets add the Studio table to our EDMX, after doing that and tweaking a few things you model will look like so:

2009-05-22_1347

I made a number aesthetic changes, for instance you will notice that I got rid of the StudioId property from Series. Studio came out named Studios, I removed the ‘s’ because it is representing a singular studio.  The next step is the creation of a partial class to “extend” our Series entity to provide for some shortcuts.  For instance, we would like a way to get the Studio name for a Series quickly and easily.  For that purpose, I added this code:

namespace MvcApplication1.Models
{
    public partial class Series
    {
        public string StudioName
        {
            get
            {
                if (!StudioReference.IsLoaded)
                {
                    StudioReference.Load();
                }
 
                return Studio.Name;
            }
        }
    }
}

To fully explain what is going on here would require an explanation of Eager vs Lazy loading in Entity Framework which is outside our scope, suffice to say this is an example of lazy loading.

Now with this in place, we can replace the reference in the Index view to StudioId with StudioName.  If you run the page now you should see the Studio Name in place of the Id, which makes the page look much better (you could also get rid of SeriesId as well).

Continuing on, we will skip the Detail action and move to the Create action.  Once again, right click on the action method in question (this time the undecorated Create()) and Select ‘Add View…’.  Select your model class and choose the Create template.  This will generate an HTML page that accepts input for each property in the class and makes the call to the decorated Create() method.  Make sure you are passing a new instance of the Series class to the view.

If you run the application and select the ‘Create’ link from the Index page you will be greeted with the fields representing the new entry, except the foreign key fields, which do not appear.  The reason for this is by default the templates do not understand the foreign key field and how to display it.  We will tackle this next.

The call to View within an action method can take an entity instance that is used to create the  strongly-type views that we have been using. There is another variable called ViewData which is a hashtable containing relevant data that you want available to the view. In Create we want to provide a list of items for a drop down list to allow users to easily select the studio the Anime was produced by.  To do this we must first generate the list, the following code takes care of this, as well as assigning the value into the ViewData hashtable:

var studioList = _entities.StudioSet.ToList().Select(st => new SelectListItem()
{
     Text = st.Name,
     Value = st.StudioId.ToString()
}).ToList();
 
ViewData["StudioList"] = studioList;

Something I would to point out about this code; because it uses Linq to Entities we cannot call instance methods on the list items as it confuses Linq to Entities during the query generation process. To work around this we will force the enumeration and query generation by the Entity Manager by call .ToList on the sequence. Once we do this, we can work with the results in a Linq to Objects context, in which we can do what we want as we create the enumeration.  The SelectListItem class is provided by Mvc and can be referenced through the DropDownList helper method in the view.  So in the view we add the following code:

<%= Html.DropDownList("StudioId", ViewData["StudioList"] as List) %>

This code uses the DropDownList HTML helper method to construct a select box with the id/name attribute of ‘StudioId’.  We use the list we stored in the ‘ViewData’ hashtable which contains our select items.

Now the Edit action is very similar to the Create action, except that when we construct the list we want to set the Selected property to the appropriate value.  Below is my implementation of the Edit view caller:

   1:  public ActionResult Edit(int id)
   2:  {
   3:       var series = _entities.Series.FirstOrDefault(s => s.SeriesId == id);
   4:       var studioList = _entities.StudioSet.ToList().Select(st => new SelectListItem()
   5:       {
   6:            Text = st.Name,
   7:            Value = st.StudioId.ToString(),
   8:            Selected = series.StudioId == st.StudioId
   9:       }).ToList();
  10:   
  11:       ViewData["StudioList"] = studioList;
  12:       return View(series);
  13:  }

Take note of line 8, we use the selected series StudioId to determine which item is pre-selected.  This, and the fact that we are using a series to pass to the View, are the main differences between the two methods.  Finally, we take a look at the Create and Edit action methods which perform the Insert/Update actions on the data.  First the Create:

   1:  [AcceptVerbs(HttpVerbs.Post)]
   2:  public ActionResult Create([Bind(Exclude="SeriesId")]Models.Series series)
   3:  {
   4:       try
   5:       {
   6:            // TODO: Add insert logic here
   7:            _entities.AddToSeries(series);
   8:            _entities.SaveChanges();
   9:   
  10:             return RedirectToAction("Index");
  11:       }
  12:       catch
  13:       {
  14:            return View();
  15:       }
  16:  }

The interesting thing about this method is its signature and it represents and very interesting feature of Mvc Framework.  If the input fields are properly named and correspond to the properties of the model class, then we can define our input parameters as a single object and Mvc will create an instance for us of that class that we can easily add to the database via the Entity Manager.  Notice, however, that we define a attribute on the parameter that states we want to ignore the SeriesId property mapping for the instance.  The reason for this is because in a Create context we do not have a SeriesId to work with.

Unfortunately, this method doesn’t work the same for the Edit action.  The reason is that even if Mvc maps the input data to an instance it did not come out of the Entity Manager and thus the changes cannot be tracked.  In this case it makes more sense to use the direct mappings for input fields to method parameters, like so:

   1:  [AcceptVerbs(HttpVerbs.Post)]
   2:  public ActionResult Edit(int SeriesId, string Name, int StudioId)
   3:  {
   4:       try
   5:       {
   6:            // TODO: Add update logic here

7: Models.Series existingSeries = _entities.Series.First(s => SeriesId==s.SeriesId);

   8:            existingSeries.Name = Name;
   9:            existingSeries.Studio = _entities.StudioSet.First(st => st.StudioId == StudioId);
  10:   
  11:            _entities.SaveChanges();
  12:   
  13:            return RedirectToAction("Index");
  14:       }
  15:       catch
  16:       {
  17:            return View();
  18:       }
  19:  }

You will notice that in this case the parameter names match the names of the input fields we define din the view. This is critical for the mapping to take place.  As you can see from the above code example, we do a look up on the Series and then update the properties accordingly before calling SaveChanges on the Entity Manager to commit the changes.

The final piece I would like to draw your attention to are the Attributes that decorate each of the actions Create/Edit.  This is important because it is what allows us to reuse code.  The attributes define these actions as being invoked only when the request type is a POST, that is when data is being saved from a form submit.  This, plus function overloading, allows us to reuse the same view for these actions as a failed save attempt will simply return the Edit/Create view with error messages visible.

To conclude, scaffolding is a very nice way to get started on webapps by removing the tedious task of coding these forms by hand. One concept that I did not cover here is the T4 template engine that allows developers to customize and create view templates. This means that you could easily create full fledged HTML patterns for a site using Mvc and easily (and consistently) implement them using Visual Studio as a selector. This would further increase the productivity of the team member and reduce the chance for coding error.

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

Advertisement

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s