Feb 03, 2014, 12:06 PM
Server-side Render to Create Client-Side Snippets of code (For Handlers)
I posted the following feature request here: http://forums.ext.net/showthread.php...lers)&p=124113
The idea in a nutshell is to allow the developer to use server-side code to generate client-side code that can be used by Control Listeners. Example:
I haven't done a lot of robust testing yet but it seems to work in the sample I will post at the end. It works by swapping out the base resource manager with a temporary one bound to a SelfRenderingPage.
The idea in a nutshell is to allow the developer to use server-side code to generate client-side code that can be used by Control Listeners. Example:
var Renderer = new ClientScript( Context );
Renderer.Script( () => {
// Any server-side code placed in here will render to client-side string
X.MessageBox.Alert( "Test", "Post-2nd Click Test." ).Show();
} );
Button1.OnClientClick = Renderer.ToScript();
I got a little impatient and decided to see if I could implement a simple version myself that did not require modifying any of the Ext.Net code. I changed some of the names from what I originally proposed and extended it to support all the modes of operation I described in the proposal. I haven't done a lot of robust testing yet but it seems to work in the sample I will post at the end. It works by swapping out the base resource manager with a temporary one bound to a SelfRenderingPage.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Web;
using System.Web.UI;
using Ext.Net;
namespace Ext.Net.Extension {
/// <summary>
/// Create Client Script string using Ext.Net Server-side api that can be attached to Control Handlers
/// </summary>
public class ClientScript : IDisposable {
protected HttpContext Context = null;
protected string script = null;
protected ResourceManager internalrm = null;
protected ResourceManager baserm = null;
public ClientScript() : this( HttpContext.Current ) { }
public ClientScript( HttpContext context ) {
this.Context = context;
SaveBaseRM();
}
/// <summary>
/// Prefered method to indicate Server-side api will be scripted to string that can be used by a Control Handler.
/// </summary>
/// <param name="ScriptFn">Function or delegate who's internal contents will be scripted</param>
public virtual void Script( Action ScriptFn ) {
if( Context == null )
throw new Exception( "Script Rendering was already Disposed." );
// Create the temporary rm if necessary
InitRM();
// Set the Current RM to the internal rm before calling script fn
SetRM( internalrm );
// Run the code you want to convert to script
ScriptFn();
// Restore Current RM to base
SetRM( baserm );
}
#if SupportingStartEnd
public bool Running { get; private set; }
/// <summary>
/// Begin scripting to Client-side code. All server-side api calls between this call and ScriptEnd will be rendered to a string.
/// Make sure to call ScriptEnd() or else Dispose will be left to the Garbage-Collector and that could be dangerous.
/// </summary>
public virtual void ScriptStart() {
if( Context == null )
throw new Exception( "Script Rendering was already Disposed." );
// Create the temporary rm if necessary
InitRM();
// Set the Current RM to the internal rm before calling script fn
Running = true;
SetRM( internalrm );
}
/// <summary>
/// End scripting to Client-side code. All server-side api calls will be rendered back to response writer.
/// </summary>
public virtual void ScriptEnd() {
if( internalrm == null )
throw new Exception( "Script Rendering was already Started." );
if( Context == null )
throw new Exception( "Script Rendering was already Disposed." );
// Set the Current RM to the internal rm before calling script fn
Running = false;
SetRM( baserm );
}
/// <summary>
/// Begin scripting to Client-side code the section inside a using statement. using( var cs = ClientScript.ScriptUsing(Context)) ) { ... }
/// Make sure you are using a using section.
/// </summary>
/// <param name="Context"></param>
/// <returns></returns>
public static ClientScript ScriptUsing( HttpContext Context ) {
ClientScript cs = new ClientScript( Context );
cs.ScriptStart();
return cs;
}
#endif
/// <summary>
/// Render the script to a string that can be used for Control Handlers
/// </summary>
/// <returns></returns>
public virtual string ToScript() {
// Build the script if required
if( script == null && internalrm != null ) {
#if SupportingStartEnd
// If Script was started, we need to end it
if( Running )
ScriptEnd();
#endif
script = internalrm.ToScript( true );
Dispose();
}
return this.script;
}
protected void SaveBaseRM() {
// Check if we're already storing the BaseRM
object o = Context.Items["BaseRM"];
if( o == null )
// Save the original ResourceManager
Context.Items["BaseRM"] = baserm = ResourceManager.GetInstance( Context );
else
baserm = (ResourceManager)o;
}
protected void InitRM() {
// If the temp rm is already initialized, exit
if( internalrm != null )
return;
// Set scope of ResourceManager to new SelfRendering Page Context
System.Web.UI.Page pageHolder = (System.Web.UI.Page)new SelfRenderingPage();
internalrm = new ResourceManager( true );
internalrm.RenderScripts = ResourceLocationType.None;
internalrm.RenderStyles = ResourceLocationType.None;
internalrm.IDMode = internalrm.IDMode;
pageHolder.Controls.Add( internalrm );
}
protected void SetRM( ResourceManager rm ) {
if( Context.CurrentHandler is Page )
( (Page)Context.CurrentHandler ).Items[typeof( ResourceManager )] = rm;
else
Context.Items[typeof( ResourceManager )] = rm;
}
public void Dispose() {
// Do nothing if already disposed
if( Context == null )
return;
// If script not already rendered, render it now
if( this.script == null )
this.script = ToScript();
// Release resources
internalrm = null;
baserm = null;
Context = null;
}
}
}
If you want to support the Start / End mechanism, define SupportingStartEnd at the beginning of the code. Even though it works, I decided it was a lot less safe than using a delegate.
Last edited by michaeld; Feb 03, 2014 at 11:22 PM.