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:
Code:
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.
Code:
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.
Sample showing it works...
Code:
<%@ Page Language="C#" EnableViewState="false" ClassName="Test45" %>
<%@ Import Namespace="Ext.Net.Extension" %>
<script runat="server">
protected void Page_Load( object sender, EventArgs e ) {
}
[DirectMethod( IDMode = DirectMethodProxyIDMode.None, ShowMask = true )]
public void Click() {
X.MessageBox.Alert( "Test", "Inside Test." ).Show();
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();
Button1.Text = "MsgBox";
Button1.Update();
}
</script>
<!DOCTYPE html>
<html>
<head id="Head1" runat="server">
<title>Test45 Sample</title>
</head>
<body>
<form id="Form1" runat="server">
<ext:ResourceManager ID="ResourceManager1" runat="server" ScriptMode="Development" SourceFormatting="true" IDMode="Explicit" />
<ext:Viewport ID="vp" runat="server" Layout="HBoxLayout">
<Items>
<ext:Panel ID="LP" runat="server" Border="true" Padding="5" Flex="1" Layout="FitLayout" Title="Test45.aspx">
<Items>
<ext:Label ID="Label1" runat="server" Html="Outer" />
</Items>
<Buttons>
<ext:Button ID="Button1" runat="server" Text="Click" OnClientClick="App.direct.Click();" />
</Buttons>
</ext:Panel>
</Items>
</ext:Viewport>
</form>
</body>
</html>
Note that after the first click of Button1, it replaces the server-side MessageBox Alert listener with the code generated by the server-side for the second click.
If you want a ComponentLoader implementation as well...
For completeness, I implemented an extension for ComponentLoader to render code in addition to AbstractControls and UserControls. This code is untested though because I don't use ComponentLoader, but the intuition should follow.
Code:
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;
using Ext.Net.Utilities;
namespace Ext.Net.Extension {
public partial class XComponentLoader : ComponentLoader {
public XComponentLoader() { }
public static void Render( HttpContext Context, Action ScriptFn ) {
CompressionUtils.GZipAndSend( ToConfig( Context, ScriptFn ) );
}
public static void Render( Action ScriptFn ) {
CompressionUtils.GZipAndSend( ToConfig( ScriptFn ) );
}
public static string ToConfig( Action ScriptFn ) {
return ToConfig( HttpContext.Current, ScriptFn );
}
public static string ToConfig( HttpContext Context, Action ScriptFn ) {
var Renderer = new ClientScript( Context );
Renderer.Script( ScriptFn );
return Renderer.ToScript();
}
public static void Render( HttpContext Context, ClientScript Renderer ) {
CompressionUtils.GZipAndSend( ToConfig( Context, Renderer ) );
}
public static void Render( ClientScript Renderer ) {
CompressionUtils.GZipAndSend( ToConfig( Renderer ) );
}
public static string ToConfig( ClientScript Renderer ) {
return ToConfig( HttpContext.Current, Renderer );
}
public static string ToConfig( HttpContext Context, ClientScript Renderer ) {
return Renderer.ToScript();
}
}
}