This project is read-only.

Handling subclasses, heterogeneous collections, optional input, and mutually exclusive subclasses.

When using TextBox or TextBoxFor we can use the convention that emty strings represent null values. Unluckly, with more complex input helpers like the DateTimeInput we have no way to insert null values. Moreover, one might want to make optional a whole object, such as, for instance an Address object

A similar issue, is the choice among mutually exlusive formats. For instance, one might have two mutually exlusive address formats, one for european countries and the other for all other countries. In this case we might have two Address classes that inherit from a common base Address class, and we would like to receive in our ViewModel the object corresponding to the format chosen by the user in the web page.

The abbove situations can be handled bye removing from the DOM all fileds the the object is rendered with, with the help of the ViewOnOff  or ViewList helpers. 

Enabling the Model Binder to receive a subclass of a class contained in the ViewModel an be done with the help of the DescendatntCast helper.

Use of ViewOnOff and ViewList helpers. 

Optional inputs can be handled by enabling the user to insert/remove the involved input fields with the help of a ViewOnOff or ViewList helpers. Since when the ViewOnOff helper hides some Html, it actually detaches it from the DOM, when the inputs fields are hidden the Model Binder receives a null input. Below an example involving a DateTime? property called Start:

 

@Html.ViewsOnOff("startDate", Model.Start.HasValue)
     //initially the Date is shown if and only if the DateTime? has a value
     @{%var DTS = Html.DateTime("Start", Model.Start);  }
     <input type='checkbox' class="startDate_checkbox"/>
     <span class="startDate">
         @DTS.Date()&nbsp;&nbsp;@DTS.Time() 
     </span>

Use of the DescendatntCast helper.

In analogy with the ViewOnOff one might think to deal with mutually exclusive choices with the help of the  ViewList, since the ViewList is able to display just one of N mutually exlusive Html nodes. However, in this case we need the DescendatntCast helper to enable the Model Binder to accept a subclass of the class declared in the ViewModel. Below the code needed to enable the insertion of two different type of objects into an item control:

 

@if (item.ViewData.Model == null)
    {
        HtmlHelper<Customer> hCustomer;
        HtmlHelper<Employed> hEmployed;
       
        <strong>@item.SelectionButton("Customer", "insertCustomer", item.PrefixedId("personType"), item.PrefixedId("customerSelection"), ManipulationButtonStyle.Link)</strong>
        <strong>@item.SelectionButton("Employed", "insertEmployed", item.PrefixedId("personType"), item.PrefixedId("employedSelection"), ManipulationButtonStyle.Link)</strong>
        <div id='@item.PrefixedId("insertCustomer")' class='PersonListItem @item.PrefixedId("personType")'>
            @item.DescendatntCast(m => m).To(out hCustomer)
            <div>
            @hCustomer.LabelFor(m => m.Name) 
            @hCustomer.TextBoxFor(m => m.Name) 
            @hCustomer.ValidationMessageFor(m => m.Name, "*")
        </div>
        <div>
            @hCustomer.LabelFor(m => m.SurName) 
            @hCustomer.TextBoxFor(m => m.SurName) 
            @hCustomer.ValidationMessageFor(m => m.SurName, "*")
        </div>
        <div>
            @hCustomer.LabelFor(m => m.ContactId)
            @hCustomer.DropDownListFor(m => m.ContactId,
                        ChoiceListHelper.Create(
                            Person.ExtractEmployed(Model.AllPersons),
                            m => m.Code,
                            m => m.SurName))
            @hCustomer.ValidationMessageFor(m => m.ContactId)
            </div>
        </div>
        <div id='@item.PrefixedId("insertEmployed")' class='PersonListItem @item.PrefixedId("personType")'>
        @item.DescendatntCast(m => m).To(out hEmployed)
        <div>
            @hEmployed.LabelFor(m => m.Name) 
            @hEmployed.TextBoxFor(m => m.Name) 
            @hEmployed.ValidationMessageFor(m => m.Name, "*")
        </div>
        <div>
            @hEmployed.LabelFor(m => m.SurName) 
            @hEmployed.TextBoxFor(m => m.SurName) 
            @hEmployed.ValidationMessageFor(m => m.SurName, "*")
        </div>
        <div>
            @hEmployed.LabelFor(m => m.Department)
            @hEmployed.TextBoxFor(m => m.Department)
            @hEmployed.ValidationMessageFor(m => m.Department)
        </div>
        </div>
 
        @item.ViewList(item.PrefixedId("personType"), "personSelected", "insertCustomer")
    }

 

 

As first step we verify that the Model is null, because this code just apply to the insertion of a new item. Then we create two helpers, one for the Customer class and the other for the Employed class, and we use them to render the two types of objects in two different divs. The ViewList attach to the DOM just the div associated to the type selected by the user. This way the model binder receive "information" on the object to be built just for the selected type. It is important that also the helpers are created within the two divs: this way also the html rendered while creating the helper is detached from the DOM if the div is not selected. 

The whole example is contained in the HeterogeneousListEditing file.

More details on the DescendatntCast helper.

By invoking the DescendatntCast<T>(m => m.MyProperty...) helper where T is a subclass of the data type of MyProperty we get an HtmlHelper<T> object that we can use to Render all properties of the object contained in MyProperty that are defined in the type T. Moreover, DescendatntCast<T> insert in the View information on the actual type of the object contained in MyProperty, that in turn may be a subclass of T. This way, when the View is posted the model binder has enough information to rebuild exactly the same type that has been passed in MyProperty. In the case  MyProperty is null no information about the actual type of  the object contained in MyProperty is available and the Model Binder is instructed to re-build an object of type T.

Now if we are sure that the content of MyProperty is never null we can define the type of MyProperty as an Interface say IMyInterface. Then, if we invoke DescendatntCast<IMyInterface> we enable the Model Binder to handle correctly any implementation of IMyInterface. This is an interesting alternative to the use of Dependency Injection in the Model Binder when we would like to work with Interfaces.

When we would like to render a collection of heterogeneous types, we don't need to use the the DescendatntCast<T> helper because all items controls of the Mvc Controls Toolkit already have  the capability to work with any subclass of the type the collection is strongly typed with. In order to use different rendering for different types we can proceed as in the example below: 

 

@Html.SortableListFor(
    m => m.AllPersons,
    _S.H<Person>(
    @<text>
    @if (item.ViewData.Model != null)
    {
        @item.HiddenFor(m => m.Code)
        <div><h3>@(item.ViewData.Model is Employed ? "Employed" : "Customer")</h3></div>
        <div>
            @item.LabelFor(m => m.Name) 
            @item.TypedEditDisplayFor(m => m.Name, simpleClick: true) 
            @item.ValidationMessageFor(m => m.Name, "*")
        </div>
        <div>
            @item.LabelFor(m => m.SurName) 
            @item.TypedEditDisplayFor(m => m.SurName, simpleClick: true) 
            @item.ValidationMessageFor(m => m.SurName, "*")
        </div>
       
        if (item.ViewData.Model is Employed)
        {
            @item.TemplateFor(m => m,
                _S.L<Employed>(
                   l =>
                    string.Format("<div>{0}{1}{2}</div>",
                    l.LabelFor(m => m.Department),
                    l.TypedEditDisplayFor(m => m.Department, simpleClick:true),
                    l.ValidationMessageFor(m => m.Department, "*")

                )), typeof(Employed))

        }
        if (item.ViewData.Model is Customer)
        {
           
            @item.TemplateFor(m => m,
                _S.L<Customer>(
                   l =>
                    string.Format("<div>{0}{1}{2}</div>",
                    l.LabelFor(m => m.ContactId),
                    l.TypedEditDisplayFor(m => m.ContactId,
                        ChoiceListHelper.Create(
                            Person.ExtractEmployed(Model.AllPersons),
                            m => m.Code,
                                 m => m.SurName), simpleClick: true),
                    l.ValidationMessageFor(m => m.ContactId, "*")
                   )), typeof(Customer))
        }
     }
.....
.....
.....

 

Basically we use the TemplateFor helper to invoke templates specific for the different types. The subclass we would like to use in the template is passed as third argument of the TemplateFor helper.

The insertion of new elements in the list, instead, requires the use of the DescendatntCast<T> helper to render information about the type we would like to create in the View, since in this case the model is null and there is no implicit information about the type to be created. The details of technique are discussed in the "Use of the DescendatntCast helper" section.

The whole example is contained in the HeterogeneousListEditing file and id discussed in the  tutorial: Editing a List of Heterogeneous Types with the Mvc Controls Toolkit

Last edited Jun 22, 2014 at 11:40 AM by frankabbruzzese, version 18

Comments

No comments yet.