ASP.NET MVC 2 – here’s my crash course

This is a long post, but hopefully this is one-stop-shopping for some of the key syntax you might need for dealing with data structures and validation with MVC 2.

I’m going through the process of learning MVC, and have a couple of websites that I’m using for that learning process. I ended up getting ASP.NET MVC 2 In Action, which is a really great book! In there, in first couple chapters, I finally “got” the missing pieces (the value) of using MVC. If you’re like me, you saw MVC as a pretty big step backwards, because the developer really has to do a lot more manual HTML. Although that can be true, the reason why I thought this is because of the samples everyone keeps using, promoted this idea. What I mean is, many of the examples show a controller that puts together a few pieces of information into ViewData, and passes that to a view. The View then has to manually deal with rendering this random data. This is pretty clunky, but that’s the example it seems everyone keeps using!

The “missing link” for me, which I learned about in the book, is that you would never/rarely every use that approach. What you SHOULD do, is always work in strongly-typed data structures. This blog post will hopefully help others, but is for me too, because this stuff still isn’t second-nature for me just yet. This post will show a complete example of the utter coolness of MVC, if you were going to actually implement real functionality. You will see no “Foo” and “Bar”, here (which I can’t stand!) – instead, this will be actual code you can use in your application. This will show how to have a pretty clean data entry screen, with validation, then show that data on another screen.

With that said, let’s get started. The example that I want to build, is a “guest book” feature of an MVC website. So, create a new MVC 2 project – which will give you a basic website to start from.

Set up the model and controllers:
The first thing we need to do, is set up the “model” and the “controllers”. The “model” is the data structure(s) that will be used for this functionality. In this case, I created a class that has properties to store “a record” of the guestbook. Here’s a class that we can start from, it’s called “GuestBookEntry.cs” and I put it in the /Models/ directory:

public class GuestBookEntry
{
    public GuestBookEntry()
    {
        Id = Guid.NewGuid();
    }
 
    public GuestBookEntry(string name, string email, string message)
        : this()
    {
        Name = name;
        Email = email;
        Message = message;
    }
 
    public string Name { get; set; }
 
    public string Email { get; set; }
 
    public string Message { get; set; }
 
    public Guid Id { get; set; }
}

So this is very simple, this is just a place where I can store the details of a GuestBook entry. Next, let’s create a controller. By-convention, I will create “GuestBookController.cs” in the /Controllers/ directory. That class starts off looking like this:

public class GuestBookController : Controller
{
    public ActionResult Index()
    {
        // This is the default, where someone navigates to:
        //    /GuestBook/
        //    /GuestBook/Index

        // Create a new "record", and pass it to the view
        GuestBookEntry model = new GuestBookEntry();

         return View(model);
    }

    [HttpPost]
    public ActionResult Submit(GuestBookEntry entry)
    {
        // Make a temporary copy of the record
        TempData["entry"] = entry;

         // Store the new entry in our Application-wide collection
        GuestBookEntryCollection entries;

         if (System.Web.HttpContext.Current.Application["entries"] == null)
        {
            entries = new GuestBookEntryCollection();
        }
        else
        {
            entries = System.Web.HttpContext.Current.Application["entries"] as GuestBookEntryCollection;
        }
        entries.Add(entry);
        System.Web.HttpContext.Current.Application["entries"] = entries;

         // Redirect to the ThankYou action (which will show the ThankYou view)
        return RedirectToAction("ThankYou");
    }

     public ActionResult ThankYou()
    {
        // Redirect, if someone tries to come here directly
        if (TempData["entry"] == null)
        {
            return RedirectToAction("Index");
        }

        GuestBookEntry entry = (GuestBookEntry)TempData["entry"];
        return View(entry);
    }

    public ActionResult Browse()
    {
        GuestBookEntryCollection entries;

         if (System.Web.HttpContext.Current.Application["entries"] == null)
        {
            entries = new GuestBookEntryCollection();
            entries.Add(new GuestBookEntry("John Doe", "john.doe@company.com", "This is a test message…1"));
            entries.Add(new GuestBookEntry("Jane Doe", "jane.doe@company.com", "This is a test message…2"));
            entries.Add(new GuestBookEntry("Edward Smith", "eddy@email.com", "This is a test message…3"));
            entries.Add(new GuestBookEntry("Steven Johnston", "sjo@domain.com", "This is a test message…4"));

            System.Web.HttpContext.Current.Application["entries"] = entries;
        }

        entries = System.Web.HttpContext.Current.Application["entries"] as GuestBookEntryCollection;

         return View(entries);
    } 
}

So let’s stop here, and look at what we have. We have controllers/actions to handle the following URL’s:

  • /GuestBook/Index (and just /GuestBook/) – this will show the data entry page
  • /GuestBook/Submit – this is where the data entry page submits it’s data (and this action only supports a POST)
  • /GuestBook/ThankYou – this is where you get sent, after the guestbook entry is submitted.
  • /GuestBook/Browse – this is where you can browse existing entries of the guestbook.

Also, GuestBookEntryCollection is simply a class in the /Models/ directory that is of type List<GuestBookEntry> – and that’s about it.

Onto the Views:
Now, we’ve handled the routing and the data side of things – but we don’t have an actual web page that we can see, that does anything. Well, the good part is, since we are dealing with a strongly-typed data structure, this is where things start getting pretty nice and elegant. Start by going into the /Views/ directory, create a directory for /GuestBook/ (reminder: MVC requires these conventions!). Within the GuestBook directory, right click and choose Add –> View… and do something like this:

image

Now, you’ll notice that it creates a rough draft of what your page might look like, including HtmlHelpers for each field. Let me stop here, there are two cool options you have here:

  • Centralize the look/feel of a display or editor, for a model – Instead, use Html.EditorForModel() only, on this page – then create an “EditorTemplates” directory in that view directory, create an .ascx and layout exactly how you want the editor to ALWAYS look, for this data structure. What I mean is, if you create a file called /Views/GuestBook/EditorTemplates/GuestBookEntry.ascx, the run-time will automatically use that (when you say EditorForModel()) – and you can centralize what you want the edit screen to look like. This is useful if you have more than one action that is going to require that editor. For example, you might have an Index action, which is what end-users use, but you might have another one called AdminEdit, where an administrator can edit that record too. If you create the “editor” for this data structure in one place, that reduces duplicate code.
  • Use shared templates for each field – within /Views/Shared you can create DisplayTemplates and EditorTemplates for specific kinds of data. For example, you might always want to show the fieldname, a colon, then the data, then a validator after it. I have probably looked at thousands of websites in the past few weeks, and unfortunately, I don’t remember where I found this – but for example, you could create an EditorTemplate for SingleLineString.asc, which might look like this:

/Views/Shared/DisplayTemplates/MultiLineString.ascx:

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<dynamic>" %>
<span id="Display<%= ViewData.TemplateInfo.HtmlFieldPrefix %>">
    <%= Html.Encode(ViewData.TemplateInfo.FormattedModelValue) %></span>

/Views/Shared/EditorTemplates/MultiLineString.ascx:

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<dynamic>" %>

<%= Html.TextArea("", (string)ViewData.TemplateInfo.FormattedModelValue,

                     new { @class = "textArea", @cols ="50", @rows="10" }) %>

So as you can see, you have a good amount of control over how a ‘MultiLineString’ will look when displayed, and when it’s being edited. You could add your own logic to also show the fieldname to the left, and a validator to the right, for example, if you’d like. More on how to use that “MultiLineString” in a minute…

Ok, now one more big thing – “what do I mean, validators? I thought those went away with ASP.NET postback/viewstate!?”. Not so. Validation is done differently, but I would argue it’s much better this way!

Adding data validation, client and server-side, for free!:
Validation is perhaps the most compelling feature I’ve seen yet in MVC. Here’s how it works – you decorate your data structures with all sorts of attributes to tell MVC all about it. Then, the View just… handles it, in whatever way is appropriate. Remember when we talked about MultiLineString above? Here is how you would use that, and automatically wire up validation AND display labels for each field. This is the same GuestBookEntry class, but with some extra attributes:

[Display(Prompt = "Please enter a name.", Order = 0)]
[DisplayName("Your Name:")]
[RegularExpression(@"^([1-zA-Z0-1@.s]{1,255})$", ErrorMessage = "Please enter a valid name.")]
[Required(AllowEmptyStrings = false, ErrorMessage = "Please enter a name.")]
[StringLength(50, MinimumLength=3, ErrorMessage="Please enter a name between 3 and 50 characters long.")]
public string Name { get; set; }

[DataType(DataType.EmailAddress, ErrorMessage = "Please enter a valid e-mail address.")]
[Display(Prompt = "Please enter an e-mail address.", Order = 1)]
[DisplayName("Your e-mail address:")]
[RegularExpression(@"^[w-]+(?:.[w-]+)*@(?:[w-]+.)+[a-zA-Z]{2,7}$", ErrorMessage = "Please enter a valid e-mail address.")]
[Required(AllowEmptyStrings = false, ErrorMessage = "Please enter an e-mail address.")]
[StringLength(100, MinimumLength = 6, ErrorMessage = "Please enter an e-mail address between 6 and 100 characters long.")]
public string Email { get; set; }

[Display(Prompt = "Please enter a message.", Order = 2, Description="Message content should go here.")]
[DisplayName("Your Message:")]
[RegularExpression(@".*", ErrorMessage = "Please enter a valid name.")]
[Required(AllowEmptyStrings = false, ErrorMessage = "Please enter a message.")]
[UIHint("MultiLineString")]
public string Message { get; set; }

[HiddenInput(DisplayValue=false)]
[Key()]
public Guid Id { get; set; }

Now, here’s where the magic is. If you were to just do a Html.EditorForModel() – this view will start doing some wonderful things. The fact that Message is decorated with that UIHint(“MultiLineString”) – that tells the View to find a MultiLineString.asc in the DisplayTemplates and EditorTemplates to show that field (depending on whether it is viewing or editing the data). The regular expresion and required field validators are also fully-respected too!

One thing to note, in order for validation to work, there are a few things you MUST do, as far as I can tell:

Step 1) Add some javascript files to your master page:

<script src="../../Scripts/MicrosoftAjax.js" type="text/javascript"></script>
<script src="../../Scripts/MicrosoftMvcValidation.js" type="text/javascript"></script>

Step 2) Enable client-side validation and create a <form/> with a using statement:

<% Html.EnableClientValidation(); %>
<% using (Html.BeginForm("Submit", "ContactUs"))
    { %>
<%= Html.EditorForModel() %>
<hr />
<input type="submit" value="Send Message" />
<% } %>

Step 3) Depending on how you are showing the fields, somewhere you need to enable/show the validators. For example, here is part of the editor template I created (/Views/GuestBook/EditorTemplates/GuestBookEntry.asc):

<fieldset>
    <legend>Message Detail:</legend>
    <div style="text-align: left;">
        <ul>
            <%= Html.ValidationSummary("The following errors must be corrected before continuing:")%>
            <%= Html.ViewData["ValidationError"] %>
        </ul>
    </div>   

     <table>
        <tr>
            <th>
                <%= Html.LabelFor<ContactUsModel, string>(model => model.Name)%>
            </th>
            <td>
                <%= Html.EditorFor<ContactUsModel, string>(model => model.Name)%>
            </td>
            <td>
                <%= Html.ValidationMessageFor(model=>model.Name,"*")%>
            </td>
        </tr>

So, I have a ValidationSummary at the top, right below that is a placeholder for a message from server-side validation (more on that in a minute). Then, for each field, I show “the appropriate” label for each field. It gets the label text from the data structure attributes (cool, eh!?). Then, I have ValidationMessageFor() – which will show a “*” if that field fails validation.

What’s Left?
OK, so by using a strongly-typed data structure, that made it very easy to create our views. It also makes it very easy to centralize our logic for data validation too. One last thing, this handles client-side validation. That makes for a nice user interface, but you really should do server-side validation as well. To do that, on the Submit() action, just add some logic like this:

// Server-side validation

try

{

    ValidateModel(entry);

}

catch (InvalidOperationException)

{

    TempData["ValidationError"] = "<span class="validation-summary-errors">Validation errors exist. Please fill out the form completely.</span>";

    return RedirectToAction("Index");

}

This does a few things, first – ValidateModel(entry) will validate the data structure against the rules defined in the attributes. In other words make sure that properties marked as required, have a value; properties marked with a regular expression, actually match that regular expression, etc. This method (unfortunately) throws an InvalidOperationException if validation fails. So, I write a message to the ViewData, and re-show the page. You might see above the line that reads <%= Html.ViewData[“ValidationError”] %> is what actually shows the error in the view, itself. So, if the end-user has javascript turned off – or is up to no-good, the server-side validation will still catch this and completely validate what was submitted, against the rules on the data structure.

Those are all the major pieces to this. Our controller supported a Browse() action too. To use this, you would just go into the Views/GuestBook folder and create a new strongly-typed “List” for that data, and it will create a grid for you. So hopefully the example above goes into enough detail to explain how to create, store, and display data from a strongly-typed data structure, with validation. To see this in action, I used much of the same concept and applied it here:

http://www.sedersoftware.com/ContactUs/

Feel free to send me an e-mail, to see the http://www.sedersoftware.com/ContactUs/ThankYou action. Meanwhile, hope this helps!

Posted in ASP.NET MVC, Uncategorized
One comment on “ASP.NET MVC 2 – here’s my crash course
  1. Kevin Suppan says:

    Hello!
    Very interesting article, thanks for sharing!
    But anyways, how would you do something like… pages on a guestbook? Let’s say: the guestbook gets 30 entries and you don’t want to show them in one page, but split them into several pages and the user can browse through the guestbook entries by clicking Site 2, 3, and so on.
    You know what I mean? So maybe something like put the current guestbook page somehow into the address (URL) or into the model and pass it, so it doesn’t have to reload the whole page but just the guestbook.
    Would really help, I’d like to embed a somehow improved version of this into my first page, so it would be nice!

    Greetz

    Like

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 )

Twitter picture

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

Facebook photo

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

Google+ photo

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

Connecting to %s

Archives
Categories

Enter your email address to follow this blog and receive notifications of new posts by email.

Join 2 other followers

%d bloggers like this: