Nov 15 2010

Defining MVC Controls 3: Datagrid, Sorting, and Master-Detail Views

Category: Asp.net | Entity Framework | MVCFrancesco @ 00:15

Defining MVC Controls 1

Mvc Controls Toolkit Datagrid Updated Tutorial

Advanced Data Filtering Techniques in the Mvc Controls Toolkit

I am back with the second tutorial about using the Datagrid of the  MVC Controls Toolkit . We are going to modify the the example of my previous post to include sorting on a mouse click on the columns.

The code of this tutorial is containe in the download page of MVC Controls Toolkit, that is here and is named BasicTutorialsCode.

As a first step we need a new parameter in our View Model to exchange sorting information with the View:

public List<KeyValuePair<LambdaExpression, OrderType>> ToDoOrder { get; set; }

The toolkit datagrid is able to exchange directly the lambda expressions to be used in the Linq queries with the Controller. Thus, we use a list of couples Lambda Expression OrderType. Where OrderType is an enumeration that specifies ascending or descending order. The whole list specifies a lexicographic sorting that is possibly based on several columns.

MVC Controls Toolkit defines extension methods to apply directly the above list either to  an IEnumerable or to an IQueryable.It is just to include the namespace: MVCControlsToolkit.Linq and any IEnumerable or IQueryable will be enriched with the method ApplyOrder that accepts the above list as argument. If an ordering is already defined on either the IEnumerable or the IQueryable the new sorting will be chained Lexicographically with it.  

Now our paged search query becomes:

result = context.ToDo.Select(item =>
                    new ToDoView() { Name = item.Name, Description = item.Description, DueDate = item.DueDate, id = item.id }).ApplyOrder(order).Select(viewItem =>
                    new Tracker<ToDoView>
                            {
                                Value = viewItem,
                                OldValue = viewItem,
                                Changed = false
                            }).Skip(toSkip).Take(pageDim).ToList();

It is worth to point out that the sorting information comes from the client, therefore a malicious user might try a denial of service attack by sending a manipulated request of sorting on a column that is too  difficult to sort (a column with no index defined on it, for instance). In order to defend ourselves from a similar attack the Transformation Handler that receives the data from the DataGrid automatically discard columns that are not decorated with the CanSortAttribute that is defined in the MVCControlsToolkit.DataAnnotations namespace. In our case we have the following Data annotations:

        [Required, CanSort, Display(Name="Name")]
        public object Name { get; set; }
        [Required, Display(ShortName = "Description")]
        public object Description { get; set; }
        [CanSort, Display(ShortName = "Due Date"), Format(DataFormatString="{0:D}")]
        public object DueDate { get; set; }

The DisplayAttribute is a standard .Net. Our DataGrid uses ShortName or Name (if ShortName is not specified) for the header of both the unsortable and the sortable columns.The FormatAttribute  inherits from the standard .Net DisplayAttribute and extends it with some properties that we will use in the 0.8 release of the MVC Controls Toolkit, where we will define typed input fields. At moment it has the same behavior of its parent. In this example we use it to specify a nice format for the date.

Now our datagrid template becomes:

<table class="ToDo" >
<tr>
<td class="ToDoHeader"><strong><%:Html.SortButtonFor(m => m.Name, sortButtonStyle: SortButtonStyle.Button) %></strong></td>
<td class="ToDoHeader"><strong><%:Html.SortButtonFor(m => m.DueDate, sortButtonStyle: SortButtonStyle.Button) %></strong></td>
<td class="ToDoHeader"><strong><%: Html.ColumnNameFor(m => m.Description) %></strong></td>
<td class="ToDoHeader"><strong></strong></td>
<td class="ToDoHeader"><strong></strong></td>
</tr>
<%:ViewData["Content"] as MvcHtmlString %>
</table>

Where we can see the use of the sort buttons, and of the ColumnNameFor helper. As default, sort buttons do not  cause post-backs, however they have a parameter to require immediate post-back. Personally I prefer avoiding immediate post-back because this way I can set up my complete lexicographic sorting and only then I require a post-back to update my grid. In any case if there is a pager operating on the grid the change of the sorting causes the page to be reset to the first page automatically.

The up and down arrows of the sort buttons are defined in CSS classes specified in the sorting helper:

  <%:Html.DataGridFor(m => m.ToDoList, ItemContainerType.tr,  "ToDoEditItem",  "ToDoDisplayItem", "ToDoGrid", "ToDoInsertItem")%>
    </div>
    <div class="ToDoPager">
                    <% var pager = Html.PagerFor(m => m.CurrPage, m => m.PrevPage, m => m.TotalPages); %>
                    <%:pager.PageButton("<<", PageButtonType.First, PageButtonStyle.Link) %>
                    <%:pager.PageButton("<", PageButtonType.Previous, PageButtonStyle.Link) %>
                    <%:pager.PageChoice(5) %>
                    <%:pager.PageButton(">", PageButtonType.Next, PageButtonStyle.Link) %>
                    <%:pager.PageButton(">>", PageButtonType.Last, PageButtonStyle.Link) %>
                </div>
    <div>
    <input type="submit" value="Save" />
    <%:Html.ManipulationButton(ManipulationButtonType.ResetGrid, "Reset", m => m.ToDoList, null, ManipulationButtonStyle.Button) %>
    <%: Html.EnableSortingFor(m => m.ToDoList, m => m.ToDoOrder, "NormalHeaderToDo", "AscendingHeaderToDo", "DescendingHeaderToDo", page: m => m.CurrPage) %>
    <%:Html.HiddenFor(m => m.TotalPages) %>
To do its job the EnableSortingFor helper needs the collection, the sorting property and also the page property. As the sort behavior of the column changes, the three CSS classes specified in the EnableSortingFor helper are applied to the buttons in order to change their look.

Another new feature is the ManipulationButtonType.ResetGrid that undos all changes done to the datagrid and not yet committed. Here commitment means commitment to a database or to any other structure, not simply a post-back: the grid remembers its previous values through post-backs! We declare that commitment took place either by calling the Confirm method of a Tracker<Item> associated to a row or by simply reloading the grid with fresh data.

In order to undo a single row delete we have introduced the undelete data button and a template to substitute deleted rows. The undelete is easy: just put the undelete button in this template! However I prefer seeing my rows disappear completely! Therefore I have not used this feature in this example (no panic….in case of errors we have the reset button..).

The edit row template has not changed from our previous tutorial, but there are some changes in the display row template:

            <td class="ToDo">
                <%: Html.ValidationMessageFor(m => m.Name, "*")%><%: Html.DisplayField(m => m.Name) %>
            </td>
            <td class="editor-field">
                <%: Html.ValidationMessageFor(m => m.DueDate, "*")%><%: Html.DisplayField(m => m.DueDate)%>
            </td>
            <td class="ToDo">
                <%: Html.ValidationMessageFor(m => m.Description, "*")%><%:  Html.DisplayField(m => m.Description)%>
            </td>
            <td class="ToDoTool">
                <%:Html.DetailLink(Ajax, "Edit Details", DetailType.Edit, "ToDoSubTasks", "Home",
                    new { 
                        id = Model.id},
                                        null)%>
                <%: Html.ImgDataButton(DataButtonType.Edit, "../../Content/edit.jpg", null)%>
                
            </td>

The use of the DisplayField helper to display not editable fields not only ensures right formatting as the use of the DisplayFor standard Mvc helper, but also it enables fields representing the same data be synchronized automatically in the view! In particular it enables detail data retrieved via Ajax not only to display in a separate form but also to update the corresponding data in the master grid.

We have just introduced our next feature: the Master-Detail Ajax helper. Next to the Edit button that performs on line editing, there is also a Detail Link helper. In order to do its job this helper needs the Ajax object of the View, a name to put on the link, the name of an Action method returning a Partial View, the name of the associated Controller, and the route parameters. Moreover we need to specify also if the detail form we are going to render is a display or an edit form.

The Partial View returned by the Action method must contain neither an Ajax  nor a standard form: an Ajax form is already supplied by the Master Detail helper(this way we enhance re-usability because the same Partial View can be used with both Ajax and normal child calls):

<% Html.DetailFormFor(Ajax, m => m.ToDoList, ExternalContainerType.div,
           "ToDoSubTasks", "Home", null, "isChangedToDo", "isDeletedToDo");%>

The arguments passed to the helper are: the View Ajax object, the Master collection, the type of container where we woluld like to receive the Ajax content, an Action method and  a Controller to receive the post, some Html attributes and two CSS classes. The first class (in our case “isChangedToDo") is applied to the display data-field(the ones defined with the DisplayField helper) of the Master grid when they are changed because of the synchronization between detail form and Master data. The edit field of the grid are not updated because they contains new data provided by the user that this way can decide if continuing with  the change in the Master grid or if accepting the changes provided by the Ajax call.

Please note that the DetailFormHelper needs to be put out of the main form, don’t forget it!!!

The grid accepst also another optional row template to be used with rows that are updated because of the synchronization between detail and master View.

The second CSS class (in our case “isDeletedToDo")  is applied to a whole row when a data item retrieved by the Ajax call is discovered to be already deleted. If the user updates a deleted row a new row is created. Obviously the final choice is left to the Controller that may decide to abort the changes in a similar case.

In order to enhance re-usability of controllers (in particular of child controllers that returns Partial Views) we defined the [AcceptViewHint] Action Filters that enables a father view to require an action method of a controller uses a particular Partial View, in a way that is analogous to a client requiring that a Web Service uses a specific protocol or a client selecting a specific endpoint. The Partial View Hint is transmitted in either a Route parameter or a post parameter whose name is ViewHint.The name passed to the filter is scanned for acceptable length and may contain only alphanumeric characters in order to prevent attacks from malicious users.

I will not go into the code of the Detail View of the example that is completely analogous to the one of the Main View that we already discussed in detail in my previous post. The only things worth to note is the use of the SortableListFor helper to allows editing of the list of subtasks associated with a ToDo item. The SortableListFor helper allows delettion/in place editing/insertion of subtasks and allows also sorting of the subtasks by simply dragging them with the mouse .  The SortableListFor uses JQuery Sortable UI element, but transferring the order from the DOM into a collection of complex type on the server side….is not an easy task to accomplish.

Below the use of the helper:

<%: Html.SortableListFor(m => m.SubTasks, "SubTasksToSort", "SubTasksToSortAddItem", 0.7f,
                                        htmlAttributesContainer: new Dictionary<string, object> { { "class", "SortableList" } }
                    )%>

0.7f is the opacity used by the JQuery Sortable, the first argument is the collection to operate on, while “SubTasksToSort” is the Item template and “SubTasksToSortAddItem” is the template for inserting a new element.

Below the Item template:

<div id='<%: Html.PrefixedId("InnerContainer") %>'>
<%: Html.TextBoxFor(m => m.Name, new Dictionary<string, object> { { "class", "ToDoDetailName" } })%><%: Html.ValidationMessageFor(m => m.Name, "*") %>
<%: Html.TextBoxFor(m => m.WorkingDays, new Dictionary<string, object> {{"class", "ToDoDetailDuration"}})%><%: Html.ValidationMessageFor(m => m.WorkingDays, "*") %>
<%: Html.SortableListDeleteButton("Delete",  ManipulationButtonStyle.Link) %>
</div>

As one can see the SortableListFor helper has it own delete buttons.

Finally the Add Item template:

<div >
<%: Html.SortableListAddButton("Insert New", ManipulationButtonStyle.Link)%>
<span id='<%: Html.SortableListNewName() %>' style="visibility:hidden">
<%: Html.TextBoxFor(m => m.Name, new Dictionary<string, object> { { "class", "ToDoDetailName" } })%><%: Html.ValidationMessageFor(m => m.Name, "*") %>
<%: Html.TextBoxFor(m => m.WorkingDays, new Dictionary<string, object> {{"class", "ToDoDetailDuration"}})%><%: Html.ValidationMessageFor(m => m.WorkingDays, "*") %>
<%: Html.SortableListDeleteButton("Delete",  ManipulationButtonStyle.Link) %>
</span>
</div>

The Add Button of the SortableListFor needs a target container that it will make visible when it is pressed. We can specify it by giving the id returned by the helper

 Html.SortableListNewName() to a span, div or other container.

At moment we have finished……Next time we will speak about typed input fields and about the advanced filtering capabilities of the 0.8 release of the MVC Controls Toolkit.

                                          Stay Tuned……

                                           Francesco

For more information or consulences feel free to contact me,

Tags: , , , , ,

Oct 30 2010

Defining MVC Controls 2: Using the DataGrid

Category: MVC | Entity Framework | Asp.netFrancesco @ 00:46

See a more recent version of this tutorial: Mvc Controls Toolkit Datagrid Updated Tutorial

Defining MVC Controls 1

Defining MVC Controls 3: Datagrid, Sorting, and Master-Detail Views

Data Filtering, in the New Mvc 3 Version of the Mvc Controls Toolkit

This post is a tutorial un how to use the Update/Delete/Insert Templated datagrid of the MVC Controls Toolkit. You can download the full code used in this tutorial here (the package is named BasicTutorialsCode and it contains also the database)..

Our Data model is simply a Todo List with three fields: Name(varchar), Description(varchar) and DueDate(date), plus the principal key id of type identity. As first step we define the Database, and a table named ToDo  with the previous four fields.

Then, we create an entity framework model based on the database just created: I called it MeetingsModel because in a future post I will extend it with the management of a meeting room.

Now it is time to create a MetaClass to handle the constraints on our ToDo items. Let say that the Name and Description fields are required (i.e they can’t be null), while for the date we have a strange constraints: the DueDate can range from 1 month before today to 6 months after today. Since today is not a definite value such constraints can not be interpreted as a Data constraints, thus its natural place is not together with the other data constraints of the data layer: we will take care of it in a few minutes! First let take care of the the other simpler constraints, and add to our project a new file named Todo.cs just under the Models folder. What we put in this file is quite standard:


using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel.DataAnnotations;
using MVCControlsToolkit.DataAnnotations;

namespace Mvc_Examples.Models
{
    [MetadataType(typeof(MetaToDo))]
    public partial class ToDo
    {

    }
    public class MetaToDo
    {
        [Required]
        public object Name { get; set; }
        [Required]
        public object Description { get; set; }
        
    }
}

Now let go to the View Model! What we need to put into the View Model? For sure the ToDo items extracted from the database. However, we would like the items be paged, so we have to insert at least a Page property containing the current page. A single Page int is enough for the MVC Controls Toolkit pager to work, but for a better paging experience it is better to supply also the total number of pages. It is ok, our pager control handle also this information, if available.

Now…the user might edit some fields and then he might change page. What does we do if there are validation errors in its editing? The better thing to do is to go back to the previous page and force him to correct the errors. Therefore, we have to remember what page we come from. Thus we need also a previous page property in our View Model. Luckily we don’t need to put it in a hidden field because our pager already offers  this type of service when a “previous page” property is available.

Summing Up, our View Model needs the following properties: CurrPage, PrevPage, and TotalPages, plus a ToDoList property containing all ToDo items.

Now we have to decide how to handle the strange constraint on the DueDate. One way to do it is to define a View Model version of the ToDo class where we can apply it. In this case we might solve in a simpler way but I prefer to be more general:

using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel.DataAnnotations;
using MVCControlsToolkit.DataAnnotations;
 
namespace Mvc_Examples.Models
{
    [MetadataType(typeof(MetaToDo))]
    public partial class ToDoView
    {
        public int? id { get; set; }
 
        public string Name { get; set; }
 
        public string Description { set; get; }
 
        [DateRange(DynamicMinimum="MinDate", DynamicMaximum="MaxDate")]
        public DateTime DueDate {set;get;}
 
        [MileStone]
        public DateTime MaxDate
        {
            get 
            {
                return DateTime.Today.AddMonths(6);
            }
            set 
            {
            }
        }
 
        [MileStone]
        public DateTime MinDate
        {
            get 
            {
                return DateTime.Today.AddMonths(-1);
            }
            set 
            {
            }
        
        
        }
 
    }
}
 

Please notice that we have used the same MetaData class of the original ToDo class. This is important in order to avoid code duplications! As a general rule when building a View Model one has to use either the original data classes, or for each data class we define a modified version and use it as a son of the View Model: this way, we can use the same metaclasses. Please avoid putting fields from more than one data class into a single View Model Class. This can be done only if data are someway aggregated and transformed into other properties, otherwise it is better to keep a one to one correspondence between data classes and classes used in the View Model: this way we increase modularity and we can reuse the metadata classes of the original data classes(duplicating code always causes a lot of problems…).

We have defined two new properties MinData and MaxData that are always equal to Today minus one month and Today plus 6 months: they define the range of possible values for the DueDate property.

The Milestone attribute that decorates these two properties is defined in the MVCControlsToolkit.DataAnnotations namespace and it do exactly nothing…

It is just an hint for the View not to put these properties  into input fields  because their only purpose in the View is giving help to the user: the view designer can use them to display a message for the user, for example.

Our constraint on the DueDate is enforced by using the DateRange attribute defined in the MVCControlsToolkit.DataAnnotations namespace. This attribute act very similarly to the normal Range validation attribute, but accepts also dynamic minimum and maximum, that are essentially the names of the properties containing the actual minimum and maximum.

Next step is the design of the data access procedures. It is not a good idea to insert them into the controller methods, because they might be useful to more than one controller, and because it is always better to keep the data layer separate from the controllers layer. We will put them as static methods of our View Model that this way becomes a kind of  a standard for accessing ToDo items(we will use it always when paged ToDo items are needed). This choice is acceptable for this application, but in different situations we might have introduced a new class.

Therefore our View Model now contains the properties we discussed before:

        public int TotalPages { get; set; }
        public int CurrPage { get; set; }
        public int PrevPage { get; set; }
        public List<Tracker<ToDoView>> ToDoList {get; set;}

Plus two static methods, say GetToDoPage and UpdatePage, respectively for retrieving one page of data, and for passing to the database the updates made by the user to one page of data.

In order to help passing changes to the database the MVC Controls Toolkit furnishes the class Tacker<T> that is a wrapper to put around a data item. It maintains two versions of the data item, the original version and the one with the changes applied by the user. Moreover, it has also a Boolean value Changed to signal that the two versions are different.

Comparison between the two versions yields easily the operation to be done on the database:

  • Old version null and new version different from null: we have an insertion
  • Old version different from null and new version null: we have a deletion
  • Both versions different from null: we have an update

After this short premise let go analyze the GetToDoPage method:

        public static List<Tracker<ToDoView>> GetToDoPage(int pageDim, out int totalPages, int page = 1) 
        {
            List<Tracker<ToDoView>> result;
            using (SiteDbEntities context = new SiteDbEntities())
            {
                int rowCount = context.ToDo.Count();
                if (rowCount == 0)
                {
                    totalPages=0;
                    return new List<Tracker<ToDoView>>();
                }
                totalPages = rowCount / pageDim;
                if (rowCount % pageDim > 0) totalPages++;
                int toSkip = (page-1) * pageDim;
                result = (from item in context.ToDo
                          orderby item.DueDate ascending
                          select new Tracker<ToDoView>()
                          {
                           Value = new ToDoView() { Name = item.Name, Description = item.Description, DueDate = item.DueDate, id = item.id },
                           OldValue = new ToDoView() { Name = item.Name, Description = item.Description, DueDate = item.DueDate, id=item.id }, 
                          Changed=false }).Skip(toSkip).Take(pageDim).ToList();
            }
            return result;
        }

The result is of type  List<Tracker<ToDoView>> since we have used our wrapper around each data item. We create a context, with the using keyword to be sure it will be disposed at the end of the operation since it contains a connection to the database that is not a managed object.

As first operation we counts the total rows, then we do some mathematics to compute the pages: the remainder operation % is needed to take into account partially filled pages.In order to reach the request page we use the Skip and Take methods. In the select construct we create also our wrapper and fill it with two copies of the same data item, that will become the previous and actual version of the data item. Please notice also that we transfer data from the ToDo object into a fresh ToDoView object.

The UpdatePage method is a little bit more complex, but quite easy, too:

        public static void UpdatePage(List<Tracker<ToDoView>> items)
        {
            using (SiteDbEntities context = new SiteDbEntities())
            {
                bool aChange = false;
                foreach (Tracker<ToDoView> item in items)
                {
                    if (item.Changed)
                    {
                        if (item.OldValue == null) //insertion
                        {
                            if (item.Value != null)
                            {
                                ToDo curr=new ToDo() 
                                         { Name = item.Value.Name, Description = item.Value.Description, DueDate = item.Value.DueDate };
                                aChange = true;
                                context.ToDo.AddObject(curr);
                            }
                        }
                        else if (item.Value == null) //deletion
                        {
                            ToDo curr=new ToDo() { Name = item.OldValue.Name, Description = item.OldValue.Description, DueDate = item.OldValue.DueDate, id=item.OldValue.id.Value };
                            context.ToDo.Attach(curr);
                            context.ObjectStateManager.ChangeObjectState(curr, System.Data.EntityState.Deleted);
                            aChange = true;
                        }
                        else//update
                        {
                            ToDo curr = new ToDo() { Name = item.Value.Name, Description = item.Value.Description, DueDate = item.Value.DueDate, id=item.Value.id.Value };
                            context.ToDo.Attach(curr);
                            context.ObjectStateManager.ChangeObjectState(curr, System.Data.EntityState.Modified);
                            aChange = true;
                        }
                    }
                }
                if (aChange)
                {
                    try
                    {
                        context.SaveChanges();
                        items.ForEach((item) => { item.Confirm(); });//confirm changes have been passed
                    }
                    catch
                    {
                    }
                }
            }
        }
                    

We have a loop an all modified items where we verify if the item has changed, and if changed we analyze what operation needs to be done on the database, as previously.explained. Please notice the use of the ObjectStateManger to set the correct state of the various objects in the different cases.  The case of the insertion is the only one that doesn’t require to set manually the state of the object.

If at least a data item has changed we do a SubmitChanges() that passes all changes in a single transaction to the database. In  case no exception is fired we call the Confirm() method of the Tracker<T> wrapper  that sets the old version equal to the new version of the data item since all changes has been passed to the database.In case of exceptions an error message should be returned to the controller that need to say to the user to retry the post in a few minutes….however we have not handled this for sake of simplicity(a too complex example doesn’t help the understanding).

Now we can go now to the controller that has just two Action methods, one for handling the initial get and the second for handling the subsequent posts:       

         public const int PageDim=5;//into an actual application this should be put in a config file
        public ActionResult Index()
        {
            int totalPages;
            ToDoViewModel result = new ToDoViewModel()
                {
                    ToDoList = ToDoViewModel.GetToDoPage(PageDim, out totalPages),
                    TotalPages = totalPages,
                    CurrPage=1,
                    PrevPage=1
                };
            return View(result);
        }

        [HttpPost]     
        public ActionResult Index(ToDoViewModel model)
        {
            if (!ModelState.IsValid)
            {
                
                model.CurrPage=model.PrevPage; //cancel possible page change and force correcting errors
                return View(model);
            }
            else
            {
                ToDoViewModel.UpdatePage(model.ToDoList);
                int totalPages;
                ToDoViewModel result = new ToDoViewModel()
                {
                    ToDoList = ToDoViewModel.GetToDoPage(PageDim, out totalPages, model.CurrPage),
                    TotalPages = totalPages,
                    CurrPage = model.CurrPage,
                    PrevPage = model.CurrPage
                };
                return View(result);
            }
        }

 

The first method just displays the first page and it is quite trivial. The second method just controls if there are validation errors. In case there are it cancels a possible page change by resetting the current page to the previous page, and then returns the same View Model it received in order to let the user correct the errors. In case everything is ok it passes the changes to the database, and retrieve the new page requested by the user. That’s all!

We finally arrived to the datagrid. In order to have the datagrid working we need to prepare 4 templates:

  1. ToDoGrid: it displays the general container where all data items will be inserted. In our case it is just a table.
  2. ToDoDisplayItem: it displays a row of data when the grid is in display mode
  3. ToDoEditItem: it displays a row of data when the grid is in edit mode
  4. ToDoInsertItem: it defines the look of the insert new row component, normally it just displays an insert button.

Well, Let go see in detail each single template.

ToDoGrid
<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<Mvc_Examples.Models.ToDoView>" %>

<table class="ToDo" >

<tr>
<td class="ToDoHeader"><strong>Name</strong></td>
<td class="ToDoHeader"><strong>Due Date</strong></td>
<td class="ToDoHeader"><strong>Description</strong></td>
<td class="ToDoHeader"><strong></strong></td>
<td class="ToDoHeader"><strong></strong></td>
</tr>
<%:ViewData["Content"] as MvcHtmlString %>
</table>

It just displays the <table> tags and the header of the table. The <%:ViewData["Content"] as MvcHtmlString %> construct defines where all data items have to be inserted. It is a kind of placeholder and it needs to be inserted “as it is” in any template that describes a datagrid container.

It is worth to point out that this template is passed an empty data item object to help the automatic construction of the container. Specifically, we can use reflection to extract all columns and we can also automatically decide some facts about the look of the container by extracting the attributes of each data item property (as for instance the display attribute).

ToDoDisplayItem
          <%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<Mvc_Examples.Models.ToDoView>" %>
          <%@ Import Namespace=" MVCControlsToolkit.Core" %>
          <%@ Import Namespace=" MVCControlsToolkit.Controls" %>
          
                      <td class="ToDo">
                          <%: Html.ValidationMessageFor(m => m.Name, "*")%><%: Model.Name %>
                      </td>
                      <td class="editor-field">
                          <%: Html.ValidationMessageFor(m => m.DueDate, "*")%><%: Model.DueDate.ToString("D")%>
                      </td>
                      <td class="ToDo">
                          <%: Html.ValidationMessageFor(m => m.Description, "*")%><%:  Model.Description %>
                      </td>
                      <td class="ToDoTool">
                          <%: Html.ImgDataButton(DataButtonType.Edit, "../../Content/edit.jpg", null)%>
                      </td>
                      <td class="ToDoTool">
                          <%: Html.ImgDataButton(DataButtonType.Delete, "../../Content/delete.jpg", null)%>

          The display item View Model is just a data item, it is not a Tracker<T> wrapper. The wrapper is handled automatically by the DataGrid.

          We don’t need to put the <tr> tag in each data item: the container tag for each item is defined in the DataGrid helper and it is automatically inserted by the DataGrid. We have also the option to supply a delegate that returns a different item container as a function of the data item and of its position in the DataGrid. 

          The last thing worth to discuss are the two data buttons: the first one switches the row in edit mode, while the second one just delete the row. I used here an image button but there are also link and normal buttons.

          ToDoEditItem
          <%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<Mvc_Examples.Models.ToDoView>" %>
          <%@ Import Namespace=" MVCControlsToolkit.Core" %>
          <%@ Import Namespace=" MVCControlsToolkit.Controls" %>
                      
                      <td class="ToDo">
                          <%: Html.ValidationMessageFor(m => m.Name, "*")%><%: Html.TextBoxFor(m => m.Name) %>
                      </td>
                      <td class="ToDo">
                          <%: Html.ValidationMessageFor(m => m.DueDate, "*")%><%: Html.DateTimeFor(m => m.DueDate, DateTime.Today).Date()%>
                      </td>
                      <td class="ToDo">
                          <%: Html.ValidationMessageFor(m => m.Description, "*")%><%: Html.TextBoxFor(m => m.Description) %>
                      </td>
                      <td class="ToDoTool" colspan="2">
                          <%: Html.HiddenFor(m => m.id) %>
                          <%: Html.ImgDataButton(DataButtonType.Cancel, "../../Content/undo.jpg", null)%>
                      </td>

          The edit template is completely analogous to the display template. The only difference being that it contains input field to allow user to edit fields.

          Please, notice the Hidden filed containing the key! It is necessary! Here we have a cancel button that undo all changes done to the data item and put the row in display mode.

          Last thing worth to point out is the DateTimeFor helper that is able to take as input, date, time or date and time. It is able to read the DateRange attribute and to enforce its constraints. This means the user is allowed to insert only dates that conforms with the constraints. More information about the DateTimeFor Helper can be found here.

          ToDoInsertItem
          %@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<Mvc_Examples.Models.ToDoView>" %>
          <%@ Import Namespace=" MVCControlsToolkit.Core" %>
          <%@ Import Namespace=" MVCControlsToolkit.Controls" %>
          
          <td colspan="5" class="ToDo"><%: Html.ImgDataButton(DataButtonType.Insert, "../../Content/add.jpg", null)%></td>
          The insert template just contains an insert button that when clicked causes a new row to appear in edit mode.
          The Datagrid

          Once we have all templates defined we just need to use the datagrid and pager helpers:

          <div>
              <%:Html.DataGridFor(m => m.ToDoList, ItemContainerType.tr,  "ToDoEditItem",  "ToDoDisplayItem", "ToDoGrid", "ToDoInsertItem")%>
              </div>
              <div class="ToDoPager">
                              <% var pager = Html.PagerFor(m => m.CurrPage, m => m.PrevPage, m => m.TotalPages); %>
                              <%:pager.PageButton("<<", PageButtonType.First, PageButtonStyle.Link) %>
                              <%:pager.PageButton("<", PageButtonType.Previous, PageButtonStyle.Link) %>
                              <%:pager.PageChoice(5) %>
                              <%:pager.PageButton(">", PageButtonType.Next, PageButtonStyle.Link) %>
                              <%:pager.PageButton(">>", PageButtonType.Last, PageButtonStyle.Link) %>
                          </div>
              <div>
              <input type="submit" value="Save" />
              <%:Html.HiddenFor(m => m.TotalPages) %>

          The first argument of the helper is the definition of the property to display in the DataGrid, as usual, the second defines the item container, and the last ones are the names of the templates defined before.

          There are also optional arguments to define html attributes(also as a function of the data item) and to pass a function to change dynamically the item container.

          The pager is composed of various parts that can be also used separately. Here I used link buttons but one can use also image or normal buttons.

          Well we have finished this short adventure,,,,

          Next post will be about the new features of the new release of the datagrid:, sorting, master-detail helpers…….

                                                                  Stay Tuned !

                                                                  Francesco

          For more information or consulences feel free to contact me

          Tags: , , , , , ,

          Sep 25 2010

          Defining MVC Controls 1

          Category: MVCfrancesco @ 22:35

          Defining MVC Controls 2: Using the DataGrid

          Defining MVC Controls 3: Datagrid, Sorting, and Master-Detail Views

          Data Filtering, in the New Mvc 3 Version of the Mvc Controls Toolkit

          This is the first of a series of posts where I will describe the features of the MVCControlsToolkit Library for MVC that can be found here. The MVC Controls Toolkit contains some Controls for MVC, and a toolset for defining easily new controls. In this first post I describe the needs that lead me to develop the MVCControlsToolkit, and its foundations.

          One of the main foundational principles of MVC is the separation of concerns between the Controller that specifies what to show and the View that specifies How to show it. In the View you have access to all data that the Controller want to display through the View Model, and you can take advantage of the full power of the .Net framework to manipulate the data contained in the View Model to give to your web page the look that you prefer and also a structure that is quite different from the way data are organized in the View Model. For instance you can decide to display a date as 3 separate inputs, one for the Year, one for the Month and the other for the Day, instead of using a single TextBox for the whole date. Unluckily, if you do this the Data Binder will not be able to recompose this three separate fields into the original DateTime object when the View is posted back.

          From one side you have the maximum freedom in rendering the View Model in the View and you can define helper methods to handle complex rendering tasks, but from the other side if there isn’t a one to one correspondence between the elements to post back in the model and the properties of the View Model, and a one to one correspondence between the hierarchical structure of the input fields names and the hierarchical structure of the model, the Model Binder isn’t able to reconstruct the View Model from the data posted by the View. Clearly this is not a design error but a conceptual problem: the Model Binder  has simply no information about how map one structure into the other!

          The example of the date can be solved by defining a custom Model Binder for the DateTime type. There, you can put the information on how to recompose the three separate fields into the original DateTime. However, this will be a poor choice for the following reasons:

          1. All DateTime properties will be mapped in the same way! We have not gained freedom in the Map between the View and the View Model! We have simply moved from a standard map to another standard map! The way a property is displayed may depend on the context, so we need the freedom to specify dynamically this map for each instance of a type.
          2. The right place where to specify information about this map is the same place where we defined what map to use: the View! Not the Global.asax where we add the custom Model Binders.
          3. Conceptually the only information that is needed to reconstruct the View Model is the way the two structures are mapped one into the other. By contrast to write a model binder we are required to handle MVC specific data structure. This means two things:
            • More code to write
            • Difficulties in the testing because of the dependencies from complex data structures that needs to be simulated by the test classes

          Conceptually, the only way to solve the above problems is by putting references to the descriptions of the maps used in the Html pages. This way, we can define dynamically the maps when we render the involved elements in the View.

          The MVC Controls Toolkit uses a default Model Binder that is able to retrieve this map references and to use them to rebuild the View Model. The programmer is only required to write the code that furnishes the minimum information possible for the reconstruction of the model: the map!

          The map is defined by providing an implementation of the  IDisplayModel interface:

          
          namespace MVCControlsToolkit.Core
          {
              public interface IDisplayModel:ISafeCreation
              {
                  object ExportToModel(Type TargetType, params object[] context);
                  void ImportFromModel(object model, params object[] context);
                  
              }
          }

          The ImportFromModel method is called by the method that renders the control. The original data structure to be transformed is passed in the model parameter.

          The ExportToModel function, instead, is called by the Data Binder to reconstruct the original data structure after that the class implementing the interface has been filled with the data extracted from the View that was posted back.

          The programmer may invoke the transformation defined by the interface implementation with the use of the InvokeDisplay helper method. The class returned by this method invocation can be chained with another transformation by calling another overload of the InvokeDispaly helper that accepts as parameters the newly created class and the interface implementation of the other transformation. At the end the transformed data are rendered into a template by passing them to the RenderIn helper.

          The use of the InvokeDisplay helper ensure that the inverse transformation will be applied by the Model Binder when the View is posted back.

          Let go see the details of the process described above with the example of the DateTime field that we already referenced before. Obviously, you don’t need to actually implement a DateTime control because the MVCControlsToolkit already comes with a sophisticate DateTimeInput control. This is just a simple example to help the understanding of the concepts exposed.

          In this case a possible simple implementation of the IDisplayModel interface is:

          public class TestDateTimeDisplay : IDisplayModel
          {
           
                  [Range(1000, 3000, ErrorMessage = "wrong year")]
                  
                  public Nullable<int> Year { get; set; }
          
                  [Range(1, 12, ErrorMessage = "wrong month")]
                  public Nullable<int> Month { get; set; }
          
                  [Range(1, 31, ErrorMessage = "wrong day")]
                  public Nullable<int> Day { get; set; }
          
                  public object ExportToModel(Type targetType, params object[] context)
                  {
                      if (Year == 0 && Month == 0 && Day == 0) return null;
                      if (!Year.HasValue && !Month.HasValue && !Day.HasValue) return null;
                      try
                      {
                          return new DateTime(Year.Value, Month.Value, Day.Value);
                      }
                      catch (Exception ex)
                      {
                          throw (new Exception(" {0} has an incorrect date format", ex));
                          
                      }
                  }
          
                  public void ImportFromModel(object model, params object[] context)
                  {
                      Nullable<DateTime> date = model as Nullable<DateTime>;
                      if(date.HasValue && date.Value != DateTime.MinValue)
                      {
                          Year = date.Value.Year;
                          Month = date.Value.Month;
                          Day = date.Value.Day;
                      }
          
                  }
              }

          The call to the ImportFromModel  call is used to fill the values of the of the Year, Month and Day properties from the original DateTime. This three properties will be rendered instead of the original DateTime property. When the View is posted back, the Model Binder first fill the Year, Month and Day properties with its normal algorithm for extracting a model from the post back data and then calls the ExportToModel method of the interface to recover the initial DateTime property. 

          The exception thrown when the date format is wrong is intercepted by the Model Binder and its message is used to produce a validation error associate to the initial DateTime property.

          The code to put in the View for rendering our control is;

          <%: Html.RenderIn("YearMonthDayDate", Html.InvokeTransform(m => m.BirthDate, new TestDateTimeDisplay()))  %>

          YearMonthDayDate is a name of a strongly typed template for the type TestDateTimeDisplay.

          In order to test this example and to do other experiments you can use a generic template for rendering classes, like this one:

          <%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<dynamic>" %>
          <table cellpadding="0" cellspacing="0" border="0">
              <% foreach (var prop in ViewData.ModelMetadata.Properties.Where(pm => pm.ShowForEdit && !ViewData.TemplateInfo.Visited(pm))) { %>
                  <% if (prop.HideSurroundingHtml) { %>
                      <%= Html.Editor(prop.PropertyName) %>
                  <% } else { %>
                      <tr>
                          <td>
                              <div class="editor-label" style="text-align: right;">
                                  <%= prop.IsRequired ? "*" : "" %>
                                  <%= Html.Label(prop.PropertyName) %>
                              </div>
                          </td>
                          <td>
                              <div class="editor-field">
                                  <%= Html.Editor(prop.PropertyName) %>
                                  <%= Html.ValidationMessage(prop.PropertyName, "*") %>
                              </div>
                          </td>
                      </tr>
                  <% } %>
              <% } %>
              </table>

          The template can be put either on the Shared folder or in the folder for a specific controller. However if you put it either within a Display or Edit folder the template will not be retrieved,

          In order to test the example you need just:

          1. Download the MVCControlsToolkit from the CodePlex site here
          2. Create a new MVC 2 project
          3. Add the interface implementation to the project
          4. Add the above template or a specific template as explained above
          5. Add a DateTime property to one of the View Models of the predefined pages that you find into a newly created project. I have chosen the RegisterModel model in the AccountModels.cs file, that is rendered into the Register.aspx View.
          6. Add the code for rendering the control into the adequate View, in my case the Register.aspx View. Add also a reference to the namespace where you defined your interface implementation.
          7. Put a breakpoint in the controller method that handles the post back of the View. In my case, the Register method of the AccountController.cs file, to see how the separated Year, Month, Day fields you insert are recomposed into the original DateTime Property, in my case the BirthDate property.

          In the next post I will analyze more advanced features of the MVCControlsToolkit.

                                                                               Stay Tuned!

                                                                                Francesco

          Tags: , , , , ,