Rabu, 16 Maret 2011

%20
4GuysFromRolla.com

Internet.com Network
Wednesday March 16th, 2011

Use MvcContrib Grid to Display a Grid of Data in ASP.NET MVC
Scott Mitchell

A Multipart Series on Grids in ASP.NET MVC
Displaying a grid of data is one of the most common tasks faced by web developers. This article series shows how to display grids of data in an ASP.NET MVC application and walks through a myriad of common grid scenarios, including paging, sorting, filtering, and client-side enhancements.

  • Displaying a Grid - this inaugural article walks through building the ASP.NET MVC demo application and retrieving and displaying database data in a simple grid.
  • Sorting a Grid - learn how to display a sortable grid of data.
  • Paging a Grid - shows how to efficiently page through a grid of data N records at a time.
  • Filtering a Grid - see how to create a filtering interface to limit what records are displayed.
  • Sorting and Paging a Grid - learn how to display a grid that is both sortable and can be paged.
  • Sorting, Paging, and Filtering - shows how to display a grid that is sortable, pageable, and filterable.

Introduction
The past six articles in this series have looked at how to display a grid of data in an ASP.NET MVC application and how to implement features like sorting, paging, and filtering. In each of these past six tutorials we were responsible for generating the rendered markup for the grid. Our Views included the <table> tags, the <th> elements for the header row, and a foreach loop that emitted a series of <td> elements for each row to display in the grid. While this approach certainly works, it does lead to a bit of repetition and inflates the size of our Views.

The ASP.NET MVC framework includes an HtmlHelper class that adds support for rendering HTML elements in a View. An instance of this class is available through the Html object, and is often used in a View to create action links (Html.ActionLink), textboxes (Html.TextBoxFor), and other HTML content. Such content could certainly be created by writing the markup by hand in the View; however, the HtmlHelper makes things easier by offering methods that emit common markup patterns. You can even create your own custom HTML Helpers by adding extension methods to the HtmlHelper class.

MvcContrib is a popular, open source project that adds various functionality to the ASP.NET MVC framework. This includes a very versatile Grid HTML Helper that provides a strongly-typed way to construct a grid in your Views. Using MvcContrib's Grid HTML Helper you can ditch the <table>, <tr>, and <td> markup, and instead use syntax like Html.Grid(...). This article looks at using the MvcContrib Grid to display a grid of data in an ASP.NET MVC application. A future installment will show how to configure the MvcContrib Grid to support both sorting and paging. Read on to learn more!

Step 0: A Brief Roadmap
This article walks through rendering a grid of data in an ASP.NET MVC application using the MvcContrib Grid HTML Helper. It is presumed that you have already read and worked through the previous articles in this series as we will rely on code created in earlier tutorials. The aim of this tutorial is to familiarize ourselves with the MvcContrib Grid and to end up with an ASP.NET MVC View that displays the name, category, quantity per unit, price, and discontinued status of all of the products in the Northwind database in a non-sortable, non-paged grid. (Future installments will look at implementing sorting and paging with MvcContrib Grid.)

As the screen shot below shows, the grid we will create in this article is identical to the one that we created in the first installment in this series, Displaying a Grid of Data in ASP.NET MVC. While the output is same, the syntax used in the View is quite different, as we will see shortly.

The products are listed in a simple grid.

Step 1: Downloading and Referencing the MvcContrib Project
To follow along with the code presented in this article you'll need to download the MvcContrib project and reference it from your ASP.NET MVC application. You can get your hands on the MvcContrib assembly (the DLL file) by either downloading the demo available at the end of this project or by going to the MvcContrib Releases page. In either event, the file you need to add to your project is MvcContrib.dll.

  • From the MvcContrib Releases page, download the MVCContrib.release.zip file - MvcContrib.dll is one of the files in the root of the ZIP.
  • From the demo available for download from this article, you'll find MvcContrib.dll in the Bin folder. Note that the demo in this article uses MvcContrib version 3.0.51.0, which was released on January 16th, 2011. A more recent version may be available.
Once you have your hands on the MvcContrib.dll file, the next step is to add a reference to it from your project. From Visual Studio's Solution Explorer, right-click on the References folder and choose Add Reference. This launches the Add Reference dialog box. From there, click the Browse button, locate the MvcContrib.dll file, and click the Add button.

Step 2: Creating a New Controller and a Base Controller Class
All of the demos for the past six articles used the same Controller, ProductsController. I decided to create a new Controller for the MvcContrib Grid demos named ProductsMvcContribController. If you are following along at your computer, right-click on the Controllers folder and choose the Add --> Controller menu option, naming the new Controller ProductsMvcContribController.

Recall that the ProductsController has a protected property named DataContext that returns an instance of the NorthwindDataContext class. (The NorthwindDataContext class is one of the classes auto-generated by the Linq-to-SQL library, which is what these demos use to access the database.) Because we need to access the database in the new Controller you may be tempted to copy and paste the DataContext property from the ProductsController class into the new ProductsMvcContribController class. But anytime you find yourself copying and pasting code you should stop and pause because it likely means you're doing something wrong. In this case, it's an indication that we're violating the DRY principle - Don't Repeat Yourself! A better approach is to create a base Controller class that defines the DataContext property and then have those Controllers that need to access the database derv ice from this base class.

Add a new class file to the Controllers folder named BaseController. Have this class derive from the Controller class and then move the DataContext property from the ProductsController class to this new base class. After these changes your BaseController class should look similar to the following. (Note: You will need to include a using statement to the System.Web.Mvc, Web.Models, and System.Data.Linq namespaces; if you run into problems or want to see the full code you can always download the complete working demo available at the end of this article.)

public class BaseController : Controller
{
   private NorthwindDataContext _DataContext=null;
   protected NorthwindDataContext DataContext
   {
      get
      {
         if (_DataContext==null)
            _DataContext=new NorthwindDataContext();

         // Eager load Category info
         var options=new DataLoadOptions();
         options.LoadWith<Product>(p=> p.Category);
         _DataContext.LoadOptions=options;

         return _DataContext;
      }
   }
}

Next, have all of the Controllers in the application extend the BaseController class. For instance, the new ProductsMvcContribController class would look like so:

public class ProductsMvcContribController : BaseController
{
   // GET: /ProductsMvcContrib/
   public ActionResult Index()
   {
      return View();
   }
}

Step 3: Implementing the ProductsMvcContrib Controller's Index Action
The purpose of a Controller action is two-fold: to create the Model and determine what View to use to render the output. The current return View() statement in the ProductsMvcContrib Controller's Index action handles the latter part - selecting the View - but we've yet to specify the Model. Because we want the View to display information about all products we can have the Model simply be the set of Product objects in the database. This information can be retrieved using this.DataContext.Products and sent to a strongly-typed View by passing the Model into the return View() statement like so:

public class ProductsMvcContribController : BaseController
{
   // GET: /ProductsMvcContrib/
   public ActionResult Index()
   {
      var model=this.DataContext.Products;

      return View(model);
   }
}

In this case the Model is quite simple - just the set of products to display. As we saw in previous tutorials, this Model gets a bit more interesting when rendering a grid that supports sorting, paging, or filtering. For now, we'll stick with our simplistic Model, but in future installments we'll have to include a bit more information in our Model to get these additional features to work.

Step 4: Creating the View
To create a View for the Index action, right-click on the method name and choose the Add View menu option. This will bring up the dialog box shown below. Leave the View Name as Index, but check the "Create a strongly-typed view" checkbox and choose the Web.Models.Product class from the drop-down. Check the "Select master page" checkbox if it is not already checked and use the ~/Views/Shared/Site.Master file as the master page.

Add a strongly-typed View for the Index action.
Adding this new View will create a ProductsMvcContrib subfolder and Index.aspx file within your Views folder. Take a moment to inspect the View's @Page directive at the top. The Inherits attribute should specify the View's strongly-typed Model as Web.Models.Product. Because the DataContext.Products property returns a collection of Product objects we need to change this to an enumeration of Web.Models.Product objects. Modify the @Page directive so that it looks like the following:

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<IEnumerable<Web.Models.Product>>" %>

At this point we're ready to construct the markup to be returned by the View. In Displaying a Grid of Data in ASP.NET MVC we did so by typing in the markup for a table, namely the <table> tag and a row of <th> tags for the header row, followed by a foreach loop that emitted a table row with <td> elements for each of the columns. However, in this article we're going to have this markup emitted for us by the MvcContrib Grid.

The MvcContrib Grid is implemented as an HTML Helper method, meaning you'll generate a grid from the View using the syntax:

<: Html.Grid(...).Columns(...) %>

The Html.Grid method returns a string of HTML, which is why we use the <%: ... %> syntax, which emits the string to the response stream. The data to display in the grid is passed into the Grid method. The Columns method defines the columns to render for the grid.

Let's look at a simple example. In our View the Model property is the enumeration of Product objects to display in the grid, so this is what we pass into the Grid method. The Columns method defines those columns to appear in the grid.

<%: Html.Grid(Model).Columns(col=> {
                              col.For(p=> p.ProductName);
                              col.For(p=> p.Category.CategoryName);
                              col.For(p=> p.QuantityPerUnit);
                              col.For(p=> p.UnitPrice);
                              col.For(p=> p.Discontinued);
                           }) %>

The syntax used for the Column method may seem a bit intimidating at first. The grid is interested in working with a collection of GridColumn objects; the Columns method is what constructs this collection. It is passed in a lambda expression that builds the columns using a fluent syntax. In short, each col.For(...) statement adds a GridColumn object to the collection of columns for a particular field in the data being displayed. In our example, the first column displays the value of the Product object's ProductName property, whereas the second displays the Product object's Category object's CategoryName value.

With this HTML Helper syntax in place, visit the page through a browser. As the screen shot below shows, we get a functional grid.

The products are listed in a simple grid.

Keep in mind that this grid was rendered without us having to write a lick of markup - no <table> tags, no <tr> tags, etc. Instead, we just called the Html.Grid method, passed it the data to display and information on what columns to display, and it did the rest. ("The rest," in this case, involves enumerating the data (the list of products) and generating the markup to display a grid in the browser.)

Step 5: Customizing the MvcContrib Grid's Columns
The display of the MvcContrib Grid requires a bit more work in order to get it to look like the grid from the first article in this series. In particular, we need to:

  • Rename the first four header columns to Product, Category, Qty/Unit, and Price.
  • Format the UnitPrice value as a currency.
  • Have the first four columns' texts left-aliged.
  • Display the red X icon for discontinued items (rather than True/False).
As we saw in the previous Step, the grid's columns are defined via a series of col.For(...) method calls. You can make additional method calls for a particular column to specify formatting and display-related settings. These just get tacked on after the For method call. For example, to specify a different name for the column's header cell use the Named method like so:

<%: Html.Grid(Model).Columns(col=> {
                              col.For(p=> p.ProductName)
                                 .Named("Product");
                              col.For(p=> p.Category.CategoryName)
                                 .Named("Category");
                              col.For(p=> p.QuantityPerUnit)
                                 .Named("Qty/Unit");
                              col.For(p=> p.UnitPrice)
                                 .Named("Price");
                              col.For(p=> p.Discontinued);
                           }) %>

To format a cell use the Format(formatSpecifier) method. To format the UnitPrice value as a currency we'd use:

<%: Html.Grid(Model).Columns(col=> {
                              ...
                              col.For(p=> p.UnitPrice)
                                 .Named("Price")
                                 .Format("{0:c}");
                              ...
                           }) %>

Note how the Format method just gets tacked onto the end of the previous method (Named). You can string together any number of the column-related methods in this manner.

Another such method is Attributes, which applies an HTML attribute to the cells in the column. By default, the CSS rules in the demo application center the text in each table cell. There are CSS classes that specify an alternative justification. We can apply these classes to a column using the Attributes method with the following syntax:

<%: Html.Grid(Model).Columns(col=> {
                              col.For(p=> p.ProductName)
                                 .Named("Product")
                                 .Attributes(@class=> "left");;
                              col.For(p=> p.Category.CategoryName)
                                 .Named("Category")
                                 .Attributes(@class=> "left");;
                              col.For(p=> p.QuantityPerUnit)
                                 .Named("Qty/Unit")
                                 .Attributes(@class=> "left");;
                              col.For(p=> p.UnitPrice)
                                 .Named("Price")
                                 .Format("{0:c}")
                                 .Attributes(@class=> "right");;
                              col.For(p=> p.Discontinued);
                           }) %>

The above syntax applies the "left" CSS class to the first three columns and the "right" CSS class to the four column, Price. Note that when specifying the class attribute name we need to preface the name "class" with an at sign (@). This is because the word class is a reserved word in C#; without the @ C# will report a compile-time error.

The final formatting-related task is to replace the Discontinued column's True/False output with an image for discontinued items and an empty string for non-discontinued items. This can be accomplished using C#'s ternary operator to output different markup depending on the product's Discontinued property value.

The following syntax starts by evaluating the expression on the left, p.Discontinued. If it evaluates to True - that is, if Discontinued is true - then the value after the question mark (?) is emitted - <img src="Content/cancel.png"%20/>.%20If%20the%20expression%20%20evaluates%20to%20False%20-%20that%20is,%20if%20Discontinued%20is%20false%20-%20then%20the%20value%20after%20the%20colon%20(:)%20is%20emitted%20-%20string.Empty.%20This%20has%20the%20effect%20of%20rendering%20an%20<img>%20element%20that%20displays%20the%20red%20X%20for%20discontinued%20products%20while%20showing%20an%20empty%20cell%20for%20non-discontinued%20products.

<%: Html.Grid(Model).Columns(col=> {
         ...
         col.For(p=> p.Discontinued ?
                          string.Format(@"<img src=""{0}""%20/>",%20Url.Content("~/Content/cancel.png"))%20                               :%20                          string.Empty%20                 
)%20             .Encode(false)%20             .Named("Discontinued");%20       })%20%>%20
%20

In%20addition%20to%20using%20the%20ternary%20statement%20in%20the%20For%20method%20we%20also%20need%20to%20use%20the%20Encode%20method,%20specifying%20that%20MvcContrib%20Grid%20should%20not%20HTML%20encode%20this%20cell's output. (If you omit the Encode(false) method call then the <img> element markup will be encoded, displaying the literal text <img src="Content/cancel.png"%20/>%20in%20the%20cell.)%20Also,%20by%20using%20a%20custom%20statement%20in%20the%20For%20method%20MvcContrib%20Grid%20cannot%20auto-determine%20the%20header%20column%20name,%20so%20we%20need%20to%20provide%20it%20via%20the%20Named%20method.

With%20these%20modifications%20in%20place%20we%20have%20arrived%20at%20a%20grid%20that%20looks%20identical%20to%20the%20one%20from%20the%20first%20article%20in%20this%20series.%20MvcContrib%20Grid%20has%20allowed%20us%20to%20construct%20such%20a%20grid%20without%20having%20to%20write%20a%20line%20of%20table%20markup.%20Instead,%20this%20markup%20is%20entirely%20generated%20by%20the%20Grid%20HTML%20Helper%20method.

%20 The products are listed in a simple grid.

Looking Forward...
MvcContrib Grid provides a markup-free way of displaying a grid of data in an ASP.NET MVC application. While there is a bit of a learning curve with MvcContrib Grid, once you familiarize yourself with the syntax I wager you'll find it an efficient tool for displaying grids. Another benefit of the MvcContrib Grid is that it has baked in support for paging and supporting. In the next article in this series we'll see how to enable this functionality.

Until then... Happy Programming!

Click here