Oct 28 2011

Low BandWidth Transfers with The Client Side Templates of the Mvc Controls Toolkit

Category: MVCFrancesco @ 23:07

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

Handling Big Amounts of Data with Client-Side Templates

Handling Big Amounts of Data with Client-Side Templates 2

This post is a tutorial on the new Client-Side templates, and on the ClientBlockRepeater of the Mvc Controls toolkit. I assume that everyone is already familiar with the Client Block feature of the Mvc Controls Toolkit.

Client-Side Templates allow a rich UI to be defined just once in a template, and then to use this template to render each of the items in a big collection contained in a Client-Side ViewModel. This way the rich interface is obtained with a low Internet BandWidth cost, since code needed to create the rich UI is sent to the client just once in a template.

Templates are defined as normal Mvc Controls Tool kit templates, by the developer, then they are rendered on the sever and transformed into client-side templates. The template is enriched with some extra code that enables both client and server side validation on the input fields that are created dynamically when the template is instantiated on each Javascript item on the client-side.Once on the client, they are instantiated with each of the data items contained into an array of the Client-Side ViewModel.

The insertion of the actual data into the template can be done statically by inserting them just once when the template is instantiated with the data item, or dynamically, in which case we create a permanent client-side binding between the data field of the data item and an html element of the template. In the case of dynamic byndings each data item acts as a ViewModel for the instance of the template that is associated to it. Dynamic instantiation of a data fields has the advantage that each time the data is changed, the html element it is bound to is automatically updated without requiring the re-instantiation of the whole template. Moreover, dynamic binding can be also two-way, in which case also modifications of the html element can be reflected back on the data field the html elements is bound. Two way bindings are necessary when we want to collect input from the user into the Client-side ViewModel.

As a conclusion dynamic bindings are more flexible, but they are also slower to render, expecially two way bindings, so if one would actually like to put on the screen thousands of items they should be planned carefully.

Suppose we want to show a list of products that the user can purchase by just inserting the quantity he would like to receive together with the desired delivery date in the same row displaying the product. This approach is quite rough and it is not efficient, however the main purpose of this tutorial is to show how client-side template works, so we will use it. In a further tutorilal we will show a better approach.

For simplicity we will just display the description of  the product. Thus our simple ViewModel description of each product is:

public class ProductView
    {
        public int Code { get; set; }
        public string Description { get; set; }
        [Range(0, 4), Display(Name="Quantity", Prompt="Insert Quantity")]
        public int? Quantity { get; set; }
        [DateRange(DynamicMinimum="MinDeliveryDate", DynamicMaximum="MaxDeliveryDate")]
        public DateTime DeliveryDate { get; set; }
        [MileStone, ScriptIgnore]
        public DateTime MaxDeliveryDate
        {
            get
            {
                return DateTime.Today.AddMonths(2);
            }
            
        }
        [MileStone, ScriptIgnore]
        public DateTime MinDeliveryDate
        {
            get
            {
                return DateTime.Today.AddDays(2);
            }
            
        }
    }

We put a range attribute on the Quantity, to allow a maximum purchase of 4 instances of the same product, than we added two read only properties just to define some constraints on the delivery date through the DateRangeAttribute as already explained in a previous tutorial.The Milestone attribute declares that the two Date bounds will not be rendered, and the ScriptIgnoreAttribute, avoid that they are rendered when the model is serialized in json.

Our full ViewModel is:

public class ToBuyViewModel
    {
        public List<ProductView> Products { get; set; }
        public int LastKey { get; set; }
    }

The LastKey property is needed to handle our paging technique: we put on the View an “AddNewPage” button, that when clicked add a new page of products to the ones already shown on the screen. Thus we have no page changes, but just the addition of new products…just a marketing technique to sell more products :)

Since we want to handle our products with cliemt-side templates, we define the whole View as a Client Block. Accordingly our Action method will return a ClientBlockViewResult by calling the ClientBlockView controller extension method:

return this.ClientBlockView(shopping, "productsClientView");

Where shopping is our ViewModel filled with data. We will use a ClientBlockRepeater to render our instantiated Client-Side templates as child of a tbody tag of a table:

@Html.ValidationSummary(false)
              <table >
              <thead>
                  <tr>
                  <td><strong>Product</strong></td>
                  <td><strong>Quantity</strong></td>
                  <td><strong>Delivery Date</strong></td>
                  </tr>
              </thead>
              
              @Html.ClientBlockRepeater(m => m.Products,
                   _S.H<ProductView>(
                        @<tr>
                            <td>
                            @item._D(m => m.Description)
                            </td>
                            <td>
                            @item.TypedTextBoxFor(m => m.Quantity)
                            @item.ValidationMessageFor(m => m.Quantity, "*")
                            </td>
                            <td>
                            @item.DateTimeFor(m => m.DeliveryDate, DateTime.Today,
                                        false).Date()
                            </td>
                     </tr>
                    ),
                    ExternalContainerType.tbody,
                    new { id = "ProductsContainer"})           
              </table>

As you can see the template passed to the repeater is a normal server side template. It is compiled and transformed into a client side template in a way that is completely transparent to the developer.

The bindings of the TypedTextBox with the Quantity, and the one of the  DateTimeInput with the DeliveryDate of each ProductView object in the Products collection of our Client-Side ViewModel are deined automatically by the Client Block engine of the Mvc Controls Toolkit, since the input fields are defined through expressions(the Client Block engine define automatically bindings according to the same name convention used by the default model binder). The Description is inserted into the template statically (that is once for all when the template is instantiated) with the _D helper.This helper renders its parameter enclosed in a span element and with the formatting defined in a FormatAttribute.

There is also an _F helper that displays its parameter with formatting but without the span element, and the _P helper that displays its parameter in a rough format without any formatting.

As you can see, thanks, to both the implicit binding definition of input fields performed by the Client Block engine and to The _D helper we have defined the Client-Side template in exactly the same way we would have defined a Server-Side template! This make the use of Client-Side template straigthforward and enhance the re-usability of templates.

Let’ handle paging now. We need just to ask a new page of products through an Ajax call to an action method: As a first step we build the Url string of the request. We pass a ViewHint= “json” parameter to our action method, just to say we want the answer in JSON. Since our action method has been decorated with the attribute:

[AcceptViewHint(JsonRequestBehavior.AllowGet)]

as explained here, the result of the action method is automatically transformed into a JsonResult.

At run time we add also the parameter that specifies the code of the product to start with. When data arrive we push it in our original Products collection…and miracle of the Client Templates, new templates are instantiated automatically for the new products! Finally we update, our LastKey property to be ready for the next page request.

@{
                    string nextPageUrl = Url.Action("Index", "Shopping", new { ViewHint = "json" });
                }

                <script language='javascript' type='text/javascript'>
                    $(document).ready(function () {
                        $('#Button1').bind('click', function () {
                            $.ajax({
                                url: "@MvcHtmlString.Create(nextPageUrl)" + "&startFrom=" + productsClientView.LastKey(),
                                dataType: "text",
                                success:
                                function (textData) {
                                    var data = $.parseJSON(textData);
                                    if (data != null && data.Products != null) {
                                        data = ko.mapping.fromJS(data);
                                        var unwrapped = productsClientView.Products();
                                        for (var i = 0; i < data.Products().length; i++) {
                                            unwrapped.push(data.Products()[i]);
                                        }
                                        productsClientView.Products(unwrapped);
                                        productsClientView.LastKey(data.LastKey());
                                    }
                                }
                            });
                        });
                    });

When the user clicks the “submit” button, in order to avoid sending a big amount of useless data to the server, we copy into another Client-Side ViewModel, that is located into another form, just the items with a Quantity greater than 0, and then submit this other form. This way, since we are submitting another form, we avoid sending all input fields that we have created to the server, and we avoid also sending our main Client-Side ViewModel that might have become very big after a lot of new page requests.

Below the other form with the secondary ViewModel:

@using (Html.BeginForm("Confirm", "Shopping"))
            {
                var confirmModel = Html.SAClientViewModel(
                    "confirmModel",
                    new PurchaseConfirmationView(),
                    initialSave: false,
                    applyBindings: false);
                    
            }

 

We rendered our secondary Client-Side ViewModel differently from our main Client-Side ViewModel since it is not part of the Server-Side ViewModel, but it is destined to an Action method that uses a different type of ViewModel. The SAClientViewModel helpers accepts directly an instance of an object and defines a Client ViewModel with it. Finally, below the javascriot function that fill the secondary Client-Side ViewModel:

<script language='javascript' type='text/javascript'>
        productsClientView.submitProductsChosen = function () {
            confirmModel.Products = new Array();
            for (var i = 0; i < this.Products().length; i++) {
                var quantity = this.Products()[i].Quantity();
                if (quantity != null && quantity != 0) {
                    confirmModel.Products.push(this.Products()[i]);
                }
            }
            if(confirmModel.Products.length>0)
                confirmModel.saveAndSubmit();
        };
    </script>

Now we need just to attach the javascript function that submits the form to the click event of some button. We can do this by defining manually a click binding for this button. We ask for a IBindingsBuilder interface to the helper of our View by calling the ClientBindings extension method, and use it to define our click binding:

@{
                    var clientModel = Html.ClientBindings();
                    var sumbitButtonBindings = clientModel.Click(m => m, "submitProductsChosen").Get();
                }
                <input type="button" value="Submit" data-bind='@sumbitButtonBindings'/>

 

That’s all! The full code is contained in the RazorClientTemplating file in the Mvc Controls Toolkit download page. Download it and enjoy!

                                                                    Francesco

Tags: , , , , ,

Apr 24 2011

Designing a Themed Control in the Mvc Controls Toolkit

Category: MVCFrancesco @ 07:09

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

Advanced Data Filtering Techniques in the Mvc Controls Toolkit

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

All controls of the Mvc Controls Toolkit are designed to allow the maximum flexibility. For instance, the DataGrid is not table based but allows a generic template to be used as item template. Other controls,such as the DateTimeInput, and the DualSelectBox are composed of several parts that can be rendered separately. Features like, Sorting, Paging and Filtering are not included in controls like the DataGrid and the SortableList but are provided through separate helpers. Finally, all controls can be styled with Css. While this approach ensures an high level of flexibility, the time needed to set-up completely a control is higher compared to the time needed to setup a pre-configured control.

In order to promote re-usability of efforts made to set up a control, the Mvc Controls Toolkit allow the definition of Themes. Each Theme is composed of a Css enriched with an image folder, and of a set of Partial Views. Each Partial View features an already set-up control. While the layout and all parts each control is composed of are defined in the Partial Views, the Css define completely the style of all controls. Thus, the controls of each theme are ready to be used, with no further effort; we just need to pass data to them! Better configuration and style options are offered by the commercial version of the Mvc Controls Toolkit.

In this post I will show how to design a control to be inserted into a theme. I assume a basic knowledge of the Theming features of the Mvc Controls Toolkit. Therefore, readers are encouraged to read the documentation page about theming here before reading this post.

We will call “Test” the theme we are going to define, and we will analyze just how to design a Table based Themed Datagrid offering paging, sorting and column filtering services.,

As a first step we set-up our folders as shown below:

css_folder_treefolder_tree

Now let open the DataGrid.cshtml file. The Themed Datagrid is invoked by the helper:

  1. public static MvcHtmlString ThemedDataGridFor<M, TItem>(
  2.             this HtmlHelper<M> htmlHelper,
  3.             Expression<Func<M, List<Tracker<TItem>>>> expression,
  4.             GridFeatures gridFeatures,
  5.             Columns<TItem> fields,
  6.             Expression<Func<M, List<KeyValuePair<LambdaExpression, OrderType>>>> orderExpression = null,
  7.             Expression<Func<M, int>> page=null,
  8.             Expression<Func<M, int>> prevPage=null,
  9.             Expression<Func<M, int>> pageCount=null,
  10.             Expression<Func<M, Expression<Func<TItem, bool>>>> filter=null,
  11.             string title=null,
  12.             string name = "DataGrid"
  13.             )
  14.             where TItem:class, new()

expression defines the collection to be rendered in the Grid, filter will collect the filtering criteria (see here), orderExpression the sorting criteria(see here), and page, prevPage, and pageCount the paging (see here). gridFeatures is a bit flag enumeration that defines the desired features selected by the user of the Themed DataGrid:

[Flags]
    public enum GridFeatures {None = 0, Edit=1, Display=2, Insert=4, Delete=8, ResetRow=16, UndoEdit=32, InsertOne=64, Paging=128, Sorting=256, Filtering=512}

For more information see the Documentation page about theming.

While fields is a collection of column definitions. Each column contains the column features(if sorting, and filtering are allowed, and an optional column header to override the one provided through DataAnnotations), and possibly Edit and Display templates for the column. One can also define custom columns that are not connected with specific fields.For more information see the Documentation page about theming.

The ThemedDataGridFor helper just packs all information received into a GridDescription object defined as:

public class GridDescriptionBase
    {
        public GridFeatures Features { get; set; }
        public List<Column> Fields { get; set; }   
    }
    
    public class GridDescription:GridDescriptionBase
    {
        public dynamic ToShow {get; set;}
        public dynamic ToOrder { get; set; }
        public string Title { get; set; }
        public dynamic HtmlHelper { get; set; }
        public dynamic Page { get; set; }
        public dynamic PrevPage { get; set; }
        public dynamic PageCount { get; set; }
        public dynamic Filter { get; set; }

    }

Then it passes this object to our DataGrid.cshtml Partial View into: ViewData["ThemeParams"]

Thus our first step is to “unpack” this object:

@{
    var options = ViewData["ThemeParams"] as GridDescription;

    Func<dynamic, HelperResult> editTemplate = null;
    if ((options.Features & GridFeatures.Edit) == GridFeatures.Edit)
    {
        editTemplate = EditItem;
    }
    Func<dynamic, HelperResult> displayTemplate = DisplayItem;
    Func<dynamic, HelperResult> insertTemplate = null;
    if ((options.Features & GridFeatures.Insert) == GridFeatures.Insert)
    {
        insertTemplate = InsertItem;
    }
}

The above instruction just verifies if the Insert and Edit services are required, and if they are required assign the name of the Insert template(InsertItem), and of the Edit template(EditItem) to the variables inserItem, and editItem. The Display template, that is not optional, is always put into the displayItem variable.

Templates are defined through Razor helpers(see the documentation about templates) that use the columns information provided by the GridDescription class to render each single item.

Below the Insert template that just renders an insert button:

@helper InsertItem(dynamic htmlHelper)
    {
        var options = htmlHelper.ViewData["ThemeParams"] as GridDescription;
    int count = 0;
    bool hasCustomField = false;
    foreach (Column column in options.Fields)
    {
        if ((column.Features & FieldFeatures.Hidden) != FieldFeatures.Hidden)
        {
            count++;
        }
        else if (column.DispalyTemplate != null)
        {
            hasCustomField = true;
        }
    }
    if ((options.Features & GridFeatures.Edit) == GridFeatures.Edit
        || hasCustomField
        || (options.Features & GridFeatures.Delete) == GridFeatures.Delete) { count++; }
    <td colspan='@count' class="Theme-DataGrid-Column">
        @DataGridHelpers.ImgDataButton(
            htmlHelper, DataButtonType.Insert,
            Url.Content("~/Content/themes/test/images/add.jpg"), null)</td>
}

 

 

 

Let’s forget for just a minute about the hasCustomField variable, we will discuss about it when analyzing the Display template.The foreach loop just counts the number of columnsto be inserted in the colspan to format properly the table.If the grid is required to work also in Edit mode or if it is required to allow row deletes we adds one column more for the some command buttons. Columns marked as hidden are jumped since they contains just hidden field that will not be rendered together with the other columns.

The imgDataButton helper that renders the insert button takes an image form the Css that is part of the “Test” theme. Its is rendered in a quite strange way, namely,  it is not invoked as an extension method but just as a normal static method of the DataGridHelpers class. This is because we are using dynamic  variables(see the GridDescription definition) to handle Types that will be known just a run-time since our DataGrid is required to work with any class. In such a case extension methods cannot be used!

In the Display template we do some initialization and then we start a foreach loop on all columns:

@helper DisplayItem(dynamic htmlHelper)
    {
        var options = htmlHelper.ViewData["ThemeParams"] as GridDescription;
        bool hasCustomField=false;
        foreach (Column column in options.Fields)

We will see what the hasCustomField variable is needed for in a short time, let’s see first how “normal” columns(the ones that are not marked as Hidden) are dealt with:

if ((column.Features & FieldFeatures.Hidden) != FieldFeatures.Hidden)
            {
            <td class="Theme-DataGrid-Column">
            @{
                if (column.DispalyTemplate == null)
                {
                      @DisplayExtensions.DisplayFor(htmlHelper, column.Field)
                }
                else
                {
                      @CoreHTMLHelpers.TemplateFor(htmlHelper, column.DispalyTemplate)
                }
             }
            </td>
            }

 

Very simple: if the user has provided a Display template for that column we just invoke it through the TemplateFor helper otherwise we simply invoke the standard Mvc DisplayFor helper. It is worth to point out that column templates are passed information on the whole row, since we passed the htmlHelper for the whole row to the TemplateFor helper. This way, each column can use information about other columns in the rendering. Moreover, we may define “custom” columns that are associated to no specific field by by using  m => m as filed selector.

Now let’s see what happens to fields marked as Hidden:

else if(column.DispalyTemplate != null){
                hasCustomField = true;
            }

If they have a custom display template we just set the hasCustomField variable, defined at the beginning of the helper to true, just to remember we have found one of them. We have done the same thing in the Insert template. Why?? Simple! Because hidden fields are rendered in the edit Templates, since they just store data for a future post, thus a display template associated with an hidden field for sure is needed to render a command button. Probably a command button that uses the principal key of the row to render either a detail view of the record or some other children objects by means of either an action link or an Ajax call. Therefore, in such a case we need to display a further column reserved for all command buttons:

if ((options.Features & GridFeatures.Edit) == GridFeatures.Edit
            || hasCustomField
            || (options.Features & GridFeatures.Delete) == GridFeatures.Delete)
        {
        <td>
            <table>
                <tr>
                    @if ((options.Features & GridFeatures.Edit) == GridFeatures.Edit)
                    {         
                        <td>
                            @DataGridHelpers.ImgDataButton(
                                htmlHelper, DataButtonType.Edit,
                                Url.Content("~/Content/themes/test/images/edit.jpg"), null)
                        </td>
                    }
                    @if ((options.Features & GridFeatures.Delete) == GridFeatures.Delete)
                    {
                        <td>
                            @DataGridHelpers.ImgDataButton(
                                htmlHelper, DataButtonType.Delete,
                                Url.Content("~/Content/themes/test/images/delete.jpg"), null)
                        </td>
                    }
                    @if (hasCustomField)
                    {
                        foreach (Column column in options.Fields)
                        {
                            if ((column.Features & FieldFeatures.Hidden) == FieldFeatures.Hidden &&
                                column.DispalyTemplate != null)
                            {
                                <td>
                                    @CoreHTMLHelpers.TemplateFor(htmlHelper, column.DispalyTemplate)
                                </td>
                            }
                        }
                    }
               </tr>
            </table>
        </td>
        }

The Edit template is completely analogous, just the command buttons are different:

@helper EditItem(dynamic htmlHelper)
    {
        var options = htmlHelper.ViewData["ThemeParams"] as GridDescription;

        foreach (Column column in options.Fields)
        {
            if ((column.Features & FieldFeatures.Hidden) != FieldFeatures.Hidden)
            {
            <td class="Theme-DataGrid-Column">
            @{
                if (column.EditTemplate == null)
                {
                      @EditorExtensions.EditorFor(htmlHelper, column.Field)
                }
                else
                {
                    if (column.EditTemplate is string)
                    {
                        string templateName = column.EditTemplate as string;
                        templateName = "Themes/Test/" + templateName;
                        @CoreHTMLHelpers.TemplateFor(htmlHelper, templateName)
                    }
                    else
                    {     
                        @CoreHTMLHelpers.TemplateFor(htmlHelper, column.EditTemplate)
                    }
                }
             }
            </td>
            }
        }
        <td>
            <table>
                <tr>          
                    <td>
                        @DataGridHelpers.ImgDataButton(
                            htmlHelper,
                            DataButtonType.Cancel,
                            Url.Content("~/Content/themes/test/images/undo.jpg"), null)
                    </td>
                    @if ((options.Features & GridFeatures.UndoEdit) == GridFeatures.UndoEdit)
                    {
                        <td>
                            @DataGridHelpers.LinkDataButton(
                                htmlHelper,
                                DataButtonType.ResetRow,
                                ThemedControlsStrings.Get("Item_ResetRow", "DataGrid"), null)
                        </td>
                    }
                    @foreach (Column column in options.Fields)
                    {
                        if ((column.Features & FieldFeatures.Hidden) == FieldFeatures.Hidden)
                        {
                            if (column.EditTemplate != null)
                            {
                                @CoreHTMLHelpers.TemplateFor(htmlHelper, column.EditTemplate)
                            }
                            else
                            {
                                <td style="width:0px">
                                    @InputExtensions.HiddenFor(htmlHelper, column.Field)
                                </td>
                            }
                        }
                    }
               </tr>
            </table>
        </td>
}

It is convenient to define also a Razor helper that renders the Header of the table, with all column names, sort, and filtering logics:

@helper DisplayHeader(dynamic htmlHelper, GridDescription options)
    {
    <tr>
        @{bool hasCustomField=false;}
        @foreach (Column column in options.Fields)
        {
            if ((column.Features & FieldFeatures.Hidden) != FieldFeatures.Hidden)
            {
            <td class="Theme-DataGrid-Header">
                <table>
                <tr>

The above Razor Helper is completely analogous to the other templates. In each column we have 2 sections. The first one is dedicated to filtering and the second one is dedicated either to a sort button with the column name inside it or just to the column name if the column is not sortable.

Let’s start with the filtering:

@if ((options.Features & GridFeatures.Filtering) == GridFeatures.Filtering && (column.Features & FieldFeatures.Filtering) == FieldFeatures.Filtering)
                {
                    <td >
                    <div class="Theme-DataGrid-Filter">
                        <div class="MvcControlsToolkit-Hover">
                            @{
                            var h1 = DataFilterClauseHelpers.DataFilterClauseFor(
                                htmlHelper,
                                options.Filter,
                                column.Field,
                                "first");
                            var h2 = DataFilterClauseHelpers.DataFilterClauseFor(
                                htmlHelper,
                                options.Filter,
                                column.Field,
                                "second");
                            bool oldClientSetting = ViewContext.ClientValidationEnabled;
                            ViewContext.ClientValidationEnabled = false;
                                }
                              <table>
                                <tr>
                                    <td>
                                        @InputExtensions.CheckBox(h1, "Selected", h1.ViewData.Model.Selected)
                                    </td>
                                    <td>
                                        @DataFilterClauseHelpers.FilterClauseSelect(h1, h1.ViewData.Model.Condition, column.Field)
                                    </td>   
                                </tr>
                                <tr>
                                    <td colspan="2">
                                        @{object val1=h1.ViewData.Model.Search;
                                          if (! h1.ViewData.Model.Selected)
                                          {
                                              val1 = string.Empty;
                                          }
                                        }
                                        @InputExtensions.TextBox(h1, "Search", val1)
                                        @{ValidationExtensions.Validate(h1, "Search"); }
                                    </td>
                                </tr>
                              </table>
                              <table>
                                <tr>
                                    <td>
                                        @InputExtensions.CheckBox(h2, "Selected", h2.ViewData.Model.Selected)
                                    </td>
                                    <td>
                                        @DataFilterClauseHelpers.FilterClauseSelect(h2, h2.ViewData.Model.Condition, column.Field)
                                    </td>   
                                </tr>
                                <tr>
                                    <td colspan="2">
                                        @{object val2=h2.ViewData.Model.Search;
                                          if (!h2.ViewData.Model.Selected)
                                          {
                                              val2 = string.Empty;
                                          }
                                         }
                                        @InputExtensions.TextBox(h2, "Search", val2)
                                        @{ValidationExtensions.Validate(h2, "Search"); }
                                        @{ViewContext.ClientValidationEnabled=oldClientSetting;}
                                    </td>
                                </tr>
                              </table>    
                            </div>
                        </div>
                    </td>
                    
                }

Filters are implemented through an hover menu that appears on mouse hover a filtering icon. All hover menu logic is contained in the Css, so we don’t have to worry about this while coding the filtering logics. Each column may contains two filter clauses, this is the minimum to implement min-max conditions. Accordingly we create the two helpers h1 and h2 to renders the filters by means of the DataFilterClauseFor helper.

We use each helper to render a checkbox to enable the filter criterion, a dropdown to select the filtering condition, and finally a TextBox for inserting the Filter parameter and the associated validation helper. We ensure that if the filter is disabled the TextBox is empty.

What’s beautiful about the DataFilterClauseFor helper is that once we have Rendered input fields, for its “Search” field, for its filter criterion(our DropDown), and for its enabled condition…it handles all filtering logics for us….We will receive the complete filter obtained combining all columns filters in our the filter property of our ViewModel..So we need just to apply it to an IQueryable to filter it (see also here for more information).

Let’s move to the second section (sorting and column names):

<td>
                <strong>
                @if ((column.Features & FieldFeatures.Sort) == FieldFeatures.Sort)
                {
                    @DataGridHelpers.SortButtonForTrackedCollection(
                        htmlHelper,
                        options.ToShow,
                        column.Field,
                        sortButtonStyle: SortButtonStyle.Button)
                }
                else
                {
                    if (column.ColumnHeader != null)
                    {
                        @column.ColumnHeader
                    }
                    else
                    {
                       @DataGridHelpers.ColumnNameForTrackedCollection(
                       htmlHelper,
                       options.ToShow,
                       column.Field)
                    }
                }
            </strong>
            </td>

options.Toshow contains the lambda expression that defines the collection to be shown in the grid. If the column sorting is required we render a sort button otherwise we render just the column name either taken from Data Annotations or from the column definition.

For all columns marked as Hidden as usual we have:

else if (column.DispalyTemplate != null)
            {
                hasCustomField = true;
            }

and finally:

@if ((options.Features & GridFeatures.Edit) == GridFeatures.Edit
            || hasCustomField
            || (options.Features & GridFeatures.Delete) == GridFeatures.Delete)
        {
            <td class="Theme-DataGrid-Header"></td>
        }

If there is the column for the command buttons, we just render an empty <td> to have the right number of <td>.in the header.

Now we are ready to render the DataGrid:

<table>
@DisplayHeader(options.HtmlHelper, options)
@DataGridHelpers.DataGridFor(options.HtmlHelper,
    options.ToShow,
    ItemContainerType.tr,
    editTemplate,
    displayTemplate,
    null,
    insertTemplate,
    enableMultipleInsert: (options.Features & GridFeatures.InsertOne) != GridFeatures.InsertOne,
    itemCss: "Theme-DataGrid-ItemCss",
    altItemCss: "Theme-DataGrid-AlternateItemCss",
    toTrack: options.GetToTrack())
</table>

toTrack contains the list of all properties we need to save in hidden fields for the changes tracking performed by the grid. This argument is optional, but if we don’t pass it the DataGrid helper just save the original values of all properties of each item. Since, the list of all properties we are using can be easily computed from the columns definition, we get it by calling the GridDescription class method GetToTrack().

Finally we add Pager, and Sorting helper if these services are required:

@if ((options.Features & GridFeatures.Paging) == GridFeatures.Paging)
{
    <div class="Theme-DataGrid-Pager">
        @{ var pager = PagerHelper.PagerFor(options.HtmlHelper, options.Page, options.PrevPage, options.PageCount);}
        @pager.PageButton(ThemedControlsStrings.Get("Page_First", "DataGrid"), PageButtonType.First, PageButtonStyle.Link)
        @pager.PageButton(ThemedControlsStrings.Get("Page_Prev", "DataGrid"), PageButtonType.Previous, PageButtonStyle.Link)
        @pager.PageChoice(5)
        @pager.PageButton(ThemedControlsStrings.Get("Page_Next", "DataGrid"), PageButtonType.Next, PageButtonStyle.Link)
        @pager.PageButton(ThemedControlsStrings.Get("Page_Last", "DataGrid"), PageButtonType.Last, PageButtonStyle.Link)
    </div>
}

@if (((options.Features & GridFeatures.Sorting) == GridFeatures.Sorting) && options.ToOrder != null)
{
        @DataGridHelpers.EnableSortingFor(
            options.HtmlHelper, options.ToShow,
            options.ToOrder, "Theme-DataGrid-NormalHeader",
            "Theme-DataGrid-AscendingHeader", "Theme-DataGrid-DescendingHeader",
            page: options.Page)
}

The whole code can be downloaded from the download page of the Mvc Controls Toolkit here.

That’s all! Not so difficult, isn’t it?

The full code is contained in the RazorThemedGrid file in the Mvc Controls Toolkit download page. Download it and enjoy!

                                                    Stay Tuned

                                                                 Francesco

Tags: , , , , ,

Apr 13 2011

Data Dictionary And Data Annotations

Category: MVC | WPF | Silverlightfrancesco @ 07:07

Very often I am involved in discussions about “the right place” where to perform Data Validation. A lot of people do not approve the use of tools that checks automatically Data Validation attributes in the UI. This happens in Mvc, Silverlight +Ria services and in both Silverlight and Wpf if you use the Validation Toolkit for Wpf & Silverlight.

The main criticism is that, according to them, Validation needs to be performed in the Business Layer, not in the Presentation Layer. Performing validation in the presentation layer enforces a duplication of the procedure that needs to be repeated in other layers. Taken literally this  observation appears fair. However, behind the above words there is the danger of confusing some concepts:

  1. Where performing the validation is different from when performing the validation. Moreover, we needs also to understand well what does where mean in object programming. For sure the Presentation layer is not where to perform the validation. However, the best time when to perform validation is when the flux of control is in the Presentation Layer, that is, immediately after the user input: this way we are able to help the user with an immediate feedback, and we eliminate immediately potentially dangerous requests that might be used for a denial of service attack.
    Thus, the best solution is performing data validation when the flux of control is in the Presentation Layer by using rules defined in other Layers(where), and that the Presentation Layer is not aware of(it just enforces them, it doesn’t need to know anything about them). This is exactly what all tools that verify Data Annotations in the Presentation Layer do!
  2. Performing validation when the flux of control is in the Presentation Layer may force to perform a second validation control at a later time either because immediately after the user input we might miss some information that is necessary to perform the validation, or because of security related issues. However, there is nothing bad in performing two validation controls if we are able to do this without duplicating code. Code duplication has a negative impact on software modifiability, but the duplication of the validation control creates no problems. Below I will show how we can avoid code duplication.

Once we cleared the way from sources of possible confusion and mistakes we can try an answer to the question: what is the best place where to define data validation rules?

Actually there is not a unique place where to put them! There are rules, whose best place is the Data Layer, other the Business Layer, and also the Presentation Layer. Data Layer scoped constraints are the ones imposed by some consistency rules of the database that might be not relevant to the application, Business Layer level constraints are all constraints imposed by Business rules(some of them are also encoded within the consistency rules of the data base). Maybe someone is surprised that there might be also Presentation Level Validation rules: later on I will show some examples.

However, before it is worth to point out that there are rules involving the very conceptual nature of data. Such rules cross the boundaries of layers and remain attached to the different representations that the same conceptual entity has in each layer. For instance, the E-Mail address of a Customer has always the same structure that is independent on how this conceptual entity is represented in the different layers. Maybe in the Presentation Layer we have no object representing a single customer and that the customer data are mixed with the ones of another conceptual entity…but notwithstanding this, if there is a Customer E-Mail it has the same structure it has in the Data or Business Layer.

Such rules can be defined in a common place and used by all Layers they are relevant to, avoiding code duplication.However, where do this cross Layers rules come from and how we can identify them?

In the  Analysys stage of an application we typically prepare a Data Dictionary containing the definition of all terms and Data that are relevant to the application.Such definitions need to be considered in all Layers to ensure the coherence of the overall application. Thus, for instance, once we have defined the structure of an Address we need to use this structure in all Layers, otherwise we will not be able to translate it from the representation it has in one Layer to the representation it has into another Layer.

Well, now we have the answer: the cross Layers validation rules just translate the Data Dictionary property types definitions into procedures that enforce their structure. In other terms such Validation rules just translate constraints that are intrinsic in the data themselves, not Business rules: the structure of an E-Mail Address is independent of the use we do and is implicit in its definition. Such properties are typically property level validation rules, while Business rule more often involve constraints spanning several properties of an object.

To understand better how to proceed in practice let consider the case of the Address. We use the definition of address to write validation rules for different representations an address might have: 1) a single rule to be used when the address is represented as a single string and a some other rules for the case the address is represented as a complex structure(Country, street, etc). For the second representation we probably need several rules, one for each field, and a final check rule for the overall complex structure.

All validation rules obtained this way may be translated into validation attributes and put in a separate dll that is referenced by all Layers.

In a well structured application the Data Dictionary not only defines the structure of all conceptual entities but also name conventions, in order to make easier “to put the fingers” in the software, and to enforce coherence among all Layers. I suggest a convention for the name of the objects of the type Customer, CustomerBusiness, CustomerView, and the use of the same property names across all Layers.

Using the same property names for all properties related to the same conceptual entities allows the definition of common meta-classes to be used in all Layers. Thus, if we have a Data Layer Customer class and a DetailCustomer, and ShortCustomer classes passed from the Business Layer to the Presentation Layer, they can both take their property level Validation attributes from the same meta-class defined once and for all in the same dll that contains also the common Validation Attributes reference by all Layers. Properties defined in the meta-class that are not used in one of the target classes are simply not validated by the validation engine. The previous assertion is true for all property level attributes but the [Required] attribute that fails the Validation if it is not defined in the target class. Therefore I advice of avoiding to include it in the common meta-classes.

As a matter of fact the required attribute doesn’t translate an intrinsic constraint on the property it is applied to, but a requirement on the whole object that contains that property. For this reason we are not able to fit it among the common validation rules shared among all Layers. For instance, the fact that an OrderDate property is required in the business Layer doesn’t imply that it is necessarely required also in the Presentation Layer, because it might be provided from a source different from the user, for instance another procedure, or the computer clock.

Often the best time to check a Required attribute is when we are sure the object has been completely filled. However, this is not a general rule because we might want the user to insert a start and stop OrderDate to list all orders included in a time interval. This is an example of Presentation Layer scoped validation rule! In such a case we can use the [Required] attribute directly on some ViewModel property.In the cases where the Required check just translate an integrity constraint of the whole object that contains the property I propose to use an object level validation attribute that checks all required fields. Accordingly, I defined a similar object level attribute in my Mvc Controls Toolkit(see the RequiredFieldsAttribute here).

I promised to show some examples of Presentation Layer scoped validation rules. I already provided the example of the dates that defines the Orders to list. In general Validation rules on data that are used to define filtering criteria are often Presentation Layer scoped Validation rules.

Tags: , , ,

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