tag:blogger.com,1999:blog-80894554617527604302024-02-20T12:13:57.882+01:00Got run over on information highwayAnonymoushttp://www.blogger.com/profile/04794529722761111917noreply@blogger.comBlogger11125tag:blogger.com,1999:blog-8089455461752760430.post-17775256143133736442012-05-01T09:59:00.000+02:002012-05-01T09:59:17.519+02:00How to open RadWindow without RadWindowManager<br />
<div class="MsoNormal">
<span lang="EN-US">I <a href="http://jakub-linhart.blogspot.com/2012/01/i-apologize-for-my-ignorance-dear.html">liberated</a>
my RadWindows from the yoke of the RadWindowManager. Now the question is how to
open such free window.</span></div>
<div class="MsoNormal">
<span lang="EN-US">Some parts
are well described in Telerik </span><a href="http://www.telerik.com/help/aspnet-ajax/window-programming-opening.html"><span lang="EN-US">documentation</span></a><span lang="EN-US">.</span></div>
<div class="MsoNormal">
<br />
<b><span style="font-family: "Calibri","sans-serif";">Client side</span></b></div>
<div class="MsoNormal">
<span lang="EN-US">Function window.radopen
is not usable for manager-less windows. RadWindow client side API should be
employed instead:</span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span style="color: blue; font-family: Consolas; font-size: 9.5pt;">$find('FreeWindow').show();</span></div>
<div class="MsoNormal">
<br />
<span lang="EN-US">It is worth
noting that the same API can be used for RadWindow placed inside
RadWindowManager.</span></div>
<div class="MsoNormal">
<br />
<b><span lang="EN-US">From code behind</span></b></div>
<div class="MsoNormal">
<span lang="EN-US">The only
one relevant opening tool on server-side is property <a href="http://www.telerik.com/help/aspnet-ajax/window-programming-server-side-properties.html">VisibleOnPageLoad</a>. When the property is true, then the window is automatically shown after the page is loaded on the
client. <a href="http://www.telerik.com/help/aspnet-ajax/window-programming-server-side-properties.html">VisibleOnPageLoad</a> is ViewState property so it has to be reset to the
false after OnInit phase otherwise the window will open at all sequent
postbacks (partial and full).</span></div>
<div class="MsoNormal">
<br />
<span lang="EN-US">Is there any other possibility?</span></div>
<div class="MsoNormal">
<br />
<b><span lang="EN-US">From code behind via startup script</span></b></div>
<div class="MsoNormal">
<span lang="EN-US">It is the
same approach as on client side but the script is registered in code behind as
a startup script. Startup script registration is quite basic topic but when it
comes to dealing with GETs/postbacks and partial postbacks within the same
application, things are getting a little bit tricky.</span></div>
<div class="MsoNormal">
<br />
<span lang="EN-US">A universal
way how to open a RadWindow from code behind is:</span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span style="color: blue; font-family: Consolas; font-size: 9.5pt;">function</span><span style="font-family: Consolas; font-size: 9.5pt;"> openWindowOnPageLoad(windowId) {</span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span style="font-family: Consolas; font-size: 9.5pt;"> <span style="color: blue;">var</span> fn = <span style="color: blue;">function</span> ()
{</span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span style="font-family: Consolas; font-size: 9.5pt;"> <span style="color: blue;">var</span> window = $find(windowId);</span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span style="font-family: Consolas; font-size: 9.5pt;">
window.show();</span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<br /></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span style="font-family: Consolas; font-size: 9.5pt;">
Sys.Application.remove_load(fn);</span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span style="font-family: Consolas; font-size: 9.5pt;"> };</span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<br /></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span style="font-family: Consolas; font-size: 9.5pt;">
Sys.Application.add_load(fn);</span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span style="font-family: Consolas; font-size: 9.5pt;">}</span></div>
<div class="MsoNormal">
<br />
<span lang="EN-US">Code
behind:</span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span style="color: #2b91af; font-family: Consolas; font-size: 9.5pt;">ScriptManager</span><span style="font-family: Consolas; font-size: 9.5pt;">.RegisterStartupScript(<span style="color: blue;">this</span>, <span style="color: blue;">this</span>.GetType(),
<span style="color: #a31515;">"openWindow"</span>, <span style="color: #a31515;">"openWindowOnPageLoad('FreeWindow');"</span>, <span style="color: blue;">true</span>);</span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<br /></div>
<div class="MsoNormal">
<span lang="EN-US">What an odd
code! Complexity is the price for robustness. Reasons are explained in this </span><a href="http://jakub-linhart.blogspot.com/2012/04/script-registration-labyrinth-startup.html"><span lang="EN-US">blog post</span></a><span lang="EN-US">.</span></div>Anonymoushttp://www.blogger.com/profile/04794529722761111917noreply@blogger.com5tag:blogger.com,1999:blog-8089455461752760430.post-8046146926003483342012-04-24T20:59:00.000+02:002012-04-24T21:00:02.676+02:00Script registration labyrinth – startup scripts and $find (alternative)<div class="MsoNormal">
<span lang="EN-US">I described
a universal way how to register startup script that invokes $find javascript
function. This solution is taken from AJAX Control Toolkit and looks like this:</span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span style="font-family: Consolas; font-size: 9.5pt;">(<span style="color: blue;">function</span> () {</span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span style="font-family: Consolas; font-size: 9.5pt;"> <span style="color: blue;">var</span> fn = <span style="color: blue;">function</span> ()
{</span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span style="font-family: Consolas; font-size: 9.5pt;"> <span style="color: blue;">var</span> control = $find(<span style="color: maroon;">'someControl'</span>);</span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span style="font-family: Consolas; font-size: 9.5pt;"> control.doSomething();</span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span style="font-family: Consolas; font-size: 9.5pt;"> Sys.Application.remove_load(fn);</span><span style="font-family: "Courier New"; font-size: 10pt;"></span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span style="font-family: Consolas; font-size: 9.5pt;"> };</span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<br /></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span style="font-family: Consolas; font-size: 9.5pt;">
Sys.Application.add_load(fn);</span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span style="font-family: Consolas; font-size: 9.5pt;">})();</span></div>
<div class="MsoNormal">
<span lang="EN-US">There are
other possibilities. I found a solution proposed on Telerik’s forum: use
setTimeout javascript function to postpone the execution of $find until all
controls are instantiated by $create functions.</span></div>
<div class="MsoNormal">
<span lang="EN-US">The trick
is used by Telerik ASP.NET Ajax controls, particularly for scripts registered
into </span><a href="http://www.telerik.com/help/aspnet-ajax/p_telerik_web_ui_radajaxcontrol_responsescripts.html"><span lang="EN-US">RadAjaxManager.ResponseScripts</span></a><span lang="EN-US"> collection. If you try this
fragment:</span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span style="color: blue; font-family: Consolas; font-size: 9.5pt;">string</span><span style="font-family: Consolas; font-size: 9.5pt;"> script = <span style="color: blue;">string</span>.Format(<span style="color: #a31515;">"alert($find('{0}'));"</span>,
Control1.ClientID);</span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span style="font-family: Consolas; font-size: 9.5pt;">AjaxManager1.ResponseScripts.Add(script);</span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<br /></div>
<div class="MsoNormal">
<span lang="EN-US">It will
result into this output:</span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; tab-stops: 45.8pt 91.6pt 137.4pt 183.2pt 229.0pt 274.8pt 320.6pt 366.4pt 412.2pt 458.0pt 503.8pt 549.6pt 595.4pt 641.2pt 687.0pt 732.8pt;">
<span style="font-family: "Courier New"; font-size: 10pt;">setTimeout(function(){alert($find('Control1'));},
0);</span></div>
<div class="MsoNormal">
<br /></div>
<div class="MsoNormal">
<span lang="EN-US">It works
well for partial postbacks but not for full postbacks. No surprise there
because RadAjaxManager is inherently tied to partial postbacks. It can be used
for full postbacks as well anyway. It is a pitty because there is no unified
way how to register startup scripts for both postback types in Telerik ASP.NET
AJAX controls (at least in current version – 2012.1.411). You have to check the
request type (GET/full postback vs. partial postback) and decide how to
register the startup script.</span></div>
<div class="MsoNormal">
<span lang="EN-US">It is so
easy to fix this approach. Just wrap the setTimeout function to init event
handler:</span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span style="font-family: Consolas; font-size: 9.5pt;">Sys.Application.add_init(<span style="color: blue;">function</span>
() {</span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span style="font-family: Consolas; font-size: 9.5pt;">
setTimeout(<span style="color: blue;">function</span> () {</span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span style="font-family: Consolas; font-size: 9.5pt;">
alert($find(<span style="color: maroon;">'Control1'</span>));</span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span style="font-family: Consolas; font-size: 9.5pt;"> }, 0);</span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span style="font-family: Consolas; font-size: 9.5pt;">});</span></div>
<div class="MsoNormal">
<br /></div>
<span lang="EN-US" style="font-family: "Calibri","sans-serif"; font-size: 11pt; line-height: 115%;">Here
is full </span><span style="font-family: "Calibri","sans-serif"; font-size: 11pt; line-height: 115%;"><a href="https://gist.github.com/2385921"><span lang="EN-US">markup and code-behind</span></a></span><span lang="EN-US" style="font-family: "Calibri","sans-serif"; font-size: 11pt; line-height: 115%;">
(Telerik ASP.NET AJAX controls are required).</span>Anonymoushttp://www.blogger.com/profile/04794529722761111917noreply@blogger.com0tag:blogger.com,1999:blog-8089455461752760430.post-6662823841708395972012-04-14T13:28:00.002+02:002012-04-14T16:12:08.336+02:00Script registration labyrinth – startup scripts and $find<span lang="EN-US">You are an
ASP.NET developer working for surrealistic corporation. You have an ASP.NET
timer on your ASPX page and it should be disabled when a particular condition
is met. This condition can be evaluated only on the server. There is </span><a href="http://msdn.microsoft.com/en-us/library/system.web.ui.timer.enabled.aspx"><span lang="EN-US">Enabled</span></a><span lang="EN-US"> property in </span><a href="http://msdn.microsoft.com/en-us/library/system.web.ui.timer.aspx"><span lang="EN-US">Timer</span></a><span lang="EN-US"> class that can be easily used to fulfill
customer’s requirement but it is against corporate identity. You are expected
to use client side API instead.</span><br />
<div class="MsoNormal">
<br /></div>
<div class="MsoNormal">
<span lang="EN-US">OK. You
have a timer:</span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span lang="EN-US" style="color: blue; font-family: Consolas; font-size: 9.5pt;"><</span><span lang="EN-US" style="color: maroon; font-family: Consolas; font-size: 9.5pt;">asp</span><span lang="EN-US" style="color: blue; font-family: Consolas; font-size: 9.5pt;">:</span><span lang="EN-US" style="color: maroon; font-family: Consolas; font-size: 9.5pt;">Timer</span><span lang="EN-US" style="font-family: Consolas; font-size: 9.5pt;">
<span style="color: red;">runat</span><span style="color: blue;">="server"</span>
<span style="color: red;">ID</span><span style="color: blue;">="BretonTimer"</span>
<span style="color: red;">OnTick</span><span style="color: blue;">="BretonTimer_Tick"</span></span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span lang="EN-US" style="font-family: Consolas; font-size: 9.5pt;"> <span style="color: red;">Interval</span><span style="color: blue;">="2000"</span> <span style="color: blue;">/></span></span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<br /></div>
<div class="MsoNormal">
<span lang="EN-US">To use
client side API of ASP.NET AJAX controls </span><a href="http://msdn.microsoft.com/en-us/library/bb397441.aspx"><span lang="EN-US">$find</span></a><span lang="EN-US"> method has to be utilized to find the control
instance - it is an client-side control instance not DOM element. Timer’s client-side API is not well documented but there are some </span><a href="http://forums.asp.net/t/1094798.aspx/1"><span lang="EN-US">clues</span></a><span lang="EN-US"> on the Internet. So let’s choose _stopTimer
function for this purpose:</span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span lang="EN-US" style="color: blue; font-family: Consolas; font-size: 9.5pt;">string</span><span lang="EN-US" style="font-family: Consolas; font-size: 9.5pt;">
script = <span style="color: blue;">string</span>.Format(<span style="color: #a31515;">"$find('{0}')._stopTimer();"</span>,
BretonTimer.ClientID);</span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<br /></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span lang="EN-US">Create a button just to test this undocumented
function:</span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span lang="EN-US" style="color: blue; font-family: Consolas; font-size: 9.5pt;"><</span><span lang="EN-US" style="color: maroon; font-family: Consolas; font-size: 9.5pt;">asp</span><span lang="EN-US" style="color: blue; font-family: Consolas; font-size: 9.5pt;">:</span><span lang="EN-US" style="color: maroon; font-family: Consolas; font-size: 9.5pt;">Button</span><span lang="EN-US" style="font-family: Consolas; font-size: 9.5pt;">
<span style="color: red;">runat</span><span style="color: blue;">="server"</span>
<span style="color: red;">ID</span><span style="color: blue;">="StopBreton"</span>
<span style="color: red;">Text</span><span style="color: blue;">="Stop!"</span></span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span lang="EN-US" style="font-family: Consolas; font-size: 9.5pt;"> <span style="color: red;">OnClientClick</span><span style="color: blue;">="$find('BretonTimer')._stopTimer(); return
false;"</span> <span style="color: blue;">/></span></span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<br />
Click on the button to test that API works well.<br />
<br /></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<b style="mso-bidi-font-weight: normal;"><span lang="EN-US">Synchronous postback</span></b></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span lang="EN-US">So as the last step this script
should be <a href="http://jakub-linhart.blogspot.com/2012/03/script-registration-labyrinth-in-aspnet.html">registered
as a startup script</a> and today’s work will be done:</span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span style="color: blue; font-family: Consolas; font-size: 9.5pt;">string</span><span style="font-family: Consolas; font-size: 9.5pt;"> script = <span style="color: blue;">string</span>.Format(<span style="color: #a31515;">"$find('{0}')._stopTimer();"</span>,
BretonTimer.ClientID);</span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span lang="EN-US" style="color: #2b91af; font-family: Consolas; font-size: 9.5pt;">ScriptManager</span><span lang="EN-US" style="font-family: Consolas; font-size: 9.5pt;">.RegisterStartupScript(<span style="color: blue;">this</span>, <span style="color: blue;">this</span>.GetType(),
<span style="color: #a31515;">"key1"</span>, script, <span style="color: blue;">true</span>);</span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<br /></div>
<div class="MsoNormal">
<span lang="EN-US">The timer
is really off after page reload. There is a last annoyance, a javascript error:
</span><span lang="EN-US" style="font-family: Consolas; font-size: 9.5pt; line-height: 115%;">$find("BretonTimer") is null</span><span lang="EN-US"></span></div>
<div class="MsoNormal">
<br />
<span lang="EN-US">Why $find
returns an instance when the script is executed within click event handler and
fails when the same script is run during page load? Let’s see how page source
looks like.</span></div>
<div class="MsoNormal">
<br />
<span lang="EN-US">Startup
script is near the page end <a href="http://jakub-linhart.blogspot.com/2012/03/script-registration-labyrinth-in-aspnet.html">as
expected</a>:</span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; tab-stops: 45.8pt 91.6pt 137.4pt 183.2pt 229.0pt 274.8pt 320.6pt 366.4pt 412.2pt 458.0pt 503.8pt 549.6pt 595.4pt 641.2pt 687.0pt 732.8pt;">
<span lang="EN-US" style="font-family: "Courier New"; font-size: 10pt;">$find('BretonTimer')._stopTimer();Sys.Application.initialize();</span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; tab-stops: 45.8pt 91.6pt 137.4pt 183.2pt 229.0pt 274.8pt 320.6pt 366.4pt 412.2pt 458.0pt 503.8pt 549.6pt 595.4pt 641.2pt 687.0pt 732.8pt;">
<span lang="EN-US" style="font-family: "Courier New"; font-size: 10pt;">Sys.Application.add_init(function()
{</span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; tab-stops: 45.8pt 91.6pt 137.4pt 183.2pt 229.0pt 274.8pt 320.6pt 366.4pt 412.2pt 458.0pt 503.8pt 549.6pt 595.4pt 641.2pt 687.0pt 732.8pt;">
<span lang="EN-US" style="font-family: "Courier New"; font-size: 10pt;">
$create(Sys.UI._Timer,
{"enabled":true,"interval":2000,"uniqueID":"BretonTimer"},
null, null, $get("BretonTimer"));</span></div>
<div class="MsoNormal">
<br /></div>
<div class="MsoNormal">
<span lang="EN-US">But what is
on the next line? It seems like something what is intended to <a href="http://msdn.microsoft.com/en-us/library/bb397487.aspx">create client-side
instance</a> of the timer control. ASP.NET AJAX design does not follow
idiosyncratic philosophy of your corporation, so it is not possible to find an instance
of control that was not created yet.</span></div>
<div class="MsoNormal">
<br />
<span lang="EN-US">Add_init
attaches event handler to the client-side <a href="http://msdn.microsoft.com/en-us/library/bb397532.aspx">init</a> event. It
reminds server-side page lifecycle. And really there is the client-side <a href="http://msdn.microsoft.com/en-us/library/bb383829.aspx">load</a> event as
well. Load event handlers are executed after init handlers and it is exactly
what you need (see <a href="http://msdn.microsoft.com/en-us/library/bb386417.aspx">Ajax Client Life-Cycle
Events</a> for more information):</span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span style="color: blue; font-family: Consolas; font-size: 9.5pt;">string</span><span style="font-family: Consolas; font-size: 9.5pt;"> script = <span style="color: blue;">string</span>.Format(<span style="color: #a31515;">"Sys.Application.add_load(function() {{
$find('{0}')._stopTimer(); }});"</span>, BretonTimer.ClientID);</span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span lang="EN-US" style="color: #2b91af; font-family: Consolas; font-size: 9.5pt;">ScriptManager</span><span lang="EN-US" style="font-family: Consolas; font-size: 9.5pt;">.RegisterStartupScript(<span style="color: blue;">this</span>, <span style="color: blue;">this</span>.GetType(),
<span style="color: #a31515;">"key1"</span>, script, <span style="color: blue;">true</span>);</span></div>
<div class="MsoNormal">
<br />
<span lang="EN-US">It works! Timer
is not running anymore and there is no javascript error.</span></div>
<div class="MsoNormal">
<span lang="EN-US">But your
sixth sense warns you that there is something wrong with this solution... What
about partial postback?</span></div>
<div class="MsoNormal">
<br /></div>
<div class="MsoNormal">
<b style="mso-bidi-font-weight: normal;"><span lang="EN-US">Partial postback</span></b></div>
<div class="MsoNormal">
<span lang="EN-US">To avoid
reloads of whole page you are forced to use an UpdatePanel. You modify the
markup in that way:</span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span style="color: blue; font-family: Consolas; font-size: 9.5pt;"><</span><span style="color: maroon; font-family: Consolas; font-size: 9.5pt;">asp</span><span style="color: blue; font-family: Consolas; font-size: 9.5pt;">:</span><span style="color: maroon; font-family: Consolas; font-size: 9.5pt;">UpdatePanel</span><span style="font-family: Consolas; font-size: 9.5pt;"> <span style="color: red;">runat</span><span style="color: blue;">="server"</span> <span style="color: red;">ID</span><span style="color: blue;">="UpdatePanel1"></span></span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span style="font-family: Consolas; font-size: 9.5pt;"> <span style="color: blue;"><</span><span style="color: maroon;">ContentTemplate</span><span style="color: blue;">></span></span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span style="font-family: Consolas; font-size: 9.5pt;"> <span style="color: blue;"><</span><span style="color: maroon;">asp</span><span style="color: blue;">:</span><span style="color: maroon;">Timer</span> <span style="color: red;">runat</span><span style="color: blue;">="server"</span>
<span style="color: red;">ID</span><span style="color: blue;">="BretonTimer"</span>
<span style="color: red;">OnTick</span><span style="color: blue;">="BretonTimer_Tick"</span>
<span style="color: red;">Interval</span><span style="color: blue;">="2000"</span>
<span style="color: blue;">/></span></span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span style="font-family: Consolas; font-size: 9.5pt;"> <span style="color: blue;"><</span><span style="color: maroon;">asp</span><span style="color: blue;">:</span><span style="color: maroon;">Button</span> <span style="color: red;">runat</span><span style="color: blue;">="server"</span>
<span style="color: red;">ID</span><span style="color: blue;">="StopBretonButton"</span>
<span style="color: red;">Text</span><span style="color: blue;">="Stop!"</span>
<span style="color: red;">OnClick</span><span style="color: blue;">="StopBretonButton_Click"</span>
<span style="color: blue;">/></span></span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span style="font-family: Consolas; font-size: 9.5pt;"> <span style="color: blue;"><</span><span style="color: maroon;">asp</span><span style="color: blue;">:</span><span style="color: maroon;">Button</span> <span style="color: red;">runat</span><span style="color: blue;">="server"</span>
<span style="color: red;">ID</span><span style="color: blue;">="JustAnotherPostbackButton"</span>
<span style="color: red;">Text</span><span style="color: blue;">="Just another
postback"</span> <span style="color: blue;">/></span></span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span style="font-family: Consolas; font-size: 9.5pt;"> <span style="color: blue;"></</span><span style="color: maroon;">ContentTemplate</span><span style="color: blue;">></span></span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span style="font-family: Consolas; font-size: 9.5pt;"> <span style="color: blue;"><</span><span style="color: maroon;">Triggers</span><span style="color: blue;">></span></span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span style="font-family: Consolas; font-size: 9.5pt;"> <span style="color: blue;"><</span><span style="color: maroon;">asp</span><span style="color: blue;">:</span><span style="color: maroon;">AsyncPostBackTrigger</span>
<span style="color: red;">ControlID</span><span style="color: blue;">="BretonTimer"</span>
<span style="color: blue;">/></span></span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span style="font-family: Consolas; font-size: 9.5pt;"> <span style="color: blue;"><</span><span style="color: maroon;">asp</span><span style="color: blue;">:</span><span style="color: maroon;">AsyncPostBackTrigger</span>
<span style="color: red;">ControlID</span><span style="color: blue;">="StopBretonButton"</span>
<span style="color: blue;">/></span></span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span style="font-family: Consolas; font-size: 9.5pt;"> <span style="color: blue;"></</span><span style="color: maroon;">Triggers</span><span style="color: blue;">></span></span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span style="color: blue; font-family: Consolas; font-size: 9.5pt;"></</span><span style="color: maroon; font-family: Consolas; font-size: 9.5pt;">asp</span><span style="color: blue; font-family: Consolas; font-size: 9.5pt;">:</span><span style="color: maroon; font-family: Consolas; font-size: 9.5pt;">UpdatePanel</span><span style="color: blue; font-family: Consolas; font-size: 9.5pt;">></span><span style="font-family: Consolas; font-size: 9.5pt;"></span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<br /></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span style="font-family: Consolas; font-size: 9.5pt;">and code behind:</span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span style="color: blue; font-family: Consolas; font-size: 9.5pt;">protected</span><span style="font-family: Consolas; font-size: 9.5pt;"> <span style="color: blue;">void</span>
StopBretonButton_Click(<span style="color: blue;">object</span> sender, <span style="color: #2b91af;">EventArgs</span> e)</span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span style="font-family: Consolas; font-size: 9.5pt;">{</span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span style="font-family: Consolas; font-size: 9.5pt;"> <span style="color: blue;">string</span> script = <span style="color: blue;">string</span>.Format(<span style="color: #a31515;">"Sys.Application.add_load(function() {{ $find('{0}')._stopTimer();
}});"</span>, BretonTimer.ClientID);</span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span style="font-family: Consolas; font-size: 9.5pt;"> <span style="color: #2b91af;">ScriptManager</span>.RegisterStartupScript(<span style="color: blue;">this</span>, <span style="color: blue;">this</span>.GetType(),
<span style="color: #a31515;">"key1"</span>, script, <span style="color: blue;">true</span>);</span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span style="font-family: Consolas; font-size: 9.5pt;">}</span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<br /></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span lang="EN-US">When “Stop!” button is clicked, timer is
stopped properly. But there is a strange feeling in your guts. Something is
wrong.</span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span lang="EN-US">Add an alert message to make sure what is
really happing:</span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span style="color: blue; font-family: Consolas; font-size: 9.5pt;">string</span><span style="font-family: Consolas; font-size: 9.5pt;"> script = <span style="color: blue;">string</span>.Format(<span style="color: #a31515;">"Sys.Application.add_load(function() {{ alert('load
event handler');$find('{0}')._stopTimer(); }});"</span>,
BretonTimer.ClientID);</span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<br /></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span lang="EN-US">Alert message “load event handler” is displayed
after pushing “Stop!” button. But when it is pushed for second time, two
message boxes are displayed. Push “Just another postback” button and you will
get two message boxes as well. It means load event handler remains attached to
the event after partial postback.</span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<br /></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span lang="EN-US">For init events it would be a catastrophic
behavior. They are used by ASP.NET AJAX framework heavily to run $create
functions. When a new event handler would be attached to init event after every
partial postback then the page would be unusable after a while because browser would
spend a lot of time executing many init identical event handlers. It can be hardly true.</span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<br /></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span lang="EN-US">Let’s do an experiment:</span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span style="color: blue; font-family: Consolas; font-size: 9.5pt;">string</span><span style="font-family: Consolas; font-size: 9.5pt;"> script = <span style="color: blue;">string</span>.Format(<span style="color: #a31515;">"Sys.Application.add_init(function() {{ alert('init
event handler'); }});"</span>, BretonTimer.ClientID);</span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<br /></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span lang="EN-US">Only one message box is shown regardless how
many times the button is clicked. So there is a fundamental difference between
load and init events. Why?</span><br />
<br />
<span lang="EN-US">It is difficult to find an answer (MSDN keep silent). If
you find one, please let me know, I would love to hear any reason. It is pretty
surreal and well suited to the corporate identity.</span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<br />
<span lang="EN-US">Load event <a href="http://ajaxcontroltoolkit.codeplex.com/SourceControl/changeset/view/edf1fbcb2745#Server%2fAjaxControlToolkit%2fToolkitScriptManager%2fToolkitScriptManager.cs">solution</a>
is well crafted by <a href="http://ajaxcontroltoolkit.codeplex.com/">AJAX
Control Toolkit</a> authors. The handler is unregistered after it is executed
for the first time:</span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span style="font-family: Consolas; font-size: 9.5pt;">(<span style="color: blue;">function</span> () {</span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span style="font-family: Consolas; font-size: 9.5pt;"> <span style="color: blue;">var</span> fn = <span style="color: blue;">function</span> ()
{</span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span style="font-family: Consolas; font-size: 9.5pt;"> $find(<span style="color: maroon;">'BretonTimer'</span>)._stopTimer();</span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span style="font-family: Consolas; font-size: 9.5pt;"> Sys.Application.remove_load(fn);</span><span style="font-family: "Courier New"; font-size: 10pt;"></span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span style="font-family: Consolas; font-size: 9.5pt;"> };</span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<br /></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span style="font-family: Consolas; font-size: 9.5pt;">
Sys.Application.add_load(fn);</span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span style="font-family: Consolas; font-size: 9.5pt;">})();</span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<br /></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span lang="EN-US">The beauty of this solution is in the fact that
you don’t need to generate a unique function name for each event handler.</span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<br /></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span lang="EN-US">Here is the final <a href="https://gist.github.com/2279118">markup and code behind</a>.</span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<br /></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<b style="mso-bidi-font-weight: normal;"><span lang="EN-US">Conclusion</span></b></div>
<div class="MsoNormal">
<span lang="EN-US">The example
with Timer control is quite absurd and may seem useless. But frameworks like
AJAX Control Toolkit or Telerik are based on $create and $find functions and
you have to register some startup scripts using $find function on and off. I
chose the Timer control as something that is known to all users of those
ASP.NET AJAX frameworks.</span></div>
<div class="MsoNormal" style="margin-top: 12.0pt;">
<span lang="EN-US">Registering startup script that uses $find function is not as
straightforward as it seems for the first time. AJAX Control Toolkit solution
is safe for synchronous and partial postbacks: </span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span style="font-family: Consolas; font-size: 9.5pt;">(<span style="color: blue;">function</span> () {</span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span style="font-family: Consolas; font-size: 9.5pt;"> <span style="color: blue;">var</span> fn = <span style="color: blue;">function</span> ()
{</span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span style="font-family: Consolas; font-size: 9.5pt;"> var
ajaxControl = $find(<span style="color: maroon;">'AJAX control'</span>);</span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span style="font-family: Consolas; font-size: 9.5pt;"> // do
something usefull</span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span style="font-family: Consolas; font-size: 9.5pt;"> };</span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<br /></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span style="font-family: Consolas; font-size: 9.5pt;">
Sys.Application.add_load(fn);</span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span style="font-family: Consolas; font-size: 9.5pt;">})();</span></div>
<div class="MsoNormal" style="margin-top: 12.0pt;">
<span lang="EN-US">It is based on client side page life cycle which is not very commonly
known. It handles a little bit shocking load event behavior that is not
documented on MSDN. It is good to know about these ASP.NET AJAX parts.</span></div>Anonymoushttp://www.blogger.com/profile/04794529722761111917noreply@blogger.com1tag:blogger.com,1999:blog-8089455461752760430.post-43616167072812610982012-03-11T21:52:00.000+01:002012-04-14T16:11:33.080+02:00Script registration labyrinth in ASP.NET WebForms<br />
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span lang="EN-US">I'm always lost when trying to register a
javascript fragment that must be executed on the client side after the postback.
There are several usable methods in ASP.NET WebForms and I never know which one
to choose.</span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<br /></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<b style="mso-bidi-font-weight: normal;"><span lang="EN-US">Page.ClientScript</span></b></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<a href="http://msdn.microsoft.com/en-us/library/asz8zsxy.aspx"><span lang="EN-US">Page.ClientScript.RegisterStartupScript</span></a><span lang="EN-US"> – registers the startup script.
What does it mean “startup”? It is something that is executed when the browser finishes
page loading, which is guaranteed by the fact that “startup” scripts are
rendered just before closing <form> tag. Almost all elements on the page
are processed by the browser at this point and can be referenced from that “startup”
script.</span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<a href="http://msdn.microsoft.com/en-us/library/btf44dc9.aspx"><span lang="EN-US">Page.ClientScript.RegisterClientScriptBlock</span></a><span lang="EN-US"> – registers the script block, so it
is rendered somewhere near after the <form> element opening. This block
is executed before the DOM is fully processed and before any “startup” scripts
are executed. “ClientScript” blocks are good place for javascript functions.</span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<br /></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<b style="mso-bidi-font-weight: normal;"><span lang="EN-US">Page</span></b></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<a href="http://msdn.microsoft.com/en-us/library/system.web.ui.page.registerstartupscript.aspx"><span lang="EN-US">Page.RegisterStartupScript</span></a><span lang="EN-US"> and </span><a href="http://msdn.microsoft.com/en-us/library/system.web.ui.page.registerclientscriptblock.aspx"><span lang="EN-US">Page.RegisterClientScriptBlock</span></a><span lang="EN-US"> are marked as obsolete and they do
nothing but Page.ClientScript call. Just ignore them.</span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<br /></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<b style="mso-bidi-font-weight: normal;"><span lang="EN-US">ScriptManager</span></b></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span lang="EN-US">Now what about </span><a href="http://msdn.microsoft.com/en-us/library/system.web.ui.scriptmanager.aspx"><span lang="EN-US">ScriptManager</span></a><span lang="EN-US"> class? RegisterClientScriptBlock
and RegisterStartupScript are there as well. What is the justification for such
duplication? The only difference, according to the documentation (</span><a href="http://msdn.microsoft.com/en-us/library/bb310408.aspx"><span lang="EN-US">here</span></a><span lang="EN-US"> and </span><a href="http://msdn.microsoft.com/en-us/library/bb338357.aspx"><span lang="EN-US">here</span></a><span lang="EN-US">), is that the scripts are “included every time
that an asynchronous postback occurs”. This formulation is slightly confusing.
Scripts registered by ScriptManager’s methods are rendered by non-asynchronous
postback as well. ClientScript’s methods are never rendered by asynchronous-postbacks.</span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<br /></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<b style="mso-bidi-font-weight: normal;"><span lang="EN-US">Script registration
after asynchronous postback</span></b></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span lang="EN-US">The javascript block is not executed if it is a
part of markup in AJAX response. Browser simply doesn’t process such block. Consider
such silly <a href="https://gist.github.com/2018283#file_client_script_registration.aspx">ASPX</a>:</span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span style="color: blue; font-family: Consolas; font-size: 9.5pt;"><</span><span style="color: maroon; font-family: Consolas; font-size: 9.5pt;">asp</span><span style="color: blue; font-family: Consolas; font-size: 9.5pt;">:</span><span style="color: maroon; font-family: Consolas; font-size: 9.5pt;">Button</span><span style="font-family: Consolas; font-size: 9.5pt;"> <span style="color: red;">runat</span><span style="color: blue;">="server"</span> <span style="color: red;">ID</span><span style="color: blue;">="Button1"</span> <span style="color: red;">Text</span><span style="color: blue;">="Do postback"</span> <span style="color: red;">OnClick</span><span style="color: blue;">="Button1_Click"</span> <span style="color: blue;">/></span></span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span style="color: blue; font-family: Consolas; font-size: 9.5pt;"><</span><span style="color: maroon; font-family: Consolas; font-size: 9.5pt;">asp</span><span style="color: blue; font-family: Consolas; font-size: 9.5pt;">:</span><span style="color: maroon; font-family: Consolas; font-size: 9.5pt;">Literal</span><span style="font-family: Consolas; font-size: 9.5pt;"> <span style="color: red;">runat</span><span style="color: blue;">="server"</span> <span style="color: red;">ID</span><span style="color: blue;">="Literal1"</span> <span style="color: blue;">/></span></span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<br /></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span lang="EN-US">and C# <a href="https://gist.github.com/2018283#file_client_script_registration.aspx.cs">code behind</a> with a script:</span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span style="color: blue; font-family: Consolas; font-size: 9.5pt;">protected</span><span style="font-family: Consolas; font-size: 9.5pt;"> <span style="color: blue;">void</span>
Button1_Click(<span style="color: blue;">object</span> sender, <span style="color: #2b91af;">EventArgs</span> e)</span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span style="font-family: Consolas; font-size: 9.5pt;">{</span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span style="font-family: Consolas; font-size: 9.5pt;">
Literal1.Text = <span style="color: #a31515;">"<script>alert('Button1
clicked')</script>"</span>;</span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span style="font-family: Consolas; font-size: 9.5pt;">}</span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<br /></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span lang="EN-US">Button1 causes postback (when clicked) and
event handler fills Literal1 with the javascript block. This javascript block
is execeuted and the alert message is shown when browser loads the
(synchronous) postback response.</span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<br /></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span lang="EN-US">Let’s move to asynchronous <a href="https://gist.github.com/2018305#file_script_manager_registration.aspx">postback</a>:</span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span style="color: blue; font-family: Consolas; font-size: 9.5pt;"><</span><span style="color: maroon; font-family: Consolas; font-size: 9.5pt;">asp</span><span style="color: blue; font-family: Consolas; font-size: 9.5pt;">:</span><span style="color: maroon; font-family: Consolas; font-size: 9.5pt;">UpdatePanel</span><span style="font-family: Consolas; font-size: 9.5pt;"> <span style="color: red;">runat</span><span style="color: blue;">="server"</span> <span style="color: red;">ID</span><span style="color: blue;">="UpdatePanel1"></span></span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span style="font-family: Consolas; font-size: 9.5pt;"> <span style="color: blue;"><</span><span style="color: maroon;">ContentTemplate</span><span style="color: blue;">></span></span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span style="font-family: Consolas; font-size: 9.5pt;"> <span style="color: blue;"><</span><span style="color: maroon;">asp</span><span style="color: blue;">:</span><span style="color: maroon;">Button</span> <span style="color: red;">runat</span><span style="color: blue;">="server"</span>
<span style="color: red;">ID</span><span style="color: blue;">="Button1"</span>
<span style="color: red;">Text</span><span style="color: blue;">="Do AJAX
call"</span> <span style="color: red;">OnClick</span><span style="color: blue;">="Button1_Click"</span> <span style="color: blue;">/></span></span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span style="font-family: Consolas; font-size: 9.5pt;"> <span style="color: blue;"><</span><span style="color: maroon;">asp</span><span style="color: blue;">:</span><span style="color: maroon;">Literal</span> <span style="color: red;">runat</span><span style="color: blue;">="server"</span>
<span style="color: red;">ID</span><span style="color: blue;">="Literal1"</span>
<span style="color: blue;">/></span></span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span style="font-family: Consolas; font-size: 9.5pt;"> <span style="color: blue;"></</span><span style="color: maroon;">ContentTemplate</span><span style="color: blue;">></span></span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span style="font-family: Consolas; font-size: 9.5pt;"> <span style="color: blue;"><</span><span style="color: maroon;">Triggers</span><span style="color: blue;">></span></span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span style="font-family: Consolas; font-size: 9.5pt;"> <span style="color: blue;"><</span><span style="color: maroon;">asp</span><span style="color: blue;">:</span><span style="color: maroon;">AsyncPostBackTrigger</span>
<span style="color: red;">ControlID</span><span style="color: blue;">="Button1"</span>
<span style="color: blue;">/></span></span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span style="font-family: Consolas; font-size: 9.5pt;"> <span style="color: blue;"></</span><span style="color: maroon;">Triggers</span><span style="color: blue;">></span></span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span style="color: blue; font-family: Consolas; font-size: 9.5pt;"></</span><span style="color: maroon; font-family: Consolas; font-size: 9.5pt;">asp</span><span style="color: blue; font-family: Consolas; font-size: 9.5pt;">:</span><span style="color: maroon; font-family: Consolas; font-size: 9.5pt;">UpdatePanel</span><span style="color: blue; font-family: Consolas; font-size: 9.5pt;">></span><span style="font-family: Consolas; font-size: 9.5pt;"></span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<br /></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span lang="EN-US">Code behind remains unchanged.</span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<br /></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span lang="EN-US">No message is shown after the Button1 is
clicked. Body of the response should look something like this:</span></div>
<pre><code>139|updatePanel|UpdatePanel1|
<input type="submit" name="Button1" value="Do AJAX call" id="Button1" />
<u><span style="font-size: small;"><script>alert('Button1 clicked')</script></span></u>
|152|hiddenField|__VIEWSTATE|/wEPDwUKLTUxODY2NjI0NQ9kFgICAw9kFgICAw9kFgJmD2QWAgI
DDxYCHgRUZXh0BSk8c2NyaXB0PmFsZXJ0KCdCdXR0b24xIGNsaWNrZWQnKTwvc2NyaXB0PmRk88Fv/gu
MXM8Xq/lT5WC+dG66354=|48|hiddenField|__EVENTVALIDATION|/wEWAgL/oouVBgKM54rGBmox+
1LEg7FJSnLOTrjRf5eL73gK|7|asyncPostBackControlIDs||Button1|0|postBackControlIDs|
||13|updatePanelIDs||tUpdatePanel1|0|childUpdatePanelIDs|||12|panelsToRefreshIDs|
|UpdatePanel1|2|asyncPostBackTimeout||90|13|formAction||WebForm8.aspx|
</code></pre>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<br /></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: 0.0001pt;">
<span lang="EN-US">Alert script is rendered into markup but it is not executed by
the browser. How is it possible that script registration via ScriptManager
works?</span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<br /></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span lang="EN-US">This <a href="https://gist.github.com/2018305#file_script_manager_registration.aspx.cs">code behind</a> modification could discover
the secret:<br />
</span><span style="color: blue; font-family: Consolas; font-size: 9.5pt;">protected</span><span style="font-family: Consolas; font-size: 9.5pt;"> <span style="color: blue;">void</span>
Button1_Click(<span style="color: blue;">object</span> sender, <span style="color: #2b91af;">EventArgs</span> e)</span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span style="font-family: Consolas; font-size: 9.5pt;">{</span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span style="font-family: Consolas; font-size: 9.5pt;"> <span style="color: #2b91af;">ScriptManager</span>.RegisterStartupScript(<span style="color: blue;">this</span>, <span style="color: blue;">this</span>.GetType(),
<span style="color: #a31515;">"script"</span>, <span style="color: #a31515;">"<script>alert('Button1
clicked')</script>"</span>, <span style="color: blue;">false</span>);</span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span style="font-family: Consolas; font-size: 9.5pt;">}</span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<br /></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span lang="EN-US">The alert message box is shown this time. Here
is the response:</span></div>
<pre><code>98|updatePanel|UpdatePanel1|
<input id="Button1" name="Button1" type="submit" value="Do AJAX call" />
|52|hiddenField|__VIEWSTATE|/wEPDwUKLTUxODY2NjI0NWRkwPvseEH2ojph8EJh0MFS5L+TksA
=|48|hiddenField|__EVENTVALIDATION|/wEWAgKaoIz3DwKM54rGBr7RMEQMtuoxxWPUsRRwWxmrCH
/Z|7|asyncPostBackControlIDs||Button1|0|postBackControlIDs|||13|updatePanelIDs||t
UpdatePanel1|0|childUpdatePanelIDs|||12|panelsToRefreshIDs||UpdatePanel1|2|asyncP
ostBackTimeout||90|13|formAction||WebForm8.aspx|45|scriptStartupBlock|
<u>ScriptContentWithTags</u>|{"text":"<u>alert(\u0027Button1 clicked\u0027)</u>"}|
</code></pre>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<br /></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span lang="EN-US">There are two significant changes in the
response: the script is not rendered (</span><span class="hps">unsurprisingly</span><span lang="EN-US">) in the Literal control and there
is something more in the response: <b style="mso-bidi-font-weight: normal;">scriptStartupBlock.</b>
ASP.NET AJAX library ensures that script
from this “section” is executed after receiving the response. ScriptManager.RegisterClientScriptBlock
is very similar but it uses <b style="mso-bidi-font-weight: normal;">scriptBlock</b>
“section”.</span></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<br /></div>
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;">
<span lang="EN-US">It is quite easy to navigate through the labyrinth
when it is clear how things work under the hood.</span></div>Anonymoushttp://www.blogger.com/profile/04794529722761111917noreply@blogger.com0tag:blogger.com,1999:blog-8089455461752760430.post-23108383933059853912012-01-08T21:35:00.000+01:002012-01-08T21:36:26.604+01:00I apologize for my ignorance, dear RadWindowI use RadWindow and RadWindowManager for a while and yet I know hardly anything about them. This StackOverflow <a href="http://stackoverflow.com/q/8654263/291379">question</a> about RadWindow in ajax scenario has initiated manifestation of my ignorance. For me it was always clear that RadWindow must be declared inside RadWindowManager and there can be only one RadWindowManager instance on a page. Perverse consequences of this combination are obvious when you use WebForms – ASPX and ASCX entanglement through RadWindow instances. ASPX must contain RadWindowManager and RadWindow that is used on the ASCX as well. What a festival of ignorance!<br />
<br />
First of all, what is RadWindowManager useful for? I managed to find 3 reasons from the documentation:<br />
<ol>
<li>It is a convenient way how to declare property values shared among several RadWindow instances.</li>
<li>It adds radopen, radalert, radconfirm and radprompt functions to the global window obect.</li>
<li>Window managment API (tile, cascade, etc...).</li>
</ol>
Documentation also says that it is possible to instantiate RadWindow without manager participation. And both ways are viable: either declaratively in markup or imperatively from code behind:<br />
<br />
<pre class="brush:csharp">var window1 = new RadWindow();
window1.NavigateUrl = "http://www.google.com";
window1.VisibleOnPageLoad = true;
window1.ID = "RadWindow1";
window1.Width = 500;
window1.Title = "Google";
window1.Height = 300;
Panel1.Controls.Add(window1);
</pre>
The last line is the most important one from my perspective. Window instance is added into normal asp Panel and no manager is involved. The window is displayed even after partial postback when the panel is ajaxified. It is due to fact that RadWindow is control like any others and it is rendered in the same way as any other controls are. The same fact is valid in the case when the window is placed within RadWindowManager. RadWindow is rendered at the same place as the RadWindowManager. In ajax scenario you have to enclose the RadWindowManager into panel that is also updated in given partial postback. It sounds weird in connection with the fact that there can be only one RadWindowManager.<br />
<br />
Fortunately this fact is not a fact but delusion. A page could <a href="http://www.telerik.com/community/forums/aspnet-ajax/window/how-to-use-multiple-radwindowmanager-in-one-page.aspx#1300195">easily</a> contain more than one manager instance. In such case radopen and similar functions always use the first manager instance.<br />
<br />
What about performance? Other managers like RadStyleSheetManager and RadScriptManager improve Telerik-based application performance. It seems to be not true for RadWindowManager according to this <a href="http://www.telerik.com/products/aspnet-ajax/getting-started/top-performance.aspx">site</a>.<br />
<br />
What is the moral of this sad story? It is not worth to be afraid to work directly with RadWindow instances. As an application does not use tons of windows it would be better to use RadWindowManager only as support for radopen & Co. functions.Anonymoushttp://www.blogger.com/profile/04794529722761111917noreply@blogger.com0tag:blogger.com,1999:blog-8089455461752760430.post-87078442621329408042011-05-22T11:02:00.000+02:002011-05-22T11:02:04.931+02:00Delegates serialization: LimitsWhen I heard about delegates’ serializability for the first time, my reaction was romantic. I dreamt about some mystical process that serializes delegate’s IL and about other magical things. It is, of course, absolute nonsense, but I love those fairy tales :).<br />
<br />
All delegates in C# are inherited directly or indirectly from Delegate. Delegate class is class like any other. It is just marked as serializable – no black magic is needed.<br />
<br />
What is buried there?<br />
<br />
Basically, there are two properties: Method (MethodInfo) and Target (object). It is all what is needed to invoke any .NET method. When delegate points to a static method, then the Target property is null. There are some other internal details that I don’t understand but I hope they are irrelevant. <br />
<br />
These facts reveal weaknesses of delegates’ serialization:<br />
<ol><li>Target property is serialized as well when using binary formatter. It could lead some serious issues when object referenced by Target is part of complex object graph. On the other side if Target is not serialized then the delegate cannot be deserialized at all – there wouldn’t be any suitable instance to make a method call.</li>
<li>Versioning issues.</li>
<li>State of static class is not serialized. Delegate of method that is using static fields (directly or indirectly) is deserialized into completely different context.</li>
</ol>In addition to these disadvantages, there is a limitation associated with anonymous delegate that is closed over a variable. It is simply not serializable.<br />
<br />
It is clear that “persistable continuation” implemented in C# by delegate serialization as it was <a href="http://mikehadlow.blogspot.com/2011/04/serializing-continuations.html">suggested </a>by Mike Hadlow would be very limited, which is a pity.Anonymoushttp://www.blogger.com/profile/04794529722761111917noreply@blogger.com1tag:blogger.com,1999:blog-8089455461752760430.post-91499296927556148502011-05-15T16:03:00.000+02:002011-05-15T16:57:58.487+02:00NHibernate: Serializing DelegatesMike Hadlow discovered very interesting <a href="http://mikehadlow.blogspot.com/2011/04/serializing-continuations.html">delegate feature</a> for me. They are serializable! In outher words they can be stored in a database, e.g. with NHibernate.<br />
<br />
Entity that represents a persistable entity could look like this:<br />
<pre class="brush:csharp">public class PersistableDelegate
{
public virtual Delegate Delegate { get; set; }
public virtual string Name { get; set; }
public virtual int Id { get; set; }
private byte[] SerializedDelegate {
get {
if (Delegate == null)
return null;
var formatter = new BinaryFormatter();
using (var stream = new MemoryStream()) {
formatter.Serialize(stream, this.Delegate);
return stream.ToArray();
}
set {
if (value != null) {
var formatter = new BinaryFormatter();
using (var stream = new MemoryStream(value))
Delegate = (Delegate)formatter.Deserialize(stream);
}
else
Delegate = null;
}
}
}
</pre><br />
Your model can manipulate with Delegate property. SerializedDelegate property provides access to serialized data of the delegate for NHibernate – it is implementation detail and is marked as private. Getter of this property returns the delegate serialized to an array and the setter reconstructs the delegate from provided data.<br />
<br />
FNH mapping is simple as breath then:<br />
<pre class="brush:csharp">public class PersistableDelegateMap : ClassMap<PersistableDelegate> {
public PersistableDelegateMap() {
Id(x => x.Id);
Map(x => x.Name);
Map(Reveal.Member<PersistableDelegate>("SerializedDelegate"))
.Length(int.MaxValue);
}
}
</pre>Reveal class makes it possible to map private property.<br />
<br />
Corresponding database table for MS SQL is:<br />
<pre class="brush:sql">CREATE TABLE [dbo].[PersistenceAction](
[Id] [int] IDENTITY(1,1) NOT NULL,
[Name] [nvarchar](255) NULL,
[SerializedDelegate] [image] NULL,
)
</pre><br />
Now it is possible to store a delegate in database:<br />
<pre class="brush:csharp">var action = new PersistableDelegate();
action.Delegate = (Action)(() => { Console.WriteLine("Hello from persisted delegate!"); });
action.Name = "First test";
session.Save(action);
session.Flush();
</pre><br />
and load back:<br />
<pre class="brush:csharp">var action = session.Query<PersistableDelegate>().Where(x => x.Name == "First test").FirstOrDefault();
action.Delegate.DynamicInvoke();
</pre><br />
I am not sure yet how can be this useful. There are definitely many limitations, e.g. lambda expression closed over local variable is not serializable. In any case such feature is very promising...Anonymoushttp://www.blogger.com/profile/04794529722761111917noreply@blogger.com0tag:blogger.com,1999:blog-8089455461752760430.post-34731799077059166162011-04-18T09:00:00.000+02:002011-04-30T11:02:29.294+02:00Integration tests, NHibernate and Sqlite, part 3 – enhanced in memory database<a href="http://jakub-linhart.blogspot.com/2011/03/integration-tests-nhibernate-and-sqlite.html">Integration tests, NHibernate and Sqlite, part 1</a><br />
<a href="http://jakub-linhart.blogspot.com/2011/04/integration-tests-nhibernate-and-sqlite.html">Integration tests, NHibernate and Sqlite, part 2 – in file database</a><br />
Integration tests, NHibernate and Sqlite, part 3 – enhanced in memory database<br />
<br />
In previous post I introduced a database scope that builds the database schema only once per test suit run. Some performance was sacrificed to achieve this: the database had to be stored in a file. Database stored in memory is obviously faster than database stored in a file. Is there a way how to move the database back to memory?<br />
<br />
Yes, we can build a master database in-memory and then make copy of this master database for each test. The problem is how to duplicate the master database. Sqlite provides <a href="http://www.sqlite.org/c3ref/backup_finish.html">Online Backup API</a>, which is not available in <a href="http://system.data.sqlite.org/index.html/doc/trunk/www/index.wiki">System.Data.SQLite</a> wrapper yet.<br />
<br />
<b>Sqlite Backup API</b><br />
Fortunately it is quite easy to wrap the required API:<br />
<pre class="brush:csharp">[DllImport("System.Data.SQLite.DLL", CallingConvention = CallingConvention.Cdecl)]
internal static extern IntPtr sqlite3_backup_init(IntPtr destDb, byte[] destname, IntPtr srcDB, byte[] srcname);
[DllImport("System.Data.SQLite.DLL", CallingConvention = CallingConvention.Cdecl)]
internal static extern int sqlite3_backup_step(IntPtr backup, int pages);
[DllImport("System.Data.SQLite.DLL", CallingConvention = CallingConvention.Cdecl)]
internal static extern int sqlite3_backup_finish(IntPtr backup);
public static void Backup(SQLiteConnection source, SQLiteConnection destination)
{
IntPtr sourceHandle = GetConnectionHandle(source);
IntPtr destinationHandle = GetConnectionHandle(destination);
IntPtr backupHandle = sqlite3_backup_init(destinationHandle, SQLiteConvert.ToUTF8("main"), sourceHandle, SQLiteConvert.ToUTF8("main"));
sqlite3_backup_step(backupHandle, -1);
sqlite3_backup_finish(backupHandle);
}
</pre><br />
There is only one catch. Handles needed by Sqlite API are stored as private fields in System.Data.SQLite wrapper. But it can be retrieved by this little trick:<br />
<pre class="brush:csharp">private static IntPtr GetConnectionHandle(SQLiteConnection source)
{
object sqlLite3 = GetPrivateFieldValue(source, "_sql");
object connectionHandle = GetPrivateFieldValue(sqlLite3, "_sql");
IntPtr handle = (IntPtr)GetPrivateFieldValue(connectionHandle, "handle");
return handle;
}
private static object GetPrivateFieldValue(object instance, string fieldName)
{
var filedType = instance.GetType().GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance);
object result = filedType.GetValue(instance);
return result;
}
</pre><br />
Yes, it is a hacky solution but it works well and it can be viewed as temporary replacement for the missing wrapper part.<br />
<br />
<b>SqliteInMemoryPrivateScope</b><br />
Modification of SqliteInFilePrivateScope is easy with such helper method.<br />
<br />
The master database is duplicated by the new wrapper method:<br />
<pre class="brush:csharp">public SqliteInMemoryPrivateScope()
{
EnsureMasterDatabaseExistence();
this.privateConnection = new SQLiteConnection(connectionString);
this.privateConnection.Open();
SqliteBackup.Backup(masterConnection, this.privateConnection);
}
</pre><br />
And a connection to the master database must be held by the scope instance. It is similar to SqliteInMemorySharedScope:<br />
<pre class="brush:csharp">private static SQLiteConnection masterConnection;
private void EnsureMasterDatabaseExistence()
{
// to support asynchronous scenario
lock (configurationSync) {
if (configuration == null) {
configuration = BuildConfiguration ();
sessionFactory = configuration.BuildSessionFactory();
masterConnection = new SQLiteConnection(connectionString);
masterConnection.Open();
SchemaExport schemaExport = new SchemaExport(configuration);
schemaExport.Execute(false, true, false, masterConnection, TextWriter.Null);
}
}
}
</pre><br />
Now we have a database scope that builds configuration, session factory and database schema only once per test suit run which saves a lot of time. In addition the database is stored in memory so integration tests run blazingly fast. The isolation between tests is very good and the test can be executed in parallel. What to want more?:).<br />
<br />
Full implementation of SqliteInMemoryPrivateScope can be found <a href="http://nhdatabasescopes.codeplex.com/SourceControl/changeset/view/12b14e36a94c#DatabaseScopes%2fSqliteInMemoryPrivateScope.cs">here</a>.<br />
Complete source code can be downloaded <a href="http://nhdatabasescopes.codeplex.com/">here</a>.Anonymoushttp://www.blogger.com/profile/04794529722761111917noreply@blogger.com3tag:blogger.com,1999:blog-8089455461752760430.post-28790905800719581992011-04-11T16:00:00.000+02:002011-04-18T12:32:09.501+02:00Integration tests, NHibernate and Sqlite, part 2 – in file database<a href="http://jakub-linhart.blogspot.com/2011/03/integration-tests-nhibernate-and-sqlite.html">Integration tests, NHibernate and Sqlite, part 1</a><br />
Integration tests, NHibernate and Sqlite, part 2 – in file database<br />
<a href="http://jakub-linhart.blogspot.com/2011/04/integration-tests-nhibernate-and-sqlite_18.html">Integration tests, NHibernate and Sqlite, part 3 – enhanced in memory database</a> <br />
<br />
<b>SqliteInMemorySharedScope issues</b><br />
The disadvantage is that the time needed byc <a href="http://nhdatabasescopes.codeplex.com/SourceControl/changeset/view/f9e824a457e8#DatabaseScopes%2fSqliteInMemorySharedScope.cs">SqliteInMemorySharedSope</a> instantiation grows with the size of the tested domain model. When the domain model is complex enough then execution of test suit may take unacceptable amount of time.<br />
<br />
<b>Storing the database in a file</b><br />
Sqlite is able to store the database in a file as well. We create a database with its schema at first and store it in a file, let’s call it master file. Then it is easy to create a copy of the master file. Individual tests touch only own copy of the master file. So isolation is preserved and dependency between the time of database scope instantiation and complexity of the domain is significantly more moderate.<br />
<br />
<b>SqliteInFileDatabaseScope</b><br />
The new session is connected to the private copy of the master file.<br />
<pre class="brush:csharp">public ISession OpenSession()
{
ISession session = SqliteInFilePrivateScope.sessionFactory.OpenSession(this.privateConnection);
return session;
}
</pre><br />
The private connection is created in the default constructor.<br />
<pre class="brush:csharp">public SqliteInFilePrivateScope()
{
EnsureMasterDatabaseExistence();
// to avoid name collisions in parallel scenario
this.privateDatabaseFileName = Guid.NewGuid().ToString() + ".db";
File.Copy(masterDatabaseFileName, this.privateDatabaseFileName);
string connectionString = BuildConnectionString(this.privateDatabaseFileName);
this.privateConnection = new SQLiteConnection(connectionString);
this.privateConnection.Open();
}
</pre><br />
The master file, configuration and session factory are created only once per test suit run.<br />
<pre class="brush:csharp">private static object configurationSync = new object();
private void EnsureMasterDatabaseExistence()
{
// to support parallel execution of unit tests
lock (configurationSync) {
if (configuration == null) {
string connectionString = BuildConnectionString(masterDatabaseFileName);
configuration = BuildConfiguration(connectionString);
sessionFactory = configuration.BuildSessionFactory();
SchemaExport schemaExport = new SchemaExport(configuration);
schemaExport.Execute(false, true, false);
}
}
}
</pre><br />
Connection string factoring:<br />
<pre class="brush:csharp">private static string BuildConnectionString(string databaseName)
{
return string.Format("Data Source={0};Version=3;New=True;", databaseName);
}
</pre><br />
And BuildConfiguration is delegate that is injected on test suit initialization (for more details see this <a href="http://jakub-linhart.blogspot.com/2011/04/unit-tests-initialization-in-visual.html">post</a>):<br />
<pre class="brush:csharp">public static Func<string, NHibernate.Cfg.Configuration> BuildConfiguration;
</pre><br />
The full example is <a href="http://nhdatabasescopes.codeplex.com/SourceControl/changeset/view/12b14e36a94c#DatabaseScopes%2fSqliteInFilePrivateScope.cs">here</a>.<br />
The same example but using Microsoft Sql Server Compact Edition instead of Sqlite is <a href="http://nhdatabasescopes.codeplex.com/SourceControl/changeset/view/12b14e36a94c#DatabaseScopes%2fMsSqlCeInFilePrivateScope.cs">here</a>.<br />
Complete source code can be downloaded <a href="http://nhdatabasescopes.codeplex.com/">here</a>.<br />
<br />
<b>Why to support parallel scenarios in Visual Studio 2010</b><br />
Tests are executed sequentially by default in Visual Studio and there is no setting to turn on parallel execution in VS2010 GUI. To achieve this, the file with test settings must be tweaked. The name of the file is Local.testsetings by default:<br />
<pre class="brush:xml"><TestSettings name="Local" id="caf44e0f-3ee4-46bf-a6ed-73aea6d93533" xmlns="http://microsoft.com/schemas/VisualStudio/TeamTest/2010">
<Description>These are default test settings for a local test run.</Description>
<Deployment enabled="false" />
<Execution parallelTestCount="2">
<TestTypeSpecific />
<AgentRule name="Execution Agents">
</AgentRule>
</Execution>
</TestSettings>
</pre><br />
Attribute <b>parallelTestCount</b> specifies how many tests are executed at one time. Parallel execution makes sense in these days of multicore processors because it can reduce the execution time of whole test suit. And it counts when doing continuous integration. You can find much more information in this blog <a href="http://blogs.msdn.com/b/vstsqualitytools/archive/2009/12/01/executing-unit-tests-in-parallel-on-a-multi-cpu-core-machine.aspx">post</a>.Anonymoushttp://www.blogger.com/profile/04794529722761111917noreply@blogger.com1tag:blogger.com,1999:blog-8089455461752760430.post-49139983306003981862011-04-04T16:00:00.000+02:002011-04-04T16:16:24.973+02:00Unit tests initialization in Visual Studio 2010<b>NHDatabaseScopes problem</b><br />
I am going to dig into a nasty detail of <a href="http://jakub-linhart.blogspot.com/">SqliteInMemorySharedScope</a> class in this post. The FNH configuration is built by the constructor of this class. NH configuration needs to know which assembly to load the mapping metadata from. If the knowledge is included right in the constructor then the SqliteInMemorySharedScope (like in previous post) is coupled with tested domain model and cannot be reused in other project. It would be neat to inject the metadata or just the configuration into it. The configuration and session factory is shared between all unit tests and there is usually no particular execution order defined for tests. The question is: how to inject all information needed by SqliteInMemorySharedScope?<br />
<br />
<b>Unit test initialization</b><br />
VS2010 offers a way how run a code before any test is executed. Just decorate a static method in test class with AssemblyInitialize attribute:<br />
<pre class="brush:csharp">[TestClass]
public class TestEnvironmentInitialization
{
[AssemblyInitialize]
public static void Initialize(TestContext context)
{
// any initialization code
</pre><br />
Some code can be executed after all tests were finished as well – attribute AssemblyCleanup.<br />
The execution order is:<br />
1) Static method marked by AssemblyInitialize attribute.<br />
2) Default constructor of a test class.<br />
3) Method marked by TestInitialize attribute.<br />
4) Method marked by TestMethod attribute.<br />
5) Method marked by TestCleanup attribute.<br />
6) Static method marked by AssemblyCleanup attribute.<br />
<br />
<b>SqliteInMemorySharedScope decoupling from domain model</b><br />
Back to the SqliteInMemorySharedScope class. A delegate that builds NH configuration is injected instead of configuration itself. The advantage, but questionable, is lazy initialization. Configuration is built only when it is really needed. <br />
SqliteInMemorySharedScope initialization:<br />
<pre class="brush:csharp">[TestClass]
public class TestEnvironmentInitialization
{
private static NHibernate.Cfg.Configuration BuildSqliteConfiguration(string connectionString)
{
var config = Fluently.Configure()
.Database(SQLiteConfiguration.Standard.ConnectionString(connectionString))
.Mappings(m => {
m.FluentMappings.AddFromAssembly(typeof(Order).Assembly);
})
.BuildConfiguration();
return config;
}
[AssemblyInitialize]
public static void Initialize(TestContext context)
{
BuildConfiguration = BuildSqliteConfiguration;
</pre><br />
And simple modification of SqliteInMemorySharedScope:<br />
<pre class="brush:csharp">public class SqliteInMemorySharedScope
{
public static Func<string, NHibernate.Cfg.Configuration> BuildConfiguration;
public SqliteInMemorySharedScope()
{
if (configuration == null) {
configuration = BuildConfiguration(connectionString);
}
</pre>SqliteInMemorySharedScope is decoupled from the domain model now and can be reused in multiple projects.<br />
<br />
Source code can be downloaded <a href="http://nhdatabasescopes.codeplex.com/">here</a>.Anonymoushttp://www.blogger.com/profile/04794529722761111917noreply@blogger.com0tag:blogger.com,1999:blog-8089455461752760430.post-2187385971994487632011-03-29T23:49:00.000+02:002011-04-18T12:32:23.410+02:00Integration tests, NHibernate and Sqlite, part 1Integration tests, NHibernate and Sqlite, part 1<br />
<a href="http://jakub-linhart.blogspot.com/2011/04/integration-tests-nhibernate-and-sqlite.html">Integration tests, NHibernate and Sqlite, part 2 – in file database</a><br />
<a href="http://jakub-linhart.blogspot.com/2011/04/integration-tests-nhibernate-and-sqlite_18.html">Integration tests, NHibernate and Sqlite, part 3 – enhanced in memory database</a> <br />
<br />
Source code can be downloaded <a href="http://nhdatabasescopes.codeplex.com/">here</a>.<br />
<br />
Purpose of unit test is to determine if specific unit of code fits for its use. Units are the smallest testable parts of a system. Integration tests evaluate that unit or subsystem is well integrated with other units or subsystems. NHibernate is quite large and complex system and if you use it in your test then it is an integration test – your test evaluates, among other things, that the unit (or something larger) plays well with NH. That is the reason why I will use integration tests term in this post, although somebody may <a href="http://ayende.com/Blog/archive/2006/10/14/UnitTestingWithNHibernateActiveRecord.aspx">disagree</a>:).<br />
<br />
Embedded databases are suitable for integration testing. It is mainly for performance and deployment reasons. Such well established and popular database is Sqlite which has some interesting features and I will use it in this post. <br />
<br />
To test something with NHibernate we need some domain model. Since my imagination is not very playful, I will use very ordinary “Order” example:<br />
<pre class="brush:csharp"> public class Order
{
public Order()
{
Rows = new Iesi.Collections.Generic.HashedSet<OrderRow>();
}
public virtual int Id { get; set; }
public virtual string ShipName { get; set; }
public virtual DateTime RequiredDate { get; set; }
public virtual Iesi.Collections.Generic.ISet<OrderRow> Rows { get; set; }
}
public class OrderRow
{
public virtual int Id { get; set; }
public virtual decimal Price { get; set; }
public virtual string Product { get; set; }
}
</pre><br />
Mapped with Fluent NHibernate:<br />
<pre class="brush:csharp"> public class OrderMap : ClassMap<Order>
{
public OrderMap()
{
Id(x => x.Id);
HasMany(x => x.Rows).Cascade.AllDeleteOrphan();
Map(x => x.RequiredDate);
Map(x => x.ShipName);
}
}
public class OrderRowMap : ClassMap<OrderRow>
{
public OrderRowMap()
{
Id(x => x.Id);
Map(x => x.Price);
Map(x => x.Product);
}
}
</pre><br />
<b><span style="font-size: small;">Naive implementation</span></b><br />
It is a boring and useless example. But we need just something to feed NHibernate and the domain model could be even a little bit simpler and more useless than this crap.<br />
<br />
We build NH configuration, session factory and database schema before each test:<br />
<pre class="brush:csharp"> string connectionString = "Data Source=:memory:;Version=3;New=True;";
var config = Fluently.Configure()
.Database(SQLiteConfiguration.Standard.ConnectionString(connectionString))
.Mappings(m => {
m.FluentMappings.AddFromAssembly(typeof(Order).Assembly);
})
.BuildConfiguration();
var sessionFactory = config.BuildSessionFactory()
var connection = new SQLiteConnection(connectionString);
connection.Open();
// build the database schema
var schemaExport = new SchemaExport(config);
schemaExport.Execute(false, true, false, connection, TextWriter.Null);
// voiala, session is ready for the test
var session = sessionFactory.OpenSession(connection);</pre>There is something worth noting in this code. Sqlite cleans up in-memory database when all connections to that database are closed. This is the reason why the connection is explicitly opened and then provided to Execute and OpenSession methods. The database would be empty without this connection sharing.<br />
<br />
Everything goes nice. Tests are pretty good isolated because they receive own database – they run in own sandboxes and cannot cause any harm by destroying data needed by other tests.<br />
Some troubles will appear as soon as the domain complexity and number of integration tests get a little bit higher. Building configuration, session factory and database schema may take several seconds and execution time of test suit may become intolerable very quickly.<br />
<br />
<b>Speedup</b><br />
It would be nice to do time consuming operation only once per a test suit run. Two things can be shared between tests without losing advantages of test isolation: NH configuration and session factory. This can save significant amount of time. The database still must be created for each individual test.<br />
<br />
Here is the complete example factored into class:<br />
<pre class="brush:csharp">public class SqliteInMemorySharedScope
{
private static ISessionFactory sessionFactory;
private static NHibernate.Cfg.Configuration configuration;
private static IDbConnection connection;
private const string connectionString = "Data Source=:memory:;Version=3;New=True;";
public SqliteInMemorySharedScope()
{
if (configuration == null) {
configuration = Fluently.Configure()
.Database(SQLiteConfiguration.Standard.ConnectionString(connectionString))
.Mappings(m => {
m.FluentMappings.AddFromAssembly(typeof(Order).Assembly);
})
.BuildConfiguration();
}
if (sessionFactory == null)
sessionFactory = configuration.BuildSessionFactory();
if (connection == null)
connection = new SQLiteConnection(connectionString);
var schemaExport = new SchemaExport(configuration);
schemaExport.Execute(false, true, false, connection, TextWriter.Null);
}
public ISession OpenSession()
{
ISession session = sessionFactory.OpenSession(connection);
return session;
}
}</pre>And here is how to use that:<br />
<pre class="brush:csharp">var scope1 = new SqliteInMemorySharedScope();
using (var session1 = scope.OpenSession()) {
var row = new OrderRow() {
Product = "Product1",
Price = 10,
};
session1.Save(row);
session1.Flush();
}
var scope2 = new SqliteInMemorySharedScope()
using (var session2 = scope2.OpenSession()) {
Assert.AreEqual(0, session2.CreateQuery("from OrderRow").List().Count);
}</pre>The configuration and session factory are built when scope1 is instantiated. When scope2 is created then only database schema must be established again.<br />
<br />
Why SqliteInMemorySharedScope?<br />
Sqlite – Sqlite database engine.<br />
InMemory – database is stored in-memory.<br />
Shared – connection is shared between all scopes.<br />
<br />
There must be a better naming scheme but I am not able to find anything that is reasonable. Any idea?Anonymoushttp://www.blogger.com/profile/04794529722761111917noreply@blogger.com0