Oren Eini

CEO of RavenDB

a NoSQL Open Source Document Database

Get in touch with me:

oren@ravendb.net +972 52-548-6969

Posts: 7,523
|
Comments: 51,145
Privacy Policy · Terms
filter by tags archive
time to read 2 min | 214 words


I am facing some really unpleasant choices at the moment. And I thought it would be so easy.
I wanted to add global error handling to all the web services that we expose as [ScriptService].
For some reason, they didn't show up in the Application_Error event, and that caused the exception details to be sent to the client.
It looks like for some strange reasons, web services exceptions don't go through the Application_Error, so I implemented a SoapExtension to handle that, but ScriptService doesn't go through the Soap layers, so it obviously doesn't work.
Then I went and looked at the code.
The whole thing is _hard coded_ inside, and there is no way in.
No way to globally capture exception being raised from script services.
No way in to add this ability.
Urgh! Argh!

I mean, it is not like I want something very special, it sounds to me like a reasonable request.
Yes, I know you can turn off the error in the config, but I would like, you know, to log this.

Of course, everything is internal in there, so I can't just go and override the RestHandler. ExecuteWebServiceCall(), I would have to have a private build of this stuff.
Just to be able to log exceptions.
time to read 3 min | 586 words

Let us assume that we have this amazing javascript function:

function test()
{
	var nums = [1,2,3,4,5,6,7];
	for(var i = 0; i<nums.length; i++)
	{
		var alertLink = document.createElement("A");
		alertLink.href = "#";
		alertLink.innerHTML = nums[i];
		
		if( nums[i] % 2 == 0)
		{
			alertLink.onclick = function() { alert('EVEN: '+ nums[i]); };
		}
		else
		{
			alertLink.onclick = function() { alert('ODD: ' + nums[i]); };
		}
		
		document.firstChild.appendChild(alertLink);
		document.firstChild.appendChild(document.createElement("BR"));
		
	}
}

Can you guess what it would generate? Quite a few undefines alerts, as a matter of fact. Why is that? Because the anonymous function is a closure, which capture not the value of i, but the i variable itself.

This means when we click on a link that this method has generated, we use the last known value of i. Since we have exited the loop, i is actually 8.

Now, in C# we have the same problem, and we can solve it by introducing a temporary variable in the loop, so we change the code to look like this:

	
function test()
{
	var nums = [1,2,3,4,5,6,7];
	for(var i = 0; i<nums.length; i++)
	{
		var alertLink = document.createElement("A");
		alertLink.href = "#";
		alertLink.innerHTML = nums[i];
		
		var tmpNum = nums[i];
		
		if( nums[i] % 2 == 0)
		{
			alertLink.onclick = function() { alert('EVEN: '+ tmpNum ); };
		}
		else
		{
			alertLink.onclick = function() { alert('ODD: ' + tmpNum ); };
		}
		
		document.firstChild.appendChild(alertLink);
		document.firstChild.appendChild(document.createElement("BR"));
		
	}
}

Try to run it, and you'll get an.. interesting phenomenon. All the links will show tmpNum as 7. Again, we captured the variable itself, not its value. And in JS, it looks like you are getting the same variable in the loop, not a new one (this is absolutely the wrong way to describe it, but it is a good lie), like you would in C#.

What is even more interesting is that you would get the exact same result here:

	
function test()
{
	var nums = [1,2,3,4,5,6,7];
	for(var i = 0; i<nums.length; i++)
	{
		var alertLink = document.createElement("A");
		alertLink.href = "#";
		alertLink.innerHTML = nums[i];
		
		
		if( nums[i] % 2 == 0)
		{
			var tmpNum = nums[i];
			alertLink.onclick = function() { alert('EVEN: '+ tmpNum ); };
		}
		else
		{
			var tmpNum = nums[i];
			alertLink.onclick = function() { alert('ODD: ' + tmpNum ); };
		}
		
		document.firstChild.appendChild(alertLink);
		document.firstChild.appendChild(document.createElement("BR"));
		
	}
}

Here we have two different lexical scopes, with respectively different variables. Looks like it should work. But the lexical scope of JS is the function, not the nearest set of curly. Both tmpNum refer to the same variable, and as such, are keeping the last value in it.

If the lexical scope is a function, we need to use a function then. Here is a version that works:

	
function test()
{
	var nums = [1,2,3,4,5,6,7];
	for(var i = 0; i<nums.length; i++)
	{
		var alertLink = document.createElement("A");
		alertLink.href = "#";
		alertLink.innerHTML = nums[i];
		
	
		if( nums[i] % 2 == 0)
		{
			var act = function(tmpEVEN)
			{
				alertLink.onclick = function() { alert('EVEN: '+tmpEVEN); };
			};
			act(nums[i]);
		}
		else
		{
			var tmpODD = nums[i];
			alertLink.onclick = function() { alert('ODD: ' + tmpODD); };
		}
		
		document.firstChild.appendChild(alertLink);
		document.firstChild.appendChild(document.createElement("BR"));
		
	}
}

And that is it for today's JS lesson.

time to read 1 min | 197 words

So I had to do it today, I had two pages, in two unrelated domains (foo.com and bar.com) and I had to open a page from one and interact with it. Security constraints disallow this, unfortantely. There are all sorts of ways around it, mostly focusing on proxies, but I didn't want to get into that for a simple page, so I decided to write my own stupid method to do it.

From foo.com, the calling page:

var url = "http://www.bar.com/someImportantPage.castle?id=15"&onCloseRedirectTo=" + 
		encodeURIComponent(window.location.href + 
"&returnUrl="+ encodeURIComponent(window.location.href) ); window.open(url);

And using JS injection for the called page (I have some limited control there), I put:

if(window.opener)
{
	var oldClose = window.close;
	window.close = function()
	{
		if(window.opener && window.returnValue )
		{
			var url = decodeURIComponent($.getURLParam('onCloseRedirectTo')) + 
							"&idToAdd=" + window.returnValue;
			window.opener.location.href = url;
		}
		oldClose();
	};
}

And voila, it works. I'll leave the how as an excersize for the reader. Suffice to say that if you want to add a local iframe to the mix you can even get it to work in an "ajaxian" fashion.

time to read 2 min | 282 words

I run into the issue of having to alert the user of some error, and it brought home the fact that English & Hebrew are not easy to mix. In fact, code such as this is consider a very bad sign, usually code that you don't really want to touch, see or believe in:

image

So, we need some sort of a way to externalize those strings, even if the only language that this application is going to use is Hebrew, simply because of the pain of mixing the two together.

After thinking about it for a while, I decided to create this view (JavascriptResources/index.brail):

<%
for de in controller.Resources:
	resourceName = de.Key
%>
	var ${resourceName} = {
	<% for item in GetParameter(resourceName): %>
		${item.Key}: '${item.Value.Replace("'","\\'") }',
	<% end %>
		Empty: ''
	};
<%
end
%>

And here is the definition of the controller:

[Resource("Processes", "MyApp.Resources.Javascript.Processes")]
[Resource("Errors", "MyApp.Resources.Javascript.Errors")]
[Resource("Messages", "MyApp.Resources.Javascript.Messages")]
public class JavascriptResourcesController : Controller
{
	public void Index()
	{
		
	}
}

But what the hell does this do, anyway? Not much, actually, but it will iterate over all the registered resources for the controller, and output something that looks like this for each of those resources:

var Processes = {
 	EmploymentType: 'Employment Type',
	Details: 'Details',
	Dates: 'The dates',
	Empty: ''
};

The nice thing is that I can now write: alert(Errors.RecursiveError);*. As a side affect (as I said, the application is just in Hebrew so I don't care about that much), I also get the usual benefits of localization.

* You have to read Hebrew to get the joke, I am afraid.

time to read 2 min | 235 words

Partial is a MonoRail term to a piece of UI that you extract outside, so you can call it again, often with Ajax. This is something that is harder to do in WebForms. Yesterday I found an elegant solution to the problem.

ASPX code:

<div id="UserDetailsDiv">
   <ayende:UserDetails runt="server" ID="TheUserDetails"/>
</div>

User Control:

<div>
Name: <asp:label ID="Name" runat="server"/> <br/>
Email: <asp:label ID="Email" runat="server"/> 
</div>

Client Side Code:

function changeUser(newUserId, div)
{
	var srv = new MyApp.Services.UserDetails();
	srv.GetUserDetailsView(newUserId, onSucccessGetUserDetailsView, null, div);
}

function onSucccessGetUserDetailsView(response, userContext)
{
	var div = $(userContext);
	div.innerHTML = response;
	new Effect.Highlight(div);
}

Web Service Code:

[WebMethod(EnableSession = true)]
public string GetUserDetailsView(int userId)
{
	User user = Controller.GetUser(userId);
	//there may be a better way to do this, I haven't bothered looking
	UserDetails userDetails = (UserDetails)new Page().LoadControl("~/Users/UserControls/UserDetails.ascx");
	userDetails.User = user;
	userDetails.DataBind();
	using(StringWriter sw = new StringWriter())
	using(HtmlTextWriter ht = new HtmlTextWriter(sw))
	{
		userDetails.RenderControl(ht);
		return sw.GetStringBuilder().ToSTring();
	}
}
time to read 3 min | 556 words

I just spent way too long trying to figure out what is wrong with this code:

<script type="text4/javascript">

        function customerUpdated()

        {

            //dostuff

        }
</script>
<
asp:DropDown ID="Customers" runat="server" DataTextField="Name"   
       OnClientChanged="customerUpdated();" DataValueField="Id"/>

The answer is below... but do try to figure it out

.

.

.

.

.

.

.

.

Can you see the problem? Perhaps this will help:

<script type="text4/javascript">

Argh!!! I must have gone over it a hundred times and didn't see it.

time to read 1 min | 64 words

By definition, unit tests should be fast. Integration tests should be as fast as possible as well. Almost by definition, Javascript is not fast. In the battle between ASP.Net Ajax and my Unit/Integration tests, so far the only loser it me. This is the point when I am going to stop dedicating any more time to making this work.

time to read 2 min | 322 words

I have just tracked down the Nthbug in my application that I can blame ASP.Net Ajax for. To be rather exact, I can blame ASP.Net Ajax performance, and to be absolutely clear, I am talking about initialization performance.

The main issue is that I keep running into is that it takes human noticable time for the extended behaviors to be set on the page. By human noticable time I means over a second. The immediate result is that users are able to interact with the page in unexpected ways. Leading to all sorts of reported bugs that are plain impossible to reproduce unless you are aware of it.

Case in point, I have a link that point to a page, and on that link, I have defined the ModalPopupExtender. In the popup, the user is expected to fill some values, after which it will post to the next page. Imagine my surprise when I get a bug report saying that the next page showing an error about missing details. I tested it, the QA tested it, everything seemed to work, but the bug kept popping up every now and then.

It took a while to understand that the key difference was how soon the link was pressed after the page was loaded. The QA saying "and now I am going to press this link" was usually just enough for ASP.Net Ajax to initialize itself correctly.

The reasoning for this is that JavaScript is inheritly slow, and ASP.Net Ajax groups all the initialization code at the bottom of the page, so it is the last thing that is being run.  This is on a dual core, 3.4Ghz and 4Gb development machine! I am sick and tired of handling race conditions as it is, the last thing that I need is trying to handle them in javascript!

FUTURE POSTS

No future posts left, oh my!

RECENT SERIES

  1. Challenge (75):
    01 Jul 2024 - Efficient snapshotable state
  2. Recording (14):
    19 Jun 2024 - Building a Database Engine in C# & .NET
  3. re (33):
    28 May 2024 - Secure Drop protocol
  4. Meta Blog (2):
    23 Jan 2024 - I'm a JS Developer now
  5. Production Postmortem (51):
    12 Dec 2023 - The Spawn of Denial of Service
View all series

Syndication

Main feed Feed Stats
Comments feed   Comments Feed Stats
}