Building Forms with MVC2 and EF4

I’ve always thought that working with forms in ASP.NET was a bit messy, but with the recent launch of Entity Framework 4 and MVC 2 (both part of Visual Studio 2010 / .NET 4), there is some really nice functionality to take advantage of. I got off to a great start by reading through Scott Guthrie’s NerdDinner sample to get a feel for MVC concepts. However, his sample is based on MVC 1 and used LINQ-to-SQL rather than EF, so I found that quite a bit of the information wasn’t applicable.This post shares some points on building MVC 2 forms more effectively using the Entity Framework. A few things that I wanted to get right in particular were business rule validations for form elements, use of foreign key associations within the form, and strong typing so that errors are caught at compile time rather than at run time.

This post demonstrates the creation of a form for creating and editing an individual Product. The Product object has a VendorId property that relates to a Vendor object.

Entity Model

Two database tables are used for this example: Product and Vendor. Use “Update Model From Database…” in the entity designer to add the database objects to the model. In this dialog there is a checkbox named “Include foreign key columns in the model”. It would be nice to not have to check this box because adding an additional VendorId property to the Product class is redundant since the same information is available via Product.Vendor.VendorId. However, these foreign key properties need to exist in order to take advantage of all the MVC goodness (as you’ll see below). This is what the model looks like:

image

Views

I wanted to use the same form for both Create and Edit actions, so I created a partial view (.ascx) and included it in two views Create.aspx and Edit.aspx using Html.RenderPartial(). Here’s the partial view:

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

    <% Html.EnableClientValidation(); %>
    <%: Html.ValidationSummary("Please correct the errors and try again.") %>

    <% using (Html.BeginForm()) {%>

        <fieldset class="form">
            <legend>Product Details</legend>
            <p>
                <%= Html.LabelFor(x => x.Product.Name) %>
                <%= Html.EditorFor(x => x.Product.Name) %>
            </p>
            <p>
                <%= Html.LabelFor(x => x.Product.Descripton) %>
                <%= Html.EditorFor(x => x.Product.Description) %>
            </p>
            <p>
                <%= Html.LabelFor(x => x.Product.Vendor) %>
                <%= Html.DropDownListFor(x => x.Product.VendorId, Model.Vendors, ""  ) %>
            </p>
            <p>
                <input type="submit" value="Create" />
            </p>
        </fieldset>

    <% } %>

Notice that strongly typed helper methods are used to render the form elements.

Also notice the drop-down, which refers to a field with an associated foreign key.

Custom Shaped ViewModel

The partial view uses the following model class:

    public class ProductFormViewModel {

        public Product Product { get; private set; }
        public SelectList Vendors { get; private set; }

        public ProductFormViewModel(Product product) {
            Product = product;
            var _repository = new VendorRepository();
            Vendors = new SelectList(_repository.FindAllVendors(), "VendorId", "Name");
        }
    }

This custom class was used instead of the Entity Framework generated Product class so that data for the vendor drop-down can be provided to the view. A simpler way of doing this (which doesn’t require a custom class) is to add the vendors to the ViewData dictionary instead, but the custom class has the benefit of providing strongly typed access to the data.

Validations

Validations can be defined declaratively using attributes. I created the following partial class with the same name as the business entity created from the EF designer:

    [Bind(Include = "Name,Description,VendorId")]
    [MetadataType(typeof(ProductMetadata))]
    public partial class Product {
    }

    public class ProductMetadata {
        [DisplayName("Product Name")]
        [Required(ErrorMessage = "Product Name is required")]
        [StringLength(50, ErrorMessage = "Name may not be longer than 50 characters")]
        public string Name { get; set; }

        [Required(ErrorMessage = "Description is required")]
        public string Description { get; set; }

        [DisplayName("Vendor")]
        [Required(ErrorMessage = "Vendor is required")]
        public int VendorId { get; set; }
    }

The MetadataType attribute allows a “buddy” class to be associated with the main class. This second class defines properties matching the main class annotated with metadata information. The metadata is automatically processed by the model binder and is used by the HTML helper methods in the view.

Notice the binding syntax that includes the foreign key association, and how attributes are added to the Vendor property in the same way as the regular fields.

Also, notice that the DisplayName attribute can be used to specify the form label that is rendered.

There is no other code required to hookup the metadata to the form view because the default model binder in MVC 2 does all this work automatically.

Displaying Errors

Here’s what the form looks like after hitting the Create button.

FormErrors

The validation messages are displayed and the input elements are given an error effect. If you wish to add an asterisk or some other error text beside each input, then generate the additional markup using the Html.ValidationMessageFor helper method.

I used the following CSS to format error information:

div.validation-summary-errors > span { font-weight: bold; }
div.validation-summary-errors li { color: #ff0000; }
.field-validation-error { color: #ff0000; }
.input-validation-error { border: 1px solid #ff0000; background-color: #ffeeee; }
.field-validation-valid { visibility: hidden; }

The .field-validation-valid class is only applicable if you’re using Html.ValidationMessageFor (or Html.ValidationMessage) and you are also doing client side validation, which is enabled by the helper method Html.EnableClientValidation. That’s because the error messages are always rendered when client validation is used.

I also used a bit of jquery to automatically align the inputs by setting the label widths to the width of the widest label. You could do this a different way or position the labels above the inputs.

Controller and Repository

I haven’t included information on the controller class because it contains nothing different than what has already been explained in the NerdDinner tutorials. The controller class doesn’t interact with the database directly, but instead calls a Repository/Manager class that encapsulates the EF data access code.

Conclusion

MVC 2 and EF 4 provides a rich technology foundation for building highly maintainable apps without needing to write typical “plumbing” code that is usually needed to connect all the bits together.

1 thought on “Building Forms with MVC2 and EF4”

  1. This was a really helpful post! I have been looking for a simple example like this for a while and yours was really well organized. Couple questions:

    Did you need to import a library to use the BindAttribute in your Product partial class? I am getting an error "Type or namespace 'BindAttribute' could not be found".

    In your controller, do you have the Create action using MVC's reflection to accept a Product as a parameter or are you using a FormCollection?

    Would you be willing to share the full source for your example? I would appreciate it.

Leave a Reply

Your email address will not be published. Required fields are marked *