Feb 6 2011

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

Category: Entity Framework | MVC | Asp.netFrancesco @ 05:11

See also an updated version of this post:  Advanced Data Filtering Techniques in the Mvc Controls Toolkit

Mvc Controls Toolkit Datagrid Updated Tutorial (updated version of Defining MVC Controls 2: Using the DataGrid)

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

The Mvc Controls Toolkit offers an interesting data filtering feature: the controller can receive directly a LinQ expression defining the filtering criteria chosen by the user when a view is posted. This way the user can choose among several filtering options by clicking buttons, or selecting checkboxes and then filling the input fields of the selected filtering options. No need to define ViewModel properties for each of the available options, the action method of the controller receives always the same data structure: a LinQ Expression. New filtering options can be easily added modularly with the no need to modify the controller and the code behind it. This increases modularity and makes easier  software maintenance.

Let see how this works in practice with the help of the same example used in my previous posts about the Mvc Controls Toolkit library(see the links next to the title). The whole code used in this example can be downloaded here(the file named BasicTutorialsCode), while screenshots of the software running are here. .

First of all we add the property destined to contain the LinQ expression to the ViewModel:

         public int TotalPages { get; set; }
        public int CurrPage { get; set; }
        public int PrevPage { get; set; }
        public List<Tracker<ToDoView>> ToDoList {get; set;}
        public List<KeyValuePair<LambdaExpression, OrderType>> ToDoOrder { get; set; }
        public System.Linq.Expressions.Expression<Func<ToDoView, bool>> ToDoFilter { get; set; }

The LinQ Expression is a map from ToDoView, that is the class the filter will be applied to, and bool, since formally it defines a function that verifies if an instance of ToDoView satisfies the criterium of the filter.

Now we can use this expression directly in the method that retrieves the items:

if (filter == null)
                {
                    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();
                }
                else
                {
                    result = context.ToDo.Select(item =>
                        new ToDoView() { Name = item.Name, Description = item.Description, DueDate = item.DueDate, id = item.id }).Where(filter).ApplyOrder(order).Select(viewItem =>
                        new Tracker<ToDoView>
                        {
                            Value = viewItem,
                            OldValue = viewItem,
                            Changed = false
                        }).Skip(toSkip).Take(pageDim).ToList();
                }

The code above shows that it was enough to add a Where(filter) instruction to our original code to get the filtered data.

Now we need to define a class for each filtering option. For sake of semplicity let just define a single filtering option. Our class needs just to implement an interface:

public interface IFilterDescription<TData>
    {
        Expression<Func<TData, bool>> GetExpression();
    }

The GetExpression function returns the actual Filter. The input fields that the user is required to fill are represented by properties of this class, and are used in the implementation of GetExpression():

public class ToDoItemByNameFilter: 
        IFilterDescription<ToDoView>
    {
        [Required]
        [Display(Prompt="chars the name of item starts with")]
        public string Name {get; set;}
        public System.Linq.Expressions.Expression<Func<ToDoView, bool>> GetExpression()
        {
            System.Linq.Expressions.Expression<Func<ToDoView, bool>> res = null;
            Name=Name.Trim();
            if (!string.IsNullOrEmpty(Name))
            {
                Name=Name.Trim();
                res= m => (m.Name.StartsWith(Name));
                
            }
            return res;
        }
    }

In our case we would like to select all items whose Name field starts with the characters inserted in the Name property defined in our ToDoByNameFilter class.

We just Trim our input and then we use it in the LinQ expression returned by GetExpression.

Our ToDoItemByNameFilter plays also the role of ViewModel for a partial view that takes care of the user interface for our filter, so we can decorate its Name property with attributes defining both validation and appearance constraints. In our case we defined a WatermarK through the Prompt property of the Display attribute.

In order to make the watermark actually apppears and the textbox add some adequate formatting we can use the TypedTextBox of the Mvc Controls Toolkit in the partial view that defines the graphical appearance of our filter:

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<Mvc_Examples.Controls.ToDoItemByNameFilter>" %>
<%@ Import Namespace=" MVCControlsToolkit.Core" %>
<%@ Import Namespace=" MVCControlsToolkit.Controls" %>
<%: Html.TypedTextBoxFor(m => m.Name, watermarkCss:"watermark") %>

Now it remains just to insert the helper of the filter in our View. In order to let the user selects among several filters we can use the ViewList defined in the Mvc Controls Toolkit. This way either by clicking some mutual exclusive checkboxes or by clicking some button, the user let the Partial View associate to a Filter to appear. In our case for sake of simplicity we give to the user just the choice between our previous Name filter and no filter at all:

<div >
    <%: Html.ViewList("ToDoFilter", "ToDoFilterSelected") %> 
        <input id="Checkbox1" type="checkbox" class = 'ToDoFilter_checkbox ByNameFilter_checkbox'/>  &nbsp; Filter by name 
        <span id='ByNameFilter' class='ToDoFilter'><%:Html.DataFilter(m => m.ToDoFilter, 
                                new Mvc_Examples.Controls.ToDoItemByNameFilter(),
                                           "ToDoFilterByName")%></span>  
    </div>
    <div >
    <input id="Checkbox2" type="checkbox" class = 'ToDoFilter_checkbox NoFilter_checkbox'/>  &nbsp; NoFilter
    <span id='NoFilter' class='ToDoFilter'></span>
    </div>

The arguments of our DataFilter helper are just an expression that selects the ViewModel property to fill with the filtering expression, an instance of the class that defines the filter, and the name of the Partial View that we defined before.

The ViewList helper selects one of two spans, one contains the filter helper and the other is simply empty. The selection is done with two checkboxes.Details on how to use of the VieList can be found in the documentation here.

By adding a new argument to our DetailView Helper we can make also our detail view appears in a jQuery UI dialog:

<% Html.DetailFormFor(Ajax, m => m.ToDoList, ExternalContainerType.div,
           "ToDoSubTasks", "Home", null, "isChangedToDo", "isDeletedToDo", detailDialog:
           new MVCControlsToolkit.Controls.DataGrid.Dialog
           {
               Title = "prova dialogo",
               Show = "slide",
               Hide = "slide",
               MinWidth=800

           });%>
</asp:Content>

THAT’S ALL!  Download the code and enjoy!

                             

                                       Stay Tuned!

                                               Francesco

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: , , , , ,