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

Page 2 of 3 FirstFirst 123 LastLast
  1. #11
    Nice. Thank you for the update.
  2. #12

    Performance Update...

    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.
  3. #13

    Official Version 1.0

    • 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 */
    Last edited by michaeld; Jan 15, 2014 at 1:11 AM.
  4. #14
    Thank you for the update! We really appreciate your activity on the forums!
  5. #15

    Example Usage of new api since Beta

    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...
    Last edited by michaeld; Jan 14, 2014 at 11:55 PM.
  6. #16
    Some slight updates to 1.0 api posted since yesterday.
  7. #17
    Thank you for the update. Please clarify are you already using that in your real application?
  8. #18
    Quote Originally Posted by Daniil View Post
    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.
  9. #19
    It is nice to hear your solution works very well with your real application!

    Quote Originally Posted by michaeld View Post
    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.

    Quote Originally Posted by michaeld View Post
    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/5...he-mvc-pattern
  10. #20
    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.

    Quote Originally Posted by Daniil View Post
    It is nice to hear your solution works very well with your real application!
    Yeah, the Proof-of-concept is no longer concept.
    Quote Originally Posted by Daniil View Post
    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.
    Quote Originally Posted by Daniil View Post
    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.

    Quote Originally Posted by Daniil View Post
    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.
    Last edited by michaeld; Jan 21, 2014 at 1:17 AM.
Page 2 of 3 FirstFirst 123 LastLast

Similar Threads

  1. Partial Post Back
    By hardik in forum 1.x Help
    Replies: 2
    Last Post: Apr 13, 2011, 4:46 AM
  2. Store does not retain value after post back
    By pooja in forum 1.x Help
    Replies: 1
    Last Post: Oct 15, 2010, 6:14 PM
  3. [CLOSED] [1.0] DirectEvents in Usercontrols
    By ljankowski in forum 1.x Legacy Premium Help
    Replies: 6
    Last Post: Jul 12, 2010, 9:39 AM
  4. [CLOSED] Post back issues
    By Etisbew in forum 1.x Legacy Premium Help
    Replies: 11
    Last Post: Jun 24, 2009, 9:06 AM
  5. [CLOSED] Post Back issues ????
    By dukefama in forum 1.x Help
    Replies: 2
    Last Post: Oct 08, 2008, 1:52 PM

Posting Permissions