Click here to Skip to main content
15,885,546 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  
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.
// ******************************************************************************
// ******************************************************************************
// ASP.NET DaST Rendering Engine
// Copyright (C) 2011 Roman Gubarenko
// Project Home: http://aspnetdast.sourceforge.net/
// ******************************************************************************
// ******************************************************************************
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
//
// ******************************************************************************
// Primary Contact: rgubarenko@gmail.com
// Learn More: http://aspnetdast.sourceforge.net/
// Project Repository: http://sourceforge.net/projects/aspnetdast/
// ******************************************************************************




using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.UI;
using System.IO;
using System.Text.RegularExpressions;
using System.Web.UI.HtmlControls;

using AspNetDaST.Web.Private;

namespace AspNetDaST.Web
{
  public abstract class DaSTPage : System.Web.UI.Page
  {
    private LiteralControl _ltrlBeforeHead;
    private LiteralControl _ltrlAfterHead;
    private LiteralControl _ltrlAfterBody;
    private HtmlGenericControl _htmlTagBody;

    internal bool IsAsyncPostBack { get; private set; }


    protected override void OnPreInit(EventArgs e)
    {
      //
      // PREPARE ASPX PAGE
      //

      // reorganize ASPX page to have needed structure of controls
      // no matter what controls it already has in its .aspx markup
      _ltrlBeforeHead = new LiteralControl("<html>\r\n");
      _ltrlAfterHead = new LiteralControl("\r\n");
      _ltrlAfterBody = new LiteralControl("\r\n</html>");
      _htmlTagBody = new HtmlGenericControl("body");

      Page.Controls.Clear();
      Page.Controls.Add(_ltrlBeforeHead);
      Page.Controls.Add(new HtmlHead());
      Page.Controls.Add(_ltrlAfterHead);
      Page.Controls.Add(_htmlTagBody);
      Page.Controls.Add(_ltrlAfterBody);
      _htmlTagBody.Controls.Add(new LiteralControl("\r\n  "));
      _htmlTagBody.Controls.Add(new HtmlForm() { ID = "DaSTPageForm" });
      _htmlTagBody.Controls.Add(new LiteralControl("\r\n"));

      // add script manager on the page
      ScriptManager scriptManager = new ScriptManager();
      scriptManager.ID = "DaSTPageScriptManager";
      scriptManager.EnablePartialRendering = true;
      scriptManager.Scripts.Add(new ScriptReference("AspNetDaST.Resources.AspNetDaST.js", "AspNetDaST"));
      _htmlTagBody.FindControl("DaSTPageForm").Controls.AddAt(0, scriptManager);

      // disable some standard features 
      Page.EnableViewState = false;
 
      base.OnPreInit(e);
    }

    protected override void OnLoad(EventArgs e)
    {
      // detect async postback using script manager
      IsAsyncPostBack = ScriptManager.GetCurrent(Page).IsInAsyncPostBack;


      //
      // BUILD DATA SCOPE TREE
      // 

      // get page (root) scope controller
      var rootController = ProvideRootController();
      if (rootController == null) throw new DaSTException("Invalid root controller");

      // build tree structure
      ScopeTreeModel scopeTree = new ScopeTreeModel(rootController);
      scopeTree.State = ScopeTreeModel.RenderState.BuildingTree;

      // restore state
      if (IsPostBack)
      {
        string strSavedState = Request.Form["__ASPNETDASTSTATE"];
        ScopeTreeTools.DaSTState.RestoreDaSTState(strSavedState, scopeTree);
      }
      
      
      // 
      // PROCESS SCOPE ACTIONS
      //

      if (IsPostBack)
      {
        scopeTree.State = ScopeTreeModel.RenderState.HandlingActions;

        string eventTarget = Request.Form["__EVENTTARGET"];
        string eventArgument = Request.Form["__EVENTARGUMENT"];
   
        try
        {
          PathExact actionPath = ScopeTreeTools.ClientBridge.ConvertClientID2ScopePath(eventTarget);
          string actionName = eventArgument.Substring(0, eventArgument.IndexOf('|'));
          object actionArg = Utility.JSON.ParseJSON(eventArgument.Substring(actionName.Length + 1));

          // before invoking the hanlder, we need to restore 
          // part of scope tree lying on current action path
          scopeTree.RebuildControllerModelsOnPath(actionPath);

          // get current pointed node
          var actionNode = scopeTree.GetNode(actionPath);

          // invoke actual action handler
          scopeTree.InvokeScopeActionHandler(
            actionNode.Controller.TreeNode, 
            actionPath,
            actionNode.Controller.TreeNode.NodePath,
            actionName,
            actionArg);
        }
        // TODO: throw more detailed error here
        catch (Exception ex) { throw new Exception("Invalid postback action data"); }
      }


      //
      // RENDER DATA SCOPE TREE
      //

      scopeTree.State = ScopeTreeModel.RenderState.RenderingTree;

      var scopeOutputs = new Dictionary<PathExact, string>();
      var treeRenderer = new ScopeTreeRenderer(scopeTree);

      if (IsAsyncPostBack)
      {
        foreach (var renderPath in scopeTree.RefreshScopePaths)
        {
          using (MemoryStream stream = new MemoryStream())
          {
            treeRenderer.RenderSubtree(renderPath, stream);
            string result = Encoding.ASCII.GetString(stream.ToArray());
            // add refreshed scope output
            scopeOutputs.Add(renderPath, result);
          }
        }
      }
      else
      {
        using (MemoryStream stream = new MemoryStream())
        {
          treeRenderer.RenderSubtree(PathExact.Empty, stream);
          string result = Encoding.ASCII.GetString(stream.ToArray());
          // add single root scope output
          scopeOutputs.Add(PathExact.Empty, result);
        }
      }

  
      //
      // SERIALIZE RENDERED SCOPE TREE
      //

      string strNewState = ScopeTreeTools.DaSTState.BuildDaSTState(scopeTree);
      ScriptManager.RegisterHiddenField(Page, "__ASPNETDASTSTATE", strNewState);
     

      // 
      // WRITE PAGE OUTPUT 
      //

      scopeTree.State = ScopeTreeModel.RenderState.WritingOutput;

      if (IsAsyncPostBack)
      {
        StringBuilder output = new StringBuilder();

        // add updatePanel response nodes
        foreach (var kv in scopeOutputs)
        {
          string scopeClientID = ScopeTreeTools.ClientBridge.ConvertScopePath2ClientID(kv.Key); 
          output.AppendFormat("{0}|updatePanel|{1}|{2}|", kv.Value.Length, scopeClientID, kv.Value);
        }

        // add panelsToRefreshIDs response node
        var updatePanelIDs = scopeOutputs.Select(kv => ScopeTreeTools.ClientBridge.ConvertScopePath2ClientID(kv.Key));
        string strUpdatePanelIDs = string.Join(",", updatePanelIDs.ToArray());
        output.AppendFormat("{0}|panelsToRefreshIDs||{1}|", strUpdatePanelIDs.Length, strUpdatePanelIDs);

        // just write it directly to response
        Page.Response.Write(output.ToString());
      }
      else
      {
        string output = scopeOutputs.Single().Value;

        // break entire html document into parts
        var htmlDoc = Utility.Rendering.DecomposeHtmlDoc(output);
        if (htmlDoc == null) throw new DaSTException("Resulting HTML document has invalid structure");

        // assign this parts to controls on ASPX page
        if (htmlDoc.Title != null) Page.Title = htmlDoc.Title;
        _ltrlBeforeHead.Text = htmlDoc.BeforeHead;
        _ltrlAfterHead.Text = htmlDoc.AfterHead;
        _ltrlAfterBody.Text = htmlDoc.AfterBody;
        Header.Controls.Add(new LiteralControl(htmlDoc.Head));
        Form.Controls.Add(new LiteralControl(htmlDoc.Body));
        // also add body tag attributes
        foreach (string key in htmlDoc.BodyAttrs.Keys)
        {
          _htmlTagBody.Attributes.Add(key, htmlDoc.BodyAttrs[key]);
        }

        // add client API startup script
        Page.ClientScript.RegisterStartupScript(
          this.GetType(),
          "ASP.NET DaST Initialization Script",
          "DaST.Scopes.initialize();\r\n",
          true);
      }
    }

    protected abstract ScopeController ProvideRootController();
  }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

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