Click here to Skip to main content
15,867,488 members
Articles / Web Development / ASP.NET

ASP.NET DaST to wrestle with MVC and WebForms

Rate me:
Please Sign up or sign in to vote.
5.00/5 (2 votes)
26 Mar 2013CPOL82 min read 23.1K   152   11   4
DaST is a new architectural pattern for building highly dynamic Web 2.0 applications. A web page is rendered as a set of randomly nested rectangles where each rectangle is controlled individually and every combination of rectangles can be partially updated via AJAX.
Abstract  

A while ago I proposed DaST as a new architectural pattern for building highly dynamic Web 2.0 apps. In short, a web page is rendered as a set of randomly nested rectangles where each rectangle is controlled individually and every combination of rectangles can be partially updated via AJAX. The concept is currently implemented for ASP.NET as the open-source ASP.NET DaST framework, Just recently I published the first stable version of the framework and completed the documentation. This article is a DaST Tutorial that sums up the work done since the project started. 

DaST technology is a big shift to web development paradigm. It was designed specifically to turn creating of highly dynamic Web 2.0 apps into a trivial task. ASP.NET DaST proves that tiny open-source toolset can accomplish the job of highest complexity, outperforming monsters like standard WebForms and MVC in simplicity, architecture, flexibility, performance, presentation separation, and many other important web development aspects. I know how this sounds, but I can promise that further in this article DaST approach will completely change your idea about web application design and development.   

Brief Overview   

First impression is important. I'm putting this 1-minute overview here, before the main article, in order to highlight some DaST features, so you can get a feeling of what this technology is and what you can achieve using it in your web applications. 

Framework

ASP.NET DaST is contained in a single .dll file. This framework is not an add-on, but a complete alternative to the standard WebForms and MVC frameworks. In the same time, ASP.NET DaST does not deny any standard features and can be smoothly integrated into existing ASP.NET applications page by page.

HTML templates 

Assume we have some user-order-item data that we want to output in tree-like form. The DaST template for this could the the following:

<div dast:scope="CustomerRepeater" class="indent">
  <b>Customer:</b> {CustomerName}
  <div dast:scope="OrderRepeater" class="indent">
    <div><b>Order:</b> {OrderID}</div>
    <div dast:scope="ItemRepeater" class="indent">
      <div><b>Item:</b> {ItemName}</div>
    </div>
  </div>
</div>

Normally, to output user-order-item data we would need 3 nested repeaters. DaST template has 3 nested scopes instead. Plus, this is clear and pure HTML, without any special markup or control-flow statements!

Controllers

The controller tells the system how to render the template. Here is a part of the controller for our previous template:

public class NonsenseExample1aController : ScopeController
{
  private void DataBind_CustomerRepeater()
  {
    var customers = DataLayer.GetCustomers(); // get customers
    CurrPath().RepeatStart(); // zeroize repeater
    foreach (var c in customers) // loop through customers
    {
      CurrPath().Repeat(); // repeat scope for current customer
      CurrPath().Replace("{CustomerName}", c.GetValue("Name")); // render name
      CurrPath("OrderRepeater").Params.Set("CUST", c); // pass to next scope
    }
  }

  private void DataBind_OrderRepeater() { ... }
  private void DataBind_ItemRepeater() { ... }
} 

The DataBind_CustomerRepeater() handler controls CustomerRepeater scope in the template. It simply retrieves customer objects and repeats the scope for each of them replacing {CustomerName} placeholder with its real value on each iteration. Two other handlers, for OrderRepeater and ItemRepeater scopes would be analogical.

Actions 

Actions mechanism is DaST substitution for events. Action is raised on the client-side using JavaScript DaST.Scopes.Action(..) function:

<a href="javascript:DaST.Scopes.Action('...', 'SubmitInput', ['abc', 123])">
  Click here to raise "SubmitInput" action
</a>

This code raises SubmitInput action that will be processed by action handler implemented inside the controller class. Last parameter is some generic data passed along with the action:

private void Action_SubmitInput(object arg)
{
   string p1 = (string)((object[])arg)[0]; // "abc"
   int p2 = (int)((object[])arg)[1]; // 123
   // processing goes here ...
}

Duplex actions/messages

One of the coolest DaST features is that you can raise actions in the opposite direction, from server to client, and handle them in your JavaScript! This mechanism is called Duplex Messaging and here is a sample:

private void Action_SubmitInput(object arg)
{
   // processing goes here ...
   CurrPath().MessageClient("InputSubmitted", new object[] {"abc", 123});
} 

This sends InputSubmitted message to the client along with the generic argument. On the page we use JavaScript to handle this message:

<script language="javascript" type="text/javascript">
  DaST.Scopes.AddMessageHandler('...', 'InputSubmitted', 
  function(data) {
    var p1 = data[0]; // "abc"
    var p2 = data[1]; // 123
    // processing goes here
  });
</script>

AJAX and partial updates

DaST has the simplest and most powerful area-based native AJAX support where we literally can update any page area we point at without extra code or markup.

private void Action_SubmitInput(object arg)
{
  // processing goes here ...
  CtrlPath("CustomerRepeater", 2, "OrderRepeater").Refresh();
}

So, we simply point at some scope and call Refresh() on it - that's all! This causes the system to re-invoke necessary binding handler(s), generate partial output, and update the corresponding container on the client-side.

Controller actions

In general case, the DaST page consists of multiple controllers (and multiple nested templates) that can communicate between each other. This is where Controller Actions mechanism is used. Here is a sample how to raise actions to talk to child or parent controller:

private void Action_SomeActionName(object arg)
{
  // some processing can go here
  CurrPath().RaiseAction("HelloFromChild", new object[] { "abc", 123 });
  CurrPath("SomeChildScope").InvokeAction("HelloFromParent", new object[] { "abc", 123 });
  // other processing can go here
}

Same as for client actions, we can pass generic parameters along with controller actions. Then HelloFromChild and HelloFromParent actions can be handled inside parent and child controllers respectively:

private void Action_HelloFromChild(object arg) // or Action_HelloFromParent for child controller
{
  string p1 = (string)((object[])arg)[0]; // "abc"
  int p2 = (int)((object[])arg)[1]; // 123
  // processing goes here
}

The syntax here is totally uniform with client action handlers.

Summary 

Surpassing WebForms in all respects, DaST also has important advantages over the most recent MVC frameworks. Just divide your page into nested rectangles and manipulate them to reach the desired output! That's what the web development should be. No need for tons of server controls, weird page lifecycle, bulky binding expressions, or anything like this. Pure HTML templates can be stored anywhere providing native CMS capabilities. Plus you get the simplest and most powerful area-based native AJAX support where you literally can update any page area you point at without extra code or markup. This concept is very fresh and unique and it’s just as simple as it sounds. 

And for those, who feel lazy about learning new framework, there's really NOT MUCH to learn about DaST. It will take you about 2 hours – just time of going through this tutorial – to learn absolutely all aspects of DaST development. It’s a simple concept, some theory, a dozen of API functions, and truly intuitive programming on the top – that’s all.    

LINKS  

Here is the list of helpful links that you may need: 

Now it's time to turn to the actual tutorial. It gives you all needed concept theory followed by coding walk-through with samples of real applications that you can download and run on your local machine. I hope you enjoy it! 


Table of Contents   


On the top level, the tutorial is divided into 3 parts:

  • Concept - gives you all necessary theoretical knowledge about the pattern
  • Coding basics - walks through the code of first part of LittleNonsense demo app
  • Advanced coding - explains advanced features and walks through the Web 2.0 part of LittleNonsense demo 
Let's start from introducing our demo application that will be used throughout the entire tutorial for all explanations. 

LittleNonsense DEMO

To demonstrate DaST technology in action, I created a LittleNonsense DEMO web application. As its name implies, this app does not make much sense in a real world; however, it perfectly demonstrates power of DaST pattern and all features of the framework. I suggest you download the source code and run this app on your local machine. 

Overall functionality of this demo app is fairly simple. It reads customer orders data from XML file and outputs it to the web page in a user-friendly form. Listing below shows a part of this source XML data file located in /App_Data folder:

Listing 0.1: Part of ../App_Data/Data/LittleNonsense.xml data source XML file

01: <?xml version="1.0" encoding="utf-8"?>
02: <LittleNonsense>
03:   <Customer id="C01" name="John">
04:     <Order id="O01" date="2012-01-01">
05:       <Item id="I01" name="IPod Touch 16GB" />
06:       <Item id="I02" name="HDMI Cable" />
07:     </Order>
08:   </Customer>
09:   <Customer id="C02" name="Roman">
10:     <Order id="O02" date="2012-04-05">
11:       <Item id="I03" name="ASUS/Google Nexus 7 Tablet" />
12:       <Item id="I04" name="Nexus 7 Protective Case" />
13:     </Order>
14:     <Order id="O03" date="2012-04-22">
15:       <Item id="I05" name="ASUS EEEPC Netbook" />
16:       <Item id="I06" name="8 Cell Battery" />
17:     </Order>
18:   </Customer>
19:   ...............
20:  </LittleNonsense>

As you can see, structure of XML data file is trivial. We have a set of customers. Each customer can have multiple orders. And each order consists of multiple order items. Each element has a unique id attribute and a second attribute carrying some meaningful data: customer name, order date, or item name.

I divided the LittleNonsense application into 2 separate parts: Example 1 and Example 2 implemented on NonsenseExample1.aspx and NonsenseExample2.aspx pages respectively. Example 1 is trivial and is used for concept explanation and Coding Basics part of this tutorial. Example 2 has much more stuff in it and demonstrates Web 2.0 and all advanced features of DaST framework. Example 2 is used for the Advanced Coding part of this tutorial and we'll come to it later. Now, let's introduce Example 1

Example 1 Intro

This example was designed to give you a feeling of DaST Rendering mechanism and show you what is so different about DaST concept. Specifically, Example 1 covers the following topics: 

  • Basics of DaST application design 
  • Simple output with nested repeaters

All this application does is reading some hierarchical data from XML file and outputting it using 3 nested repeaters. Below is a screenshot of Example 1 output:

ex1-ui

Fig. EX1: Screenshot of NonsenseExample1.aspx page output

So, we just repeat customers, orders for each customer, and items for each order, displaying the result in the form of user-friendly hierarchical tree. The id attribute of each element from XML file is displayed in square brackets before actual text data. In further sections I’ll do a complete Example 1 source code walkthrough with all details and explanations. Right now I’d like start with some theoretical knowledge and explanation of DaST Concept.


1. DaST Concept

First of all, DaST is much more than a framework – it’s a whole new web development pattern and a unique application design concept. The ASP.NET DaST project is a .NET implementation of this pattern. But the pattern can be implemented for any other platform such as PHP (upcoming project), JSP, etc. The concept started from a very simple thought that every web page is nothing, but a bunch of data produced by server-side logic and presented to the user in the client browser. The entire page rendering process can be separated in two steps: 

  1. Generating a bunch of data values 
  2. Presenting these values using HTML markup

To accomplish these steps, DaST uses a combination of template and controller. Controller is a back-end class that generates the values. Template is a W3C compliant and valid HTML file where the values get inserted. And it’s just as simple as it sounds: 

  • Developer implements a controller class to produce a set of string values 
  • DaST inserts these values into corresponding HTML template 
  • Resulted HTML output is sent back to the client browser

Besides obvious simplicity and flexibility of this approach, HTML templates give us theoretical maximum of presentation separation! The central question here is what happens in between the controller and the template during rendering and how the values are delivered to the specific spots within the HTML template... And this is exactly what DaST engine is for! DaST stands for data scope tree. You’ll see why in a second, but before going into more details, let me illustrate DaST rendering approach by a simple picture that you will use for your reference:

DaST Concept

Fig. 1.1: Top-level design of DaST rendering process

This picture visualizes the whole point of DaST pattern and illustrates the use of template/controller combination to get page output. Now, I’ll explain Fig 1.1 and give the formal definition of the concept: 

  • Central idea of the pattern is that every web page can be viewed as a set of randomly nested independent functional units – data scopes. And due to hierarchical structure of data scopes, the whole web page becomes just a data scope tree (hence the name “DaST”). 
  • Page output is fully controlled via HTML template which can also be decomposed into a tree of data scopes. Physical representation of data scope inside the HTML template is a fragment of HTML markup enclosed in DIV or SPAN container element with dast:scope attribute. Breaking page into the data scopes and choosing the right structure of the tree is up to the developer, but usually data scope contains some strongly cohesive UI elements. Along with regular HTML markup, data scope can have placeholders like {SomeValue} replaced with real data values on rendering. Placeholders are those specific spots where the values get delivered from the controller class. 
  • The controller class generates the values individually for each data scope via a set of scope binding handlers – one handler corresponds to one or more data scopes. Binding handler responsibility is to manipulate the scope and bind placeholders inside it to real values. 
  • Event handling is substituted by actions mechanism. Besides binding handlers, the controller can implement a set of action handlers for corresponding actions raised on the client side or by parent or nested controllers.

This is the top-level concept definition. Next we will take a closer look at the scope tree.

1.1. Scope Tree

Scope tree is essentially a mechanism that gives the developer full control over the page output. In the beginning of page rendering process, DaST builds the scope tree by parsing the HTML template. This tree structure is exposed to the developer via Scope Tree API, so developer task inside action and binding handlers of the controller class reduces to pointing at the specific scopes in the tree and manipulating them: bringing real values to placeholders, repeating scope content, and others. Scope tree structure is not constant. Once the tree is initially parsed from the HTML template, it may change during further rendering process. For example, when some scope gets repeated, the tree gets an additional branch coming from this scope. We will distinguish 2 primary states of the scope tree: initial scope tree and rendered scope tree.

Initial scope tree

Initial scope tree is the one that is parsed from the HTML template in the beginning of rendering process. In other words, initial tree is a structural skeleton of the HTML template. Template defines the initial tree, and vice versa, having initial scope tree, we can build valid and fully functional HTML template consisting of only scope container elements. For better understanding I will depict all scope trees that I talk about. I’ll use the following notation: 

  • Data scope is represented by black rectangle with scope name inside. 
  • Connector line coming out of the scope represents scope content. 
  • Red arrow with “repeat” label indicates that scope content is repeated. 
  • Grey rectangles and connector lines show branches added to the tree during rendering process. 
  • Blue text or markup is used for comments and explanations.

Let’s now create the initial scope tree for our Example 1. To achieve the UI shown on Fig EX1 in Example 1 Intro section, it’s obvious that we need 3 nested repeaters. In DaST terms this a tree of 3 nested scopes repeating their content. Fig 1.2 shows the initial scope tree that I came up with:

scopetree-template

Fig. 1.2: Initial scope tree for Example 1 part of LittleNonsense demo

A couple of important things to note: 

  • Every data scope tree has NULL scope as its root by definition. If template has no scopes inside the markup, then data scope tree consists of a single NULL scope. 
  • Connector line coming out of ItemRepeater scope does not have a leaf node on its end. This means that ItemRepeater scope has content but no nested child scopes.

Now let’s talk about the rendered scope tree.

Rendered scope tree  

Rendered scope tree is the one that initial tree turns into in the end of rendering process. In other words, rendered tree represents the resulting web page. It’s always bigger than initial tree, because of new branches appeared due to the repeated scopes.

Recalling graph theory we can make a couple of observations: 

  • Initial tree is always a sub-tree of the rendered scope tree. 
  • Rendered tree can be obtained by repeating scopes in the initial tree.

Back to our Example 1. Looking at the UI on Fig EX1 in Example 1 Intro section and the initial tree on Fig. 1.2, let’s now depict the rendered scope tree:

scopetree-rendered

Fig. 1.3: Rendered scope tree for Example 1 part of LittleNonsense demo

The initial tree which is a sub-tree of the rendered scope tree is depicted in black color. Grey part is whatever is added after all needed scopes are repeated to get the desired output.

Blue comments help you to understand how scopes in the rendered tree correspond to the resulting output of Example 1 page. There are 3 customers in the XML data file, so CustomerRepeater content is repeated 3 times for customers "John", "Roman", and "James". Customer "Roman" has 2 orders, so OrderRepeater content is repeated 2 times for "[O02]" and "[O03]" order IDs. And so on.

Scope paths 

As I mentioned earlier, the developer controls page output by manipulating the scopes via Scope Tree API. Before manipulating the specific scope, it must be selected using scope path. The scope path is simply a way of addressing scope in the tree. Every data scope is uniquely identified by its scope path. When web page is rendered, string versions of scope paths are used in id attributes of the corresponding scope container elements. Uniqueness of scope path guaranties uniqueness of scope container element id attribute.

Scope path consists of segments. Segment is a combination of the 0-based repeating axis and the scope name. Repeating axis indicates the repeating iteration of the container scope. If parent container scope does not repeat its content, then repeating axis stays 0.

In the source HTML of Example 1 resulting page, scope container elements look like the following:

<div id="SCOPE$0-CustomerRepeater$1-OrderRepeater$0-ItemRepeater">

The id attribute contains the string representation of the path for this scope. The actual path follows after “SCOPE” prefix and segments are delimited by “$” sign. NULL scope is always the same, so we don’t include it in the scope paths. 

Looking at the id attribute above, we can immediately tell which scope this DIV corresponds to on Fig 1.3. Just follow the path: take 1st (axis 0) CustomerRepeater, then take 2nd (axis 1) branch to OrderRepeater, then 1st branch to ItemRepeater. So, this path points to the ItemRepeater scope with "[O02] 2012-04-05" text on it (see Fig 1.3). 

Note that scope container id attributes always use absolute paths, but inside the controller callbacks, we can also use relative scope paths. Relative paths start from the current scope i.e. the one that current action or binding handler is executing for. 

1.2. Controller

Controller is a back-end class that tells the system how to render the scope tree. The scope tree is defined by the template associated with the controller. You can think of DaST controller and template combinations as a conceptual replacement for standard ASP.NET server controls or MVC views/controllers. Every controller can consist of other nested controllers, each of them responsible for its own part of the scope tree.

To control the desired part of the tree, the controller needs to be attached to the specific scope in the initial scope tree. When controller is attached to the scope, it becomes responsible for rendering of the partial scope tree (or sub-tree) starting from this scope. The sub-tree is rendered by the rendering traversal procedure that visits scopes one after another, calls corresponding binding handlers, and generates output (more details in Page Rendering section). Attaching controllers to their scopes is a part of tree model building which happens in the very beginning of the rendering process, so we’re only talking about the initial scope tree here. This is done before any of action or binding handlers are invoked, so the scope tree still has its initial structure parsed from the template. By default, DaST page is driven by one top-level controller called root controller associated with the root template. Root controller is always attached to the NULL scope of the tree defined by this template. If there are no other controllers, root controller becomes responsible for rendering the entire scope tree.

For example, in Example 1 app for simplicity I used a single top-level root controller responsible for the entire scope tree. But if I attached some child controller to the OrderRepeater scope on Fig 1.2 and rendering traversal went beyond the OrderRepeater, then the responsible controller would be the one attached to OrderRepeater rather then the root controller attached to the NULL scope. And this controller would be used to execute action and binding handlers.

All corresponding coding techniques are explained in depth in Controller Class section.

Scope Tree API

Scope Tree API is the interface for talking to the scope tree from inside action and binding handlers in the controller class. In order to get the desired page output, our task reduces to manipulating specific scopes in the tree. After scope is pointed using scope path (see Scope Paths), we can perform certain manipulations on it such as: 

  • Bind value to a placeholder within the scope. 
  • Show, hide, or repeat the content of the scope. 
  • Refresh the scope (DaST-AJAX partial update). 
  • Get or set generic name-value params of the scope   
  • And some others …

For more details go to Scope Tree API Reference.

1.3. Page Rendering

DaST page rendering process is the period from client request hitting the page until the response is sent back to the client. In standard ASP.NET we used to call it a “page lifecycle”, but I intentionally do not use this term here to emphasize the difference. There is no weird sequence of page and child control events where execution goes back and forth, no questions which event to use to do the certain task, no ambiguity at all. DaST rendering process is defined with mathematical precision and it is the most beautiful part of DaST pattern.

Client-server workflow

Let’s start from looking at the top-level picture of client-server interaction and request processing flow. Below is the diagram showing this flow step-by-step:

workflow

Fig. 1.4: Top-level request processing flow

So, page loads initially, then reloads multiple times in response to some events on the client side. Overall workflow looks pretty standard except for having DaST page instead of the regular page. Below is the more detailed descriptions of steps marked with red circles on Fig 1.4

  • 1. User navigates to the page. Initial page load request goes to the server. 
  • 2. Request processing is handed to the specific controller. Rendering process executes.  
    • For initial page load, rendering process outputs the entire page. 
  • 3. Response is ready and initial page output is displayed in the user browser. 
  • 4. User continues working on this page and invokes some client action that initiates another request. 
  • 5. Request processing is handed to the specific controller. Rendering process executes. 
    • This time, since there is a client action, this action is processed first. Then rendering process outputs the page partially, only for scopes that requested the refresh. 
  • 6. The refreshed content from server response is applied to the specific scopes that were refreshed. The page is updated in the user browser. 
  • Steps 4-6 are repeated until user navigates to another page.

There is a couple of important things to note here. First, there is no such thing as full page postback in DaST. Every client action in DaST results in partial refresh of the specific scopes on the page. Whatever you wish to update, needs to be wrapped into the data scope.

Second, in terms of code-behind design, DaST partial update has huge advantage over the standard AJAX based on UpdatePanel control. The standard async postback re-executes all your code unless surrounded by ugly IsAsyncPostBack conditionals. DaST re-executes binding handlers for the refreshed scopes only! All details are in the next section.

DaST rendering process 

Now let’s delve into the page rendering process and see what happens behind the scenes. Like everything else in DaST, rendering process is based on the scope tree defined by the template. Note that rendering process illustrated on Fig 1.1 is a bit simplified, because there is only a single root controller responsible for the entire scope tree. In real apps, one page is usually rendered by multiple nested controllers, each of them responsible for its own part of the scope tree i.e. scope sub-tree (see Controller section). So, let’s formalize the rendering process now. On the top-level, it can be divided into 3 steps: 

  • Step 1: Client Action Processing – System processes client action by invoking the corresponding action handler implemented inside the controller class. 
  • Step 2: Select Starting Scope – System selects the specific scope inside the tree, starting from which the sub-tree will be rendered. 
  • Step 3: Tree Rendering Traversal – The most important step where actual rendering of the scope tree takes place. The traversal procedure goes through all scopes in the current sub-tree (which begins with the selected starting scope) one-by-one in the strictly defined traversal order. Traversal algorithm is a strict post-order walk with top-to-bottom in-order traversal. This means that data scopes in the tree are visited in the same order as their corresponding container elements are encountered in the template that’s exactly what we need for proper page output. 

Now let’s elaborate what system actually does during these steps. This has to be done separately for initial and subsequent page loads (see Workflow section), because there are some differences in how steps are executed in these two states. So, first, assume we’re on the initial page load. Rendering steps for this case are below: 

  • Step 1: Client Action Processing
    This step is skipped, because there is no action for initial page load. 
  • Step 2: Select Starting Scope
    For initial page load the entire scope tree needs to be rendered, so starting scope will be the root scope of the scope tree i.e. the NULL scope. 
  • Step 3: Tree Rendering Traversal
    Starting from the root scope, system goes through the scope tree in the traversal order. FOR-EACH visited scope, DaST retrieves the responsible controller (recall Controller section) and does the sub-steps below. I want to emphasize that the following sub-steps are executed for every scope in the current sub-tree:
    • Step 3.1: Controller model preparation – Scope tree is associated with handlers inside the controller class. If model for this controller is already prepared, the whole sub-step is skipped. This condition ensures on-demand model setup: whenever traversal hits a scope with non-setup controller model, the preparation procedure executes. This sub-step breaks further into smaller steps:
      • Step 3.1.1: Specific template/controller pair is selected for request processing. 
      • Step 3.1.2: HTML template is parsed into the scope tree internal data structure. 
      • Step 3.1.3: Controller setup methods are invoked to associate handlers with scope tree (see example in Controller Coding sections). 
    • Step 3.2: Scope rendering output – Current scope is rendered and the resulting output is added to the page output. This sub-step is where the binding handler gets called and it breaks into the following smaller steps: 
      • Step 3.2.1: Corresponding binding handler is invoked in the controller. 
      • Step 3.2.2: Inside the binding handler we use Scope Tree API to do several things:   
        • Insert real values into the placeholders 
        • Repeat the scope needed number of times 
        • Manipulate the scope, set parameters, hide, show, etc. 
      • Step 3.2.3: Scope output generated and added to the page output. 
  • We’re done! Our page is complete and output is sent to the client browser. 

Now, assume we’re on the subsequent page load. Then rendering steps look like the following:   

  • Step 1: Client Action Processing
    This time we do have a client action. The entire actions mechanism is described in Actions section. In short, action handler is implemented inside some controller. To select this controller, DaST needs to know the structure of the scope tree. So, before action is processed, system tries to build partial model of the scope tree by executing Step 3.1 for scopes on the path to the controller. After tree is restored, action handler is invoked on the target controller. 
  • Step 2: Select Starting Scope
    For subsequent page load the scope tree needs to be rendered partially, starting from the scope explicitly refreshed inside the action handler. There can be 0 or more refreshed scopes. In case of multiple refreshed scopes, each of them becomes the starting scope for the traversal in the corresponding sub-tree.   
  • Step 3: Tree Rendering Traversal
    If no scopes are refreshed, this step is skipped. Otherwise, everything is absolutely the same as in Step 3 for initial page load, with the only difference that instead of one big scope tree, DaST has to render multiple sub-trees. The system traverses sub-trees one after another, re-invoking appropriate binding handlers. Each sub-tree results in an output packed into the response along with outputs from other sub-trees, and the response is sent back to the client.   
  • We’re done! Our page is complete and output is sent to the client browser. In case of multiple outputs, they are picked up by the client script and applied to the corresponding scope container elements to achieve standard AJAX partial update behavior.

And this is it! Sounds unbelievable, but this simple, clear, and transparent approach renders any page no matter how complex it is! Just look how clean, uniform, and well-defined DaST rendering process is in comparison with standard ASP.NET rendering, weird page lifecycle, event precedence, tons of server controls with nested data binding delegates, and so on! Even MVC, being is a huge step ahead of the WebForms, and being very well organized on the server-side, uses control flow and code generation constructs in views and partial views that immediately kills every hope for proper presentation separation. DaST pattern does not have any of these problems. It just dumps all this complexity at once!

Rendering in controller

Since the developer deals only with the controller class, we also want to look at rendering process from the controller point of view. Inside the controller class, execution can be divided into 3 separate stages:   

Based on these simple stages, we always know the exact order of execution for all functions inside the controller. You’ll see how this works in coding walkthrough sections that follow.

For now, we’re done with concept theory! At this point we know enough to start going through the real coding examples and talk about DaST development techniques. There is a bit more theory left, but we will learn the rest as we go through the code samples in this tutorial. 


2. Coding Basics

To learn basics of DaST programming, we will walk through the code of Example 1 part of LittleNonsense demo application. For deeper understanding I suggest you download the source code and have it in front of you for all explanations. Also, all functions in this tutorial belonging to Scope Tree API are followed by "[*]" link that take you directly to API Reference for this function. 

So, let’s get started with coding now. Before doing anything we need to reference DaST framework. ASP.NET DaST is contained in a single AspNetDaST.dll and you have to drop this DLL into the /Bin folder of your web application. All classes from Scope Tree API are located in the AspNetDaST.Web namespace.

2.1. DaST Page

The request URL in DaST leads to a physical .aspx page, which is similar to WebForms and different from MVC’s pattern based request routing. The content of .aspx file is ignored – this page is needed only as a request entry point. After hitting the page, the request processing workflow goes to DaST rendering engine. In other words, DaST framework overrides the entire rendering of the standard ASP.NET page.

Look at Example 1 now. The request hits NonsenseExample1.aspx page and the purpose of the corresponding code-behind is to transfer the flow to the specific DaST controller. The source code of the code-behind is below:

Listing 2.1: NonsenseExample1.aspx.cs file 

1: public partial class NonsenseExample1 : AspNetDaST.Web.DaSTPage 
2: { 
3:   protected override AspNetDaST.Web.ScopeController ProvideRootController() 
4:   { 
5:     return new NonsenseExample1Controller(); 
6:   } 
7: }

So, to turn regular.aspx page to DaST page you do the following:   

  • Create the standard .aspx page and just leave it default (its content is ignored anyway) 
  • Inherit page class from AspNetDaST.Web.DaSTPage instead of System.Web.UI.Page 
  • Implement single ProvideRootController() method returning the root controller instance

This is it! Now you have a DaST page driven by the specified root controller.

The question you might have at this point is why not to use URL pattern-based request routing directly to the controller, just like the way it is done in MVC and what is the purpose of having that intermediate.aspx page that does nothing? Ok, the main reason and a huge benefit of this design is that DaST pages can coexist with standard ASP.NET pages in one application without any extra settings! This allows the developers to smoothly transition existing ASP.NET apps to DaST-based apps page by page. I tried to smoothly integrate DaST into the existing ASP.NET infrastructure, so we can still use all standard features like HTTP context, session handling, cache management, etc. without changes.

2.2. HTML Template

Template is passed to the controller as plain text, so the physical location of the template can be anywhere: files, database, remote server, etc. For LittleNonsense app all templates are stored as .htm files under /App_Data/Templates/ folder. Let’s now build the template for Example 1.

In ASP.NET and MVC we built pages and views by reverse-engineering the desired page look into the set of server controls. Same thing in DaST – we take the desired UI and reverse-engineer it into the data scope tree. In Scope Tree section we’ve already found that Example 1 UI on Fig EX1 can be achieved with 3 nested data scopes. The initial scope tree is on Fig 1.2 and the corresponding template is on the listing below:

Listing 2.2: ../App_Data/Templates/NonsenseExample1.htm

01: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
02: <html xmlns="http://www.w3.org/1999/xhtml">
03: <head>
04:   <title>ASP.NET DaST - Little Nonsense | {TodayDate}</title>
05:   ....................
06: </head>
07: <body class="some-class-{TodayDate}">
08:   ....................
09:   <div dast:scope="CustomerRepeater" class="indent">
10:     <b>CUSTOMER:</b> [{CustomerID}] {CustomerName}
11:     <div dast:scope="OrderRepeater" class="indent">
12:       <div><b>ORDER:</b> [{OrderID}] {OrderDate}</div>
13:       <div dast:scope="ItemRepeater" class="indent">
14:         <div><b>ITEM:</b> [{ItemID}] {ItemName}</div>
15:       </div>
16:     </div>
17:   </div>
18: </body>
19: </html>

You can see our 3 scopes represented by nested DIV containers with dast:scope attributes and placeholders. On the rendering output stage (see Rendering section), the scopes are repeated and placeholders are replaced by the corresponding values giving us the desired output.

You might notice the {TodayDate} placeholder in the BODY tag class and in the TITLE. It’s there just to demonstrate that values can be inserted anywhere in the template including HEAD section. However, we cannot put scopes into this section, because container elements like DIV are not permitted in the HEAD tag. Whatever markup this tag has, it is considered as a part of NULL scope content.

Designing the HTML template is a creative process. Templates can get more complex depending on the kind of UI your are trying to achieve. In the same time, it is always a way less complex than MVC views or, moreover, stardard .aspx markup pages, because template always stays just the valid HTML with scope containers, no matter how sophisticated your UI is.

Well-formedness

Since the entire DaST processing revolves around the data scope tree, it is important that the initial scope tree is properly parsed from the template (see rendering step 3.1.2 in Rendering Process section) . Due to this, DaST engine is very sensitive to bad-formed HTML in your templates. It is the responsibility of the developer to make sure that HTML template is well-formed (all tags are properly closed, attributes have name/value format, etc). If DaST is not able to parse your template, the engine will throw the exception with more detailed explanation of the problem.

Currently I use a grammar-based HTML parser to extract data scope tree from the template. Later on this might be changed to the regex-based parser, so DaST will be more forgiving to syntax errors in the templates.

Resulted page output

After template from Listing 2.2 is completely rendered, we have the desired Example 1 page shown on Fig EX1. From this moment the control flow returns back to .aspx page that completes the output. In other words, DaST does not just output the resulted page as is, but does this via standard Web.UI.Page facilities. Behind the scenes the following happens to the resulted page: 

  • Page is decomposed into head and body parts 
  • Head part is inserted into .aspx page header 
  • Body part is inserted into .aspx page body literal 
  • Body attributes are added to the body element of .aspx page

There are some good reasons for mimicking the standard .aspx page. First, we need the seamless DaST integration into the standard ASP.NET. Second, in order to save implementation time and make my life easier, it was decided to use standard MS AJAX library with DaST add-on instead of creating my own AJAX layer, because it already has all those postback client scripts and everything else.

Creating the dedicated light-weight DaST AJAX layer is on the development list. In the beginning, I wanted to prove the concept ASAP, obviously, and did not bother with my own AJAX. Now, when fully functional library is released, it’s time to clean, polish, and add more features. It is possible that PHP DaST framework gets the jQuery based AJAX library from the beginning and then I will port it for ASP.NET DaST.

2.3. Controller Class

Controller is where the serious programming starts. From the DaST Concept section you should already have a good top-level picture of what controller class is and what it is for. In LittleNonsense demo all controllers are stored in .cs files under /App_Code/Controllers/ folder. From Listing 2.1 we see that NonsenseExample1.aspx page uses root controller class called NonsenseExample1Controller for processing all client requests. Let me give full source code for this class first, and then in the rest of the section I will explain this code line by line: 

Listing 2.3: ../App_Code/Controllers/NonsenseExample1Controller.cs

01: public class NonsenseExample1Controller : ScopeController
02: {
03:   public override string ProvideTemplate()
04:   {
05:     return Utils.LoadTemplate("NonsenseExample1.htm"); // load temlate content from anywhere
06:   }
07:  
08:   public override void InitializeModel(ControllerModelBuilder model)
09:   {
10:     // use one binding handler to bind all nested scopes (except for root scope)
11:     model.SetDataBind(new DataBindHandler(DataBind_ROOT));
12:     model.Select("CustomerRepeater").SetDataBind(new DataBindHandler(DataBind_CustomerRepeater));
13:     model.Select("CustomerRepeater", "OrderRepeater").SetDataBind(new DataBindHandler(DataBind_OrderRepeater));
14:     model.Select("CustomerRepeater", "OrderRepeater", "ItemRepeater").SetDataBind(new DataBindHandler(DataBind_ItemRepeater));
15:   }
16:  
17:   //
18:   private void DataBind_ROOT()
19:   {
20:     CurrPath().Replace("{TodayDate}", DateTime.Now.ToString("yyyy-MM-dd")); // output some example values
21:   }
22:  
23:   private void DataBind_CustomerRepeater()
24:   {
25:     var customers = DataLayer.GetCustomers(); // get all customers
26:  
27:     CurrPath().RepeatStart(); // zeroize repeater
28:     foreach (var c in customers) // loop through customers
29:     {
30:       CurrPath().Repeat(); // repeat scope for current customer
31:       CurrPath().Replace("{CustomerID}", c.GetValue("ID")); // bind customer id
32:       CurrPath().Replace("{CustomerName}", c.GetValue("Name")); // bind customer name
33:       CurrPath("OrderRepeater").Params.Set("CUST", c); // pass customer to next scope
34:     }
35:   }
36:  
37:   private void DataBind_OrderRepeater()
38:   {
39:     var c = CurrPath().Params.Get<object>("CUST"); // customer passed in params
40:     var orders = DataLayer.GetOrders((string)c.GetValue("ID")); // customer orders
41:  
42:     CurrPath().RepeatStart(); // zeroize repeater
43:     foreach (var o in orders) // loop through orders
44:     {
45:       CurrPath().Repeat(); // repeat scope for current order
46:       CurrPath().Replace("{OrderID}", o.GetValue("ID")); // bind order id
47:       CurrPath().Replace("{OrderDate}", o.GetValue("Date")); // bind order date
48:       CurrPath("ItemRepeater").Params.Set("Order", o); // pass order to next scope
49:     }
50:   }
51:  
52:   private void DataBind_ItemRepeater()
53:   {
54:     var o = CurrPath().Params.Get<object>("Order"); // order passed in params
55:     var items = DataLayer.GetOrderItems((string)o.GetValue("ID")); // order items
56:  
57:     CurrPath().RepeatStart(); // zeroize repeater
58:     foreach (var i in items) // loop through items 
59:     {
60:       CurrPath().Repeat(); // repeat scope for current item
61:       CurrPath().Replace("{ItemID}", i.GetValue("ID")); // bind item id
62:       CurrPath().Replace("{ItemName}", i.GetValue("Name")); // bind item name
63:     }
64:   }
65: }

As I already mentioned, Example 1 is driven by a single root controller whose code is on listing above. This controller is responsible for the entire scope tree starting from the NULL scope. Before delving into actual code, I want to talk about controller implementation structure. 

Top-level structure 

Let’s look at the structure of the controller on Listing 2.3. On the top-level, functions inside the controller class can be split into 3 categories that strictly correspond to 3 rendering stages (see Controller Rendering section): 

       
  1. Model setup callbacks (lines 3-15) – executed during Stage 1. There are only 2 mandatory setup functions that we must override inside the controller: ProvideTemplate() [*] and InitializeModel() [*]. 
  2.    
  3. Action handlers – executed during Stage 2
    There are no action handlers on listing above, so you can refer to Listing 3.3, lines 39-65 instead. There can be 0 or more handlers to process client as well as controller actions. To improve readability of my source code, I always start action handlers with “Action_” prefix followed by action name e.g. Action_SubmitInput(), but you can choose the names at your own preference.
  4.    
  5. Binding handlers (lines 18-64) – executed during Stage 3
    There can be 0 or more handlers corresponding to the scopes in the current sub-tree. To improve readability of my source code, I always start binding handlers with “DataBind_” prefix followed by scope name e.g. DataBind_CustomerRepeater() or DataBind_ROOT() for the controller root scope. The above listing has 4 binding handlers in total: DataBind_ROOT(), DataBind_CustomerRepeater(), DataBind_OrderRepeater(), and DataBind_ItemRepeater().

Next, before taking closer look, let's learn some basic APIs that we use to control scope tree.

Basic APIs to know 

Before we delve into the controller source code, let’s learn some basic APIs. I already mentioned that the entire Scope Tree API consists of just a couple of classes with a dozen of functions in total.  Knowing these, you’ll be able to create apps of any complexity level. In this section I’m going to highlight the most important APIs that we need to understand the code on Listing 2.3.

First, ControllerModelBuilder [*] class is used for working with initial scope tree and to setup tree model on Stage 1. Instance of this class is passed to InitializeModel() [*] callback in model parameter. Following is usage example: 

model.Select("CustomerRepeater", "OrderRepeater").SetDataBind(new DataBindHandler(SomeHandler));

This call consists of 2 steps: 1) pointing the scope with Select() [*] function, and 2) calling one of model setup functions. Function Select() [*] takes scope path specification as parameter and moves the scope pointer accordingly. Path consist of scope names only, because we work with the initial scope tree. Path specified is relative to the controller root scope. Then SetDataBind() [*] function associates handler function with the pointed scope. You can also call HandleAction() [*] to bind action handler or SetController() [*] to attach the child controller to the selected scope. I chose to force chained function call syntax everywhere because it looks very compact and readable. And this is all we need to setup tree model on Stage 1.

Second, ScopeFacade [*] class is used for working with the rendered scope tree and manipulate the scopes from inside your action and binding handlers on Stage 2 and Stage 3. It has a few functions and properties and throughout this tutorial all these members will be explained in greater details.

Same as for initial tree, before calling any member of  ScopeFacade [*], the specific scope has to be selected. This is done by CurrPath() [*] and CtrlPath() [*] functions that take scope path (see Scope Paths) specification as optional parameter and return an instance of  ScopeFacade [*] pointing at the selected scope. The difference between these 2 functions is that CurrPath() [*] starts from the current scope i.e. the scope for which current binding handler is executed, and CtrlPath() [*] starts from the controller root scope. So they are just like a Select() [*function for model setup stage, but work in the rendered scope tree. Following is usage example:

CurrPath("CustomerRepeater", 2, "OrderRepeater").Replace("{SomeValue}", "Hello World!");

Again, chained syntax is used. First call is to point the specific scope, and next call is one of  ScopeFacade [*] members. In our case it’s a Replace() [*] function that takes placeholder name and a replacement value. Replace() [*function has several overloads allowing different combinations of parameters. Note that this time I specified path with repeating axis for OrderRepeater scope. If axis is not specified, it’s considered 0.

Finally, frequently used APIs are Params [*] and StoredParams [*] properties of  ScopeFacade [*class. These two allow us to associate some generic parameters with scopes. Essentially Params [*is a way of passing data between the scopes during the rendering process. StoredParams [*], as it follows from its name, allows us to persist parameters between subsequent postbacks. Read the API description for these properties for more info. It is important to understand that DaST uses same controller instance to process the entire sub-tree that it’s responsible for and we should not create any instance members inside the controller to store any data. All data must be stored outside and if there is a need for passing objects between scopes, Params [*] property should be used. Params [*] property is of ParamsFacade<span style="font-size: 14px;"> </span>[*] type that has a set of overloaded functions to get and set scope parameters. 

Ok, now we know enough to start digging into the code.

Source code details 

The order in which controller functions are executed is strictly defined by the rendering process and traversal flow (see Rendering Process). As the system goes through this process and traverses the scope tree, the callback functions inside responsible controllers get invoked one after another until page output is complete. In this section I’ll explain all controller functions in the order of their execution. Before reading further, you need to recall  Rendering Process and Controller Rendering sections, because I’ll frequently reference them. Below I’ll follow the controller rendering stages, list callbacks in execution order, and describe what they do and how they work.

It all starts with client request hitting the NonsenseExample1.aspx page. Example 1 does not use actions, so we only talk about initial page load here. Code-behind logic on Listing 2.1 transfers all further processing to NonsenseExample1Controller class given on Listing 2.3.

Execution begins from Stage 1: Controller model preparation. As I mentioned on step 3.1 of rendering process, controller preparation is executed ONCE per controller whenever traversal procedure hits the scope whose responsible controller model is not setup yet. So, for our controller whose model is not setup yet, DaST needs to call 2 setup functions: 

  • System calls ProvideTemplate() [*] on line 3.

The purpose of overriding this function is to return the specific template as plain text. On line 5 I use utility function to read template by name from directory on the site and return it. Such simple approach is cool, because we can store our templates anywhere and build flexible CMS engines. Once template is returned, system proceeds with the next function call:   

  • System calls InitializeModel() [*on line 8.

This function has the important task to associate scope tree with the controller. In the previous sub-section I explained how ControllerModelBuilder [*] class is used for setting up the tree model. So, on line 11 we associate DataBind_ROOT() with the controller root scope which is the same as NULL scope in our case. Notice that I omitted Select() [*] call, so pointer stays at the controller root. Next, on line 12 we associate DataBind_CustomerRepeater() handler with CustomerRepeater scope. To point at the CustomerRepeater scope, we use Select() [*] function passing relative path to it. On lines 13-14 we do the same for OrderRepeater and ItemRepeater scopes. This time paths consist of multiple segments.

At this point, model setup is complete and the system turns to Stage 2: Action processing. In Example 1 we don’t have any actions, so this stage is simply skipped. No callbacks get invoked here. Actions is more advanced topic and you’ll see how it works in second part of this tutorial. Finally, system comes to Stage 3: Scope tree rendering output. This is the stage where actual rendering happens and our scope tree turns into the rendered scope tree on Fig 1.3. During this stage the traversal visits scopes one by one, invokes binding handlers, and outputs the result. So, traversal starts from visiting the NULL scope of the tree: 

  • System calls DataBind_ROOT() on line 18.
    Current path is SCOPE.

For better understanding, I will always specify the current scope path i.e. the one that  CurrPath() [*] points to if called without parameters. This time current path is SCOPE meaning that  CurrPath() [*] points to the NULL scope. The DataBind_ROOT() handler has only a single line of code. In the previous sub-section I explained how ScopeFacade [*class is used to manipulate scopes from inside action and binding handlers. So, on line 20 we use CurrPath() [*] without parameter to point at the current scope which is the root scope. And then we call Replace() [*] on it to output formatted date into the {TodayDate} placeholder. Recall that this placeholder is used in our template on Listing 2.2 inside TITLE and BODY tags that belong to the NULL scope. And we’re done here. Next traversed scope is CustomerRepeater

  • System calls DataBind_CustomerRepeater() on line 23.
    Current path is SCOPE$0-CustomerRepeater.

The content of CustomerRepeater scope needs to be repeated for every customer. The {CustomerID} and {CustomerName} placeholders need to be replaced on every iteration with appropriate customer data. So, on line 25 we retrieve the list of customers. We do it using data layer utility that returns entities from XML. Then we call RepeatStart() [*] on the current scope to reset repeating axis counter to 0. This function must be always called before repeating scope content, because by default there is always 1 axis for every scope. On lines 28-34 we loop through all customer objects, repeat the scope, and replace the placeholder. The loop iterates 3 times for customers “John”, “Roman”, and “James”. On line 30 the Repeat() [*] function is called instructing the system to repeat content of the pointed scope once. This function has to be called before anything else on the current iteration. On line 31-32 we use already familiar syntax to paste values into {CustomerID} and{CustomerName} placeholders in the current scope. On line 32 we point to the OrderRepeater scope and use Params [*] property to pass customer object to this scope as parameter. In the previous sub-section I explained how Params [*] property works. The customer saved on this step will be retrieved when execution comes to OrderRepeater binding handler, and we will know whose orders to display. So, our next scope visited by traversal is OrderRepeater

  • System calls DataBind_OrderRepeater() on line 37.
    Current path is SCOPE$0-CustomerRepeater$0-OrderRepeater.

Here we need to do pretty much the same as before, but for customer orders. On line 39 we start from retrieving the customer object passed from the previous traversal step. Current customer object is for “John”. After we get the customer, we use data layer to get the list of his orders. Then, on line 42 we prepare the scope for repeating again by calling RepeatStart() [*]. Finally, on lines 43-49 we do the loop analogical to previous binding handler, but this time for customer orders, replacing placeholders with specific order ID and date values. In the end of iteration, we, again, use Params [*] to save order object for ItemRepeater scope. We’re done here and traversal continues to ItemRepeater scope: 

  • System calls DataBind_ItemRepeater() on line 52.
    Current path is SCOPE$0-CustomerRepeater$0-OrderRepeater$0-ItemRepeater.

Everything is the same here as before, but now for ItemRepeater. First, we retrieve order object from scope parameters. Then get order items from XML. Then loop through them, repeat the scope, and replace the placeholders with item values. This time we don’t need to save anything in Params [*], because there are no more scopes. Now look at the scope tree on Fig 1.3. At this point we have completely traversed the first branch, and the traversal would stop here if we had only one customer. But since we have multiple repeated customers, the traversal has to travel all branches. So, our next visited scope is OrderRepeater again, but on the next repeated branch: 

  • System calls DataBind_OrderRepeater() on line 37.
    Current path is SCOPE$0-CustomerRepeater$1-OrderRepeater.

Notice the repeating axis of OrderRepeater scope in the current path – it is 1, not 0, because we’re on the second branch now! The customer object retrieved on line 39 is now for “Roman” i.e. the one saved on the 2nd repeating iteration of the previous CustomerRepeater scope. Everything else is the same – get the list of orders for current customer and repeat the scope to display them. Continuing traversal on the same branch, next visited scope is ItemRepeater

  • System calls DataBind_ItemRepeater() on line 52.
    Current path is SCOPE$0-CustomerRepeater$1-OrderRepeater$0-ItemRepeater.

As before, first, we retrieve the saved order object for “O02”. Then get its items with “I03” and “I04” IDs. Then repeat the scope for every item and replace placeholders. Next visited scope is ItemRepeater again: 

  • System calls DataBind_ItemRepeater() on line 52.
    Current path is SCOPE$0-CustomerRepeater$1-OrderRepeater$1-ItemRepeater.

It’s the same scope again, but with the different scope path. We are here, because customer “Roman” has 2 orders and we need to output items for order “O03” now. So, order object saved at the current path corresponds to “O03” and we repeat it 2 items. This branch is complete and traversal goes back to OrderRepeater

  • System calls DataBind_OrderRepeater on line 37.
    Current path is SCOPE$0-CustomerRepeater$2-OrderRepeater.

The repeating axis for current scope is 2, because we are now on the 3rd branch for customer “James”. Everything else is the same as before and next scope is ItemRepeater

  • System calls DataBind_ItemRepeater() on line 52.
    Current path is SCOPE$0-CustomerRepeater$2-OrderRepeater$0-ItemRepeater.

Again, except for different path, functionality is analogical to previously visited ItemRepeater scopes. And finally, traversal visits ItemRepeater again, because "James" has one more order: 

  • System calls DataBind_ItemRepeater() on line 52.
    Current path is SCOPE$0-CustomerRepeater$2-OrderRepeater$1-ItemRepeater.

And we’re DONE! Our tree is completely rendered and output is ready.

Traversal between controllers

In our simplified Example 1 of LittleNonsense where the page is driven by a single top-level controller, you might get a feeling that traversal cannot go anywhere until it finishes work in the current controller. It’s not true. In case of multiple controllers, the traversal can go beyond the current controller at any time. The traversal simply travels the rendered scope tree and for every visited scope system executes functions inside the currently responsible controller. For example, look at the scope tree on Fig 1.3 and imagine that there is a nested controller attached to ItemRepeater scope. If this were a case, then instead of executing DataBind_ItemRepeater() inside the root controller, the system would execute this handler inside the controller, attached to ItemRepeater. Moreover, the system would have to run the model preparation stage for this nested controller before calling any binding handlers. After it’s done, the traversal would return back to calling DataBind_OrderRepeater() on root controller for the repeated axis. So, execution can go beyond the bounds of the current controller and then come back again.

Conclusion

Look how clear and intuitive your controller code is! No matter how big your scope tree grows – the rendering part of the controller always consists of a plain list of binding handlers. This means that structural complexity of your code does not grow even for sophisticated Web 2.0 designs! This, in its turn, results in simple and readable source code as well as slim application architecture. At this point the first part of this tutorial is completed. We’ve learned basics of DaST design and development and it’s time to delve into true Web 2.0 programming world and see the full power of DaST in action.

3. Advanced DaST and Web 2.0

In this part of the tutorial we will learn the rest of DaST features needed for building highly complex and dynamic Web 2.0 sites. To demonstrate all these new features, I designed Example 2 part of LittleNonsense demo. Further in this section I’ll walk through the source code of this app with all necessary explanations.

3.1. Example 2 Complete Source Code

I’ll start from giving the complete source code for all templates and controllers that participate in Example 2 implementation. This section will be used as code reference for all explanations throughout the rest of this tutorial. At the moment you can skip through it quickly.

First of all, this time the request entry point is NonsenseExample2.aspx page. In NonsenseExample2.aspx.cs file everything is simple and analogical to what we’ve seen already in the previous example (see DaST Page section). The system is told to use NonsenseExample2Controller for processing all requests coming to this page. 

Now, as before, I'll give all code listings of Example 2 and will explain everything line by line throughout the rest of this article. Let's start from NonsenseExample2.htm file containing template for the root controller: 

Listing 3.1: ../App_Data/Templates/NonsenseExample2.htm

01: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
02: 
03: <html xmlns="http://www.w3.org/1999/xhtml">
04: <head>
05:   <title>ASP.NET DaST - Little Nonsense | {TodayDate}</title>
06:   <link href="res/CSS/common.css" type="text/css" rel="stylesheet" />
07:   <script type="text/javascript" src="res/jQuery/jquery-1.7.1.min.js"></script>
08: </head>
09: <body class="some-class-{TodayDate}">
10: 
11:   <div style="font-size: x-large; background-color: Gray; color: White; padding: 10px;">Little Nonsense | ASP.NET DaST Application DEMO</div>
12:   <div style="margin: 0 10px;"><a href="NonsenseExample1.aspx">Example 1</a> | <a href="NonsenseExample2.aspx">Example 2</a></div>
13:   <div style="margin: 20px; font-size: x-large; font-weight: bold;">Example 2: Partial Update, Actions, and Messages</div>
14: 
15:   <div dast:scope="CustomerRepeater" class="gizmo">
16:     <div dast:scope="Header" class="gizmo headerScope"></div>
17:     <div dast:scope="Customer" class="gizmo">
18:       <div dast:scope="Header" class="gizmo headerScope"></div>
19:       <div class="txt"><b>CUSTOMER:</b> [{CustomerID}] {CustomerName}</div>
20:       <div dast:scope="OrderRepeater" class="gizmo">
21:         <div dast:scope="Header" class="gizmo headerScope"></div>
22:         <div dast:scope="Order" class="gizmo">
23:           <div dast:scope="Header" class="gizmo headerScope"></div>
24:           <div class="txt"><b>ORDER:</b> [{OrderID}] {OrderDate}</div>
25:           <div dast:scope="ItemRepeater" class="gizmo">
26:             <div dast:scope="Header" class="gizmo headerScope"></div>
27:             <div dast:scope="Item" class="gizmo">
28:               <div dast:scope="Header" class="gizmo headerScope"></div>
29:               <div class="txt"><b>ITEM:</b> [{ItemID}] {ItemName}</div>
30:             </div>
31:           </div>
32:         </div>
33:       </div>
34:     </div>
35:   </div>
36: 
37:   <script language="javascript" type="text/javascript">
38: 
39:     function markScopeUpdated(scopeID)
40:     {
41:       $("div[id='" + scopeID + "'].gizmo").addClass("updated bold")
42:         .find(".gizmo").addClass("updated");
43:     }
44: 
45:     function hideRepeaterHeaders()
46:     {
47:       // leave only first gizmo header in case of repeater gizmos
48:       $(".gizmo").each(function () { $(this).find("> .headerScope:not(:nth-child(1))").hide(); });
49:     }
50: 
51:     // add server message handler
52:     DaST.Scopes.AddMessageHandler("{ScopeID}", "RefreshedFromServer", function (data)
53:     {
54:       markScopeUpdated(data.ScopeID);
55:       hideRepeaterHeaders();
56:     });
57: 
58:     // some initial load UI setup
59:     $(document).ready(function ()
60:     {
61:       $("form").attr("autocomplete", "off");
62:       hideRepeaterHeaders();
63:     });
64: 
65:   </script>
66: </body>
67: </html>

Next listing is for child controller template used to display the header section of all scopes:

Listing 3.2: ../App_Data/Templates/ScopeHeader.htm
01: <div class="hdr">
02:   <span class="high-on-update"><b>Updated:</b> {Updated}</span> | 
03:     <b>ID:</b> {ScopeID}<br />
04:   <b>Pass data:</b> <input type="text" value="" /> 
05:     <a href="javascript:$.ScopeHeader('submit', '{ScopeID}', 'self')">Header Only</a> | 
06:     <a href="javascript:$.ScopeHeader('submit', '{ScopeID}', 'parent')">Parent Scope</a> | 
07:     <a href="javascript:$.ScopeHeader('submit', '{ScopeID}', 'child')">First Child Scope</a> | 
08:     <!--showfrom:data-posted-->
09:   <b>Passed:</b> <span class="passedData">{PostedData}</span>
10:   <!--showstop:data-posted-->
11: </div>
12: 
13: <script language="javascript" type="text/javascript">
14: 
15:   // since script is output for every header, 
16:   // avoid redefinition using if clause here
17:   if (!$.ScopeHeader)
18:   {
19:     (function ($)
20:     {
21:       var methods =
22:       {
23:         submit: function (scopeID, target)
24:         {
25:           // clear all highlighted scope borders
26:           $(".gizmo").removeClass("updated bold");
27: 
28:           // collect data and raise action
29:           var $scope = $("[id='" + scopeID + "']");
30:           var input = $scope.find("input:text").eq(0).val();
31:           if (typeof (input) == "string" && input.length > 0)
32:           {
33:             DaST.Scopes.Action(scopeID, 'RefreshFromClient', [input, target]);
34:           }
35:           else alert("You did not input any data for: " + scopeID);
36:         }
37:       };
38: 
39:       $.ScopeHeader = function (options)
40:       {
41:         if (typeof (options) == "string" && methods[options])
42:         {
43:           methods[options].apply(this, Array.prototype.slice.call(arguments, 1));
44:         }
45:       };
46: 
47:     })(jQuery);
48:   }
49: 
50:   // add server message handler
51:   DaST.Scopes.AddMessageHandler("{ScopeID}", "RefreshedFromServer", function (data)
52:   {
53:     markScopeUpdated("{ScopeID}");
54:   });
55: 
56: </script>

Next listing is root controller class itself:

Listing 3.3: ../App_Code/Controllers/NonsenseExample2Controller.cs

001: public class NonsenseExample2Controller : ScopeController
002: {
003:   public override string ProvideTemplate()
004:   {
005:     return Utils.LoadTemplate("NonsenseExample2.htm"); // load temlate content from anywhere
006:   }
007: 
008:   public override void InitializeModel(ControllerModelBuilder model)
009:   {
010:     // scope binding handlers
011:     model.SetDataBind(new DataBindHandler(DataBind_ROOT));
012:     model.Select("CustomerRepeater").SetDataBind(new DataBindHandler(DataBind_CustomerRepeater));
013:     model.Select("CustomerRepeater", "Customer").SetDataBind(new DataBindHandler(DataBind_Customer));
014:     model.Select("CustomerRepeater", "Customer", "OrderRepeater").SetDataBind(new DataBindHandler(DataBind_OrderRepeater));
015:     model.Select("CustomerRepeater", "Customer", "OrderRepeater", "Order").SetDataBind(new DataBindHandler(DataBind_Order));
016:     model.Select("CustomerRepeater", "Customer", "OrderRepeater", "Order", "ItemRepeater").SetDataBind(new DataBindHandler(DataBind_ItemRepeater));
017:     model.Select("CustomerRepeater", "Customer", "OrderRepeater", "Order", "ItemRepeater", "Item").SetDataBind(new DataBindHandler(DataBind_Item));
018: 
019:     // set child controllers
020:     model.Select("CustomerRepeater", "Header").SetController(new ScopeHeaderController());
021:     model.Select("CustomerRepeater", "Customer", "Header").SetController(new ScopeHeaderController());
022:     model.Select("CustomerRepeater", "Customer", "OrderRepeater", "Header").SetController(new ScopeHeaderController());
023:     model.Select("CustomerRepeater", "Customer", "OrderRepeater", "Order", "Header").SetController(new ScopeHeaderController());
024:     model.Select("CustomerRepeater", "Customer", "OrderRepeater", "Order", "ItemRepeater", "Header").SetController(new ScopeHeaderController());
025:     model.Select("CustomerRepeater", "Customer", "OrderRepeater", "Order", "ItemRepeater", "Item", "Header").SetController(new ScopeHeaderController());
026: 
027:     // handle client actions
028:     model.Select("CustomerRepeater", "Header").HandleAction("RaisedFromChild", new ActionHandler(Action_RaisedFromChild));
029:     model.Select("CustomerRepeater", "Customer", "Header").HandleAction("RaisedFromChild", new ActionHandler(Action_RaisedFromChild));
030:     model.Select("CustomerRepeater", "Customer", "OrderRepeater", "Header").HandleAction("RaisedFromChild", new ActionHandler(Action_RaisedFromChild));
031:     model.Select("CustomerRepeater", "Customer", "OrderRepeater", "Order", "Header").HandleAction("RaisedFromChild", new ActionHandler(Action_RaisedFromChild));
032:     model.Select("CustomerRepeater", "Customer", "OrderRepeater", "Order", "ItemRepeater", "Header").HandleAction("RaisedFromChild", new ActionHandler(Action_RaisedFromChild));
033:     model.Select("CustomerRepeater", "Customer", "OrderRepeater", "Order", "ItemRepeater", "Item", "Header").HandleAction("RaisedFromChild", new ActionHandler(Action_RaisedFromChild));
034:   }
035: 
036:   //
037:   #region Action Handlers
038: 
039:   private void Action_RaisedFromChild(object arg)
040:   {
041:     if ((string)arg == "parent")
042:     {
043:       CurrPath(-1).Refresh(); // refresh parent scope of the root scope of header controller
044:       CtrlPath().MessageClient("RefreshedFromServer", new { ScopeID = CurrPath(-1).ClientID }); // notify client of refreshed scope
045:     }
046:     else if ((string)arg == "child")
047:     {
048:       // find name of the next scope depending on current scope
049:       string nextScope = null;
050:       string currScope = CurrPath(-1).ClientID.Split('-').Last();
051:       switch (currScope)
052:       {
053:         case "CustomerRepeater": nextScope = "Customer"; break;
054:         case "Customer": nextScope = "OrderRepeater"; break;
055:         case "OrderRepeater": nextScope = "Order"; break;
056:         case "Order": nextScope = "ItemRepeater"; break;
057:         case "ItemRepeater": nextScope = "Item"; break;
058:       }
059:       // invoke action on Header scope of the first child scope
060:       if (nextScope != null)
061:       {
062:         CurrPath(-1, nextScope, "Header").InvokeAction("InvokedFromParent", null);
063:       }
064:     }
065:   }
066: 
067:   #endregion
068: 
069:   //
070:   #region Binding Handlers
071: 
072:   private void DataBind_ROOT()
073:   {
074:     CurrPath().Replace("{TodayDate}", DateTime.Now.ToString("yyyy-MM-dd")); // output some example values
075:     CurrPath().Replace("{ScopeID}", CurrPath().ClientID); // bind scope client id
076:   }
077: 
078:   private void DataBind_CustomerRepeater()
079:   {
080:     var customers = DataLayer.GetCustomers(); // get all customers
081:     CurrPath().RepeatStart();
082:     foreach (var customer in customers)  // repeat scope for each customer
083:     {
084:       CurrPath().Repeat();
085:       CurrPath("Customer").StoredParams.Set("CustomerID", customer.GetValue("ID")); // pass customer ID to each child Customer scope
086:     }
087:   }
088: 
089:   private void DataBind_Customer()
090:   {
091:     var customerID = CurrPath().StoredParams.Get<string>("CustomerID"); // get customer ID set in previous binding handler
092:     var customer = DataLayer.GetCustomer(customerID); // get customer object by ID
093:     CurrPath().Replace("{CustomerID}", customer.GetValue("ID"));
094:     CurrPath().Replace("{CustomerName}", customer.GetValue("Name"));
095:   }
096: 
097:   private void DataBind_OrderRepeater()
098:   {
099:     var customerID = CurrPath(-1).StoredParams.Get<string>("CustomerID"); // get customer ID from parent Customer scope
100:     var orders = DataLayer.GetOrders(customerID); // get orders by customer ID
101:     CurrPath().RepeatStart();
102:     foreach (var order in orders) // repeat scope for each order
103:     {
104:       CurrPath().Repeat();
105:       CurrPath("Order").StoredParams.Set("OrderID", order.GetValue("ID")); // pass order ID to each child Order scope
106:     }
107:   }
108: 
109:   private void DataBind_Order()
110:   {
111:     var orderID = CurrPath().StoredParams.Get<string>("OrderID"); // get order ID set in previous binding handler 
112:     var order = DataLayer.GetOrder(orderID); // get order object by ID
113:     CurrPath().Replace("{OrderID}", order.GetValue("ID"));
114:     CurrPath().Replace("{OrderDate}", order.GetValue("Date"));
115:   }
116: 
117:   private void DataBind_ItemRepeater()
118:   {
119:     var orderID = CurrPath(-1).StoredParams.Get<string>("OrderID"); // get order ID from parent Order scope
120:     var items = DataLayer.GetOrderItems(orderID); // get order items by order ID
121:     CurrPath().RepeatStart();
122:     foreach (var item in items) // repeat scope for each item
123:     {
124:       CurrPath().Repeat();
125:       CurrPath("Item").StoredParams.Set("ItemID", item.GetValue("ID"));  // pass item ID to each child Item scope
126:     }
127:   }
128: 
129:   private void DataBind_Item()
130:   {
131:     var itemID = CurrPath().StoredParams.Get<string>("ItemID"); // get item ID set in previous binding handler 
132:     var item = DataLayer.GetOrderItem(itemID); // get item object by ID
133:     CurrPath().Replace("{ItemID}", item.GetValue("ID"));
134:     CurrPath().Replace("{ItemName}", item.GetValue("Name"));
135:   }
136: 
137:   #endregion
138: }

And final listing is for the child controller used for all scope headers:

Listing 3.4: ../App_Code/Controllers/ScopeHeaderController.cs
01: public class ScopeHeaderController : ScopeController
02: {
03:   public override string ProvideTemplate()
04:   {
05:     return Utils.LoadTemplate("ScopeHeader.htm"); // load template content from anywhere
06:   }
07: 
08:   public override void InitializeModel(ControllerModelBuilder model)
09:   {
10:     // scope binding handlers
11:     model.SetDataBind(new DataBindHandler(DataBind_ROOT));
12: 
13:     // handle client actions
14:     model.HandleAction("RefreshFromClient", new ActionHandler(Action_RefreshFromClient));
15:     model.HandleAction("InvokedFromParent", new ActionHandler(Action_InvokedFromParent));
16:   }
17: 
18:   //
19:   #region Action Handlers
20: 
21:   private void Action_RefreshFromClient(object arg)
22:   {
23:     // client call was DaST.Scopes.Action(scopeID, 'RefreshFromClient', [input, target]);
24:     string input = (string)((object[])arg)[0]; // 1st elem of passed JSON array
25:     string target = (string)((object[])arg)[1]; // 2nd elem of passed JSON array
26: 
27:     switch (target)
28:     {
29:       case "self": break;
30:       // notify parent scope by rasing action passing string target as parameter
31:       case "parent": CtrlPath().RaiseAction("RaisedFromChild", "parent"); break;
32:       case "child": CtrlPath().RaiseAction("RaisedFromChild", "child"); break;
33:       default: throw new Exception("Unknown target");
34:     }
35: 
36:     CtrlPath().Params.Set("PostedData", input); // set posted data parameter
37: 
38:     CtrlPath().Refresh(); // refresh control root scope
39:     CtrlPath().MessageClient("RefreshedFromServer", null); // notify client of refreshed scope
40:   }
41: 
42:   private void Action_InvokedFromParent(object arg)
43:   {
44:     CtrlPath().Refresh(); // refresh control root scope
45:     CtrlPath().MessageClient("RefreshedFromServer", null); // notify client of refreshed scope
46:   }
47: 
48:   #endregion
49: 
50:   //
51:   #region Binding Handlers
52: 
53:   private void DataBind_ROOT()
54:   {
55:     CurrPath().Replace("{Updated}", DateTime.Now.ToString("HH:mm:ss")); // bind update time
56:     CurrPath().Replace("{ScopeID}", CurrPath().ClientID); // bind scope client id
57:     if (CurrPath().Params.Has("PostedData")) // if PostedData param is set for the scope ...
58:     {
59:       var data = CurrPath().Params.Get<string>("PostedData"); // retrive posted data containing user input
60: 
61:       CurrPath().Replace("{PostedData}", data); // bind input value to output it
62:       CurrPath().AreaConditional("data-posted", true); // display data-posted conditional area 
63:     }
64:     else
65:     {
66:       CurrPath().AreaConditional("data-posted", false); // hide data-posted conditional area
67:     }
68:   }
69: 
70:   #endregion
71: }

As you can see, there is lots of interesting stuff in the code. Below I’ll do the quick introduction into the Example 2 demo,  then I’ll talk about nested controllers and new features in binding handler, and finally, I’ll turn to the most exciting part which is actions, AJAX partial updates, and duplex messaging.

3.2. Example 2 Intro

I designed Example 2 application to demonstrate all DaST programming techniques that you might possibly need for creating complex Web 2.0 designs: AJAX partial updates, multiple nested controllers, event handling, duplex messaging, and others. In the same time this demo is quite simple and straightforward. As you might have already guessed, DaST partial update mechanism is based on the scope tree just like everything else in DaST world. Putting it simple, every scope in the tree can be refreshed independently. Main idea in Example 2 is to visualize physical scope containers of the rendered scope tree on the resulting page and allow partial updates of random scopes, so that you can actually see how scope tree works, how scopes get updated, how data is passed, how controllers communicate, feel the workflow, and feel the real power of DaST pattern.

New scope tree

In Example 1 our initial scope tree consisted of 3 repeater scopes (see Scope Tree section). Such structure allows us to update the entire repeaters, but not individual repeated items. In Example 2 we wrap repeated items into separate scopes so that partial updates can be also applied to these individual items. As a result, in addition to CustomerRepeater, OrderRepeater, and ItemRepeater scopes, our tree gets Customer, Order, and Item scopes. Next, for better scope visualization I want to output a special info header for each scope in the tree. This header will be displayed on the top of every scope container and will show scope client ID, last updated timestamp, and link buttons to raise actions and do partial update. I want to enclose header into a separate Header scope that will be a first child scope of every other scope in our tree. The resulted initial scope tree for Example 2 is shown below:

example2-scopetree-template

Fig. 3.1: Initial scope tree for NonsenseExample2.aspx page of LittleNonsense demo

Having this structure of the scope tree, I get the control over every possible part of the page. I can apply partial updates or pass data anywhere I want in any scope combination. Now let’s see the UI output of this demo.

UI overview

A part UI output of this demo is shown below on Fig EX2:

image_thumb_7

Fig. EX2: Part of Example 2 output

As you can see, it is essentially the same hierarchical structure as in Example 1 based on the same XML data file of customer orders, but with more functionality and more sophisticated UI. Main thing I do to visualize the scopes is a dotted border around every scope container. Now we can actually see the scopes and ensure that their nesting configuration strictly corresponds to the initial scope tree on Fig 3.1. Also, the border around the scopes that were partially updated due to the last postback will have a red color. Scopes updated directly will have a bold red border, and scopes updated as result of parent scope update will have a regular red border. Shortly you’ll see how this looks. Second, all output of the actual data values from XML input file, is marked with blue background. This is done just to visually distinguish it from other markup on the page not related to values in XML file. And finally, every scope has a header highlighted with yellow background. The header section, as I said before, is wrapped into its own Header scope, so you can see the dotted border around header as well. Let’s take a closer look at the header now.

Header controller 

Info header not only has its own scope, but also its own controller! Using this example I demonstrate working with multiple controllers, all related techniques, and APIs. If we tried to implement scope tree from Fig 3.1 using single controller with single template (as we did before in Example 1), we would end up duplicating header section code in both template and back-end controller. It is very intuitive to factor header section out into the separate controller with partial template and reuse it every time a scope needs header section.

Header UI output 

First of all, header contains some basic info about the current scope. You can see the “Updated” timestamp value which changes when scope is partially updated. This helps you distinguish partially updated scopes from non-updated. Next value is “ID” which contains the client ID of the current Header scope. Recall that scope ID is build using the scope path which is unique for each scope. Displaying this value in the header helps to understand how scope paths are built and and how they correspond to hierarchical nesting structure. Note that the displayed ID is always for the current Header scope. To find the ID of the parent scope, we just need to remove last segment from original path e.g. if  SCOPE$0-CustomerRepeater$0-Header  points at the Header scope, then SCOPE$0-CustomerRepeater points to the parent CustomerRepeater scope and so on.

Header action links

The most interesting part of the scope header is the input box and 3 link buttons. These are needed to demonstrate data input and all Web 2.0 related mechanisms. The idea is that you enter random text in the input field and click one of the links on the left. This raises client action which passes your input data to the server, processes it, applies partial update to the specific scopes, and initiates duplex reply message, handled on the client side. The whole process will be explained in details through the rest of tutorial. Right now I’ll just describe the functionality of these 3 link buttons. So, let’s pick the scope header and enter “Hello World!!!” in it.

image_thumb_8

Now click 1st “HeaderOnly” link. The result will be the following:

image_thumb_9

Here you see the bold red border around the header meaning that scope was directly updated. When we clicked the link button, the action was raised and our input text was passed to the server side along with the action. On the server side we tell the system to display this text within the scope and apply partial update to this scope to reflect the changes. This is a typical interaction workflow for all Web 2.0 apps. As a result, you see your input text output in the “Passed” field on the right and the scope is highlighted. So “Header Only” link means pass the text and update only the header. Now input “Hello World 2!!!” in the same field and click the 2nd link “Parent Scope”. There result is below:

image_thumb_10

This time the header and the parent scope were updated directly. Directly means that we will explicitly ask the system to update these scopes. All child scopes are also updated as result parent scope update, so they have a regular red border. The meaning of updating the parent scope is to show you how nested controllers communicate using controller actions. Since header has its own controller and ItemRepeater is in responsibility of the root controller, in order to update ItemRepeater we will have to notify the root controller. I will explain this interesting process in depth in the further sections. Finally, input “Hello World 3!!!” and click the 3rd link “First Child Scope”. You’ll see the following:

image_thumb_11

Now we see that header of the current scope is updated as well as the header of the first child scope. This is even more sophisticated controller communication, we need to notify the root controller first, and the root controller needs to access current scope’s first child and notify its header. So it’s two steps: from child controller to parent, and from parent to another child controller. Now it’s time to delve into the code and see how this all actually works. And we will start from going through rendering and binding handlers part of the controllers which is already familiar to us from the previous example.

3.3. Controller Setup And Binding Handlers

Before reading further, please recall all theory from Controller section.

In the current section we will walk through the source code of Example 2  controller binding handlers as well as controllers setup. From the Controller Class section in the previous example we already know how binding handlers work and what they are for. But this time the example application has much richer functionality and there are some new features to highlight in connection with rendering process. And, of course, the main difference is that now we have multiple nested controllers with multiple templates, so we want to see how everything works in this case. 

General info  

In Example 2 we use two different controllers and separate templates for each of them. The root controller is NonsenseExample2Controller on Listing 3.3 and its template is in NonsenseExample2.htm on Listing 3.1. The other controller is ScopeHeaderController on Listing 3.4 and its template is in ScopeHeader.htm on Listing 3.2. This controller is needed to display header section for every scope on the page.

Now look at the scope tree on Fig 3.1. We will need to attach child ScopeHeaderController to every Header scope in the tree to make it responsible for outputting the header. Attaching child controllers is a part of the model preparation rendering stage (recall Controller Rendering section). From HTML template point of view, the container element for the scope, to which you’re going to attach the controller, must be empty i.e. contain no markup. This is natural, because every child controller has its own template whose rendered result gets populated into that empty container during rendering traversal.

Rendering traversal is not impacted by presence of child controllers anyhow. It still goes through the scope tree in the same traversal order executing binding handlers inside appropriate controllers. So, for currently traversed data scope, system finds the responsible controller and attempts to execute the corresponding binding handler inside it. Recall Traversal Between Controllers section above where I mentioned how execution can go back and forth between parent and child controllers.

Now it’s time to touch some source code starting from HTML templates.

Multiple templates 

Our root template is on Listing 3.1. It is responsible for the entire UI except header section. Root templates always look like a complete web page with <HTML> and <BODY> tags, while child templates only contain partial HTML fragments.

All scope containers defining the data scope tree are on lines 15-35. Nesting structure is similar to our previous example, but we have more scopes this time. The multiple Header scopes (lines 16, 18, 21, 23, 26, and 28) are the ones to which ScopeHeaderController will be attached. In the template these scopes look like the following:

16: <div dast:scope="Header" class="gizmo headerScope"></div>

So, as I already said, if we want to attach the controller to the scope, its container must be empty; otherwise, the error is thrown. Child controller has its own template populated into this empty container after rendering.

There is also a JavaScript block on lines 37-65. Most of this JavaScript is needed just for fancier output except for lines 52-56 where I use one of the coolest DaST features to handle message sent from the server side. In combination with client actions, this mechanism is called duplex messaging and I’ll talk about all this in depth in our next sections.

Now look at the child template on Listing 3.2. On lines 1-11 it has some markup to output the header section UI. Note that there are no nested scopes here, so the scope sub-tree that child ScopeHeaderController is responsible for a single Header scope which is also a root scope of this controller in the same time. The markup is trivial. There are some placeholders to display updated timestamp and scope ID and link buttons to raise actions on lines 5-7. Actions will be explained in depth in Actions section. On lines 8-10 we use another DaST feature called conditional area. I’ll explain this feature in one of the next sections. Child template also includes a JavaScript block on lines 13-56. I use typical jQuery syntax to create a $.ScopeHeader plugin to prepare data and raise actions. This all will be explained in Actions section. Notice the if condition on line 17 – it is needed to avoid plugin class redefinition, because there are multiple Header scopes on the resulted page so this script will be pasted multiple times. I could simply place it in the parent template and the problem would disappear, but I just wanted to show how to write fully independent templates containing mix of markup and scripts.

Setting up child controllers

Now let’s turn to the source code of our controllers. Look at the new scope tree on Fig 3.1. First thing we need to do in the root controller is to attach the child ScopeHeaderController to all Header scopes to make it responsible for rendering the header section. Child controller should be attached in the InitializeModel() [*] override of the parent controller using SetController() [*] API where the instance of the desired child controller is passed. 

So, on lines 20-25 of our top-level controller on Listing 3.3 we attach ScopeHeaderController to every Header scope in the scope tree. Every call in InitializeModel() [*] follows the same idea all the time – the target scope is selected using Select() [*] and the desired setup API is called on it. So, the typical call to attach the child controller looks like the following: 

20: model.Select("CustomerRepeater", "Header").SetController(new ScopeHeaderController());

The SetController() [*] expects an instance of the controller class to be passed as parameter. We can pass the same instance everywhere or use the new one every time like I did – it does not matter, because by design inside the controller we do not rely on instance anyway and use context-dependent facilities like Params [*] collection and others.

Now, after controllers are attached, our scope tree is ready for traversal. Leaving all actions related stuff for the future sections, let’s just do a quick overview of binding handlers inside both controllers. From the Controller Class section we already know how binding handlers work and what they are for. So, let’s start from the root controller on Listing 3.3.

Root controller binding handlers

On lines 11-17 of Listing 3.3 we do some boilerplate code to associate binding handlers with the scopes in the tree using SetDataBind() [*]. Here we have more scope binding expressions than our previous controller in Example 1, simply because there are more scopes this time. We bind all scopes that we have in the scope tree on Fig 3.1 except for Header scope, because this scope has an attached child controller that becomes responsible for rendering this scope.

Binding handler implementations are on lines 72-135. In the previous example we only had handlers for 3 nested repeaters. Now we have to add more handlers for Customer, Order, and Item scopes wrapping individual items inside the corresponding repeaters. Other than that, the idea is pretty much the same: retrieve items, repeat scope in a loop, save current item in params, etc. Let’s go through some of the binding handlers really quick.

First binding handler is DataBind_ROOT() on line 72 for the controller root scope which is also a NULL scope. Nothing special about it. Just replace some placeholders in the root scope.

Next one is DataBind_CustomerRepeater() on line 78. This is already familiar to us: we get the list of customer objects, loop through them, and repeat the current scope. Unlike previous example, we don’t have to replace any placeholders here, because for each item in this repeater we have a dedicated Customer scope whose binding handler should do all replacements. The only thing we need is to pass the customer object to the next traversed scope using scope parameters. 

And here is the interesting moment. On line 85, to pass customer to the next scope I use StoredParams [*] collection instead of Params [*]:

85: CurrPath("Customer").StoredParams.Set("CustomerID", customer.GetValue("ID"));

This time instead of the complete customer object, I pass the ID that can be used to retrieve this customer directly. Why I cannot use Params [*] to do this? Params [*] work only when we need to pass data between the handlers executed on the same traversal run. But since in Example 2 every scope can be randomly refreshed, causing the traversal to re-execute binding handlers starting from this scope, I cannot rely on Params [*] anymore – the customer ID simply will not be there unless  DataBind_CustomerRepeater() is executed on the current traversal run. Using  StoredParams [*] instead solves the problem easily, because values saved in  StoredParams [*] persist during consequent actions and postbacks.

I would advice against heavy use of  StoredParams [*], because its idea is similar to the standard ASP.NET VIEWSTATE which adds some overhead between Ajax requests. All you can do with  StoredParams [*], you can also do without it by just passing parameters along with actions. For our example I just want to demonstrate how to use  StoredParams [*] if you really need to.

Next handler is DataBind_Customer() on line 89. Its implementation is trivial. On line 91 we retrieve previously saved customer ID. Even if traversal starts from current binding handler (partial update), the  StoredParams [*] will still contain the needed customer ID value saved there on the initial page load when traversal went through the entire tree.  On line 92 we get the customer object using customer ID. And on lines 93-94 we replace the placeholders.

Next one is DataBind_OrderRepeater() on line 99. And here we see one more interesting thing – negative number in the scope path:

99: var customerID = CurrPath(-1).StoredParams.Get<string>("CustomerID");

Read the CurrPath [*] API description and refer to the Example 2 scope tree on Fig 3.1. Current scope is OrderRepeater and negative -1 tells the system to go 1 scope backwards i.e. point at the previous Customer scope. Since customer ID was saved with the Customer scope, we can always retrieve this ID by pointing at this customer scope. 

Everything else in this handler is the same as for previous repeater. We retrieve a list of orders by customer ID, repeat scope in a loop, and pass current order ID to Order scope on each iteration.

And so on. Other binding handlers in NonsenseExample2Controller are analogical.

Child controller binding handlers

Now let’s see what’s going on in the child ScopeHeaderController on Listing 3.4. The Header scope that this controller is attached to, becomes the root scope of this controller. Except the root scope, there are no more scopes in this controller. So we only need one binding handler for the root scope.

The handler is set on line 11 and the callback function DataBind_ROOT() is on line 53. This binding handler will render the entire header UI. First of all, on lines 55 and 56 we replace {Updated} and {ScopeID} placeholders with current timestamp and ClientID [*] of the current scope respectively. Take a look at the template on Listing 3.2 where all these values are inserted. Next, on line 57, we use Params.Has() [*] to check if parameter named "PostedData" is set. If yes, then this data is output to the {PostedData} placeholder; otherwise, we hide the entire output area. On the initial load, "PostedData" parameter is not set. On the subsequent loads, it is set for the current scope by the action handler in response to user input in the text box within the scope header. This will be explained in depth in our next Actions section. For now, last thing in binding handlers that needs explanation is usage of AreaConditional() [*] API on lines 62 and 66.

Conditional areas 

Take a quick look at the AreaConditional() [*] API. This is simply a way to hide or show a fragment of your HTML code depending on some condition. This fragment has to be wrapped into a special comment tag with the special syntax. In our child controller template on Listing 3.2 we have such conditional on lines 8-10:

08: <!--showfrom:data-posted-->
09: <b>Passed:</b> <span class="passedData">{PostedData}</span>
10: <!--showstop:data-posted-->

This area is controlled from the binding handler using AreaConditional() [*]  API.  On line 66 of Listing 3.4 we have the following statement: 

66: CurrPath().AreaConditional("data-posted", false);

This tells the system to remove specified area from the resulting page. On line 62 we pass TRUE for the second parameter meaning that area needs to be added to the resulted page. Same thing we could achieve by wrapping conditional area in the scope and hiding it when necessary, but we don’t want to flood our scope tree with unnecessary scopes. Conditionals are the light-weight approach to toggling different UI areas on the page.

Area conditionals can be nested to achieve logical AND or put beside each other for logical OR. You can combine conditions in many different configuration to achieve your UI requirements. One thing to remember about conditionals is that they should not contain nested scopes. It will not break anything, but from the design point of view, it does not make sense to hide scopes using parent conditional area. In such case a parent scope should be used to maintain a clear scope tree.

3.4. Scope Actions

Actions and duplex messaging mechanism in combination with partial updates is another outstanding feature that makes DaST different from other frameworks. Main asset of DaST concept is that simplicity and flexibility of the core features design are both at the maximum level in the same time, while other modern server-page engines usually have to sacrifice one for the sake of the other. Actions mechanism is a DaST replacement for event handling. The typical action workflow is the following:

  • User interacts with the web page (clicking buttons, etc.). 
  • Client script rises an action with optional data value. 
  • The request with action info is sent to the server side. 
  • Action handler with data value parameter is executed inside the controller. 
  • Scopes are refreshed (if needed) and action completes with partial update. 

In our Example 2, action is raised when user inputs some text into the textbox located in the header area of every scope and clicks one of the link buttons on the right. In Intro section I’ve already described the differences of those 3 link buttons and the resulting action top-level workflow. Let’s go though this process from the very beginning and explain everything in details.

Rising Actions

Action is raised in the client browser using DaST.Scopes.Action() [*] API. The scopePath passed to this function is the ID of the scope whose responsible controller should be used to handle the event. The actionName parameter identifies the action within the controller. Finally, actionArgs is a generic data parameter passed along with an action. The DaST.Scopes.Action() [*]  function can be called from anywhere within your web page. You can put it in the onclick attribute of HTML element or call it from your JavaScript – there are no limitations. Now back to our example on Listing 3.2. Assume we input “Hello World!!!” and clicked “Header Only” link button as described in Example 2 Intro section. The result of this action is also described in Example 2 Intro  section. Now let’s follow the code and see what happens and how exactly it works. The “Header Only” link button is on line 5 of Listing 3.2 and it looks like the following:

5: <a href="javascript:$.ScopeHeader('submit', '{ScopeID}', 'self')">Header Only</a>

All 3 buttons call the same submit method of $.ScopeHeader plugin (I use jQuery plugin syntax just because I like it) passing different parameter to it depending on which button is clicked. The method is implemented on lines 23-36. Even though it’s not directly related to actions, I’ll give a bit of explanation here. The purpose of submit method is to get input text from the corresponding text box, prepare some values, and raise action passing the text and the prepared values as parameters. Since “Header Only” is clicked, the target parameter equals “self”. On line 26 I clear all bolded and red borders bringing them to the initial state before Ajax call. On line 29 I use passed scopeID value to find the current scope container where button is clicked. Note that jQuery will not accept DaST id specification with “$” delimiters, so I have to use attribute selector. I’ll change the delimiters in the future to solve the problem. Next, on line 30 I get the currently input text. Next, we have an if clause on line 31 and warning message display on line 35 in case of the invalid input. Finally, if input is valid, we come to line 33 where all interesting stuff happens:

33: DaST.Scopes.Action(scopeID, 'RefreshFromClient', [input, target]);

This is our long waited call to  DaST.Scopes.Action() [*] API to raise a client action. The scopeID param is reused here to point at the scope whose responsible controller should handle an action. Recall that we’re currently in the template for one of the Header scopes, so scopeID will point to one of Header scopes depending on where we clicked the button. And the controller attached to this scope is ScopeHeaderController, so the system will use its instance to handle current action, which is the desired behavior. Second parameter is action name and I chose "RefreshFromClient". It’s self-explanatory telling that “this is a refresh request coming from client”. Note that action name has to be unique for the entire controller, not for the target scope. Last parameter is the generic data value that will be passed to the action handler on the server side. This value has to be a JSON-serializable object. In our case we pass the JSON array where we put input text and a target (whose value is "self" in our case). Now action is raised. Let’s see how to handle this action on the server side.

Handling actions

In the controller class, actions are bound to their handlers inside the InitializeModel() [*] method using HandleAction() [*] API. There is no need to point at the specific scope using Select() [*], because client actions are bound on per-controller basis. The action handler function must have a generic object argument that equals the data value passed to DaST.Scopes.Action() [*] on the client side. Inside action handler, CurrPath() [*] always points to the scope specified by scopePath parameter in DaST.Scopes.Action() [*] call. Back to our example. As we know from previous section, the system chooses ScopeHeaderController attached to Header scope to process our "RefreshFromClient" action. The code for this controller is on Listing 3.4. First thing we have to do is to bind the action to its handler and this is done on line 14 as following:

14: model.HandleAction("RefreshFromClient", new ActionHandler(Action_RefreshFromClient));

So, you pass action name and the handler function. Implementation of Action_RefreshFromClient() handler is on lines 21-40. Let’s go through the source code of this handler and explain what it does. The purpose of this handler is to take the text input by the user, save it in the "PostedData" param for the scope that submitted this data, and then refresh certain scopes to make them re-render. Since we used “Header Only” button, we only need to refresh the current Handler scope (target is equal to "self"). From section Child Control Binding section we already know how DataBind_ROOT() binding handler on lines 53-68 works. On re-render, this handler is called again. This time "PostedData" value is set and the conditional area reveals the HTML snippet with actual input data, so you see your “Hello World!!!” output in red rectangle (see Header Action Links section).

First of all, handler function has a generic arg parameter which is set to the JSON value passed to DaST.Scopes.Action() [*] from the client side. JSON objects are represented in .NET as combination of arrays and name-value dictionaries so it’s quite simple to work with them. Recall that I passed array of two values: input text and target. On lines 24 and 25 I retrieve these values into local variables. Next, on line 27, we switch on target value which does nothing in our case, because target equals "self". All other cases will also be explained in further tutorial sections. CurrPath() [*points to the controller root i.e. the Header scope. On line 36 we save our “Hello World!!!” text under "PostedData" name to the current Header scope, so that it will be picked up inside the DataBind_ROOT() on re-rendering. Finally, we notice two more interesting expressions in the end of the action handler. On line 38 we see the DaST partial update used to refresh the current Header scope. And line 39 demonstrates DaST duplex messaging mechanism used to notify the client side of the event and pass data to it. Both these cool features are explained in depth on the next two sections.

3.5. Partial Updates

In DaST partial update can be applied to any scope or multiple scopes inside the tree. If scope is refreshed, its child scopes are refreshed as well. DaST does not refresh any scopes on a postback unless it is explicitly instructed to do so. There are no things like UpdatePanel triggers or similar stuff from standard ASP.NET. To refresh the scope in DaST, you need to point it using CtrlPath() [*] or CurrPath() [*and call Refresh() [*] function. If you do this on multiple scopes, all of them are refreshed. It’s just that simple. When Refresh() [*] is called on the pointed scope, this scope is placed into the refresh queue. After completion of Stage 2: Action processing, the Stage 3: Rendering output starts (see Controller Rendering section). This time, instead of traversing the scope tree from the NULL scope, the tree is traversed partially starting from the scopes in the refresh queue. The resulting output for each sub-tree is sent back to the client and is applied to the corresponding scopes on the web page. It’s important to understand that during partial update, DaST executes binding handlers for the refreshed scopes ONLY! This is a mathematically correct approach to partial update, when partial UI refreshing results it partial code execution on the back-end. Compare to ugly UpdatePanel based updates in standard ASP.NET, where every partial update executed the entire page lifecycle. Now back to our sample code on Listing 3.4. The refresh is done on line 38 by the following instruction:

38: CtrlPath().Refresh();

The “Header Only” link refreshes only one scope which is a Header scope corresponding to the header where the link button is clicked. Due to this instruction, the DataBind_ROOT() on line 53 will be re-executed during rendering traversal and the newly generated output will be applied to the corresponding scope on the web page. The resulting UI from clicking “Header Only” link was shown in Header Action Links section. To highlight the specific scope with red borders I need to be able to detect on the client side whether this scope was refreshed. For this purpose I can use one of my favorite DaST features – duplex messaging.

3.6. Duplex Messaging

Putting it simple, client messaging mechanism is like actions, but in the opposite direction. While action is raised on the client side and handled on the server side, the message is raised on the server side and is handled by JavaScript in the client browser! Actions and client messages complement each other allowing true both-ways duplex communication between the client and the server. This mechanism is something that DaST can be really proud of, because with all its simplicity, it brings unmatched Web 2.0 capabilities to your apps. The syntax is uniform like everything else in DaST: to send message to the client browser you need to point at the scope and call MessageClient() [*] function on it passing message ID and JSON data object. In the client script we use DaST.Scopes.AddMessageHandler() [*] JavaScript API to add handler for the specific scope and message ID combination. Back to our code on Listing 3.4. On the last line 39 of our action handler we see the following instruction:

39: CtrlPath().MessageClient("RefreshedFromServer", null);

Here I send the "RefreshedFromServer" message to the client side, so that client code can handle this message and update the desired scope borders accordingly. The second parameter to MessageClient() [*] is some generic data value that have to be JSON-serializable object as before. This object is passed to the client and is given to the JavaScript message handler as argument. In our case we don’t need to pass any data, so just pass null. Let’s now see how the message is handled on the client side. In Listing 3.2 on lines 51-54 we see the following:

51: DaST.Scopes.AddMessageHandler("{ScopeID}", "RefreshedFromServer", function (data)
52: {
53:   markScopeUpdated("{ScopeID}");
54: });

And this is a JavaScript handler for our message! The {ScopeID} is replaced by the actual value pointing at the current Header scope. Recall that inside our action handler, the "RefreshedFromServer" message was also sent for the Header scope. Third parameter of DaST.Scopes.AddMessageHandler() [*] is a callback function with data param that gets the JSON representation of the value, passed to MessageClient() [*]. I love JSON, because it’s consistent everywhere. Note that this code is not a part of  if(!$.ScopeHeader)  condition starting from line 17. This condition need to avoid $.ScopeHeader plugin redefinition, because it’s always the same for all Header scopes. But code on lines 51-54 is always different due to different values of {ScopeID} and in our case message handler needs to be added for every Header scope on the page. Next, inside the callback function on line 53 I call a helper markScopeUpdated() function to give our scope the updated look and feel. This helper function is implemented inside the root template on Listing 3.1 on lines 39-43. You can see that I simply select the updated scope container and assign CSS classes to it so it becomes red and bold. Also I make all child containers red, but not bold, so that only the directly updated scope becomes bold. This achieves the UI we could see in Example 2 UI section. And this is it! Our scope gets updated, message is handled, UI is adjusted. Now it's time to see what other two link buttons do.

3.7. Controller Communication

Last topic that we need to learn is controller-to-controller communication. In DaST controllers talk to each other using controller actions. One of the biggest DaST assets is that syntax of controller actions is absolutely uniform with actions raised on the client. Assume we entered “Hello World 2!!!” into the header of ItemRepeater as shown in Header Action Links section. We already know how “Header Only” button works, but the other two buttons require a bit more programming.  Main difference is that for “Header Only” button both action handling and scope refreshing were done in one ScopeHeaderController, because we only needed to refresh the Header scope. For “Parent Scope” and “First Child Scope” buttons we also need to refresh the parent ItemRepeater scope that root NonsenseExample2Controller is responsible for. So, what we need to do here is to refresh the Header scope and then tell the parent controller to refresh the ItemRepeater. To communicate between controllers DaST provides controller actions mechanism.

Raising action from child controller 

The child controller can raise action that can be handler in the parent controller. Actions are raised using RaiseAction() [*] function where we pass action name and generic argument. This action can be handled on a parent controller using HandleAction() [*] API with syntax uniform with client actions handling. The only difference is that data item does not have to be JSON-serializable this time, because object is passed on the server side. Also, when handling client action, there is no need to point the scope before calling HandleAction() [*], but in case of controller action, we must point at the scope to which the desired child controller is attached. Back to our example. Assume we input “Hello World 2!!!” as shown in Header Action Links section and clicked “Parent Scope” button. The action is raised again by line 33 on Listing  3.2, but this time target equals "parent". Our action handler on line 21 of Listing 3.4 is executed again and this time switch clause on line 27 chooses case for "parent":

31: case "parent": CtrlPath().RaiseAction("RaisedFromChild", "parent"); break;

This is where we raise our "RaisedFromChild" action to be handled in a parent controller. I pass “parent” as generic data parameter because I’ll need to use this value later. After this line is executed, the action is raised and becomes visible to the parent controller which is in our case a root NonsenseExample2Controller on Listing 3.3. First thing we need to handle this action is to associate the action handlers. This is done on lines 28-33 of Listing 3.3 where I point all Header scopes (to which ScopeHeaderController is attached) one by one and associate the Action_RaisedFromChild() action handler with "RaisedFromChild" action. So, Action_RaisedFromChild() on line 39 gets called in response to the "RaisedFromChild" action with arg equal to "parent". The if clause on line 41 gets satisfied and lines 43 and 44 are executed. CurrPath() [*] still points and the Header scope, so, to refresh the ItemRepeater, we must go 1 step back in the scope tree first and then refresh. This is done on line 43. On line 44 we send a duplex "RefreshedFromServer" message to the client passing scope ID as parameter to notify our UI about the scope refresh. In the root template on Listing 3.1 this message is handled using the following code on line 52:

52: DaST.Scopes.AddMessageHandler("{ScopeID}", "RefreshedFromServer", function (data)
53: {
54:   markScopeUpdated(data.ScopeID);
55:   hideRepeaterHeaders();
56: });

And this calls markScopeUpdated() helper function already familiar to us from the previous section. This function takes the ID of the refreshed scope as a parameter and I use the data.ScopeID that I passed to the previous MessageClient() [*] call. Could it be simpler? Ok, cool, but this is not it. After Action_RaisedFromChild() handler completes, the execution returns to Action_RefreshFromClient() handler on Listing 3.4 to line 36. The statements on lines 36, 38, and 39 get executed and I've already explained them in the previous section. So, as a result, the Refresh() [*] is called on both ItemRepeater and Header scopes. Since this Header is a child of ItemRepeater, it does not really matter if we call Refresh() [*] on the Header scope, because it is updated anyway as a child of the refreshed scope. Next thing to note is that we send two duplex messages to the client during current action handling procedure: on line 44 of Listing 3.3 and on line 39 of Listing 3.4. This, in its turn, means that both message handlers will be invoked on the client side: one on line 52 of Listing 3.1 and the other on line 51 of Listing 3.2. As a result, both Header and ItemRepeater scopes get the bold red borders.

Raising action from parent controller

Now the other way around – parent controller can also raise action handled in its child controller. Or it’s more precise to say invoke action on a child controller. Actions are invoked using InvokeAction() [*] function where you pass action name and generic data item. Before calling this API, you have to point at the scope to which the desired controller is attached. Everything else is very similar to raised controller actions in the previous section. Action is handled using uniform syntax and HandleAction() [*] API (no need to point at the scope, because action is invoked per controller). Now let’s see how the last button works. Assume we enter some “Hello World 3!!!” into the header of ItemRepeater and click “First Child Scope” link as shown in Header Action Links section.  This one is the most complex case. Here, instead of updating the parent ItemRepeater scope, we want to update Header scope of the first direct child of the ItemRepeater. There is no real meaning in this operation – I just want to demonstrate various updating and controller communication techniques here. So, in this case, the child ScopeHeaderController still notifies the parent NonsenseExample2Controller, and instead of updating ItemRepeater scope, the controller pushes notification further to the header of its first child scope i.e. to another child ScopeHeaderController. Let’s see how this looks in the code. The action is raised same way by line 33 on Listing 3.2, but this time target equals "child". Our action handler on line 21 of Listing 3.4 is executed again and this time switch clause on line 27 chooses case for "child":

32: case "child": CtrlPath().RaiseAction("RaisedFromChild", "child"); break;

So, we raise "RaisedFromChild" controller action again  similar way we did for the previous button, but now passing "child" as data.  Action_RaisedFromChild() callback on line 39 on Listing 3.3 is executed and this time flow goes to the second if branch on lines 46-64. Now look at the scope tree on Fig 3.1. For all explanations here I assumed we  used buttons from header of ItemRepeater. So, action is raised from the controller attached to the Header scope whose parent is ItemRepeater. Our purpose is to access header of the first child Item scope of ItemRepeater passing appropriate path to CurrPath() [*] and notify it of the event. So the task reduces to finding the right path. Knowing that CurrPath() [*] points to the original Header scope and looking at the tree on Fig 3.1 it’s not hard to see that relative path will be -1$0-Item$0-Header i.e. one step back to ItemRepeater and then to first Item and its Header. The problem is that in general case we don’t know which Header raised the action and we have to build the desired path based on the current path. This is what I do on lines 49-58. Basically, I take the last segment on the scope ID and depending on the scope name, I take the proper next scope. That switch is a bit ugly, but this is just a quick and dirty solution. As a result, we have our path and use it to invoke action on the child controller on line 62:

62: CurrPath(-1, nextScope, "Header").InvokeAction("InvokedFromParent", null);

I point at the desired Header scope that ScopeHeaderController is attached to and call InvokeAction() [*] API passing "InvokedFromParent" for action name and null for generic data item. In ScopeHeaderController on line 15 this action is bound to the Action_InvokedFromParent() handler defined on line 42. The code of this handler is trivial – first, we refresh current scope (which is a Header scope pointed for previous InvokeAction() [*] call) and send another "RefreshedFromServer" message to the client. So, this time we also updated 2 scopes and sent 2 corresponding duplex messages: one for original Header and the other for the Header of the first child scope. The UI result of this operation is on the last picture in Header Action Links section. Only 2 Header scopes are updated and not anything else.

Conclusion

Congratulations! This tutorial is complete and now you're DaST expert! I know it's unusual that such limited set of tools can replace standard frameworks like WebForms or MVC, but it really can. Design your web page UI, divide it in scopes, and use DaST to control you markup areas individually – this is all. That's how web development should look like – simple and intuitive. No need to learn tons of server controls, weird page lifecycle events, grid/repeater/whatever binding expressions, etc. And a  huge benefit of DaST is fully separated presentation – the HTML design group can work on real templates while developers work with skeleton templates. Also, whenever existing app needs UI changes, the HTML designer can do it without interrupting programmers.

Of course, the ASP.NET DaST framework is still on the early stage. At the moment, we have a well defined pattern, proven concept, and working rendering core that already allow us to do a lot. But there are still some features that will be added in the near future. Within the next couple of months I want to accomplish the following: 

  • Separate jQuery-based Ajax layer. Get rid of FORM tag and all unnecessary ASP.NET scripts on the page.  
  • Make DaST fully compatible with MVC apps, so MVC lovers will be able to use it too. 

If you wish to join the project (or PHP DaST) - do it right now! If you have any bright ideas/suggestions, please share with me. Watch for updates and follow @rgubarenko on Twitter.  I'm open to any type of questions and will be glad to hear your feedback. Please drop me few lines what you think about all this. All useful links are below. 

LINKS  

License

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


Written By
Software Developer (Senior)
Canada Canada
Software Architect with over 15 years in IT field. Started with deep math and C++ Computer Vision software. Currently in .NET and PHP web development. Creator of DaST pattern, open-source frameworks, and plugins. Interested in cutting Edge IT, open-source, Web 2.0, .NET, MVC, C++, Java, jQuery, Mobile tech, and extreme sports.

Comments and Discussions

 
SuggestionPut the CodePlex link on the top, so it's not necessary to register on to get the code Pin
Alexey Kozlovsky27-Mar-13 20:48
Alexey Kozlovsky27-Mar-13 20:48 
General++++ Totally agreed! Pin
craighart4-Apr-13 18:42
craighart4-Apr-13 18:42 
GeneralRe: Put the CodePlex link on the top, so it's not necessary to register on to get the code Pin
rgubarenko5-Apr-13 18:39
rgubarenko5-Apr-13 18:39 
General5! Spent half of my day reading this - it was worth it! Pin
Alexey Kozlovsky27-Mar-13 20:37
Alexey Kozlovsky27-Mar-13 20:37 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.