Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / ASP.NET

Incoding Framework: template part

2.23/5 (4 votes)
6 Sep 2015CPOL10 min read 8.4K  
Disclaimer: the article represents a survey on the transformation of JSON data into html on the client part (browser) and reveals the operation details of template в Incoding Framework (search, formation, local storage and plugging in one’s own engine).

Article-8_Small

Other articles in the series "Incoding Framework" :

Why is the server one not good ?

Before answering this question, one should study what asp.net mvc can offer.  Razor  - is a server template, which is by default a feature of asp.net mvc, it has a huge functionality and can perform C# code declared within the cshtml  file framework, therefore it does not cause problems, so, what is the matter? When the fed back result for Action  is built up by forming ( View or PartialView )  html  on the server one will get a ready-made content, rather than “pure” data, which may cause the following problems:

  • One may not use Action  when developing a mobile application or a third-party client (API )
  • The traffic used for data transmission grows due to the bulky html content compared to the compact JSON  data.
  • The forming takes up the server resources, which can be avoided by delegating them to the client. (they are more numerous, so we don’t mind).

Note: as far as the client is concerned, it is of course a joke, that we don’t mind, the actual performance will be dealt with later on in this article. We may say that content formation on server side has much more opportunities, but possesses a set of problems which can't be solved without turning to client part..

There is a solution

КThough  Razor is good, modern applications require the data to be fed back as JSON or xml, therefore server-based html construction does not fit every scenario. On using client pure template engine. A list of requirement was dressed up, that can be implemented in our “wrapper”.

  • Typing
  • IML Integration
  • Thin template
  • Replacement for the “hot” one
  • Something of your own

Typing

Magic string

Razor’s advantage is that there is intellisense support and instrument for refactor ( Rename, Delete ). Comparing it to the client’s one, let us consider an example in order to see the difference.

@each(var item in  Model)
{ First Name: @item .FirstName  }

Note: Server implementation allow to receive a model scheme and subsequently use the known in advance rather than dynamic types. 

{{each data}}
 First Name: {{FirstName}} 	 
{{/data}}
Note: an example of  template for handlerbars, where ordinary strings are used and Visual Studio can’t calculate in advance the areas included.

FirstName being renamed for Name via special instruments for refactor, such as  R#,  will affect View, but this is not supported by handlerbars since there is no link between the code and template. To solve this task a builder  has been made up for snap-to for the selected engine ( handlebars,mustaches etc. ) .

C#
using (var template = Html.Incoding().ScriptTemplate<Model>(tmplId))
{
   using (var each = template.ForEach())
   { First Name: @each.For(r= >r.FirstName)  }
}

ScriptTemplate vs Template

Incoding Framework has two ways of creating  template

  • ScriptTemplate -  the resulting markup is placed in script  tag with a preset Id
HTML
<script id="templateId" type="text/x-mustache-tmpl">
   {{#data}}
       <option {{#Selected}}selected="selected"{{/Selected}} value="{{Value}}">
        {{Text}}
       </option>
   {{/data}}
</script>

Note: any type should be indicated as type except for javascript, in order for the browser not to perform the embedded code

  • Template -  “pure” resulting mark-up.

Note: this ways is useful when template  feeds back as a result from  Action ( a detailed review will be represented in the  integration with IML  block) 

Syntax

The ITemplateSyntax includes methods used for building up  template, in order to register it an inscription should be added to IoC ( Bootstarpper.cs )

registry.For<ITemplateFactory>().Singleton().Use<TemplateHandlebarsFactory>();

Note: Handlebars ( default by nuget  ) and  Mustaches  implementations are currently set, but individual TemplateSyntax may b written basing on their example. . 

  • ForEach  - is a cycle on collection ( razor –based counterpart of @foreach(var item in Model) {} )
    • On major (data received in response )
C#
using(var each = template.ForEach()) { //some template }
    • On embedded
C#
@using (var innerEach = each.ForEach(r = > r.Itmes))
{ // some template }
  • NotEach -displays the content if there is no data ( razor-based counterpart of  @if(Model.Count == 0) { } )
C#
using (var each = template.NotEach()){ // some template }

Note: can be used together with ForEach

HTML
<ul>
  @using (var each = template.ForEach())
  {  <li>@each.For(r = > r.Title) / @each.For(r = > r.Code)</li>  }
  @using (var each = template.NotEach())
  { <li> No data </li> }
</ul>
  • For  - displays the content of the indicated field (razor-based counterpart of   Model.Title )
C#
each.For(r = > r.Title)
  •  ForRaw - displays the content of the indicated field without coding  HTML ( razor-based analogue of Html.Raw(Model.Title) )
C#
each.ForRaw(r = > r.Title)
  • Is - means to display, if the field is  True or  NOT NULL ( razor-based counterpart of @if(Is != null || Is){} )
C#
using(each.Is(r = > r.Property)) { // some template }
  • Not - means to display, if the field is   False or NULL (razor-based counterpart of  @if(Is == null || !Is) {} )
C#
using(each.Not(r = > r.Property)) { // some template }
  • Inline - depending on the field value displays the true content ( True or NOT NULL ) or false content ( False or NULL ) ( razor –based counterpart of @(Is ? “true-class” : “false-class”) )
C#
@each.Inline(r = > r.Is, isTrue: "true-class", isFalse: "false-class")

Note:  Is and Not are used to build big blocks, while  Inline draws back the content at once. Note: Inline has counterpart IsInline, NotInLine, that only show one part of the condition. 

    • Set class as red if true
HTML
<span class="@each.IsInline(r= >r.Is,"red")"></span>
or use special syntax Razor
<span class="@each.IsInline(r= >r.Is,@<text><span>red></span></text>)"></span>
    •   Hide element if true or show overwise
HTML
<span style="@each.Inline(r= >r.Is,isTrue:"display:block",isFalse:"display:none")"></span>
    •  Display title if true
C#
using (each.Is(r = > r.Is))
{ <h3>Header</h3> }
C#
@each.IsInline(r= >r.Is,@<h3>Header</h3>
  • Up -  to go up the hierarchy
each.Up().For(r = > r.Property)

Note: the method is necessary when it is necessary to get the value (condition) of the field, but within the domestic ForEach

HTML
using (var each = template.ForEach())
{
   using (var innerEach = each.ForEach(r = > r.Items))
   {  @each.Up().For(r= >r.ParentProperty)   }
}
  • Summary
HTML
@using (var template = Html.Incoding().Template<ComplexVm>())
{
  using (var each = template.ForEach())
  {
    <div> 
     <ul style="@each.IsInline(r = > r.IsRed, "color:red;")">
        @using (var countryEach = each.ForEach(r = > r.Country))
        {
         <li>
            @using (each.Up().Is(r = > r.IsRed))
            { 
     <span>Country @countryEach.For(r = > r.Title) from red group @each.Up().For(r = > r.Group)</span> 
            }	 	 
            @using (each.Up().Not(r = > r.IsRed))	 	 
            { 
      <span>Country @countryEach.For(r = > r.Title) by group @each.Up().For(r = > r.Group)</span> 
            }	 	 
           <dl>	 	 
             @using (var cityEach = countryEach.ForEach(r = > r.Cities))	 	 
             { <dd> City: @cityEach.For(r = > r.Name) </dd> }	 	 
           </dl>	 	 
         </li>	 	 
        } 	 	 
    </ul>	 	 
 </div>	 
 } 
}

Note: downloading the example is available on  GitHub  

integration with IML

C#
@(Html.When(JqueryBind.InitIncoding)   
      .AjaxGet(Url.Action("FetchCountries", "Data"))
      .OnSuccess(dsl = > dsl.Self().Core().Insert.WithTemplate(tmplId.ToId()).Html())
      .AsHtmlAttributes()
      .ToDiv())

IML has methods (AjaxGet, Submit, AjaxPost), to get the data, which can further be inserted via the Insert. The data can be html content or json objects. In order to insert json objects template is used, the path to which is specified by Selector. NOTE: Starting with version 1.2, methods or WithTemplateById WithTemplateByUrl are preferable to use.

  • WithTemplateById -to find a dom element on Id, which contains  template
C#
dsl.Self().Core().Insert.WithTemplateById(tmplId) // by Selector.Jquery.Id(tmplId)

NB: to build template ScriptTemplate(tmplId)should be used in this case.  

C#
@{
    string tmplId = Guid.NewGuid().ToString();
    using (var template = Html.Incoding().ScriptTemplate<CountryVm>(tmplId))
    {
        <ul>
            @using (var each = template.ForEach())
            { <li>@each.For(r => r.Title) / @each.For(r => r.Code)</li> }
        </ul>
    }
}
@(Html.When(JqueryBind.InitIncoding)
      .Do()
      .AjaxGet(Url.Action("FetchCountries", "Data"))
      .OnSuccess(dsl => dsl.Self().Core().Insert.WithTemplateById(tmplId).Html())
      .AsHtmlAttributes()
      .ToDiv())
  • WithTemplateByUrl - Download layout on ajax
dsl.Self().Core().Insert.WithTemplateByUrl(url) // by Selector.Incoding.AjaxGet(url)
Controller
C#
public ActionResult Template()
{
    return IncView();
}
View Template
C#
@using (var template = Html.Incoding().Template<AgencyModel>())
{
    using (var each = template.ForEach())
{ <span> @each.For(r = > r.Name)</span> }
}

 N.B.: when building up template for ajax Template rather than ScriptTemplate method should be used. 

View IML
C#
Html.When(JqueryBind.InitIncoding)
    .Do()
    .AjaxGet(Url.Action("GetAgencies", "IncAgency"}))
    .OnSuccess(dsl = > 
       dsl.Self().Core().Insert.WithTemplateByUrl(Url.Action("Template", "IncAgency")).Append()
              )
    .AsHtmlAttributes()
    .ToDiv()

Does each template has its own Action ? There are two solutions that allow not to duplicate the code in action:

  • Shared action
C#
public class SharedController : IncControllerBase
{
   public ActionResult Template(string path)
   {
      return IncView(path);
   }
}

Note: extensions for Url can be built, in order to add the path check via ReSharper annotation 

public static class UrlExtensions
{
    public static string Template(this UrlHelper urlHelper, [PathReference] string path)
    {
        return urlHelper.Action("Template", "Shared", new { path = path });
    }
}
C#
Url.Dispatcher().AsView("path to template")

 Note: MVD was initially positioned as a universal template loader

Id vs Url

Initially, we used a dom element (script) as storage template layout, but gradually switched to boot with ajax, which has the following advantages:

  • Re-use template on different View

Note: for Id it was realized bringing out template into Layout (another master page)

  • Layout is factored out of View

Note: for Id it was realized through partial view

  • Lazy load (template is loaded on demand)

Note: especially true when using tabs

thin template

We chose engine, which support logic less template, such as Mustaches, Handlerbars, because this approach allows to simplify View, moving complex logic on the server, where it is easier "to deal with the complexity." For clarity, a task that will be solved in the "normal" way and using logic less

  • Ordinary
if(Model.Count <= 5 && Model.Type == TypeOf.Product)
{//something code }
  • Logic Less
if(Model.IsLimitProduct) // public bool IsLimitProduct { get {return Count <= 5 && TypeOf == Product }}
{ //something code }

In the first case, we calculate the value in View, but in logic less we calculate the expression beforehand on the server that has the following advantages:

  • Re-use in other scenarios
  • Unit Test can be covered
  • Less code into View layout

ПAdvantages of logic less manifest when increasing complexity of the tasks, because to expand and maintain the conditions easier on the server side than in View

Replacement for the "hot""

Task appeared, after problems had been discovered with mustaches, which slowed down in ie 8 and below, and also had problems with inserting large amounts of data (more than 30 entries). Since the implementation of mustaches used in a number of projects, the choice of engine should be simple, so that any could be used. Note: the code, described below, is available at GitHub

JavaScript Code
JavaScript
function IncJqueryTmplTemplate() {
    this.compile = function(tmpl) {
        return tmpl;
    };
    this.render = function(tmpl, data) {
        var container = $('<div>');
        $.tmpl(tmpl, data).appendTo(container);
        return container.html();
    };
}

  Note: Since not all engine support pre compile, tmpl can be returned unchanged

Layout Code
HTML
<script type="text/javascript">
    ExecutableInsert.Template = new IncJqueryTmplTemplate();
</script>

 Note: The code is in layout, because it must be before the first call Insert.WithTemplate

Template Code
HTML
@{ string tmplId = Guid.NewGuid().ToString(); }
<script type="jquery-tmpl" id="@tmplId">
    {{each data}}
      <li>${Title}</li>
    {{/each}}
</script>

 note: template is built on a "pure" jquery tmpl, without using ITemplateSyntax , but the implementation can be written and registered in IoC

IMl Code
C#
@(Html.When(JqueryBind.InitIncoding)
      .Do()
      .AjaxGet(Url.Action("FetchCountries", "Data"))
      .OnSuccess(dsl => dsl.Self().Core().Insert.WithTemplateById(tmplId).Html())
      .AsHtmlAttributes()
      .ToTag(HtmlTag.Ul))

  Note: from IML, nothing has changed, so template engine can be easily replaced without significant alterations   

Something of one’s own

Faster, much faster !!

For the first versions of framework, we stored template in dom elements (script), but this method did not allow to build lazy load, so then we switched to ajax loading, which had other problems:

  • If many elements are loaded immediately on one home page that the pool of browser requests is filled up

note: this is partly solved by using cache, but the request will still be, though the status is 304

  • Absence of pre-compile for engine

Note: especially true when there is a large number of inserted data (more than 30 objects)

Once and forever

The solution was found in Local Storage, which allows to save template in browser and then to use it all the time. Will there always be one template? – To answer this question, let’s look at the code (pseudocode) of this mechanism operation.

C#
if(storage.Contain(selector.ToKey() + TemplateFactory.Version)
  return storage.Get(selector.ToKey() + TemplateFactory.Version);

var template = selector.Execute();
storage.Add(template.PreCompile());
return storage.Get(selector.ToKey() + TemplateFactory.Version);
  • Line 1 - check availability template in local storage

Note: selector and version are keys (details about version below)

  • Line 2 -  return  template from local storage
  • Line 4 - obtain selector value
  • Line 5 - add template to local storage

Note: before adding pre compile should be done

  • Line 6 -return   template from local storage

And if I change the layout template, but the key will remain the same? - In order to solve this problem, we have added versioning globally. To specify the current version of all template field TemplateFactory.Version must be set using JavaScript

HTML
<script>
TemplateFactory.Version = '@Guid.NewGuid().ToString()';
</script>

Note: the code should be carried out before calling Insert.WithTemplate, so it is best to be placed in Layout On the example I set Guid, which guarantees the new version after the full (F5) page reloading, but this behavior is relevant only for the development process, and when the code will be laid out in production, it is necessary to fix the version. 

  • Debug    - while  developping the mark up activity rate  in the template is high, so the version is updated as often as possible (we use Guid, to maintain its uniqueness)
  • Release -  after the project has been uploaded on the server, a fixed version must be installed
Layout
HTML
<script type="text/javascript">
TemplateFactory.Version = '@CurrentSettings.CurrentVersion';
</script>
Current Version
C#
public string CurrentVersion
{
   get
   {
#if DEBUG
    return Guid.NewGuid().ToString();
#else
    return Assembly
            .GetExecutingAssembly()
            .GetName()
            .Version.ToString();
#endif
   }
}

 note: TeamCity build.number used as a fixed version. Our approach to the continuous integration ( TeamCity, rake ) will be thoroughly considered in the subsequent papers.      

One for all and all for one

When building template, it is necessary to specify only the model type, but it is not considered if it is a collection or a single object. Some template engine require to specify what it will be - a collection or a single object (for example handlebars {{# with data}} or {{# each autors}}), so we decided to remove this condition and not to take into account the amount of items when template constructing. Owing to this feature we did not need to add method With (which not all engine support). 

Collection
[HttpGet]
public ActionResult FetchCountries()
{
    return IncJson(GetCountries());
}
Single
[HttpGet]
public ActionResult FetchCountry()
{
    return IncJson(GetCountries().FirstOrDefault());
}
Share template
@using (var template = Html.Incoding().Template<CountryVm>())
{
 <ul>
@using (var each = template.ForEach())
{ <li>@each.For(r = > r.Title) / @each.For(r = > r.Code)</li> }
 </ul>
}

Client is not a server or Our hook (Repeated mistakes)

During training employees to use client template, I compiled a list of the most frequent mistakes that because they write code, as well as for the server template

GUID -in view it is used to specify a unique name for the element within the pages, but in the case of the client template it has pitfalls. Condition: each tag li must have a unique Id

@using (var each = template.ForEach())
{
    var uniqueId = Guid.NewGuid().ToString();
    <li id="@uniqueId"></li>
}

Code contains an error, because the variable uniqueId will be calculated once on the server, bypass of the collection will take place on client. To test this, we must see the resulting layout.

{{each data}}
<li id="EAAF08F8-0F09-4DA7-BE64-6D0075749D76"> </li>
{{/each data}}

For the code to work correctly, the logic of guid calculation has to be transfered to Model

@using (var each = template.ForEach())
{
    <li id="@each.For(r= > r.UniqueId"></li> // UniqueId {get {return Guid.NewGuid().ToString(); }}
}

 Conclusion

We constantly develop each Incoding Framework component, contributing new features to it with every version and "polish" the old ones and certainly template is not an exception, to make sure one can see our BugTracker. All I have described in the article are proven by personal experience constructions and practices that allow to develop cross-platform applications initially, but not to reconstruct the architecture when it costs a lot.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)