Core Functions: Defining Your Controls! 

Designing Controls with the IDispalyModel interface

Designing new Controls is as easy as implementing the IDisplayModel Interface, plus writing a template and possibly some javascript.

Suppose we want to display a Date contained in a DateTime property as 3 different integer inputs. Later,  the separated Year, Month and Day can be rendered with DropDowns or TextBoxes. The problem here is recomposing back Year, Month and Day into the original DateTime when the View is posted. You can say to the MVCControlsToolkit how to translate the two different representations one into the other simply by implementing the  IDisplayModel Interface:

 
public class TestDateTimeDisplay : IDisplayModel
{
 
        [Range(1000, 3000, ErrorMessage = "wrong year")]
        
        public Nullable<int> Year { get; set; }

        [Range(1, 12, ErrorMessage = "wrong month")]
        public Nullable<int> Month { get; set; }

        [Range(1, 31, ErrorMessage = "wrong day")]
        public Nullable<int> Day { get; set; }

        public object ExportToModel(Type targetType, params object[] context)
        {
            if (Year == 0 && Month == 0 && Day == 0) return null;
            if (!Year.HasValue && !Month.HasValue && !Day.HasValue) return null;
            try
            {
                return new DateTime(Year.Value, Month.Value, Day.Value);
            }
            catch (Exception ex)
            {
                throw (new Exception(" {0} has an incorrect date format", ex));
                
            }
        }

        public void ImportFromModel(object model, params object[] context)
        {
            Nullable<DateTime> date = model as Nullable<DateTime>;
            if(date.HasValue && date.Value != DateTime.MinValue)
            {
                Year = date.Value.Year;
                Month = date.Value.Month;
                Day = date.Value.Day;
            }

        }
    }

Exceptions thrown in the ExportToModel method are automatically transformed into error messages that are associated with the initial DateTime property and that can be Displayed with the ValidationMessageForhelper. The initial DateTime property can be decorated with annotations that will be used when displaying the View.

The context parameter of the ImportFromModel method is passed by the helper that displays the control and can be used do drive how the control is diplayed. Pay attention: The same parameter will not be automatically recovered  at posting time. Thus, if you need it when the page is posted you have to put it in some field of your class.

The context parameter of the ExportToModel method at the moment are not used and are reserved for future expansions.

IDisplayModel interface implementation defines a reversible transformation that is applied on the View Model. The inverse transformation is automatically applied by the MVCControlToolkit default binder when the View is posted back. 

Once defined your reversible transformation you can invoke it with one of the two overloads of InvokeDisplay helper:

 
public static RenderInfo<NM> InvokeTransform<VM, M, NM>(
            this HtmlHelper<VM> htmlHelper,
            Expression<Func<VM, M>> expression,
            NM newModel,
            object[] args = null)
            where NM : IDisplayModel


public static RenderInfo<NM> InvokeTransform<VM, M, NM>(
            this HtmlHelper<VM> htmlHelper,
            RenderInfo<M> renderInfo,
            NM newModel,
            object[] args = null)
            where NM : IDisplayModel

The output of the invocation is a RenderInfo class that can be used as input to another transformation. This way transformations can be chained. The second overload is used when the input comes from a previous transformation. The optional args parameter is passed as context to the ImportFromModel method. 

The simplest way to render the output of the invocation is through the RenderIn helper:

public static MvcHtmlString RenderIn<VM, M>(
            this HtmlHelper<VM> htmlHelper,
            object template,
            RenderInfo<M> renderiNFO)
Where the template parameter specifies a strongly typed template for displaying your control (for an introduction to templates, please see here). Thus, the code for displaying our simple date ontrol is:
@Html.RenderIn("YearMonthDayDate", Html.InvokeTransform(m => m.PersonalData.BirthDate, new TestDateTimeDisplay()))

That's all! 

You can also decide to use the RenderWith function that returns an HtmlHelper<M>, where M is the "transformed model". Then you can use this HtmlHelper to render the type M in the original View without the need to istantiate a template:

 

public static HtmlHelper<M> RenderWith<VM, M>(
            this HtmlHelper<VM> htmlHelper,
            RenderInfo<M> renderiNFO)

 

An example of the use of RenderWith is shown below, in the section about the IUpdateModel interface.

You can also decide to write an Html helper. In such a case you need to call the InvokeTransform method from your helper. The RenderInfo<T> that is returned contains:

  1. The model to render, 
  2. The prefix to use, 
  3. A string to chain with  your final output string. You can get this string by calling the GetPartialRendering method. Pay Attention! This call returns a non-empty string only at its first invocation! This behaviour is a protection to avoid this string be rendered more than once.

Designing Controls with the IUpdateModel interface

The IUpdateModel Interface allows one to combine data from different properties at any depth in the View Model(or in a subpart of the View Model) into a new data structure to be rendered. When the View is posted back the input received into this new data structure is used to update the initial model in a custom way . The same properties of the View Model can be used by several  implementations of theIUpdateModel interface and can be update several times when the View is posted. The peculiarities of the IUpdateModel interface are used in the implementation of the RenderEnumerableFor and DataGridFor helpers and in general in most of the helpers tha works with collections..

Suppose we want to implement a control to display various  quantities of type float in the View Model as percentages plus their sum. This transformation allows the use of Sliders to allocate the a total quantity to different destinations. A simple example of use is the partition of a total investment into a portfolio of separated investments.

public class PercentageUpdater:IUpdateModel
    {
        public float[] Percentages { get; set; }
        public string[] Labels { get; set; }
        public float Total { get; set; }
        public object UpdateModel(object model, string[] fields)
        {
            float partialTotal = 0f;
            foreach (float percentage in Percentages) partialTotal += percentage;
            if (partialTotal != 0f)
            {
                for (int index = 0; index < fields.Length; index++)
                {
                    new PropertyAccessor(model, fields[index]).Value = (Percentages[index]/partialTotal) * Total;
                }
            }
            return model;
        }

        public void ImportFromModel(object model, object[] fields, string[] fieldNames, object[] args = null)
        {
            Percentages=new float[fields.Length];
            Labels=new string[fieldNames.Length];
            Total = 0;
            for(int index = 0; index < fields.Length; index++ )
            {
                Percentages[index] = (float)fields[index];
                Labels[index]=new PropertyAccessor(model, fieldNames[index]).DisplayName;
                Total += Percentages[index];
            }
            if (Total != 0f)
            {
                for (int index = 0; index < fields.Length; index++)
                {
                    Percentages[index] = Percentages[index]*100 / Total;
                }
            }
        }
    }

As in the case of the IDisplayModel, IUpdateModel contains the methods form performing both the initial transformation and the transformation to apply when the page is posted. In our case when the page is posted, pecrcentages are transformed back into absolute values.

The first parameter of the UpdateModel method contains the initial model, while the second parameter contains all the properties to be used for creating the new data structure in string format (a string of names separated by dots). The PropertyAccessor class allows access to the properties located by such strings. In particolar we can setget the value of the property and we can retrive its attributes, and in particular the value of the DisplayName attribute that is used to define the label to apply when the property is rendered. Our Control uses the DisplayName to render the label of each percentage.

The first parameter of the ImportFromModel method contains the model, the second one the values of the fields to be used and the third one their names (as before names separated by dots). The last parameter is a set of optional arguments passed by the method that invokes the implementation of the ImportFromModel interface.

Once defined the IUpdateModel one can invoke the transformation it defines with the help of the InvokeUpdateTransform helper:

public static RenderInfo<NM> InvokeUpdateTransform<VM, M, NM>(
            this HtmlHelper<VM> htmlHelper,
            Expression<Func<VM, M>> expression,
            NM newModel,
            string[] expressions=null,
            object[] pars=null)
            where NM : IUpdateModel

public static RenderInfo<NM> InvokeUpdateTransform<VM, M, NM>(
            this HtmlHelper<VM> htmlHelper,
            RenderInfo<M> renderInfo,
            NM newModel,
            string[] expressions = null,
            object[] pars=null)
            where NM : IUpdateModel

 

Rendering is done as in the case of the IDisplayModel interface. Below an example of use in an investment application:

@Html.RenderIn("Investments",
                    Html.InvokeUpdateTransform(m => m,
                        new PercentageUpdater(),
                        new string[]
                        {
                            "PersonalData.ForexInvestment",
                            "PersonalData.FuturesInvestment",
                            "PersonalData.SharesInvestment"
                        }))
                 

 

Or with the Help of the RenderWith function:

 

                @{ var transformHelper =
                  Html.RenderWith(
                    Html.InvokeUpdateTransform(m => m.PersonalData,
                        new PercentageUpdater(),
                        new string[]
                        {
                            "ForexInvestment",
                            "FuturesInvestment",
                            "SharesInvestment"
                        }));
                 }
                 <div>Investments</div>
                <div>
                    <table>
                        <tr>
                          <td>Total Amount to Invest</td>
                          <td>@transformHelper.TextBoxFor(m => m.Total)</td>
                          <td>@transformHelper.ValidationMessageFor(m => m.Total, "*")</td>
                        </tr>
       
            @for (int index = 0; index < transformHelper.ViewData.Model.Percentages.Length; index++)
            {  
                            
             <tr>
               <td>@MvcHtmlString.Create(transformHelper.ViewData.Model.Labels[index])</td>
               <td>@transformHelper.RangeFor(m => m.Percentages[index], new { style = "text-align:right", min = 0d, max = 100d })</td>
               <td></td>
              </tr>
            }  
           
                    </table>

                </div>

 

That's all!

Last edited Jun 22, 2014 at 12:00 PM by frankabbruzzese, version 7

Comments

No comments yet.