Feb 22 2017

Custom Model Binding in Asp.net Core, 2: Getting Time + Client Time Zone Offset

Category: MVC | Asp.net core | Asp.netFrancesco @ 05:51


Custom Model Binding in Asp.net Core Mvc, 1

Custom Model Binding in Asp.net Core, 3: Model Binding Interfaces

The second post of the “Custom Model Binding” series is dedicated to “fixed representation” complex types, that is to types that for some reason cannot be handled as “simple types” but that are rendered with some pre-defined input fields. For a general discussion, on Custom Model Binding, please refer to the first post of the series. As an example of the general technique we will design and install a custom model binder for the DateTimeOffset .Net type. The DateTimeOffset is a generalization of the Datetime that contains also the Time Zone offset associated to the DateTime value. We can’t use the simple technique described in the first post of the series for two reasons:

  1. The DateTimeOffset is a system type so we can’t apply a custom TypeConverterAttribute to its definition.
  2. We need the DateTime and Time Zone offset be rendered into separate fields so that the user may edit date and time with a datetime-local Html5 input type. The Time Zone offset part will be stored in an hidden input field that will be handled automatically by some JavaScript. The hidden field must be updated each time the datetime-local input value changes because the Time Zone Offset is not constant but depends on the selected date (Daylight saving time on/off). On the server side we need a custom model binder that is able to combine both inputs into a DateTimeOffset instance.

More specifically, a DateTimeOffset that is rendered and then posted to the server again passes through the following transformations:

  1. DateTime and Time Zone offset are rendered in the two dedicated input fields described above.
  2. Once on the client side some JavaScript converts the DateTime/Time Zone offset pair into the client time zone, so that user may edit it in a datetime-local input field (we suppose the browser supports datetime-local)
  3. Each time the user changes the datetime value the associated Time Offset field is updated by some JavaScript
  4. Once the form is posted a custom model binder combines both the DateTime and the client Time Zone Offset into an unique DateTimeOffset.
  5. During the business processing the DateTimeOffset may be converted in other Time Zones, and possibly is rendered again, thus returning to step 1.

Summing up we need:

  • A custom TagHelper to render DateTimeOffset into the 2 input fields format described above
  • Some JavaScript for the conversion in the client Time Zone, and for updating automatically the hidden field
  • A Custom Model for DateTimeOffset types
  • Some wire up code to “connect” our custom model binder.

The above steps are a kind of standard, since custom model binders are always designed together with their compatible rendering counterparts.

A Custom TagHelper for DateTimeOffset

As a first step let create a new asp.net core project named “FixedRepresentationModelBinding” and let select “no authentication”.

Then create a new Folder called “TagHelpers”, and add a new DateTimeOffsetInputTagHelper class.

Our TagHelper should execute before the Asp.net Mvc standard input  TagHelper and should intercept all DateTimeOffset rendering:

 

using System;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Razor.TagHelpers;

namespace FixedRepresentationModelBinding.TagHelpers
{
    [HtmlTargetElement("input", Attributes = ForAttributeName,
        TagStructure = TagStructure.WithoutEndTag)]
    public class DateTimeOffsetInputTagHelper : TagHelper
    {
        public override int Order
        {
            get
            {
                return int.MinValue;
            }
        }
        private const string ForAttributeName = "asp-for";
        [HtmlAttributeName("type")]
        public string InputTypeName { get; set; }
        [HtmlAttributeName("value")]
        public string Value { get; set; }

        [HtmlAttributeName(ForAttributeName)]
        public ModelExpression For { get; set; }

        [HtmlAttributeNotBound]
        [ViewContext]
        public ViewContext ViewContext { get; set; }

        public override void Process(TagHelperContext context, TagHelperOutput output)
        {
            if(For.Metadata.UnderlyingOrModelType == typeof(DateTimeOffset))
            {
                var fullName = ViewContext.ViewData
                    .TemplateInfo.GetFullHtmlFieldName(For.Name);
                var dvalue = (DateTimeOffset?)For.Model;
                var offset = dvalue.HasValue ?
                    Convert.ToInt32(dvalue.Value.Offset.TotalMinutes) :
                    0;
                if (String.IsNullOrEmpty(InputTypeName))
                    InputTypeName = "datetime-local";
                if (!output.Attributes.ContainsName("type"))
                    output.Attributes.Add("type", InputTypeName);

                if (String.IsNullOrEmpty(Value))
                {
                    Value= dvalue.HasValue ?
                        string.Format("{0:00}-{1:00}-{2:00}T{3:00}:{4:00}:{5:00}",
                        dvalue.Value.Year, dvalue.Value.Month, dvalue.Value.Day,
                        dvalue.Value.Hour, dvalue.Value.Minute, dvalue.Value.Second) :
                        string.Empty;
                }
                if (!output.Attributes.ContainsName("value"))
                    output.Attributes.Add("value", Value);
                output.Attributes.Add("data-has-offset", "true");

                fullName = fullName.Length > 0 ? fullName + ".O" : "O";
                output.PostElement.AppendHtml("<input name='" +
                    fullName + "' type='hidden' value='"+offset+"' />");
            }
        }
    }
}

The Order property returning int.MinValue ensures that out TagHelper is the first to be executed.

The InputTypeName property is filled with the   input “type” if any is specified. If no type is specified it is automatically set to “datetime-local” and copied into the output attributes dictionary

Similarly, if no input “value” is specified, value is taken from the model contained in the For property, serialized with the yyyy-MM-ddTHH:mm:ss format (the format required by a datetime-local input field) and copied to the output dictionary.

The input field itself is not rendered by our TagHelper since its rendering is left to the standard input TagHelper that is executed immediately after our custom TagHelper. However,

the standard TagHelper will use the value and type we have set in the output attributes.

We add the attribute “data-has-offset=’true’” to the output attributes in order to retrieve all DateTimeOffset/rendering input fields from JavaScript.

The last instruction adds further Html to the Html rendered by the standard TagHelper, with the help of PostElement.AppendHtml. This way we add the input type hidden containing the Time Zone offset. The offset is expressed in minutes, since some time zones have an hours+minutes format. Finally, hidden input name is the same as the main input name but with a “.O” postfix.

For more information about custom TagHelpers, please refer to the official documentation.

Now we need to register all TagHelpers contained in the web site dll. Open the _ViewImports.cshtml file contained in the Views folder and add the following line:

@addTagHelper *, FixedRepresentationModelBinding

Now we can test our TagHelper.

Go to “Views\Home\Index.cshtm” and replace the whole content with:

@model Nullable<DateTimeOffset>

@{
    ViewData["Title"] = "Fixed representation Model Binding Test";
    var datetimeTest = Model;
}

<h2>@ViewData["Title"]</h2>

<form asp-action="Index" asp-controller="Home">
    <div class="form-group">
        <label asp-for="@datetimeTest">Local date/time:</label>
        <input asp-for="@datetimeTest" class="form-control">
    </div>
    <button type="submit" class="btn btn-default">Submit</button>
</form>

Model must be assigned to the variable datetimeTest otherwise the input field would have empty name and id.

This problem arises because in our very simple test the DateTimeOffset struct is not a property of a bigger model. With our trick the input field is named “datetimeTest” that must match the name of the parameter of the Home controller Index method, that we are going to substitute in the Home controller:

public IActionResult Index(DateTimeOffset? datetimeTest)
        {
            //datetimeTest = new DateTimeOffset(2000, 12, 2, 20, 0, 0,
            //    new TimeSpan(0, 5, 0, 0));
            return View(datetimeTest);
        }

In order to verify that our TagHelper is actually able to render a DateTimeOffset just un-comment the commented lines and run the project. If the selected browser supports datetime-local Httml5 inputs you should see a well formatted date+time. Moreover, looking at the Html sources the hidden input should contain 300 (5 hours times 60 minutes)

If everything is ok, comment again the un-commented lines.

Now is time to add the needed JavaScript. For simplicity, we may add it with an in-line script:

@section scripts{
    <script type="text/javascript">
        (function ($) {
            function dateFromISO8601(isoDateString) {
                var parts = isoDateString.match(/\d+/g);
                var isoTime = Date.UTC(parts[0], parts[1] - 1,
                    parts[2], parts[3], parts[4], parts[5]);
                var isoDate = new Date(isoTime);
                return isoDate;
            }
            function localDateFromISO8601(isoDateString) {
                var parts = isoDateString.match(/\d+/g);
                var isoDate = new Date(parts[0], parts[1] - 1,
                    parts[2], parts[3] || 0, parts[4] || 0, parts[5] || 0);
                

                return isoDate;
            }
            $("[data-has-offset]").each(function () {
                var jThis = $(this);
                var val = $.trim(jThis.val())
                var date = val ? dateFromISO8601(val) : null;
                //now we date have UTC + Server offset
                //since val is interpreted as UTC date but is not
                if (date) {
                    var offset = parseInt(this.nextElementSibling.value);
                    
                    date = new Date(date.getTime()
                        - offset * 1000 * 60);
                    //after subtracting server offset we have right date
                    var clientOffset = -date.getTimezoneOffset();
                    //we change sign because getTimezoneOffset returns UTC-local time
                    //while we need local time - UTC
                    this.nextElementSibling.value = clientOffset;
                    date = new Date(date.getTime()
                        + clientOffset * 1000 * 60);
                    //We add client offset because toISOString
                    //serialize UTD date not local date so we need to increase
                    //UTC date + clientOffset
                    jThis.val(date.toISOString().substr(0, 19));
                }
            })
            .change(function(evt){
                var target=evt.target;
                var val = target.value.trim();
                if (val) {
                    target.nextElementSibling.value =
                       -localDateFromISO8601(val).getTimezoneOffset();
                    //We must call localDateFromISO8601 because date is in local time
                    
                }
            });
        })(jQuery)
    </script>
}

We select all input fields with the “data-has-offset” attribute, that are the ones to process. First of all we convert the DateTime/Time-Zone-Offset pairs into the client time zone. Dates math is done by converting everything in milliseconds: the date with get Time, and the offset by multiplying it times 60*1000. Then we apply a change event handler to all selected inputs, in order to update the Time Zone offset each time the date+time changes.

A Custom Model Binder for DateTimeOffset

Custom model binders for “fixed representations” usually mimic the standard Mvc SimpleTypeModelBinder, the only difference being that the SimpleTypeModelBinder processes just one input field while “fixed representations” model binders process several input fields. As you can see in the source code the SimpleTypeModelBinder uses TypeConverters to deserialize types; for our simple model binder, instead, a DateTime and an integer parsers will be enough.

Let add a new “ModelBinding” folder and create a DateTimeOffsetModelBinder class. Our model binder must implement the interface IModelBinder that contains the single method BindModelAsync:

using System;
using System.Globalization;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.AspNetCore.Mvc.ModelBinding;

namespace FixedRepresentationModelBinding.ModelBinding
{
    public class DateTimeOffsetModelBinder: IModelBinder
    {
        public Task BindModelAsync(ModelBindingContext bindingContext)
        {
            if (bindingContext == null)
            {
                throw new ArgumentNullException(nameof(bindingContext));
            }

            var valueProviderResult = bindingContext.ValueProvider
                .GetValue(bindingContext.ModelName);
            var auxValueProviderResult = bindingContext.ValueProvider
                .GetValue(bindingContext.ModelName+".O");
            if (valueProviderResult == ValueProviderResult.None ||
                auxValueProviderResult == ValueProviderResult.None)
            {
                // no entry found for some of the two needed input fields
                //return without setting result, that is, return a failure
                return TaskCache.CompletedTask;
            }
            //store value retrieved for Date+Time in model state
            //this way, in case of format errors user may
            //correct input. No need to store also time zone offset,
            //since it cannot be edited by the user
            bindingContext.ModelState
                .SetModelValue(bindingContext.ModelName, valueProviderResult);

            try
            {
                var value = valueProviderResult.FirstValue;
                var auxValue = auxValueProviderResult.FirstValue;
                object model;
                if (string.IsNullOrWhiteSpace(value) ||
                    string.IsNullOrWhiteSpace(auxValue))
                {
                    //empty fields, means null model
                    model = null;
                }
                else
                {
                    int offset = int.Parse(auxValue, CultureInfo.InvariantCulture);
                    DateTime dt = DateTime.Parse(value, CultureInfo.InvariantCulture);
                    model = new DateTimeOffset(dt, new TimeSpan(0, offset, 0));
                }
                //if model is null and type is not nullable
                //return a required field error
                if (model == null &&
                    !bindingContext.ModelMetadata
                        .IsReferenceOrNullableType)
                {
                    bindingContext.ModelState.TryAddModelError(
                        bindingContext.ModelName,
                        bindingContext.ModelMetadata
                        .ModelBindingMessageProvider.ValueMustNotBeNullAccessor(
                            valueProviderResult.ToString()));

                    return TaskCache.CompletedTask;
                }
                else
                {
                    bindingContext.Result =
                        ModelBindingResult.Success(model);
                    return TaskCache.CompletedTask;
                }
            }
            catch (Exception exception)
            {
                //in case parsers throw a FormatException
                //add error to the model state.

                bindingContext.ModelState.TryAddModelError(
                    bindingContext.ModelName,
                    exception,
                    bindingContext.ModelMetadata);

                
                return TaskCache.CompletedTask;
            }
        }
    }
}

As a first step we use the value provider passed in the bindingContext to get the string values of our two input fields. If provided we use them to build a DateTimeOffset structure with the help of DateTime and int parse methods. In case of errors an error message is added to the ModelState. If the model binder returns TaskCache.CompleteTask without setting bindingContext.Result the framework assumes that model binding failed.

Installing Our Model Binder

There are various options to inform the framework on when to use our model binder:

  1. Decorate a ViewModel property or an Action Method parameter with a ModelBinderAttribute specifying the type of our model binder. In this case just that property or parameter will be bound with our model binder.
  2. Decorate the class or struct to bind with our model binder with the ModelBinderAttribute. In this case the model binder is used for each occurrence of the target type. Unluckily, we can’t follow this path since DateTimeOffset is a system type, so we can’t apply any attribute to it.
  3. Installing the model binder in the standard model binding pipeline. Also in this case the model binder is used for each occurrence of our target type.

Since we want our model binder be used on each occurrence of DateTimeOffset structures, and since option 2 is not viable we are forced to adopt option 3. Since in option 3 the model binder is not attached to any type or property we must instruct explicitly the standard model binding pipeline on when to use our model binder. This is done by defining an implementation of the IModelBinderProvider interface that exposes the single GetBinder method. Let call DateTimeOffsetModelBinderProvider our implementation, and place it in the already defined “ModelBinding” folder:

using System;
using Microsoft.AspNetCore.Mvc.ModelBinding;

namespace FixedRepresentationModelBinding.ModelBinding
{
    public class DateTimeOffsetModelBinderProvider : IModelBinderProvider
    {
        public IModelBinder GetBinder(ModelBinderProviderContext context)
        {
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }

            if (context.Metadata.UnderlyingOrModelType == typeof(DateTimeOffset))
            {
                return new DateTimeOffsetModelBinder();
            }

            return null;
        }
    }
}

The implementation is straightforward, if the current type to bind is either a DateTimeOffset or a DateTimeOffset? our provider returns an instance of our model binder, otherwise it returns null. A null result informs the model binding pipeline to try the next IModelBinderProvider implementation.

In fact all installed IModelBinderProvider implementations are placed into an IList contained in the ModelBinderProviders property of the MvcOptions object. Accordingly, we may have our model binder working by adding its associated provider to this IList.

The MvcOptions default object may be modified in several ways, The easiest way being to substitute:

services.AddMvc()

in the Startup.cs class, with:

services.AddMvc(o =>
{
    o.ModelBinderProviders.Insert(0, new DateTimeOffsetModelBinderProvider());
});

 

 

We added our provider on top of the list so it is tried first, to avoid that some other “standard” provider might select a different model binder.

Done!

Run the application, and when the home page appears, place a breakpoint in the Index method of Home controller that will receive our submitted data. Now select select a date+time, submit and verify what is received by the Index method: a DateTimeOffset containing the date and time selected in the browser, an the right client Time Zone offset!

That’s all for now

In a short time the last episode of our series: model binding interfaces!

Stay tuned!

Francesco

Tags: , ,

Jan 29 2017

Custom Model Binding in Asp.net Core Mvc, 1

Category: Asp.net core | Asp.net | MVCFrancesco @ 06:48


Custom Model Binding in Asp.net Core, 2: Getting Time + Client Time Zone Offset

Custom Model Binding in Asp.net Core, 3: Model Binding Interfaces

This is the first of a series of 3 posts on how to write custom model binders in Asp.net core Mvc. Three posts on three different ways to customize model binding in three different scenarios.

The concept of “simple type” plays a key role in model binding. A Simple type is not just a string, numbers, dates, or any other.net basic type but any type we decide to render as a string in a single input field. Below three examples of types that’s worth to render as “simple types”:

  1. An YouTube Url type, that contains the id of the you tube Url, but is rendered as an actual Url according to some format settings (it might implement IFormattable, for instance), and that “Model Binds” any correct YouTube Url.
  2. A Month type that contains month and year information and that is rendered in Month ISO format (YYYY-MM) into an HTML5 Month input.
  3. A Week type that contains week and year information and that is rendered in Week ISO format (YYYY-Www)  into an HTML5 Week input.

However, we may represent as “simple types” arbitrarily complex data structures by converting them from/to single strings in JSON format. Thus, they are “simple” just because they fit an a single input field.

Types that are not simple are “complex”. In turns, complex types may be split into 2 categories: fixed representation types, and variable representation types. Fixed representation types are rendered with a constant number of input fields whose names do not depend on the value of each instance. Roughly, they are always rendered with the “same input fields”.

For instance, we may represent the Month type mentioned above also as a fixed representation complex type using an input field for the year and another input field for the month.

IEnumerables and generic types must be considered variable representation types if their properties are rendered in different input fields. However, they may always be worked as simple types by using a single input field and JSON serialization.

Simple types, fixed representation types and variable representation types need different model binding techniques.

Variable representation types are the more difficult to deal with and will be analyzed in the last post of the series, since, in general, they require the model binder be invoked recursively to bind their sub-parts.

Simple types are the easier to model bind since it is enough to specify how they must be serialized and de-serialized. The remainder of this post is dedicated to them. We use the Month type example.

As a first step let create a new asp.net core project named “SimpleModelBinding” and let select “no authentication”.

Then create a new Folder called “Types”, and add a new Month structure:

public struct Month
{
    public uint YearNumber { get; private set; }
    public uint MonthNumber { get; private set; }
   

    public Month(uint yearNumber, uint monthNumber)
    {
        YearNumber = yearNumber;
        MonthNumber = monthNumber;
        if (MonthNumber < 1 || MonthNumber > 12) throw new FormatException();
        if (yearNumber < 1) throw new FormatException();
    }
    public static Month MinValue { get { return new Month(1, 1); } }
    public static Month MaxValue { get { return new Month(9999, 12); } }
}
Add conversion to/from DateTime:
public DateTime ToDateTime()
{
    return new DateTime((int)YearNumber, (int)MonthNumber, 1);
}

public static Month FromDateTime(DateTime t)
{
    return new Month((uint)t.Year, (uint)t.Month);
}

public static implicit operator Month(DateTime t)
{
    return FromDateTime(t);
}

public static implicit operator DateTime(Month m)
{
    return m.ToDateTime();
}

Finally add also ToString and Parse methods:

public override string ToString()
{
    return string.Format("{0:0000}-{1:00}", YearNumber, MonthNumber);
}

public static Month Parse(string s)
{
    return FromDateTime(DateTime.Parse(s));
}

public static bool TryParse(string s, out Month m)
{
    DateTime dt;
    var res = DateTime.TryParse(s, out dt);
    if (res) m = FromDateTime(dt);
    else m = new Month(1, 1);
    return res;
}

Add month and year may be useful too:

public Month AddMonths(int months)
{
    if (months == 0) return this;
    months = (int)MonthNumber + months;
    var years = months / 12;
    months = months % 12;
    if (months < 0)
    {
        months += 12;
        years--;
    }
    return new Month((uint)(years + YearNumber), (uint)(months));

}
public Month AddYears(int years)
{
    return new Month((uint)(YearNumber + years),
        MonthNumber);
}

If you want you may also define all comparison operators.

Since we already have adequate parsing and ToString methods we are very close to our goal.

We need just to inform Asp-net core Mvc model binding engine on which functions to call to serialize/de-serialize the Month type. This, doesn’t require the definition of a custom model binder but the definition of a TypeConverter that is able to convert between Month and string. In fact, Asp-net core Mvc model binding engine assumes that a type is “simple” exactly when a similar TypeConverter exists.

Let add a new class called MonthTypeConverter to our Types folder:

using System;
using System.ComponentModel;
using System.Globalization;

namespace SimpleModelBinding.Types
{
    public class MonthTypeConverter : TypeConverter
    {
        public override bool CanConvertFrom(
            ITypeDescriptorContext context,
            Type sourceType)
        {
            return sourceType == typeof(string) ||
                sourceType == typeof(DateTime);
        }
        public override object ConvertFrom(ITypeDescriptorContext context,
            CultureInfo culture, object value)
        {
            return value is string ? Month.Parse(value as string) :
                Month.FromDateTime((DateTime)value);
        }
        public override bool CanConvertTo(ITypeDescriptorContext context,
            Type destinationType)
        {
            return destinationType == typeof(string) ||
                destinationType == typeof(DateTime);
        }
        public override object ConvertTo(ITypeDescriptorContext context,
            CultureInfo culture, object value, Type destinationType)
        {
            if (destinationType == typeof(string))
                return ((Month)value).ToString();
            return ((Month)value).ToDateTime();
        }
    }
}

The code is self explicative two bool functions that return true if the conversion from/to a type is possible and two functions that actually perform the conversion from/to. I enabled conversion from/to both DateTime and string but for the purpose of model binding conversion from strings is enough.

Now let declare that this type converter is associated with the Month struct by decorating the Month definition as shown below:

[System.ComponentModel
    .TypeConverter(typeof(MonthTypeConverter))]
public struct Month
{
    ...
    ...

Done! Now we need just to test our Month struct.

Go to the HomeController and substitute the Index method with:

 

public IActionResult Index(Nullable<Month> monthTest)
{
    return View(monthTest);
}

Then go to “Views\Home\Index.cshtm” and replace the whole content with:

@model Nullable<SimpleModelBinding.Types.Month>
@{
    ViewData["Title"] = "Simple Type Model Binding Test";
    var monthTest = Model;
}

<h2>@ViewData["Title"]</h2>

<form asp-action="Index" asp-controller="Home" >
    <div class="form-group">
        <label asp-for="@monthTest">Month:</label>
        <input asp-for="@monthTest" type="month" class="form-control">
    </div>
    <button type="submit" class="btn btn-default">Submit</button>
</form>

Model must be assigned to the variable monthTest otherwise the input field would have empty name and id.

This problem arises because in our very simple test the month struct is not a property of a bigger model. With our trick the input field is named “monthTest” that must match the name of the parameter of the Home controller Index method.

Now select a browser that supports input of type Month (last versions of Edge, Safari, Chrome, Firefox, will do) and run the project.

Select a month, and before submitting place a breakpoint in the Index action method. Once the breakpoint is hit you should see that the month parameter contains the month you have chosen in the browser:

SimpleModelBinderDebug

 

 

 

 

 

In a similar way you might define a Week type and a custom model binding for it.  The Asp.net core version of the Mvc Controls Toolkit, contains Week and Month types together with custom model binding, extensions of RangeAttribute and all validation rules, fallbacks for browsers that do not support any HTML5 and more…

 

That’s all for now

In a short time the second post of our series.

Stay tuned!

Francesco

Tags: , , ,

Jan 2 2017

Defining Custom Client Validation Rules in Asp.net Core MVC

Category: Asp.net | Asp.net core | MVCFrancesco @ 22:15

This time my post has been published on the MSDN Blog! Enjoy it!

……..The way Asp.net MVC handles client side validation has relied on unobtrusive validation since Asp.net MVc 3. All client validation rules for each input field are extracted from various sources, including validation attributes, and the type of property to be rendered and encoded in the content of Html5 data…….Read full article

Tags: , , , ,

Dec 2 2016

Increasing productivity in your Asp.net core projects

Category: Asp.net | Asp.net core | MVCFrancesco @ 07:47

Often, during web development we complaint about the time we spend in “standard tasks” and dream of coding just our application peculiarities, thus concentrating just on our specific problem. For sure project templates and scaffolding save some “set up time” and avoid errors, but they do not save too much coding time.

In this post I’ll describe some good practices to increase web pages productivity at the price of some more set up time, and  how the new Asp.net core version of the Mvc Controls Toolkit  may help you with this. For the ones new to the Mvc Controls Toolkit, the Mvc Controls Toolkit is an open source project released with the same license of the whole Asp.net core project, so you may freely use it with no limitations and may also participate to its improvement.

The problem with most of “standard tasks” is that they are not “completely standard” so they cannot be completely automated. However, for each of them we may somehow define and automate a kind of “average task” where we can reach 90% of all actual tasks with small manual modifications. We increase further productivity if these manual modifications are achieved with name conventions, and declarations, such as, for instance, data annotations, options objects, and Tag-Helper attributes.

So for instance you might spend some set-up time to define a few “standard submit forms”, an automated procedure to create them given a few declarative information, and then use them to produce quickly, say 90%, of your views. That 90% is not a quite random number, but the result of statistics I carried out with the help of some of my customers. In fact, on the average 80-90% of all Views of a typical business application are quite standard and just 10-20% require a more complex design. In turn, among the last 10-20% just 3-7% are peculiar to the specific project since the remaining 7-13% consist of instances of general problems, so we may consider automating also part of them.

In the remainder of this post I’ll analyze different project areas that might benefit from this approach and that is worth to “attack”, pointing out the kind of automation to adopt and how the Mvc Control toolkit might help you in the task.

Business and DB Layer

The simplest optimization in this area is avoiding manual copies of DB model properties into ViewModel/DTO properties:

.Select(m => new InvoiceViewModelShow
                {
                    Id = m.Id,
                    Amount = m.Amount,
                    Reference = m.Reference,
                    FileName = m.FileName,
                    Date = m.Date,
                    Start = m.Start,
                    Stop = m.Stop,
                    ShipName = m.Ship.Name,
                    Paid = m.Paid,
                    Approved = m.Approved,
                    ....
                    ...,

                }).ToListAsync();

You might say that’s easy, with AutoMapper! Unluckily you can’t use AutoMapper since the above expression doesn’t perform a copy operation between two in-memory objects, but it is translated into an SQl query. Extracting the whole DB objects from the database and then copying them into DTO/ViewModels with AutoMapper would be unefficient, since you might extract a lot information you don’t need from the Data Base. So, the only solution is creating dynamically LinQ queries.

The MvcControlsToolkit.Core.Business Nuget package that is a completely independent part part of the Mvc Controls Toolkit contains a projection operator that do exactly this job. It uses a same name convention and nested objects like Ship.Name are mapped by concatenating all property names: Ship.Name ===> ShipName. User must specify just properties that doesn’t conform to the name convention. Expression content is not limited to a creation operator but may contain also nested conditions with several ViewModel creations in the various condition branches, like in the example below:

m => m.Maintenance == null ?
   new ProductViewModelDetail{}:
   new ProductMaintenanceViewModelDetail{
       MaintenanceYearlyRate = (decimal)m.Maintenance.YearlyRate
   }

Please notice that the ProductViewModelDetail constructor doesn’t contain any property since all properties may be inferred using the name convention.

Expressions are cached in various ways to avoid the performance cost of re-creating them each time they are used.

Please refer to the official documentation for more information.

Further speed-up may be achieved by defining generic CRUD Repositories that takes care of all main single-table operations: paged retrieval with projection on a ViewModel, detail retrieval, and all update operations that receive directly ViewModels/DTOs as parameters.

MvcControlsToolkit.Core.Business contains a DefaultCRUDRepository<Ctx, M> class where Ctx is the DB context type and M the DB model type, that is able to update automatically also entities related to M. It implements the not generic interface ICRUDRepository, so Ctx, and M may be hidden to the presentation layer. In fact all ICRUDRepository operations have the format: Add<T>, Delete<U>, Update<T>, UpdateList<T>. UpdateList<U>, etc. Where all generics are ViewModel/DTO types. Please refer to the documentation for more details, and further useful properties .

You may inherit from DefaultCRUDRepository<Ctx, M> or build your own generic class and then add also further operations that recur in several places, and with several types in your application.

Controllers

You may speed up Controller code implementation in several ways, namely:

  • By creating generics based abstract base controllers to inherit from.
  • By implementing ActionFilters for recurring controller pre/post processing.
  • By implementing MiddleWare that pre-processes all requests
  • By implementing controller utilities classes.

Base controller generics are usually ViewModels/DTOs types. In case of WebApi controllers their main purpose is handling client/server communication protocols. In fact for better modularity and reliability all application WebApi controllers should be based on well defined abstract protocols designed during your application preliminary analysis.

Also standard controllers may benefit of inheriting from base controllers with “standard action methods”, to handle recurrent “standard” Ajax update tasks , such as showing a detail show/edit modal, and updating a modified grid item.

Both type of controllers need a JavaScript library counterpart that interfaces them with the client side. So base controllers adoption factor out and “standardize” both server side and client side code.

Base controllers “standard” action methods may be connected with problem specific business logic either by calling protected virtual methods the inheriting controller may override, or by accepting implementations of "standard" repository interfaces as constructor arguments. Generic Repository interfaces may be instantiated by problem specific implementations passed by the inheriting controllers, which in turn receive them in their constructors through dependency injection.

At moment Mvc Controls toolkit contains just a single standard ServerCrudController<VMD, VMS, D> controller that offers Ajax assistance to Views showing item lists. More specifically, it offers automatic Ajax assistance to pages in-line Ajax item updates, and in showing detail Show/Edit modals. VMD is the item detail ViewModel type , VMS the in-line item ViewModel, and D the principal key type of both of them.

ServerCrudController<VMD, VMS, D> uses the ICRUDRepository interface to communicate with the business layer, but it has also various properties and methods the inheriting controller may override to customize further action methods behavior. For detail please refer to the official documentation.

In future versions we will add also WebApi controllers implementing useful protocols, as well as facilities to get easily filtering and sorting conditions passed in the query string in OData format.

SercerCrudController is a good example of how to use a standard controller, and how to connect it with business logics:

public class CGridsController :
    ServerCrudController<ProductViewModelDetail,
        ProductViewModel, int?>
{
    public CGridsController(ProductRepository repository,
        IStringLocalizerFactory factory, IHttpContextAccessor accessor)
        : base(factory, accessor)
    {
        Repository = repository;
    }
    public async Task<IActionResult> Products(int? page)
    {
        int pg = page.HasValue ? page.Value : 1;
        if (pg < 1) pg = 1;

        var model = new ProductlistViewModel
        {
            Products = await Repository.GetPage<ProductViewModel>(
            null,
            q => q.OrderBy(m => m.Name),
            pg, 5)
        };
        return View(model);
    }      
}

repository of type ProductRepository injected with DI in the inheriting controller constructor is assigned to the Repository base controller property of type ICRUDRepository. This is enough to connect ServerCrudController  ajax-assistance action methods to the application business logic. Then, we add the Action Method that prepares data for the View that is enhanced with Ajax assistance.

For sure base generic controllers are the most powerful tool to factor out controller code, but request pre/post processing with either MiddleWare  or per-controller ActionFilters may ensure further improvements and contribute to a cleaner modular design. If you have doubts about how to implement your pre/post processing consider that with the new Asp.net core 1.1 MiddlewareFilter you may use any MiddleWare also as an Action filter. Examples of how useful request pre/post processing code is factored-out by Middleware are: authentication cookies processing, culture selection processing, and in general user preferences processing discussed in the last section of this post. In the next version of the Mvc Controls Toolkit we will add Middleware for processing also filtering/sorting information contained in the query string in OData format.

Views

Usage of standard templates that render data according to ViewModel data annotations and/or parameters drastically reduces the development time, and contribute to Web Pages standardization. In turn, Web pages standardization reduce the time needed for a user to learn how to use the web application.  One may increase, the “scope” of a template by allowing the developer to customize part, of it, as for instance, how a single property is rendered.

Mvc Controls Toolkit templates rendering is organized around RowTypes and Columns. RowTypes contain overall infos and templates for rendering a whole data item while Columns contain infos and templates for rendering a single property. RowTypes templates call Column templates, so the developer may provide “standard” RowTypes and Column templates, and then on each page he/she may control how to display data items by providing adequate settings for RowType/Columns, and by providing some custom Column template. RowTypes, and Colum settings are automatically extracted from data annotations and property types but user my override them by providing supplementary infos in RowType/Column description TagHelpers.

Here an example of an edit form rendered with a standard bootstrap-based layout and whose settings are entirely extracted from data annotations and property types.Order, input are displayed is based on the Order property of the DisplayAttribute, while the number of Bootstrap grid system slots assigned to each input is based on percentage width specifications contained in ColumnLayoutAttributes. Developer may provide specifications for several screen widths. The kind of input is selected automatically by the standard Column template based on the property type/DataTypeAttribute. So, for instance,  enums are rendered with a selects and boolean with a checkboxes.The above detail-form TagHelper uses global templates defined with partial views inside the Mvc Controls Toolkit dlls. However, in general, user may specify the name of a different partial view, or the name of a ViewComponent  in the detail-form  tag itself. The developer may also override globally the standard template Partial Views by adding Partial Views with the same names in the Shared folder.

Here an example where rendering has been customized with the help of row-type and column TagHelpers. The exterrnal-key-remote tag specifies that the TypeId property contains an external key, and provides all needed information for the user to select the right key value with an autocomplete, by typing the display names associated to the various keys. Settings specified in the row-type tag are applied only if the ViewModel is the ProductMaintenanceViewModelDetail subclass of the  ProductViewModelDetail base ViewModel, otherwise settings specified directly in the detail-form tag are applied.

Here “standard templates” featuring a grid with edit in-line Ajax capabilities based on the Ajax assistance furnished by the ServerCrudController<VMD, VMS, D>.

User options

Another area where you may achieve further development-time optimizations is User option handling. Having, a centralized register containing all user options with the possibility to inject them where needed via dependency injection may help to factor out some code and simplify  your overall design. Basically you may have  user options sources and stores all attached to an unique global per-request register. Sources may be input fields, url parameters and cookies for unlogged user and User Claims or custom tables for logged users. Cookies, User Claims, and custom tables may work also as stores where to save user options received through input fields or url parameters.

The MvcControlsToolkit.Core.Options nuget package (that is a completely independent part of the Mvc Controls Toolkit) offers all services described above. You may collect user options provided through input forms or url parameters without writing controller code since they are automatically processed by MvcControlsToolkit.Core.Options middleware. Then you may save them in cookies, user claims and/or custom tables with no need to write any code. Developer may configure all above services with a few simple declarative options in the MvcControlsToolkit.Core.Options middleware. It is a typical example of how MiddleWare may speed up controllers development.

For more information please refer to the official documentation.

 

That’s all for now!

More examples and tutorials in posts to come!

Francesco

 

.

 

 

 

Tags: , , ,

Jul 17 2016

Available Asp.net core version of the Mvc Controls Toolkit Live Examples!

New 1.0.1 bugs fix release of the Mvc Controls toolkit! Available also Live Examples at this link!

Enjoy!  & Stay tuned

In a short time ajax update server grid and batch update server grid

Francesco

Tags: , , , ,

Jul 1 2016

Ap.net Core 1.0.0 RTM Version of the Mvc Controls Toolkit Ready

Category: Asp.net core | WebApi | MVC | Htnl5 fallback | Asp.netFrancesco @ 20:00

The first Asp.net Core 1.0.0 RTM release of the Mvc Controls Toolkit is available for download! This is the link that explains how to install it, while a starting example may be downloaded here. Pay attention! You must follows all installation steps also for running the example, since the example among other things has also the purpose of becoming familiar with installation, and configuration stuffs.

Enjoy!  & Stay tuned

In a short time tutorials, live examples, and a complete documentation web site

Francesco

Tags: , , ,

Jun 25 2016

Ap.net Core Rc2 Version of the Mvc Controls Toolkit Ready

Category: Asp.net core | WebApi | Htnl5 fallback | MVC | Asp.netFrancesco @ 02:40

The first Asp.net Core Rc2 release of the Mvc Controls Toolkit is available for download! This is the link that explains how to install it, while a starting example may be downloaded here. Pay attention! You must follows all installation steps also for running the example, since the example among other things has also the purpose of becoming familiar with installation, and configuration stuffs.

Enjoy!  & Stay tuned

In a short time tutorials, live examples, and a complete documentation web site

Francesco

Tags: , , ,

Nov 20 2015

New Mvc6 Controls Toolkit

Category: TypeScript | JavaScript | MVC | Asp.netFrancesco @ 04:50

Web development changed dramatically in the last few years and the Mvc Controls Toolkit team run after all changes to offer a state of the art toolkit, but now it is time to redesign the whole toolkit from the scratch! JavaScript world evolved, and web applications reliy always more on client side techniques. New JavaScript frameworks appears and evolve quickly, thus the need of a better separation between server side and client side world. The new Mvc 6 Controls Toolkit answers to these new Web Development boundaries are: TypeScript, a better separation of JavaScript world coordinated with server side C# code through trans-compilation of C# classes and attributes, native support of the most common client side frameworks like Angular.js, and Knockout.js, and open support for other client side framework by means of providers,…and more!

 

Please leave you feedback on the specifications of the new Mvc 6 Controls Toolkit!

 

Francesco

Tags: , , , , , , ,

Oct 31 2015

Building Complex Controls with Asp.Net MVC 6 TagHelpers

Category: Asp.net | MVCFrancesco @ 04:09

Asp.Net Mvc 6 proposes a new option to Html Helpers: Tag Helpers. Tag Helpers are similar to Html Helpers but they use an Html tags – like syntax. Basically, they are custom tags with custom attributes that are translated into standard Html tags during server side Razor processing.

They are somehow similar to Html5 custom elements but they are processed on the server side, so they don’t need JavaScript to work properly and their code is visible to search engine robots. Html 5 elements are not fully supported by all main browsers, but they are somehow simulated on all browsers by several JavaScript frameworks, like, for instance, Knockout.js.

Thus, one might plan to build TagHelper based Mvc controls that create their final Html either on the server side or on the client side with the help of a JavaScript framework that supports custom elements. More specifically, the same Razor View might generate either the final Html or some custom elements based code to be interpreted on the client side by a JavaScript framework, depending on some settings specified either in the View itself, or in the controller, or in some configuration file. Both server and client side generation have their vantages and disadvantages(among them server controls are visible to search engines, but client controls are more flexible), so the possibility to change between them without changing the Razor code appears very interesting.

This post is not a basic tutorial on Tag Helpers, but a tutorial on how to implement advanced template based controls, like grids, menus, or tree-views with TagHelpers. An introduction to TagHelpers is here; please read it if you are new to custom Tag Helpers implementation.

This tutorial assume you have:

  1. A Visual Studio 2015 based development environment. If you have not installed VS 2015, yet, please refer to my previous post on how to build your VS 2015 based development environment.
  2. Asp.Net 5 beta8 installed. Instructions on how to move to beta8 may be found here.

 

Template Based Controls

Complex controls like TreeViews and Grids use templates to specify how each node/row is rendered. Usually, they have also default templates, so the developer needs to specify templates just for the “pieces” that need custom rendering. For instance, in the case of a grid a developer wishing to use the default row template might need to specify templates just for a few columns that need custom rendering. Controls may allow several more custom templates, such as a custom pager template, a custom header template, a footer template and so on.

In this tutorial I’ll show just the basic technique for implementing templates with TagHelpers. For this purpose we define a simple <iterate>…</iterate> TagHelper that instantiates a template on all elements of an IEnumerable. Moreover, all inputs field created in the output Html will have the right names for the Model Binder to read back the IEnumerable when the form containing the <iterate> tag is submitted (see here if you are new to model binding to an IEnumerable).

The Test Project

Open VS 2015 and select: File –> New –> Project…

Project

Now select “ASP.NET Web Application” and call the project “IterateTagHelperTest” (please use exactly the same name, otherwise you will not be able to use my code “as it is”  because of  namespace mismatches).

MVC6

Now choose “Web Application” under “ASP.NET Preview Templates”, and click “OK” without changing anything else.

 

We will test our new TagHelper with a new View handled by the HomeController.

We need a ViewModel, so as a first step go to the “ViewModels” folder and add a child folder named “Home” for all HomeController ViewModels.

Then add a new class called “TagTestViewModel.cs” to this folder.

Finally, delete the code created by the scaffolder and add the following code:

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Threading.Tasks;
  5.  
  6. namespace IterateTagHelperTest.ViewModels.Home
  7. {
  8.     public class Keyword
  9.     {
  10.         public string Value { get; set; }
  11.         public Keyword(string value)
  12.         {
  13.             Value = value;
  14.         }
  15.         public Keyword()
  16.         {
  17.             
  18.         }
  19.  
  20.     }
  21.     public class TagTestViewModel
  22.     {
  23.         public IEnumerable<Keyword> Keywords { get; set; }
  24.     }
  25. }

It is a simple ViewModel containing an IEnumerable to test our iterate TagHelper.

Now move to the HomeController and add the following using:

  1. using IterateTagHelperTest.ViewModels.Home;

 

Then add a Get and a Post action methods to test our TagHelper (without modifying all other action methods):

  1. [HttpGet]
  2. public IActionResult TagTest()
  3. {
  4.     return View(new TagTestViewModel
  5.     {
  6.         Keywords = new List<Keyword> {
  7.         new Keyword("ASP.NET"),
  8.         new Keyword("MVC"),
  9.         new Keyword("Tag Helpers") }
  10.     });
  11. }
  12. [HttpPost]
  13. public IActionResult TagTest(TagTestViewModel model)
  14. {
  15.     return View(model);
  16. }

 

Now go to the Views/Home folder and add a new View for the newly created action methods. Call it “TagTest” to match the action methods name.

Remove the default code and substitute it with:

  1. @model IterateTagHelperTest.ViewModels.Home.TagTestViewModel
  2. @{
  3.     ViewBag.Title = "Tag Test";
  4. }
  5.  
  6. <h2>@ViewBag.Title</h2>

We will insert the remainder of the code after the implementation of our TagHelper. Now we need just a title to run the application and test that everything we have done works properly.

Before testing the application we need a link to reach the newly defined View. Open the Views/Shared/_Layout.cshtml default layout page and locate the Main Menu:

 

  1. <ul class="nav navbar-nav">
  2.     <li><a asp-controller="Home" asp-action="Index">Home</a></li>
  3.     <li><a asp-controller="Home" asp-action="About">About</a></li>
  4.     <li><a asp-controller="Home" asp-action="Contact">Contact</a></li>
  5. </ul>

 

And add a new menu item for the newly created page:

  1. <ul class="nav navbar-nav">
  2.     <li><a asp-controller="Home" asp-action="Index">Home</a></li>
  3.     <li><a asp-controller="Home" asp-action="About">About</a></li>
  4.     <li><a asp-controller="Home" asp-action="Contact">Contact</a></li>
  5.     <li><a asp-controller="Home" asp-action="TagTest">Test</a></li>
  6. </ul>

Now run the application and click on the “Test” top menu item: you should go to our newly created test page with title “Tag Test”.

Now that our test environment is ready we may move to the TagHelper implementation!

Handling Template Current Scope

A template is the whole Razor code enclosed within a template-definition TagHelper. For instance, in the case of a grid we might have a <column-template asp-for=”….”> </column-template > TagHelper that encloses custom column templates, a <header-template > </header-template > that encloses custom header templates, and so on. Since our simple <iteration> TagHelper,  includes a single template we will take the whole <iteration> TagHelper content as template. In the case, of more complex controls the main control TagHelper  usually includes several children template-definition TagHelpers.

According to our previous definition, each template may contain both: tags, Razor instructions and variable definitions. Since the same template is typically  called several time  we need a way to ensure that the scope of all Razor variables is limited to the template itself. Moreover, all asp-for attributes inside the template must not refer to the Razor View ViewModel but to the current model the template is being instantiated on.

Something like:

  1.      <iterate asp-for="Keywords">
  2.          @using (var s = Html.NextScope<Keyword>())
  3.          {
  4.              var m = s.M;
  5.              <div class="form-group">
  6.                  <div class="col-md-10">
  7.                             <input asp-for="@m().Value" class="form-control" />
  8.                  </div>
  9.              </div>
  10.          }
  11.      </iterate>

should do the job. Where Html.NextScope<Keyword>() takes the the current scope passed by the father <iterate> TagHelper put it in a variable, and makes it the active scope. s.M() takes the current model from the current scope. s.M must be a function to avoid that m is included in the input names(Keyword[i].m.Value, instead of the correct Keyword[i].Value).

When the template execution is terminated the scope object is disposed, and its dispose method removes it form the top of the scopes stack, and re-activate the previously active scope, if any(we need a stack, because templates might nest).

The <iteration> TagHelper  takes the list of Keywords thanks to its asp-for=”Keywords” attribute, and call the template on each object of the list:

  1. foreach (var x in model)
  2. {
  3.     TemplateHelper.DeclareScope(ViewContext.ViewData, x, string.Format("{0}[{1}]", fullName, i++));
  4.     sb.Append((await context.GetChildContentAsync(false)).GetContent());
  5.    
  6. }

The TemplateHelper.DeclareScope helper basically put all scope information into an item of the Razor View ViewData dictionary, where the previously discussed Html.NextScope method can take it. The current scope contains the current model, and the HtmlPrefix to be added to all names. In our case Keywords[0]…Keywords[n]. This way all input controls will have names like “Keywords[i].Value”, instead of simply “Value”. This is necessary for model binding to work properly when the form is submitted.

When the scope is activated by Html.NextScope  the HtmlPrefix is temporarily put into the TemplateInfo.HtmlFieldPrefix field of the Razor View ViewData dictionary. This is enough to ensure, it  automatically prefixes all names. When the current scope is deactivated  the previous TemplateInfo.HtmlFieldPrefix value is restored.

In the next two sections i give all implementation details of both the scope stack , and of the <iterate> TagHelper.

Implementing the Scope Stack

We insert the Scope Stack code in a new folder. Go to the Web Project root and add a new folder called HtmlHelpers.

We start with the definition of an interface containing all scope informations. Under the previously created HtmlHelpers folder add a new interface called ITemplateScope.cs. Then substitute the scaffolded code with:

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Threading.Tasks;
  5. using Microsoft.AspNet.Mvc.ViewFeatures;
  6.  
  7. namespace IterateTagHelperTest.HtmlHelpers
  8. {
  9.     public interface ITemplateScope : IDisposable
  10.     {
  11.         string Prefix { get; set; }
  12.         ITemplateScope Father { get; set; }
  13.     }
  14.     public interface ITemplateScope<T> : ITemplateScope
  15.     {
  16.         Func<T> M { get; set; }
  17.     }
  18. }

The interface contains a generic that will be instantiated with the template model type. The model is returned by a function in order to get the right names inside the template (this way all names start from the first property after the function, see previous section). All members that do not depend from the generic are grouped into the not-generic interface ITemplateScope, the final interface inherit from. Moreover, ITemplateScope inherit from IDisposable since it must be used within a using statement. Together with the model the scope contains the current HtmlPrefix(see previous section), and a pointer to the father scope(this way, a template has access also to the father template model in case of nested templates).

Now we are ready to implement the Html.NextScope<T>   and TemplateHelper.DeclareScope methods. The whole implementation is based on an active scopes stack whose stack items are instances of a class that is declared private inside the TemplateHelper static class:

private class templateActivation
{
    public string HtmlPrefix { get; set; }
    public object Model { get; set; }
    public ITemplateScope Scope { get; set; }
}

The class contains the father HtmlPrefix to restore when the scope is deactivated, a not-generic pointer to the scope interface, and the scope model as an object since the model is not accessible through the ITemplateScope interface. The same class is used to pass scope information among method calls.

The DeclareScope method store new scope information in the ViewData dictionary

private const string lastActivation = "_template_stack_";

 

public static void DeclareScope(ViewDataDictionary vd, object model, string newHtmlPrefix)
{
    var a = new templateActivation
    {
        Model = model,
        HtmlPrefix = newHtmlPrefix
    };
    vd[lastActivation] = a;
}

Then NextScope<T> takes it to activate a new scope:

private const string lastActivation = "_template_stack_";
private const string externalActivation = "_template_external_";

 

public static ITemplateScope<T> NextScope<T>(this IHtmlHelper helper)
{
    var activation = helper.ViewContext.ViewData[lastActivation] as templateActivation;
    if (activation != null)
    {
        helper.ViewContext.ViewData[lastActivation] = null;
        var stack = helper.ViewContext.ViewData[templateStack] as Stack<templateActivation>;
        ITemplateScope father = null;
        if (stack != null && stack.Count > 0)
        {
            father = stack.Peek().Scope;
        }
        if (father == null)
        {
            father = helper.ViewContext.ViewData[externalActivation] as ITemplateScope;
        }
        return new TemplateScope<T>(helper.ViewContext.ViewData, activation)
        {
            M = () => (T)(activation.Model),
            Prefix = activation.HtmlPrefix,
            Father = father
        };
    }
    else return null;
}

If a newly created scope is found a new instance of the TemplateScope<T> class that implements the ITemplateScope<T> class is created. The TemplateScope<T> class is declared as private inside the TemplateHelper class. The stack is accessed just to find the father ITemplateScope. If the stack is empty the method try to get the father scope from another ViewData entry, that might be used to keep activation information across partial view calls(not implemented in this example).

The stack push is handled in the TemplateScope<T> constructor:

  1. public TemplateScope(ViewDataDictionary vd, templateActivation a)
  2. {
  3.     this.vd = vd;
  4.     a.Scope = this;
  5.     PushTemplate(a);
  6.  
  7. }

While the stack pop is handled by the TemplateScope<T> Dispose method:

  1. public void Dispose()
  2. {
  3.     PopTemplate();
  4. }

In order to add the whole implementation described above to your project, go to the previously created HtmlHelpers folder and add a class called TemplateHelper.cs. Then replace the scaffolded code by the code below:

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Threading.Tasks;
  5. using Microsoft.AspNet.Mvc.Rendering;
  6. using Microsoft.AspNet.Mvc.ViewFeatures;
  7.  
  8. namespace IterateTagHelperTest.HtmlHelpers
  9. {
  10.  
  11.     public static class TemplateHelper
  12.     {
  13.         private const string templateStack = "_template_stack_";
  14.         private const string lastActivation = "_template_stack_";
  15.         private const string externalActivation = "_template_external_";
  16.         private class templateActivation
  17.         {
  18.             public string HtmlPrefix { get; set; }
  19.             public object Model { get; set; }
  20.             public ITemplateScope Scope { get; set; }
  21.         }
  22.         private class TemplateScope<T> : ITemplateScope<T>
  23.         {
  24.  
  25.  
  26.             public TemplateScope(ViewDataDictionary vd, templateActivation a)
  27.             {
  28.                 this.vd = vd;
  29.                 a.Scope = this;
  30.                 PushTemplate(a);
  31.  
  32.             }
  33.             private ViewDataDictionary vd;
  34.             public string Prefix { get; set; }
  35.             public Func<T> M { get; set; }
  36.             public ITemplateScope Father { get; set; }
  37.             public void Dispose()
  38.             {
  39.                 PopTemplate();
  40.             }
  41.             private void PushTemplate(templateActivation a)
  42.             {
  43.                 var activation = new templateActivation
  44.                 {
  45.                     HtmlPrefix = vd.TemplateInfo.HtmlFieldPrefix,
  46.                     Model = a.Model,
  47.                     Scope = a.Scope
  48.                 };
  49.                 var stack = vd[templateStack] as Stack<templateActivation> ?? new Stack<templateActivation>();
  50.                 stack.Push(activation);
  51.                 vd[templateStack] = stack;
  52.                 vd.TemplateInfo.HtmlFieldPrefix = a.HtmlPrefix;
  53.             }
  54.             private void PopTemplate()
  55.             {
  56.                 var stack = vd[templateStack] as Stack<templateActivation>;
  57.                 if (stack != null && stack.Count > 0)
  58.                 {
  59.                     vd.TemplateInfo.HtmlFieldPrefix = stack.Pop().HtmlPrefix;
  60.                 }
  61.             }
  62.         }
  63.         public static void DeclareScope(ViewDataDictionary vd, object model, string newHtmlPrefix)
  64.         {
  65.             var a = new templateActivation
  66.             {
  67.                 Model = model,
  68.                 HtmlPrefix = newHtmlPrefix
  69.             };
  70.             vd[lastActivation] = a;
  71.         }
  72.         public static ITemplateScope<T> NextScope<T>(this IHtmlHelper helper)
  73.         {
  74.             var activation = helper.ViewContext.ViewData[lastActivation] as templateActivation;
  75.             if (activation != null)
  76.             {
  77.                 helper.ViewContext.ViewData[lastActivation] = null;
  78.                 var stack = helper.ViewContext.ViewData[templateStack] as Stack<templateActivation>;
  79.                 ITemplateScope father = null;
  80.                 if (stack != null && stack.Count > 0)
  81.                 {
  82.                     father = stack.Peek().Scope;
  83.                 }
  84.                 if (father == null)
  85.                 {
  86.                     father = helper.ViewContext.ViewData[externalActivation] as ITemplateScope;
  87.                 }
  88.                 return new TemplateScope<T>(helper.ViewContext.ViewData, activation)
  89.                 {
  90.                     M = () => (T)(activation.Model),
  91.                     Prefix = activation.HtmlPrefix,
  92.                     Father = father
  93.                 };
  94.             }
  95.             else return null;
  96.         }
  97.     }
  98. }

Now we are ready to move to the TagHelper implementation.

Implementing the <iteration> TagHelper

Go to the root of the web Project and add a folder called TagHelpers, then add a new class called IterateTagHelper.cs to this folder. Substitute the scaffolded code with the code below:

  1. using Microsoft.AspNet.Mvc.Rendering;
  2. using Microsoft.AspNet.Razor.Runtime.TagHelpers;
  3. using System.Text;
  4. using Microsoft.AspNet.Mvc.ViewFeatures;
  5. using System.Threading.Tasks;
  6. using System.Collections;
  7. using IterateTagHelperTest.HtmlHelpers;
  8.  
  9.  
  10. namespace IterateTagHelperTest.TagHelpers
  11. {
  12.     [HtmlTargetElement("iterate", Attributes = ForAttributeName)]
  13.     public class IterateTagHelper : TagHelper
  14.     {
  15.         private const string ForAttributeName = "asp-for";
  16.         [HtmlAttributeNotBound]
  17.         [ViewContext]
  18.         public ViewContext ViewContext { get; set; }
  19.         [HtmlAttributeName(ForAttributeName)]
  20.         public ModelExpression For { get; set; }
  21.  
  22.         public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
  23.         {
  24.             var name = For.Name;
  25.             var fullName = ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(name);
  26.             IEnumerable model = For.Model as IEnumerable;
  27.             output.TagName = string.Empty;
  28.             StringBuilder sb = new StringBuilder();
  29.             if (model != null)
  30.             {
  31.                 int i = 0;
  32.                 foreach (var x in model)
  33.                 {
  34.                     TemplateHelper.DeclareScope(ViewContext.ViewData, x, string.Format("{0}[{1}]", fullName, i++));
  35.                     sb.Append((await context.GetChildContentAsync(false)).GetContent());
  36.  
  37.                 }
  38.             }
  39.             output.Content.SetContentEncoded(sb.ToString());
  40.  
  41.         }
  42.  
  43.     }
  44. }

Tag attributes are mapped to properties with the help of the HtmlAttributeName attribute, and are automatically populated when the IterateTagHelper instance is created. The asp-for attribute that in our case selects the collection to iterate on is mapped into the For property whose type is ModelExpression. As a result of the match a ModelExpression instance containing the collection, and its name is created.

The ViewContext property is not mapped to any tag attribute since it is decorated with the HtmlAttributeNotBound attribute. Instead it is populated with the Razor View ViewContext since it is decorated with the ViewContext attribute. We need the View ViewContext to extract the View ViewData dictionary.

The remainder of the code is straightforward:

  1. We get the name of the asp-for bound property.
  2. We add a possible HtmlPrefix to the above name by calling the GetFullHtmlFieldName method. We need it to pass the right HtmlPrefix to the scope of each IEnumerable element. Without the right prefix the collection can’t be bound by the receiving Action Method when the form is submitted.
  3. We extract the collection and cast it to the right type.
  4. Since we don’t want to enclose all template instantiations within a container we set the output.TagName to the empty string.
  5. We create a StringBuilder to build our content.
  6. For each IEnumerable element we create a new scope with the right HtmlPrefix, and the we get the element HTML by calling GetChildContentAsync. We pass a false argument to avoid that the method might return the previously cached string(otherwise we would obtain N copies of HTML of the first collection element).
  7. Finally we set the string created by chaining all children HTML as a tag content by calling the SetContentEncoded method. The Encoded postfix avoids that the string be HTML encoded.

Importing HtmlHelper and TagHelper

Now we need to import our Html Helper and our TagHelper. We may import them either locally in each View using them or globally by adding the import instructions to the Views/_ViewImports.cshtml View. Before the addition the Views/_ViewImports.cshtml file should look like this:

  1. @using IterateTagHelperTest
  2. @using IterateTagHelperTest.Models
  3. @using IterateTagHelperTest.ViewModels.Account
  4. @using IterateTagHelperTest.ViewModels.Manage
  5. @using Microsoft.AspNet.Identity
  6. @addTagHelper "*, Microsoft.AspNet.Mvc.TagHelpers"

Add:

@using IterateTagHelperTest.HtmlHelpers
@addTagHelper "*, IterateTagHelperTest"

To get:

@using IterateTagHelperTest
@using IterateTagHelperTest.Models
@using IterateTagHelperTest.ViewModels.Account
@using IterateTagHelperTest.ViewModels.Manage
@using Microsoft.AspNet.Identity
@addTagHelper "*, Microsoft.AspNet.Mvc.TagHelpers"
@using IterateTagHelperTest.HtmlHelpers
@addTagHelper "*, IterateTagHelperTest"

The @addTagHelper "*, IterateTagHelperTest" instruction imports all TagHelpers contained in the Web Site dll(whose name is IterateTagHelperTest).

Testing our TagHelper

No we may finally test our TagHelper. Opens the previously defined Views/Home/TagTest.cshtml View and replace its content with the content below:

@model IterateTagHelperTest.ViewModels.Home.TagTestViewModel
@using IterateTagHelperTest.ViewModels.Home
@{
    ViewBag.Title = "Tag Test";
}

<h2>@ViewBag.Title</h2>
<div>
    <form asp-controller="Home" asp-action="TagTest" method="post" class="form-horizontal" role="form">

        <iterate asp-for="Keywords">
            @using (var s = Html.NextScope<Keyword>())
            {
                var m = s.M;
                <div class="form-group">
                    <div class="col-md-10">
                        <input asp-for="@m().Value" class="form-control" />
                    </div>
                </div>
            }
        </iterate>
        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <button type="submit" class="btn btn-default">Submit</button>
            </div>
        </div>
    </form>
</div>

Now run the application, and click the “Test” main menu item to go our test page. You should see something like:

TestPage

The TagHelper actually instantiates the template all over the Keywords IEnumerable! The input field names appears correct:

 

Names

Let put a breakpoint in the receiving Action Method of the HomeController to verify that the IEnumerable is bound properly:

 

breakpoint

 

Now let modify a little our Keywords and let submit the form. When the breakpoint is hit let inspect the received model:

 

ModelBinding

 

That’s all for now! The whole project may be downloaded here.

Comments are disabled to avoid spamming, please use my contact form to send feedback:

Stay Tuned!

Francesco

Tags: , , ,

May 7 2015

JavaScript Intensive Web Applications 4: JSON based Ajax and Single Page Applications

Category: WebApi | MVC | JavaScriptFrancesco @ 06:19

JavaScript Intensive Web Application 1: Getting JavaScript Intellisense

JavaScript Intensive Web Applications 2: Enhancing Html with JavaScript

JavaScript Intensive Web Applications 3: Enhancing Applications with Ajax

jsFarmIntellisense

In this last post of the series, I discuss the use of JSON based Ajax calls and client side View Models. I will propose also a simple implementation of a knockout.js binding to apply a generic jQuery plug-in to an Html node. The post is concluded with a short analysis of Single Page Application frameworks.

In my previous post we have seen that Html returning Ajax calls update the needed parts of an Html page while keeping unmodified the remainder of the page. This allow a  tighter interaction between user and server because the user may work on other areas of the page while waiting for a server response, and he/she may ask  supplementary information to the server when he/she is in the middle of a task without loosing the whole page state.

The user experience may be improved further if we are able to maintain the whole state of the current task on the client, because this way we reduce further the need to communicate with the server: the user may prepare all data for the server while receiving immediately all needed help and suggestions with no need to communicate with the server in this first stage. Communication with the server is needed only after everything has been prepared. For instance, the user may modify all data contained in a grid, recurring to a detail  window when needed. Entities connected with one-to-many relations with the main list may be edited in the detail view. Everything without communicating with the server! Then, when all changes have been done, the user performs a single submit, and updates the global state of the system. The server answer may contain corrections performed by the server to the data supplied by the user, that are automatically applied to the client copy of the data.

In other words maintaining the whole state of a task on the client side allows a tighter user-machine cooperation since this cooperation may be performed without waiting for remote server answers. However, the increased complexity of the client side requires a robust and modular architecture of the client code. In particular, since we move logics, and not only UI on the client side, Html nodes that are  mainly UI staffs must be supported by JavaScript models. Models and Html nodes should cooperate while keeping separation of concerns between logics and UI. This means that all processing must take pace on models that are then rendered by using client side templates. Accordingly, Ajax calls can’t return Html anymore, but must return JavaScript models.

Summing up, all architectures where the whole state of the current task is maintained on the client should have the following features:

  1. JSON communication with the server. The format of the data exchanged between server and client might be also Xml based, but as a matter of fact at the moment, the simpler JSON protocol is a kind of standard.
  2. Html is created dynamically by instantiating client templates, thus this kind of Web Applications are not visible to search engines.
  3. The state of client and server must be kept aligned, by performing simultaneous updates on both client and server in a transactional fashion. This means, for instance, that if a server update fails for some reason the client must be able to restore the state of the last client server synchronization.

As a matter of fact at the moment point 3 has not received the needed attention also in sophisticated Single Page Application frameworks, that don’t supply general tools to face it, so the problem is substantially left to custom solutions of the developers.

In the case of Html based Ajax communication we have seen that, since the communication is substantially based on form submits, the server relies on all input fields having adequate names to build a model that then is passed to the Action methods that serve the client requests. In JSON based commutations, instead,  input fields names are completely irrelevant since action methods receive substantially JavaScript models.

Html Ids, and CSS classes are also used as “addresses” to select Html nodes to enhance with JavaScript code.  Several frameworks like knockout.js and angular.js avoid the use of these ids and CSS classes as a way to attach JavaScript behavior to Html nodes. In their case, model properties are “connected” to Html nodes through the so called bindings that are substantially communication channels between Html nodes and the JavaScript properties that updates one of them when the other changes. They may be one-way or two ways. Bindings may also connect Html nodes with JavaScript functions, and the developer may also define custom bindings, thus bindings solve completely the problem of connecting Html nodes with JavaScript code with no need to provide unique ids, or selection-purpose CSS classes.

Below how to use a custom knockout.js binding for applying jQuery Plug-ins to Html nodes:

 

  1. <input type="button" value="my button" data-bind="jqplugins: ['button']"/>
  2. <input type="button" value="my button"
  3. data-bind="jqplugins: [{ name: 'button', options: {label: 'click me'}}]"/>

 

The binding name is followed by an array whose elements may be either simple strings, in case there are no plug-in options, or objects with a name and an option property. As you can see in knockout.js bindings are contained in the Html5 data-bind attribute.

Below the JavaScript code that defines the jqplugins custom binding:

 

  1. (function ($) {
  2.     function applyPlugin(jElement, name, options) {
  3.         if (typeof $.fn[name] !== 'function') {
  4.             throw new Error("unrecognized plug-in name: " + name);
  5.         }
  6.         if (!options) jElement[name]();
  7.         else jElement[name](options);
  8.     }
  9.     ko.bindingHandlers.jqplugins = {
  10.         update: function (element, valueAccessor, allBindingsAccessor) {
  11.             var allPlugins = ko.utils.unwrapObservable(valueAccessor());
  12.             var jElement = $(element);
  13.             for (var i = 0; i < allPlugins.length; i++) {
  14.                 var curr = allPlugins[i];
  15.                 if (typeof (curr) === 'string')
  16.                     applyPlugin(jElement, curr, null);
  17.                 else {
  18.                     applyPlugin(jElement,
  19.                         ko.utils.unwrapObservable(curr.name),
  20.                         ko.utils.unwrapObservable(curr.options));
  21.                 }
  22.             }
  23.         }
  24.     }
  25. })(jQuery)

 

The code above enables the use of all available jQuery plug-ins on all knockout.js based architectures, so that we can move to advanced client architectures based to knockout.js without renouncing to our favorite widgets and CSS/JavaScript frameworks like jQuey UI, Bootstrap, jQuery Mobile, and Zurb Foundation.

 

As a next step we may pass from storing the whole state of a single task, to storing the whole application state on the client side, which implies that the whole application must live in a single Html physical page(otherwise the whole state would be lost). Similar applications are called Single Page Applications.

In a Single Page Application Virtual pages are created dynamically by instantiating client templates that substitute the Html of any previous virtual  page in the same physical page. The same physical page may show simultaneously several virtual pages in different areas. For instance, a virtual page might play the role of master, and another the role of detail page.

Most of Single Page Application frameworks have also the concept of virtual link and/or of routing, and may connect the virtual pages to the browser history, so that the user may navigate among virtual pages with usual links and with the browser buttons.

But… why re-implementing the whole browser behavior inside a single physical page? What are the advantages of Single Page Applications compared to “multiple physical pages applications” based on Client View models?

In general having the whole application state on the client side reduces further the need to communicate with the server, thus increasing the responsiveness to the user inputs. More specifically:

  1. Once the client templates needed to create a new virtual page have been downloaded from the server further accesses to the same virtual page become very fast. On the contrary, loading a complex client model based page that is able to store the whole state of a task may be time consuming, so saving this loading time improves the user experience considerably.
  2. The state of a previously visited virtual page may be maintained so that the user finds the virtual page in exactly the same state he/she left it. This improves the cooperation between different tasks that are someway connected: the user may move forth and back between several virtual pages with the browser buttons while performing a complex task without loosing the state of each page.
  3. The same physical page may contain simultaneously several virtual pages in different areas. Thus, the user may move forth and back between several virtual page in one area, while keeping the content of another area. This scenario enables advanced form of cooperation between virtual pages.
  4. The whole Single Page Application may be designed to work also off-line. When the user has finished working the whole application state may be saved in the local storage and restored when he/she needs to perform further changes, or when he/she can go on-line to perform a synchronization with the server.

The main problem Single Page Application developers are faced with is keeping a large JavaScript codebase modular and maintainable.  Since virtual pages are actually client templates <-> ViewModel pairs, the concept of virtual page itself has been conceived in a way to increase modularity. However several virtual pages need also a way to cooperate that doesn’t undermine their modularity and the independence of each virtual pages from the remainder of the system.

In particular:

  1. Each virtual page definition should not depend on the remainder of the system to keep modularity, which, in turn, implies that virtual pages may not contain direct references to other external data structures.
  2. Notwithstanding point 1, some kind of cooperation that doesn’t undermine modularity, must be achieved among model-view pairs and among model-view pairs and the application models. A modular cooperation may be achieved by injecting interfaces that connect each model-view pair with the external environment as soon as a model-view pair is added to the page.
  3. Pointers, to data structures contained inside each virtual page should be either avoided or handled by resource managers to avoid they are used when a virtual page has been released or when it is not in an active state.

Separation is ensured someway by the concept of ViewModel itself. Durandal.js uses AMD modules to encode ViewModels. AMD protocol is a powerful technique for both loading dynamically and injecting other code modules that the current module might depend on and consequently for handling a large JavaScript codebase. However, the dependency tree is hardwired, so the injection mechanism is more adequate to inject code than dynamic data structures that might depend on the state of the ongoing computation. Accordingly, the full achievement of point 2) requires an explicit programming effort. Angular.js uses a custom dependency injection and module loading mechanism. That mechanism is easier to use, but it is less adequate for managing large codebases (in my opinion , not adequate at all). However, the fact that the injection mechanism is less structured make easier the injection of dynamic data structures when a model-view pair is instantiated.

In general most of frameworks ensure separation with some kind of cooperation, but no frameworks offer a completely out-of-the-box solution for point 2, and an out-of-the-box solution form managing the lifetime of pointers that have been injected into model-view pairs to ensure an adequate cooperation in the context of the ongoing computation (point 3). More specifically, the lifetime of pointers to AMD modules(or other types of dynamically loaded modules), that have been injected are automatically handled, but there is no out-the-of-the-box mechanism for managing pointers that a model-view pair might have to data structures contained into another model-view pair, so the developer has the burden of coding all controls needed to ensure the validity of each pointer, in order to avoid the use of pointers to data structures contained in model-view pairs that have been removed from the page.

The need for a more robust solution to problems 2 and 3 is among is among the reasons that pushed me to to implement a custom Single Page Application framework in the Data Moving Controls suite. The Data Moving SPA framework (see here, and here) relies on contextual rules that “adapt” each virtual page that is being loaded to the “current context”. Where the “current context” includes both interface implementations that connect the virtual page to the remainder of the system and information about the current state of the application, such as, if the user is logged or not, the current culture (that is the browser language and culture settings), and so on. Contextual rules are used also to redirect a not logged user to a login virtual page and to verify if the user has the needed authorizations to access the current virtual page. The interface implementations passed by the contextual rules to the virtual page View Models include also all resource managers needed  for  sharing data structures among all application virtual pages safely. Another communication mechanism is the possibility to pass input data to any page that is being loaded. Such input data are analogous to the input data passed in the query string. In fact, this input may be included also in virtual links.

Another big challenge of Single Page Applications is the duplication of code in both client and server side. In fact, the same classes, input validation criteria, and other metadata must be available on both client and server side, and when the languages used by the two sides are different, this become a big problem. The Meteor framework uses JavaScript on both server and client, and allow code sharing between the two sides. The main  price to pay for this solution is the use of a language that is not strongly typed also on the server side.  In the Data Moving SPA we faced this problem by equipping SPA server with dynamic JavaScript files implemented with Razor views. This way JavaScript data structures may be obtained by serializing in JavaScript their equivalent .Net data structures.

Another important problem all SPA must solve is the data synchronization between Client and Server. Durandal.js works quite well with Breeze.Js that offers some synchronization services for the case the server may be modeled as an OData source. Breeze.Js may be adapted also to most of all other SPA framework, but this solution is acceptable only if there is almost no business logics between the client and the server side database. In fact, only in this case the server API may be exposed as an OData source only, with no need of more complex communication. 

Meteor,  takes care of sever/client synchronization in a way that is completely transparent to the developer. A similar solution facilitates the coding of simple applications, but may be inadequate for complex business systems that needs to control explicitly communication between client and server.

The Data Moving SPA framework offers retrievalManagers to submit a wide range of (customizable) queries(that includes also OData queries)  to the server, while viewModelUpdatesManagers and updatesManagers take care of synchronizing a generic data structure with the server in a transactional fashion, by taking into account both changes in several Entity Sets (additions, modifications, and deletes), and changes in temporary data structures(core workspaces). As a result of the synchronization process they may return either errors that are automatically dispatched in the right places of the UI, in case of failure, or remote commands that apply modifications to the client side data structure to be synchronized with the server. While the synchronization process is completely automatic, the developer has full control on which data to synchronize, and when to synchronize them, and also the possibility to customize various part of the process.

 

That’s all! This post ends the short series about JavaScript intensive web application. This series is no way a tutorial that describes extensively all details of the techniques that has been discussed, but just a guide on how to select the right technique for each application and on how to solve some architectural issues that are not usually discussed in other places.

 

Stay tuned! 

Francesco

Tags: , ,