PDA

View Full Version : Add UserControls in DirectEvents/Methods and have them automatically Reload on Post-back through messaging in Hidden Fields



michaeld
Dec 18, 2013, 2:54 PM

michaeld
Dec 19, 2013, 5:35 AM
I have revised the code above with the latest tech improvements.

Daniil
Dec 20, 2013, 1:18 PM
Thank you for sharing! It looks very interesting!

Should be "messanging" replaced with "messaging" in the title?

michaeld
Dec 20, 2013, 3:01 PM
Thank you for sharing! It looks very interesting!

Should be "messanging" replaced with "messaging" in the title?

Yes, I can't fix it. Please, if you can.

It's still a work in progress. Vladimir is posing I really need to switch the model to UserControl loading instead of UserControlRender, but as yet I haven't figured out a way to successfully do that.

michaeld
Dec 23, 2013, 3:55 AM
This is an all new version that better handles Page Life-Cycle better (per Vladimir's suggestion) and adds support to handle destroyed windows. I have also moved this project to a separate library.



// I have removed this beta implementation and posted a newer version further in the thread...


Project Status
This is the first implementation I feel strongly could be a release candidate. The remaining issues relate to child control participation in layout (i.e. between Items and Content). I need help from the Ext.Net team to get this working.

Also I believe you will need version 2.4 with at least trunk updates up to 5576. Versions after 2.4.X or after 2.5 should work fine.

I have also posted an example application that tests various ways controls can be rendered/added/destroyed on different panels.

michaeld
Dec 23, 2013, 4:01 AM
TestApp.aspx - Note this page inherits from XPage to ensure that it automatically loads Registered Controls


<%@ Page Language="C#" EnableViewState="false" ClassName="TestApp" ViewStateMode="Disabled" Inherits="Ext.Net.Ext.XPage" %>
<%@ Import Namespace="Ext.Net.Ext" %>


<script runat="server">
protected void Page_Load( object sender, EventArgs e ) {
if( XExt.IsAjaxRequestNotRendering )
return;
// Initialize Panel2 only first time
Panel2.Items.Add( new UserControlLoader() { Path = "TestCtlDateBtn.ascx", UserControlID = "P2DateBtnUCL", ClientIDMode = System.Web.UI.ClientIDMode.Predictable } );
Panel2.AddDirectControl( "TestCtlDateBtn.ascx", "P2_DateLd_", false, null, true );
}
protected void P1Test( object sender, DirectEventArgs e ) {
}
protected void P3Test( object sender, DirectEventArgs e ) {
Panel3.AddDirectControl( "TestCtlDateBtn.ascx", "P3_Date_", true, null, true );
Panel3.AddDirectControl( "TestCtlChainLoadDate.ascx", "P3_Chain_", true, null, true );
}
protected static int p4cnt = 1; // be aware: can reset on apppool recycle
protected void P4Test( object sender, DirectEventArgs e ) {
// Render the New Account Ask Question
Panel4.AddDirectControl( "TestCtlDateBtn.ascx", "P4_Cnt_" + ( p4cnt++ ).ToString(), false, null, true );
}
protected void P5Test( object sender, DirectEventArgs e ) {
Panel5.AddDirectControl( "TestCtlChainLoadDate.ascx", "P5_ChainAndDate_", true,
uc => { ((Hidden)uc.FindControl("Hid")).Text = Panel2.ID; }
, true );
}
protected static int pDM = 1; // be aware: can reset on apppool recycle
[DirectMethod( IDMode = DirectMethodProxyIDMode.None )]
public void AddToPanel( int panel ) {
var Panel = (Ext.Net.Panel)FindControl( "Panel" + panel.ToString() );
Panel.AddDirectControl( "TestCtlDateBtn.ascx", "DirMethod_" + ( pDM++ ).ToString(), true, null, true );
}


</script>


<!DOCTYPE html>
<html>
<head id="Head1" runat="server">
<title>TestApp Sample</title>
</head>
<body>
<form id="Form1" runat="server">


<ext:ResourceManager ID="ResourceManager1" runat="server" ScriptMode="Development" SourceFormatting="true" DisableViewState="true" />
<ext:Viewport ID="vp" runat="server" Layout="VBoxLayout">
<LayoutConfig>
<ext:VBoxLayoutConfig Align="Stretch" />
</LayoutConfig>
<Items>


<ext:Container ID="Row1" runat="server" Border="true" Padding="5" Flex="1" Layout="HBoxLayout">
<LayoutConfig>
<ext:HBoxLayoutConfig Align="Stretch" />
</LayoutConfig>
<Items>


<ext:Panel ID="Panel1" runat="server" Title="Form Panel" Border="true" Padding="5" Flex="1" Layout="VBoxLayout" AutoScroll="true">
<LayoutConfig>
<ext:VBoxLayoutConfig Align="Stretch" />
</LayoutConfig>
<Items>
<ext:FormPanel ID="FormP1" runat="server" >
<Items>
<ext:TextField ID="NameT1" runat="server" FieldLabel="Name" LabelAlign="Top" />
</Items>
<Buttons>
<ext:Button runat="server" Text="Save" OnDirectClick="P1Test" />
</Buttons>
</ext:FormPanel>
</Items>
</ext:Panel>


<ext:Panel ID="Panel2" runat="server" Title="2 DateBtn Load" Border="true" Padding="5" Flex="1" Layout="VBoxLayout" AutoScroll="true">
<LayoutConfig>
<ext:VBoxLayoutConfig Align="Stretch" />
</LayoutConfig>
<Items>
<ext:UserControlLoader runat="server" Path="TestCtlDateBtn.ascx" UserControlID="P2DateUCLEmbed" />
</Items>
</ext:Panel>


<ext:Panel ID="Panel3" runat="server" Title="2 DateBtn Direct + Chain" Border="true" Padding="5" Flex="1" Layout="VBoxLayout" AutoScroll="true">
<LayoutConfig>
<ext:VBoxLayoutConfig Align="Stretch" />
</LayoutConfig>
<Items>
<ext:Button ID="P3Btn" runat="server" Html="Add 2 DateBtn DE" OnDirectClick="P3Test" />
</Items>
</ext:Panel>


</Items>
</ext:Container>


<ext:Container ID="Row2" runat="server" Border="true" Flex="1" Padding="5" Layout="HBoxLayout">
<LayoutConfig>
<ext:HBoxLayoutConfig Align="Stretch" />
</LayoutConfig>
<Items>


<ext:Panel ID="Panel4" runat="server" Title="Unlimited DE Add Dates" Border="true" Padding="5" Flex="1" Layout="VBoxLayout" AutoScroll="true">
<LayoutConfig>
<ext:VBoxLayoutConfig Align="Stretch" />
</LayoutConfig>
<Items>
<ext:Button runat="server" Text="Add Date" OnDirectClick="P4Test" />
</Items>
</ext:Panel>


<ext:Panel ID="Panel5" runat="server" Title="Add Button to Add to Panel2" Border="true" Padding="5" Flex="1" Layout="VBoxLayout" AutoScroll="true">
<LayoutConfig>
<ext:VBoxLayoutConfig Align="Stretch" />
</LayoutConfig>
<Items>
<ext:Button runat="server" Text="Add Date to Both Panels" OnDirectClick="P5Test" />
</Items>
</ext:Panel>


<ext:Panel ID="Panel6" runat="server" Title="Panel6" Border="true" Padding="5" Flex="1" Layout="VBoxLayout" AutoScroll="true">
<LayoutConfig>
<ext:VBoxLayoutConfig Align="Stretch" />
</LayoutConfig>
<Items>
</Items>
</ext:Panel>


</Items>
</ext:Container>


</Items>
</ext:Viewport>
</form>
</body>
</html>


TestCtlDateBtn.ascx


<%@ Control Language="C#" ClassName="TestCtlDateBtn" %>
<%@ Import Namespace="Ext.Net.Ext" %>
<script runat="server">
protected void Page_Load( object sender, EventArgs e ) {
if( XExt.IsAjaxRequestNotRendering )
return;
// Initialize only first time
Label1.Html = Label1.Html + this.ClientID;
}
protected static int wcnt = 1;
protected void Button1_DirectClick( object sender, DirectEventArgs e ) {
Label1.Append( " Launched Window" );
// Make unique id non-modal window
Page.AddDirectControl( "TestCtlWindow.ascx", "CWndw_" + this.ClientID + wcnt++.ToString(), null, true );
}
</script>
<ext:Label ID="Label1" runat="server" Html="Date+Btn: " />
<ext:DatePicker ID="Date" runat="server" />
<ext:Button ID="Button1" runat="server" Text="Show Window" OnDirectClick="Button1_DirectClick" />


TestCtlWindow.ascx


<%@ Control Language="C#" ClassName="TestCtlWindow" %>
<%@ Import Namespace="Ext.Net.Ext" %>
<script runat="server">
protected void Page_Load( object sender, EventArgs e ) {
if( XExt.IsAjaxRequestNotRendering )
return;
// Initialize only first time
Label1.Html = Label1.Html + this.ClientID;
Wndw1.RemoveRegisteredControlOnDestroy( this );
}
protected void ShowAlert_DirectClick( object sender, DirectEventArgs e ) {
Label1.Append( " Launched Alert" );
X.Msg.Alert( "Alert", Button1.ClientID + " DirectClick" ).Show();
}
protected void ShowWndw_DirectClick( object sender, DirectEventArgs e ) {
Label1.Append( " Launched Window" );
X.Msg.Prompt( "Window", "Which Panel # do you wish to add TestCtlDateBtn via DirectMethod to (1-6)?", new JFunction("if(btn=='ok') App.direct.AddToPanel(input);", "btn", "input") ).Show();
}
</script>
<ext:Window ID="Wndw1" runat="server" Title="Alert" CloseAction="Destroy">
<Items>
<ext:Label ID="Label1" runat="server" Html="Date+Btn: " />
<ext:DatePicker ID="Date" runat="server" />
</Items>
<Buttons>
<ext:Button ID="Button1" runat="server" Text="Show Alert" OnDirectClick="ShowAlert_DirectClick" />
<ext:Button ID="Button2" runat="server" Text="Show Another Window" OnDirectClick="ShowWndw_DirectClick" />
</Buttons>
</ext:Window>


TestCtlChainLoadDate.ascx


<%@ Control Language="C#" ClassName="TestCtlChainLoadDate"
%>
<%@ Import Namespace="Ext.Net.Ext" %>
<script runat="server">
public string AddParentLoadID { get; set; }


protected void Page_Load( object sender, EventArgs e ) {
// UserControlLoader in Page Load - Load CtlDateBtn
Panel1.Items.Add( new UserControlLoader() { Path = "TestCtlDateBtn.ascx", UserControlID = "CChnDtLd_"+ClientID, ClientIDMode = System.Web.UI.ClientIDMode.Predictable } );
//Panel1.AddDirectControl( "TestCtlDateBtn.ascx", "CChnDtDE_", true, null, true ); // !! Bug with mappath


// Make button1 Visible if AddParentLoad has been assigned
if( !string.IsNullOrEmpty(Hid.Text) )
Button1.Visible = true;
}
protected void Button1_DirectClick( object sender, DirectEventArgs e ) {
var comp = (ComponentBase)Page.FindControl( Hid.Text );
comp.AddDirectControl( "TestCtlDateBtn.ascx", "SendP2_" + ClientID, false, null, true );
Context.Items.Remove("CtlID");
}
</script>
<ext:Panel ID="Panel1" runat="server" Flex="1" Frame="true" Title="Chain" Padding="5">
<Items>
<ext:Hidden ID="Hid" runat="server" />
<ext:Button ID="Button1" runat="server" Text="Add to Another Panel" OnDirectClick="Button1_DirectClick" Visible="false" />
</Items>
</ext:Panel>
<ext:Label ID="Label1" runat="server" Html="TestCtlDateBtn" />






More tests coming for Panel 1 & 6

Daniil
Dec 23, 2013, 5:40 AM
Thank you for sharing all the code!


The remaining issues relate to child control participation in layout (i.e. between Items and Content). I need help from the Ext.Net team to get this working.


Please clarify what should I do to reproduce the issue (-s)? Maybe, it is better to start a new forum thread for each issue and cross link.

michaeld
Dec 24, 2013, 2:18 AM
Please clarify what should I do to reproduce the issue (-s)? Maybe, it is better to start a new forum thread for each issue and cross link.

You can reproduce the issue by running TestApp. But you'll have to note the issue where I call AddDirectControl with items set to true. The added children in the User Control don't participate in layout correctly because of the new Component(). I need AddDirectControl( AbstractContainer Parent, ... ) to support layout of the children in the UserControl properly when items is passed with true. Note: I have updated the api above from ComponentBase Parent to AbstractContainer Parent. But currently, I am describing the issue here: http://forums.ext.net/showthread.php?27623-Page-FindControl-cannot-find-parent-by-id-in-Page_Load-of-a-UserControl-that-invoked-by-ResourceManger-RaisePostbackEvent()/page2

Daniil
Dec 24, 2013, 9:20 AM
Ok, let's discuss that and find some solution first:
http://forums.ext.net/showthread.php?27644&p=123090&viewfull=1#post123090

michaeld
Dec 25, 2013, 3:10 AM
Okay, I posted an edit to the api above. It now properly handles layouts when items=true.
TestApp was not changed.

Daniil
Dec 25, 2013, 2:03 PM
Nice. Thank you for the update.

michaeld
Jan 07, 2014, 12:00 PM
I'm happy to report that since moving to non-refresh DirectEvent/Method UserControl update model that only pushes destroy and render changes to the client, page update performance has gone up 60-85%. This is not unexpected since there is a heavy extjs/extnet javascript page-load runtime when refreshing pages before this model. Full refresh can takes about 1-3 second for client-side load run-times. Now updates to the page that look like page reloads take a few hundred ms. I've had to implement a lot more code to handle history.pushState urls to create book-markable pages, and I'll post an update to the code above that helps support this model and remove/replace in the next day or so.

michaeld
Jan 14, 2014, 3:10 AM
New version has different namespace name (Had to rename because compiler was getting confused with Ext being used twice).
AddDirectControl can now be used in both Page_Load or Direct Event/Method and safely Register Controls for reload in either mode. You can also skip the Render if you wish to render the Parent instead
Added more method prototypes and finalized api
Added RemoveRegisteredControl to prevent destroyed controls from reloading; Add Destroy listener with RemoveRegisteredControlOnDestroy
Added ParentRefresh which replace the entire Parent
Added ChildrenRefresh which only ReRenders the Children inside a Container
Added RemoveRegisteredControlsFromParent to remove Registered Controls of a Parent ID
Added ability to Subscribe to Global Named Events & Trigger EventHandlers in multiple UserControls to update their own sections from a single DirectEvents/Method.
Added Javascript api for push/popState urls to create perceived new pages on ajax updates.
Added easy access properties to XPage and added XUserControl with same
Improved comments/api-documentation



DynamicUserControls.cs


using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Configuration;
using Ext.Net;
using Ext.Net.Extension.Encryption;


namespace Ext.Net.Extension {


/// <summary>
/// Ext.Net Extension Methods
/// </summary>
public static partial class XExt {


#region Auto-Load/Manage Registered Controls


public readonly static string AS_EncryptionKey = ConfigurationManager.AppSettings["EncryptionKey"];
public enum PostRenderModes { None = 0, Render, ReRender, Update, UpdateContent, UpdateLayout };


/// <summary>
/// Registered a control to be auto-loaded by LoadRegisteredUserControls
/// </summary>
public static void RegisterUserControl( this System.Web.UI.Control Parent, string ucPath, string ctlID, ClientIDMode idmode = ClientIDMode.Predictable, PostRenderModes rendermode = PostRenderModes.None ) {
RegisterUserControl( Parent.Page, Parent, ucPath, ctlID, idmode, rendermode );
}
/// <summary>
/// Registered a control to be auto-loaded by LoadRegisteredUserControls
/// </summary>
public static void RegisterUserControl( this System.Web.UI.Control Parent, string ucPath, UserControl uc, ClientIDMode idmode = ClientIDMode.Predictable, PostRenderModes rendermode = PostRenderModes.None ) {
RegisterUserControl( Parent.Page, Parent, ucPath, uc.ClientID, idmode, rendermode );
}
/// <summary>
/// Registered a control to be auto-loaded by LoadRegisteredUserControls
/// </summary>
public static void RegisterUserControl( this Page Page, string ucPath, string ctlID, ClientIDMode idmode = ClientIDMode.Predictable, PostRenderModes rendermode = PostRenderModes.None ) {
RegisterUserControl( Page, null, ucPath, ctlID, idmode, rendermode );
}
/// <summary>
/// Registered a control to be auto-loaded by LoadRegisteredUserControls
/// </summary>
public static void RegisterUserControl( this Page Page, string ucPath, UserControl uc, ClientIDMode idmode = ClientIDMode.Predictable, PostRenderModes rendermode = PostRenderModes.None ) {
RegisterUserControl( Page, null, ucPath, uc.ClientID, idmode, rendermode );
}
private static void RegisterUserControl( Page Page, System.Web.UI.Control Parent, string ucPath, string ctlID, ClientIDMode idmode, PostRenderModes rendermode ) {
// Make sure EncryptionKey is set in web.config
if( string.IsNullOrEmpty( AS_EncryptionKey ) )
throw new ArgumentException( "To use this function, you must have set EncryptionKey in your appsettings of your web.config" );
// Skip Key Create if if we've already created the Key once in this Page life-cycle
var ruckey = UserControlAutoLoadKey;
if( string.IsNullOrEmpty( ruckey ) ) {
// Skip Key Create if the client has returned the key; we're still in the life-cycle of the same client page
ruckey = Page.Request.Form["RUCKey"];
if( string.IsNullOrEmpty( ruckey ) ) {
// Render the New Key Partial based on a new Unique Guid
ruckey = Guid.NewGuid().ToBase64();//ToString("N"); // alternate-way
// Add the key to the stream
( new Hidden() {
ID = "RUCKey",
Text = ruckey,
IDMode = IDMode.Static
} ).Render();
}
// Save the Key so it can be reused if another control is registered in this same Page life-cycle
UserControlAutoLoadKey = ruckey;
}
// Render the Encrypted Control information - ctl path, new ctlid, ctl parent id, idmode, and additional render instructions
( new Hidden() {
ID = "_RUC" + ctlID,
Text = Crypto.EncryptStringAES(
string.Concat( ucPath, ";", ctlID, ";", Parent == null ? "null" : Parent.ID, ";", idmode.ToString(), ";", rendermode.ToString() ),
AS_EncryptionKey + ruckey // combine the page key and the server key
),
IDMode = IDMode.Static
} ).Render();
}


/// <summary>
/// Add this to your aspx Page.Page_Load to ensure registered UserControls get readded to existing parent controls.
/// Make sure this function is called if X.IsAjaxRequest or IsPostback
///
/// If your Page contains UserControls that have dynamic run-time created controls, instead override RaisePostBackEvent in your Page as follows
/// protected override void RaisePostBackEvent( IPostBackEventHandler source, string eventArgument ) {
/// Page.LoadRegisteredUserControls(source);
/// base.RaisePostBackEvent( source, eventArgument );
/// }
/// </summary>
public static void LoadRegisteredUserControls( this Page Page ) {
// Unnecessary if in not in Postback
if( !X.IsAjaxRequest )
return;
// Only process if RUCKey exists
var Request = Page.Request;
var ruckey = Request.Form["RUCKey"];
if( string.IsNullOrEmpty( ruckey ) )
return;
ruckey = AS_EncryptionKey + ruckey;
// Construct & Load Registered User Control List
int len = Request.Form.Count;
if( len <= 0 )
return;
var keys = Request.Form.AllKeys;
for( int i = 0; i < len; i++ ) {
var ruc = keys[i];
if( ruc.StartsWith( "_RUC" ) ) {
var rucstr = Crypto.DecryptStringAES( Request.Form[i], ruckey );
var split = rucstr.Split( ';' );
if( split == null || split.Length != 5 )
throw new Exception( "Registered User Control Invalid: " + Request.Form.AllKeys[i] );
var ucPath = split[0];
var ctlName = split[1];//uc.ClientID
var parentid = split[2];//parent.ID
var idmode = split[3];
var rm = split[4];
var rendermode = (PostRenderModes)Enum.Parse( typeof( PostRenderModes ), rm );
var ctl = Page.LoadControl( ucPath );
ctl.ID = ctlName;
ctl.ClientIDMode = (ClientIDMode)Enum.Parse( typeof( ClientIDMode ), idmode );
if( parentid != "null" ) {
var pctl = X.GetCmp( parentid );//ctl.Page.FindControl( parentid );
if( pctl == null )
throw new Exception( "Could not find Parent Control ID: " + parentid );
pctl.Controls.Add( ctl );
} else
Page.Controls.Add( ctl );
switch( rendermode ) {
case PostRenderModes.None:
break;
case PostRenderModes.Render:
( (BaseControl)ctl ).Render();
break;
case PostRenderModes.ReRender:
( (AbstractComponent)ctl ).ReRender();
break;
case PostRenderModes.Update:
ctl.Update();
break;
case PostRenderModes.UpdateContent:
( (AbstractContainer)ctl ).UpdateContent();
break;
case PostRenderModes.UpdateLayout:
( (AbstractComponent)ctl ).UpdateLayout();
break;
}
}
}


}


/// <summary>
/// Remove a registered control to prevent it from being loaded again by LoadRegisteredUserControls
/// </summary>
public static void RemoveRegisteredControl( this UserControl uc ) {
// Remove the Registered Hidden control
X.AddScript( RemoveRegisteredControlClient( uc ) );
}
/// <summary>
/// Remove a registered control to prevent it from being loaded again by LoadRegisteredUserControls
/// </summary>
public static void RemoveRegisteredControl( string ctlID ) {
// Remove the Registered Hidden control
X.AddScript( RemoveRegisteredControlClient( ctlID ) );
}
/// <summary>
/// Remove a registered control to prevent it from being loaded again by LoadRegisteredUserControls; Doesn't actually do the work, Client Script rendered to attach to listener
/// </summary>
public static string RemoveRegisteredControlClient( string ctlid ) {
// Remove the Registered Hidden control
return string.Concat( "App._RUC", ctlid, ".destroy();" );//\n\rExt.net.ResourceMgr.destroyCmp('App._RUC", ctl.ClientID, "');" );
}
/// <summary>
/// Remove a registered control to prevent it from being loaded again by LoadRegisteredUserControls; Doesn't actually do the work, Client Script rendered to attach to listener
/// </summary>
public static string RemoveRegisteredControlClient( this UserControl ctl ) {
// Remove the Registered Hidden control
return string.Concat( "App._RUC", ctl.ClientID, ".destroy();" );//\n\rExt.net.ResourceMgr.destroyCmp('App._RUC", ctl.ClientID, "');" );
}
/// <summary>
/// Adds Destroy Listener to AbstractComponent to remove the Registered Control from being loaded again by LoadRegisteredUserControls
/// Usage example: UserControl with Window with CloseAction="Destroy" - Wnd1.RemoveRegisteredControlOnDestroy(ctl);
/// </summary>
public static void RemoveRegisteredControlOnDestroy( this AbstractComponent comp, UserControl ctl ) {
// Remove the Registered Hidden control on Destroy
comp.AddListener( "destroy", new JFunction( RemoveRegisteredControlClient( ctl ) ) );
}
/// <summary>
/// Remove registered child controls of Parent to prevent them from being loaded again by LoadRegisteredUserControls; Doesn't actually do the work, Client Script rendered to attach to listener
/// </summary>
public static void RemoveRegisteredControlsFromParent( this AbstractContainer Parent ) {
RemoveRegisteredControlsFromParent( Parent.Page, Parent.ID );
}
/// <summary>
/// Remove registered child controls of Parent to prevent them from being loaded again by LoadRegisteredUserControls; Doesn't actually do the work, Client Script rendered to attach to listener
/// </summary>
public static void RemoveRegisteredControlsFromParent( this Page Page, string parentID ) {
// Only process if RUCKey exists
var Request = Page.Request;
var ruckey = Request.Form["RUCKey"];
if( string.IsNullOrEmpty( ruckey ) )
return;
ruckey = AS_EncryptionKey + ruckey;
// Tranverse Registered User Control List and look for Parent ClientID
int len = Request.Form.Count;
if( len <= 0 )
return;
var keys = Request.Form.AllKeys;
for( int i = 0; i < len; i++ ) {
var ruc = keys[i];
if( ruc.StartsWith( "_RUC" ) ) {
var rucstr = Crypto.DecryptStringAES( Request.Form[i], ruckey );
var split = rucstr.Split( ';' );
if( split == null || split.Length != 5 )
throw new Exception( "Registered User Control Invalid: " + Request.Form.AllKeys[i] );
var parentid = split[2];
if( parentid == parentID ) {
var ctlName = split[1];
RemoveRegisteredControl( ctlName );
//Request.Form.Remove("_RUC" + ctlName );// !! need to save the removal to prevent again
}
}
}
}

#endregion

#region Add/Replace Direct Controls

public delegate void BindFn( System.Web.UI.Control ctr );

/// <summary>
/// Add a User Control during a Direct Event that needs to be Rendered to Page, like adding a Bin object like Window
/// To Replace the existing, use same CtlName; To add new each time, make ctlName unique
/// </summary>
public static UserControl AddDirectControl( this Page Page, string ucPath, string ctlName, BindFn bindFn = null, bool Register = false ) {

// Load the User Control
var uc = (UserControl)Page.LoadControl( ucPath );
uc.ClientIDMode = System.Web.UI.ClientIDMode.Predictable;
uc.ID = ctlName;


// Convert UserControl to UserControlLoader
var ucl = new UserControlLoader();
ucl.Items.Add( uc );


// Give a chance to bind before adding the control
if( bindFn != null )
uc.Init += delegate( object sender, EventArgs e ) {
bindFn( uc );
};


// Render the control
HttpContext Context = HttpContext.Current;
Context.SetDirectRendering( true );
ucl.Render();
Context.SetDirectRendering( false );


// Make the control available to the Page Control Tree so it can be found if required - must be performed after Render
Page.Controls.Add( uc );


if( Register && X.IsAjaxRequest )
RegisterUserControl( Page, ucPath, uc, ClientIDMode.Predictable );


return (UserControl)uc;
}


/// <summary>
/// Add a UserControl to Parent Container without destroying any contents in the Parent; especially significant in a DirectEvent/Method when add/replacing controls in the parent.
/// To Replace an existing UserControl, use same CtlName; To add continuous UserControls of the same type, ensure ctlName uniqueness
/// </summary>
/// <param name="Parent">Parent Container to add the new User Control to</param>
/// <param name="ucPath">Path to UserControl to load</param>
/// <param name="ctlName">Name of the UserControl - must be specified</param>
/// <param name="items">true to add new UserControl to Parent's items; false to ContentControls</param>
/// <param name="bindFn">Function to bind data to the UserControl before it's Load event</param>
/// <param name="Register">true to Register the Control for Auto-reloading by LoadRegisteredUserControls</param>
/// <param name="SkipRender">true to skip Rendering from occuring in the DirectEvent; use only if rendering the Parent manually</param>
/// <returns>the created UserControl</returns>
public static UserControl AddDirectControl( this AbstractContainer Parent, string ucPath, string ctlName, bool items = false, BindFn bindFn = null, bool Register = false, bool SkipRender = false ) {

// Load the User Control
var uc = (UserControl)Parent.Page.LoadControl( ucPath );
uc.ClientIDMode = ClientIDMode.Predictable;
uc.ID = ctlName;


bool isajax= X.IsAjaxRequest;
HttpContext Context = isajax ? HttpContext.Current : null;


if( items ) {


// Convert UserControl to UserControlLoader
UserControlLoader ucl = new UserControlLoader();
ucl.Items.Add( uc );
// Render each control if in an DirectEvent/Method request
if( isajax ) {
// Give a chance to bind before adding the control
if( bindFn != null )
uc.Init += delegate( object sender, EventArgs e ) {
bindFn( uc );
};
// Add the item to the Items of the Parent so it can be rendered to the correct parent
Context.SetDirectRendering( true );
Parent.Items.Add( ucl );
Context.SetDirectRendering( false );
// Render each component in the UserControl control
if(SkipRender==false)
foreach( var control in ucl.Components ) {
control.Render( Parent.ClientID, RenderMode.AddTo );
}
} else {
// Not in DirectEvent/Method, just add the control normally for rendering
Parent.Items.Add( ucl );
// Give a chance to bind data to the control before adding
if( bindFn != null )
bindFn( uc );
}


} else {


// Render each control if in an DirectEvent/Method request
if( isajax ) {
// Give a chance to bind before adding the control
if( bindFn != null )
uc.Init += delegate( object sender, EventArgs e ) {
bindFn( uc );
};
// Create a wrapping container around the UserControl control !! Required until support for UserControlLoader.Render supported
if( SkipRender == false ) {
var cont = new Container() { Border = false };
Context.SetDirectRendering( true );
cont.ContentControls.Add( uc );
Parent.Items.Add( cont );
Context.SetDirectRendering( false );
cont.Render();
} else {
// If Skipping render, we don't need a container
Context.SetDirectRendering( true );
Parent.ContentControls.Add( uc );
Context.SetDirectRendering( false );
}
} else {
// Not in DirectEvent/Method, just add the control normally for rendering
Parent.ContentControls.Add( uc );
// Give a chance to bind before adding the control
if( bindFn != null )
bindFn( uc );
}


}
// If Automatice UserControl loader turned on, create the Registered Hidden fields
if( Register )
RegisterUserControl( Parent, ucPath, uc, ClientIDMode.Predictable );


return uc;
}


/// <summary>
/// Add a UserControl to Parent Container but entirely refreshing the Parent.
/// </summary>
/// <param name="Parent">Parent Container to add the new User Control to</param>
/// <param name="ucPath">Path to UserControl to load</param>
/// <param name="ctlName">Name of the UserControl - must be specified</param>
/// <param name="items">true to add new UserControl to Parent's items; false to ContentControls</param>
/// <param name="bindFn">Function to bind data to the UserControl before it's Load event</param>
/// <param name="Register">true to Register the Control for Auto-reloading by LoadRegisteredUserControls</param>
/// <returns>the created UserControl</returns>
public static UserControl AddDirectControlRefreshParent( this AbstractContainer Parent, string ucPath, string ctlName, bool items = false, BindFn bindFn = null, bool Register = false ) {
var uc = AddDirectControl( Parent, ucPath, ctlName, items, bindFn, Register, true ); // skip user control render because Parent Refresh will render
RefreshParent( Parent );
return uc;
}

/// <summary>
/// ReRender the entire Parent Container - This will destroy and replace all children and the entire frame of the parent and its controls
/// Before calling RefreshParent, make sure you call RemoveRegisteredControlsFromParent if you registered User controls to this parent and AddDirectControl with SkipRender=true if adding this way
/// </summary>
public static void RefreshParent( this AbstractContainer Parent ) {
if( X.IsAjaxRequest )
Parent.ReRender();
}


/// <summary>
/// ReRender only the Child Items of the Parent Container Only (e.g. Panel will not rerender buttons, topbars, bottombars, frames, etc)
/// Before calling RefreshParent, make sure you call RemoveRegisteredControlsFromParent if you registered User controls to this parent and AddDirectControl with SkipRender=true if adding this way
/// Be aware, you may need to destroy children manually on the client if more existed in the original render than do in the Refresh
/// </summary>
public static void RefreshChildren( this Panel Parent ) {
if( !X.IsAjaxRequest )
return;
// ReRender Items
var childitems = Parent.Items;
for( int i = 0, len = childitems.Count; i < len;i++ )
childitems[i].Render();
}


/// <summary>
/// Remove a registered control to prevent it from being loaded again by LoadRegisteredUserControls; Doesn't actually do the work, Client Script rendered to attach to listener
/// </summary>
public static string RemoveControlClient( string ctlID ) {
// Remove the Registered Hidden control
return string.Concat( "App.", ctlID, ".destroy();" );
}
public static UserControl ReplaceDirectControl( this AbstractContainer Parent, string ucPath, string ctlName, bool items = false, BindFn bindFn = null, bool Register = false ) {
X.AddScript( RemoveControlClient( Parent.ClientID ) );
return AddDirectControl( Parent, ucPath, ctlName, items, bindFn, Register );
}


public static UserControl ReplaceDirectControl( this AbstractContainer Parent, string ctlIDToReplace, string ucPath, string ctlName, bool items = false, BindFn bindFn = null, bool Register = false ) {
X.AddScript( RemoveControlClient( ctlIDToReplace ) );
return AddDirectControl( Parent, ucPath, ctlName, items, bindFn, Register );
}


/// <summary>
/// Remove a registered control to prevent it from being loaded again by LoadRegisteredUserControls
/// </summary>
public static void RemoveControl( string ctlID ) {
// Remove control
X.AddScript( RemoveControlClient( ctlID ) );
}
public static string GetUserControlAutoLoadKey( this HttpContext Context ) {
return (string)Context.Items["UserControlAutoLoadKey"];
}
public static string UserControlAutoLoadKey {
get {
return (string)HttpContext.Current.Items["UserControlAutoLoadKey"];
}
set {
HttpContext.Current.Items["UserControlAutoLoadKey"] = value;
}
}


public static bool GetDirectRendering( this HttpContext Context ) {
var o = Context.Items["DirectRendering"];
return o != null ? (bool)o : false;
}
public static void SetDirectRendering( this HttpContext Context, bool value ) {
Context.Items["DirectRendering"] = value;
}
public static bool DirectRendering {
get {
return HttpContext.Current.GetDirectRendering();
}
set {
HttpContext.Current.SetDirectRendering(value);
}
}
public static bool GetIsAjaxRequestNotRendering( this HttpContext Context ) {
return ( X.IsAjaxRequest && !Context.GetDirectRendering() );
}
public static bool IsAjaxRequestNotRendering {
get {
return ( X.IsAjaxRequest && !DirectRendering );
}
}

#endregion


#region Additional Functions

/// <summary>
/// http://stackoverflow.com/questions/1032376/guid-to-base64-for-url
/// </summary>
public static string ToBase64( this Guid guid ) {
var enc = Convert.ToBase64String( guid.ToByteArray() ).Replace( '/', '-' ).Replace( '+', '_' ).Replace( "=", "" );
int len = enc.Length;
if( len > 0 && enc[len - 1] == '=' ) {
len--;
if( len > 0 && enc[len - 1] == '=' )
len--;
return enc.Substring( 0, len );
}
return enc;
}
public static Guid GuidFromBase64( this string base64 ) {
base64 = base64.Replace( '-', '/' ).Replace( '_', '+' ) + "==";
return new Guid( Convert.FromBase64String( base64 ) );
}

#endregion

}


public class XPage : Page {


/// <summary>
/// Ensures that RegisteredUserControls are loaded after dynamic user controls are added and before ResourceManager.RaisePostBackEvent
/// </summary>
protected override void RaisePostBackEvent( IPostBackEventHandler source, string eventArgument ) {
// If Page Implents RaisePostback, only process for ResourceManager
if( ( source is ResourceManager ) )
Page.LoadRegisteredUserControls();
base.RaisePostBackEvent( source, eventArgument );
}


public bool DirectRendering {
get { return Context.GetDirectRendering(); }
set { Context.SetDirectRendering( value ); }
}
public bool IsAjaxRequestNotRendering {
get {
return ( X.IsAjaxRequest && !Context.GetDirectRendering() );
}
}


}


public class XUserControl : UserControlLoader {
public bool DirectRendering {
get { return Context.GetDirectRendering(); }
set { Context.SetDirectRendering( value ); }
}
public bool IsAjaxRequestNotRendering {
get {
return ( Ext.Net.X.IsAjaxRequest && !Context.GetDirectRendering() );
}
}


}


}


Encryption.cs


using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;


namespace Ext.Net.Extension.Encryption {
/// <summary>
/// http://stackoverflow.com/questions/202011/encrypt-decrypt-string-in-net
/// </summary>
public class Crypto {
private static byte[] _salt = Encoding.ASCII.GetBytes( "o6806642kbM7c5" );


/// <summary>
/// Encrypt the given string using AES. The string can be decrypted using
/// DecryptStringAES(). The sharedSecret parameters must match.
/// </summary>
/// <param name="plainText">The text to encrypt.</param>
/// <param name="sharedSecret">A password used to generate a key for encryption.</param>
public static string EncryptStringAES( string plainText, string sharedSecret ) {
if( string.IsNullOrEmpty( plainText ) )
throw new ArgumentNullException( "plainText" );
if( string.IsNullOrEmpty( sharedSecret ) )
throw new ArgumentNullException( "sharedSecret" );


string outStr = null; // Encrypted string to return
RijndaelManaged aesAlg = null; // RijndaelManaged object used to encrypt the data.


try {
// generate the key from the shared secret and the salt
Rfc2898DeriveBytes key = new Rfc2898DeriveBytes( sharedSecret, _salt );


// Create a RijndaelManaged object
aesAlg = new RijndaelManaged();
aesAlg.Key = key.GetBytes( aesAlg.KeySize / 8 );


// Create a decryptor to perform the stream transform.
ICryptoTransform encryptor = aesAlg.CreateEncryptor( aesAlg.Key, aesAlg.IV );


// Create the streams used for encryption.
using( MemoryStream msEncrypt = new MemoryStream() ) {
// prepend the IV
msEncrypt.Write( BitConverter.GetBytes( aesAlg.IV.Length ), 0, sizeof( int ) );
msEncrypt.Write( aesAlg.IV, 0, aesAlg.IV.Length );
using( CryptoStream csEncrypt = new CryptoStream( msEncrypt, encryptor, CryptoStreamMode.Write ) ) {
using( StreamWriter swEncrypt = new StreamWriter( csEncrypt ) ) {
//Write all data to the stream.
swEncrypt.Write( plainText );
}
}
outStr = Convert.ToBase64String( msEncrypt.ToArray() );
}
}
finally {
// Clear the RijndaelManaged object.
if( aesAlg != null )
aesAlg.Clear();
}


// Return the encrypted bytes from the memory stream.
return outStr;
}


/// <summary>
/// Decrypt the given string. Assumes the string was encrypted using
/// EncryptStringAES(), using an identical sharedSecret.
/// </summary>
/// <param name="cipherText">The text to decrypt.</param>
/// <param name="sharedSecret">A password used to generate a key for decryption.</param>
public static string DecryptStringAES( string cipherText, string sharedSecret ) {
if( string.IsNullOrEmpty( cipherText ) )
throw new ArgumentNullException( "cipherText" );
if( string.IsNullOrEmpty( sharedSecret ) )
throw new ArgumentNullException( "sharedSecret" );


// Declare the RijndaelManaged object
// used to decrypt the data.
RijndaelManaged aesAlg = null;


// Declare the string used to hold
// the decrypted text.
string plaintext = null;


try {
// generate the key from the shared secret and the salt
Rfc2898DeriveBytes key = new Rfc2898DeriveBytes( sharedSecret, _salt );


// Create the streams used for decryption.
byte[] bytes = Convert.FromBase64String( cipherText );
using( MemoryStream msDecrypt = new MemoryStream( bytes ) ) {
// Create a RijndaelManaged object
// with the specified key and IV.
aesAlg = new RijndaelManaged();
aesAlg.Key = key.GetBytes( aesAlg.KeySize / 8 );
// Get the initialization vector from the encrypted stream
aesAlg.IV = ReadByteArray( msDecrypt );
// Create a decrytor to perform the stream transform.
ICryptoTransform decryptor = aesAlg.CreateDecryptor( aesAlg.Key, aesAlg.IV );
using( CryptoStream csDecrypt = new CryptoStream( msDecrypt, decryptor, CryptoStreamMode.Read ) ) {
using( StreamReader srDecrypt = new StreamReader( csDecrypt ) )


// Read the decrypted bytes from the decrypting stream
// and place them in a string.
plaintext = srDecrypt.ReadToEnd();
}
}
}
finally {
// Clear the RijndaelManaged object.
if( aesAlg != null )
aesAlg.Clear();
}


return plaintext;
}


private static byte[] ReadByteArray( Stream s ) {
byte[] rawLength = new byte[sizeof( int )];
if( s.Read( rawLength, 0, rawLength.Length ) != rawLength.Length ) {
throw new SystemException( "Stream did not contain properly formatted byte array" );
}


byte[] buffer = new byte[BitConverter.ToInt32( rawLength, 0 )];
if( s.Read( buffer, 0, buffer.Length ) != buffer.Length ) {
throw new SystemException( "Did not read byte array properly" );
}


return buffer;
}
}


}


GenericNamedEvents.cs


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Web;
using System.Web.UI;


namespace Ext.Net.Extension {


/// <summary>
/// Multiple Global Named Events can be subscribed for and triggered
/// Lazy Singleton model proposed here: http://csharpindepth.com/Articles/General/Singleton.aspx
/// </summary>
public static class GlobalNamedEvents {


public static Dictionary<string, SortedDictionary<int, NamedEvent>> Get( HttpContext Context ) {
var o = Context.Items["GlobalNamedEvents"];
if( o == null )
Context.Items["GlobalNamedEvents"] = o = new Dictionary<string, SortedDictionary<int, NamedEvent>>();
return ( Dictionary<string, SortedDictionary<int, NamedEvent>>)o;
}


public class NamedEvent {
public string eventname;
public int order = 1000;
public string id;
public EventHandler evt;
public bool invoked = false;
}


/// <summary>
/// Subscribe/Wire event to eventname - one per id
/// </summary>
/// <param name="eventname">name of event to trigger</param>
/// <param name="action">EventHandler to subscribe to this trigger</param>
public static void Subscribe( HttpContext Context, NamedEvent namedevt ) {
// Check if the named event already exists
var Events = Get( Context );
SortedDictionary<int, NamedEvent> evt=null;
if( !Events.TryGetValue( namedevt.eventname, out evt ) ) {
// eventname not found, so create with first namedevent
Events.Add( namedevt.eventname, new SortedDictionary<int, NamedEvent>() { { namedevt.order, namedevt } } );
return;
}
// The item was already found, but we need to check if the id already appears
foreach( var nevt in evt ) {
if( nevt.Value.id == namedevt.id )
return;
}
// Existing type was not found, so add the item
evt.Add( namedevt.order, namedevt );
}


/// <summary>
/// Trigger all GenericEvents associated with the eventname
/// </summary>
/// <param name="eventname">name of event to trigger</param>
/// <param name="sender">caller</param>
/// <param name="args">arguments</param>


public static void Trigger( HttpContext Context, string eventname, object sender = null, EventArgs args = null ) {
var Events = Get( Context );
var evt = Events[eventname];// will exception if eventname has not been subscribed
// Invoke each subscriber - not important to lock since it should invoke only namedevents available at time of the trigger
foreach( var nevt in evt ) {
var callevt = nevt.Value;
if( !callevt.invoked ) {
callevt.invoked = true; // prevent calling again
callevt.evt.Invoke( sender, args );
}
}
}



}
}


javascript support functions.


/* Execute Function by Name */
function executeFunctionByName(functionName) {
var args=Array.prototype.slice.call(arguments,2);
var namespaces=functionName.split('.');
var func=namespaces.pop(),e;
for(var i=0;i<namespaces.length;i++)
e=window[namespaces[i]];
return e[func].apply(window,args);
}
/* Execute Function by Name End */


/* Html5 & Redirect History */
function isHtml5() {
return !!document.createElement('canvas').getContext;
}
/* Indicates if browser supports pushState */
function hasPushState() {
return !!history.pushState?true:false;
}
/* Calls psFn if pushState supported; nonpsFn if not. Allows you to call support two models depending on browser pushState Support */
function usePushState(psFn,nopsFn) {
if(hasPushState()) psFn(); else { if(nopsFn) nopsFn(); else psFn(); }
}
/* Add new url to browser address bar if pushstate is supported by browser. If popStateFn & popStateFnArgs passed, it will call the callback onPopState with the arguments (on browser back/fwd buttons).
Also replaces document form action so that url on postback changes Request.Url with new url */
function replaceUrl(url,popStateFn,popStateFnArgs) {
url = url.toString();
if(document.forms&&document.forms[0]) document.forms[0].action=url;
if(hasPushState()) {
var state={};state.fromurl = url;
if(popStateFn) state.popStateFn=popStateFn;
if(popStateFnArgs) state.popStateFnArgs=popStateFnArgs;
history.pushState(state,document.title,url);
}
}
var popped, initialURL;
if(hasPushState()) {
popped=false; initialURL=location.href;
window.onpopstate=function(evt) {
// Ignore inital popstate that some browsers fire on page load
var initialPop=!popped&&location.href==initialURL;
popped=true;
if(initialPop) return;
if(document.forms&&document.forms[0]) document.forms[0].action=location.href;
if(evt.state&&evt.state.popStateFn) executeFunctionByName(evt.state.popStateFn, evt.state.popStateFnArgs);
};
}
/* Html5 & History End */

Daniil
Jan 14, 2014, 3:50 AM
Thank you for the update! We really appreciate your activity on the forums!

michaeld
Jan 14, 2014, 10:23 AM
To populate and Replace Parent Container


ViewC.RemoveRegisteredControlsFromParent();
// Add controls to ViewC
ViewC.AddDirectControl( "~/Test.ascx", TestCtl, true, null, false, true ); // skip user control render because RefreshParent will render
ViewC.RefreshParent();



To subscribe multiple UserControls to a Global Named Event to be Triggered by a DirectEvent/Method, add this to all User Controls that require a refresh of any Control or Container in that UserControl.


// Subscribe in Page_Init of each UserControl
protected void Page_Init( object sender, EventArgs e ) {
GlobalNamedEvents.Subscribe( Context,
new GlobalNamedEvents.NamedEvent() { eventname = "ResultsViewRefresh", order = 100, id = this.ClientID, evt = RefreshResults}
);
}

​ // EventHandler to call when ResultsViewRefresh Triggered
public void RefreshResults( object sender, EventArgs args ) {
PopulateSelected( (CLookupValues)Context.Items["CValues"] ); // Example: This would refresh the ResultsView
PopulateSaveButton(); // Example: This would update a Button in this UserControl
SelP.RefreshParent(); // Refresh The Parent Container
}


To trigger the Global Named Event using a DirectMethod in any UserControl


[DirectMethod( IDMode = DirectMethodProxyIDMode.None, ShowMask = true )]
public void RefreshResults() {
GlobalNamedEvents.Trigger( Context, "ResultsViewRefresh", this, null );
}

Please note that the above example only looks to register a single subscriber to run RefreshResults. If however, more than one UserControl subscribed to the same Global Event Name to its own EventHandler, this trigger will call each UserControl's EventHandler that Subscribed by property order. One trigger can technically update hundreds of controls that render their own required section without having to refresh the page or know anything about the others. Each UserControl must maintain its own state (which can be initialized in the Page_Load or inside the EventHandler). Also note that EventHandlers are invoked in the Page Life-cycle at the time of the Trigger call (which should be inside a DirectMethod or Event as shown in the example which ensures it occurs after all UserControls have had their Load events completed). You can trigger the event from different DirectEvents/Methods, but Trigger will prevent invoking the EventHandler twice for the same control.


To call the DirectMethod above in the client-side code with a revised pushState url. In my example, if the browser is html5, this example will use history.pushState. If the browser is not html5, I revert to page redirection (you can choose another secondary model if you prefer like using hashtags instead):


var Selector = {
refresh: function (url) {
usePushState(
/* if html5 history.pushState is available, use DirectMethod */
function() {
replaceUrl(url, 'Selector.onPopState'); /* replace browser address with url - call onPopState on back/fwd */
App.direct.RefreshResults(url); /* call DirectMethod */
},
/* if history.pushState is not available, use redirection */
function() {
Ext.net.Mask.show({ 'msg': 'One moment, refreshing...' });
document.location = url; /* redirect to revised url */
}
);
}.
onPopState: function() {
App.direct.RefreshResults(location.href);
}
};

Now just call Selector.refresh() from a listener handler for a button or something.


I will update TestApp code later with a more comprehensive example of the new api set above...

michaeld
Jan 15, 2014, 1:10 AM
Some slight updates to 1.0 api posted since yesterday.

Daniil
Jan 15, 2014, 4:24 AM
Thank you for the update. Please clarify are you already using that in your real application?

michaeld
Jan 18, 2014, 4:44 AM
Thank you for the update. Please clarify are you already using that in your real application?

Yes, all the public-facing pages on the site have been converted on Dev. I haven't posted these updates to production yet because I'm in the process of replacing the entire site with this new model.

At present, I am able to select any filter on the public results page and refresh the results view and the selection and update the ads banner. These operations take less than a second now instead of the 3+ seconds it took before to refresh an entire page. It feels much more real-time.

I am considering going even further with the total no-refresh model between result selection and detail views too because it still takes too long. But in order to do that, I will have to extend the Global Named Events api into the client api with messaging to indicate what page it's actually on so it can determine how much of the page it needs to refresh on popState.

This would effectively mean, I started on one aspx page where I did the GET method, and switched to another underneath and only updated the ajax view differential between the two pages without ever refreshing, and being able to popState back to the original page with the back button. Effectively the masterpage would be the only binding containers between them. I will need to make sure I standardize somehow so those mastercontrol names don't change.

I think it's safe to say, if successful I'll have pushed the envelop in Ext.Net. I don't know what others are doing with WebService rendered pages, but this, at least may be the boundaries most pushed with WebForms and UserControls (full-destruct and load) on pages. The only other application I know that does this is Facebook. Can they do this with MVC? It would seem possible with partial views.

Daniil
Jan 20, 2014, 12:53 PM
It is nice to hear your solution works very well with your real application!


I don't know what others are doing with WebService rendered pages, but this, at least may be the boundaries most pushed with WebForms and UserControls (full-destruct and load) on pages.

Probably, something similar, but with some extra work, because it won't be possible to access the page's controls directly as we can do it in a page's code behind by IDs.


The only other application I know that does this is Facebook. Can they do this with MVC? It would seem possible with partial views.

Well, yes, partial views in MVC is an analog of user controls in WebForms. As far as I know Facebook uses PhP, but I don't know for 100% they implemented the MVC pattern, but probably, yes.
http://stackoverflow.com/questions/5318945/do-any-of-these-popular-sites-use-the-mvc-pattern

michaeld
Jan 21, 2014, 1:23 AM
So I should note that yes, this weekend I was able to get this ajax destroy-and-render pattern that I described above to work. I'll post 1.1 of the code that adds messaging between client/server which made it possible to switch the underlying aspx page with a post without having to refresh the entire page. It's kind of scary being so loosely-coupled and there is a lot to test that may break still, and there are limitations, but I can honestly say, I've done it.


It is nice to hear your solution works very well with your real application!
Yeah, the Proof-of-concept is no longer concept.

Probably, something similar, but with some extra work, because it won't be possible to access the page's controls directly as we can do it in a page's code behind by IDs.
Yeah, I do not have that limitation. Working around it could be difficult but perhaps not that much more.

Well, yes, partial views in MVC is an analog of user controls in WebForms.
Yes, but what I mean more is the destroy-and-render pattern. The closest equivalent I've actually seen demoed in the examples is desktop. It seems to use a destroy-and-render pattern but more along the lines of windows which supports delete on close.


As far as I know Facebook uses PhP, but I don't know for 100% they implemented the MVC pattern, but probably, yes.
I don't mean MVC pattern or the programming language, I mean Facebook stays entirely in the client-side code between pages. It does not refresh it's page between clicks like a standard anchor. The address bar changes, the page changes, but though it looks entirely like you've moved to a different page, they have destroyed and rendered everything below the menubar on the client-side and remain stable. In fact, looking at the request and response headers, you find they are indeed moving the underlying page in the eval without performing a full refresh of the page.

This is what I have effectively pulled off now as of last night with Ext.Net.

Daniil
Jan 21, 2014, 6:04 AM
but I can honestly say, I've done it.

My congratulations!



Yes, but what I mean more is the destroy-and-render pattern. The closest equivalent I've actually seen demoed in the examples is desktop. It seems to use a destroy-and-render pattern but more along the lines of windows which supports delete on close.


Do you mean this example?
http://examples2.ext.net/#/Desktop/Introduction/Overview/

I don't quite understand where you see a destroy-and-render pattern... Could you, please, clarify?



I don't mean MVC pattern or the programming language, I mean Facebook stays entirely in the client-side code between pages. It does not refresh it's page between clicks like a standard anchor. The address bar changes, the page changes, but though it looks entirely like you've moved to a different page, they have destroyed and rendered everything below the menubar on the client-side and remain stable. In fact, looking at the request and response headers, you find they are indeed moving the underlying page in the eval without performing a full refresh of the page.


Well, yes, they are doing something similar to what you are doing.

michaeld
Jan 21, 2014, 7:54 AM
http://examples2.ext.net/#/Desktop/Introduction/Overview/ Yes!


I don't quite understand where you see a destroy-and-render pattern... Could you, please, clarify?

I guess you're right, I only just started coining destroy-and-render in this last message, so I'll simply clarify that I started calling it this because of the analogous is simply container1.ReRender() in a DirectEvent. It's a destroy-and-render operation that does nothing but cleanup memory, client objects and layouts. Nothing too serious, but every operation is a client-side eval operation instead of a page refresh.

That is, we're just deleting a zone on a page and replacing it with another. It might not even contain the zones that were originally there or the page we started on. And I'm now doing this exclusively, even with the browser back and forth buttons.

The reason I feel a need to emphasize this description is that not only am I destroying-and-rendering new objects that no longer synch with the server's original page but now I'm also cross-posting without a redirect to other pages without the page having any idea of what was there on the client in the first place; only that it knows it needs to destroy-and-replace something. It's loosely-coupled.

So destroy-and-render, as a pattern, means to me that I no longer can rely on pages synching with the aspx information anymore because it may have been destroyed. It's up to me to pass back state information back and forth to the server about what's "actually" there just like you would on a form, except that instead of form data, about the controls that the form data may represent as well. The master page controls, for instance, are the bounders on the client. But they may have been created from an entirely different page. It's up to me to ensure the master page (or pages) have the common framework between so they hold together despite moving sideways without redirects.

So now that I'm going both upwards and sideways replacing sections of the screen contents, that's what I mean by destroy-and-render as a pattern. Maybe it's just a bad name but that's what I mean by it.

I mean conventional wisdom might say, this is a bad pattern cross-posting like this and I'd probably better off just building a HttpModule or HttpHandlers and just use user-controls for the primary contents of the page based on the url. The only reason I can't do this is I already have Modules and handlers dealing with a lot of specific overhead, and the pages already are loading a lot of user controls this way already too (I mean, that's what this entire thread is about). All I did here was prove it's possible now to have the option to offer both. Upwards (nested) and sideways.

Daniil
Jan 23, 2014, 4:43 AM
Thank you for the detailed explanation. A destroy-and-render pattern... Personally, I like how you call it.

michaeld
Feb 04, 2014, 1:05 PM
I'm long overdue with a new version of the code with some bug fixes but I'm trying to resolve a few issues first.