Wednesday, September 10, 2008

Chrome and its processes

Wow, i gotta say "Thanks" to Scott Hanselman (who i must say is an excellent technical speaker, i met him briefly at TechEd NZ, not that he would remember me). Almost exactly a week ago i questioned all the processes being started by Google Chrome (here), and in an excellent post he has answered that exact question. You can find his post here

The problem with his answer is that it produces a lot more work for me - now that i know a little bit i have to go and research and become familiar with that design pattern. The possibilities could be interesting, maybe this would be a cool way to allow third parties to integrate into your application and give them a very limited access to your data or authentication services (i'm presuming that Chrome and IE8 totally isolate their addons).

Keywords: google, chrome, IE8, processes

Thursday, September 4, 2008

Chrome, 24 hours after installation

Okay, i need to correct a statement i made about Chrome in a previous blog post. If i'm going to criticise products then it is only fair that i be as accurate as possible.

Yesterday i stated "it must offer me some way to configure its there is no excuse for not having an options/settings UI". This could possibly be interpreted to mean that Chrome has no configuration options at all, when it does in fact have some. It kind of has some very basic tab configuration, you can clear the stored cookies and history, and there are some basic security settings, but not a lot else.

But wait, there's more!! In the last 24 hours i have found a few more things to have a spew about:

 - my machine is 64bit, running a 64 bit operating system. So why did the Chrome installer decide to install the 32 bit (x86) version of the application? Is it because some lazy slack ass developers have not compiled an x64 version? There must be something like 10 billion 64 bit machines out in the wild by now, so why is there no 64 bit version of Chrome?

 - if i go to the options and try to change the proxy settings.... WAIT ONE COTTON-PICKING MOMENT!!!!! Changing those proxy settings is also going to change the proxy settings for IE, and any other application that uses a plugin browser component, such as Windows Explorer, my RSS reader, Outlook, etc. WTF???!!!! Why doesn't Chrome use it's own set of proxy settings, instead of relying on the system ones? Firefox and Opera both have their own settings, why couldn't Chrome?

 - i can clear the browser history, but i can't alter any other history settings, like how long to keep it. I don't want my history retained. When i type Ctrl-T to open a new tab, i don't want a bunch of "you recently visited these sites" links. I want that new tab to be empty, with the focus already set to the address bar so that i can start typing. This is how real men browse, only weenies and Mac fanboi's want a bunch of recently visited links automatically populated onto their new tab.

 - it is OPEN SOURCE!!!! OMG!!! GASP!!! Yawn. Like the world hasn't already had umpteen open source browsers*, like a very famous one called, ummm, "Mozilla Firefox". Exactly what was the point of making it open source? Is it because that is the trendy thing to do at the moment? Or was it done that way so that Google could put their hand on their heart and swear on their mother's grave that "honestly, we are not trying to take over the world and 0wn all your base, and track everything you type in the address bar and every site you visit and send lots of secret tracking data to our great database in the sky, honestly, just check our source code"? Yeah right. Unlike a lot of morons out there, i do not trust or respect a product any more simply because it is open source. What is the point in releasing YAOSB? (Yet Another Open Source Browser).

I know that Chrome is in beta (and probably will be for the next three decades), but the only feature that has impressed me so far is the rendering speed. The rest of the browser has been somewhat underwhelming.

*Don't believe me? Check this link, scroll about halfway down, look at the table immediately underneath the General Information heading. Remember, those are just the browsers that made it big. Now check Sourceforge, there are about one zillion OSS web browsers under development there. I think that is enough to prove the point, we don't need to go check the various other publicly open source repositories.

Keywords: google, chrome, open source software

Wednesday, September 3, 2008

Google Chrome

Application being named and shamed: Google Chrome

I downloaded Chrome today to have a play with it, and before i start i must mention that it is a beta, but i think the shortcomings i am going to critique are important. I installed this onto a machine running Vista Ultimate, so your experience may differ. I am typing this blog post in Chrome, not that it really matters to what i am going to say :)

First problem: It's called "Chrome". This is also the term used to describe the title bar and menu bar area etc of the application window. This screenshot is the chrome area of IE:

It might be a cool sounding name and easy to market, but wtf using a piece of terminolgy that is in common use amongst Microsoft folks? Is this a sly shot at the company you are in close competition with?

Second: When you download Chrome, you are downloading a stub installer that when run goes back to the net without asking or notifying me and retrieves the rest of the install package. This may make life easier when you roll out the beta, because if any issues occur you can just fix the main install and you don't have to replace the stub which people have already downloaded. But it is bad because the installer has not told me what version of the product i am installing, and the version that i install on another machine tomorrow can be different from the version i installed today. The installer needs to be fully open and informative about what it is doing and exactly what it is installing. Where did the install package come from? Why do i not get a chance to virus scan it before installation?

Third: Chrome automatically installed itself into a user specific area on my machine. At no stage did i get asked for an install location. This is incredibly bad - this is my machine, and i dictate where things go on it (as it happens, i don't install *any* applications to my C: drive, they all go onto a drive reserved specifically for applications). 

Fourth: This is also bad because Chrome's data gets stored in the same location, and Chrome provides no way of changing that. This means that anything that is downloaded gets cached in this location. For me this is unacceptable - all the browsers i use (IE, Firefox and Opera) are set to store their cache items on a totally different drive. This means i can quickly and easily check, search or delete the cached content without starting the applications or navigating deeply into a folder structure. It means i can easily set ACLs (permissions) on folders and files, all in the one place. It also means that temporary files are not needlessly filling up my boot drive!!!!

Fifth: WTF is with all the processes being started by Chrome? This was with two tabs opened:

Sixth: if i right-click in the chrome area of Chrome (that was funny!!!!!!!), i get a context menu, and one of the options is "Task Manager":

too late Google - that name is already taken, it is the little system utility that i start up to check on system resources or to kill errant processes. But you know that already, so why choose that name for your dialog? Call it something else, maybe "Application Tasks", or "Tab Manager".

I have nothing against Google, and to be honest there are some things i also like about Chrome (but i'll blog about those once i've had more time to play). But to make simple and fundamental mistakes like they have is unacceptable, and if i was an IT administrator there would be no way i would allow this browser on my corporate network. Just because you are Google you don't have exemption from the rules or conventions.

When an application installs on my machine, it must:
1: if it fetches extra components then it must tell me what they are and where they are coming from - it is my machine and i have the right to know.
2: it must give me an option of where to install it to. If they want to restrict my choices that is fine, but i should have the right to cancel the install if i don't like the options available to me.
3: it must offer me some way to configure its options, like where the cached items are to be stored, and the tab behaviour i want, and whether i want to allow script to execute in pages, etc. A lack of configurability is fine in alpha software, but not beta. Labelling your software as "beta" means you are on the home stretch towards "going gold", not just starting the race**, so there is no excuse for not having an options/settings UI.

* i know that installing an application into %APPDATA% could solve a couple of issues, namely the application should have no issue directly accessing its data files/folders because Vista's file virtualisation shouldn't be triggered, but that's not the point - this area is for data, not the application. Or maybe Vista restricted the install to installing there as it was downloading (installing) from an untrusted location. At the very least the application should be installed into %PROGRAMFILES%. Better still it should give me an option, defaulting to %PROGRAMFILES%.

** i have to mention that this is something that Google seems to have redefined with their other applications, like Gmail. It is labelled as "beta" as soon as it is opened to the public, and is still called "beta" several (4? 5?) years later. Will this happen with Chrome as well?

Keywords: google, chrome, sub optimal, bad install

Saturday, August 30, 2008

Cannot debug web based apps in Visual Studio (VS2008)

The company or product being named and shamed: Eset NOD32 AntiVirus.

Here's why: I was trying to debug a Silverlight application i had just whipped up, but i had an issue - i would press F5 in Visual Studio, IE (IE7 to be exact) would pop up, but i would just get a "Internet Explorer cannot display the webpage" message in IE, along with the usual list of suggestions to fix the problem. Upon checking the url in the address bar, i see it is pointing to the correct place:


so of course i initially think the problem was my fault and spend some time checking the code for errors, but of course there were none. So i loaded up a current web application that i am working on, hit F5, and the same thing happens. It has been about three weeks since i worked on that web application, and the only changes that have been made to this machine in that time are when i installed NOD32 and SP1 for Vista. As i had installed SP1 just a day ago, and because it is a major install (i.e. heaps of critical OS files get updated), i thought that it must have caused the issue. So i did what any self respecting geek does - i asked Google. It's not easy formulating a search query when you don't know exactly what the problem is, so i started with:

vista sp1 cannot debug web application

and after checking a few likely hits on the list i start noticing a recurring theme: Eset NOD32 was being blamed for the behaviour. The problem occurs because NOD32 adds an entry to your hosts file:

it's the entry at the bottom, the one that says "::1 localhost". I'm not sure exactly *why* it adds the entry, but obviously it's there to foil those badass pieces of malware that people get when they visit dodgy porn sites. But it also has the side effect of breaking the mapping between the loopback and the name "localhost", so when Cassini spins up and tries to serve my web or silverlight application it cannot resolve "localhost" back to the local machine.

Fixing this is easy - just remove the entry from the hosts file. And i have to say shame on you Eset, for not considering this behaviour, and for not checking for the presence of development tools in your install process.

To remove the entry:
- find your hosts file. This resides in %windows%\system32\drivers\etc, it is just called "hosts", with no suffix. This location is hidden, so you will have to make sure that both hidden and system files are visible.
- if you are running XP, you can just load the file into Notepad, remove the line, save it, then rerun your VS project.
- if you are running Vista, you have to jump through a few more hoops first. You need to take ownership of the file, to do this start a command prompt with administrative privileges:

either type Start-R, type cmd and then press Ctrl-Shift-Enter, and accept the UAC prompt
- or -
Start->All Programs->Accessories, right click on Command Prompt, choose Run As Administrator, and accept the UAC prompt

Then type the following commands:

take ownership of the file (so that you can change the ACLs on it)

c:\>takeown /f c:\windows\system32\drivers\etc\hosts

then you need to grant yourself full access rights to the file:

c:\>icacls c:\windows\system32\drivers\etc\hosts /grant [YourUserName]:f

then you can just load it into Notepad, remove the line, and save it. Of course it also wouldn't be a bad idea to revoke your access rights to the file once you have edited it, just to make sure it doesn't "accidentally" get modified without you realising it (modifying this file is an easy attack vector for malware). This is what the process looks like:

Those whited-out bits are just my username, you don't need to know what that is :) Interestingly enough, when i edit this file and save it, Windows Defender captures the change and prompts me to accept or revoke the change, but this never happened when i installed NOD32. I can't believe that Defender gets suspended while an application is installed (otherwise it would never detect that some malware just installed itself), so maybe NOD32 is doing something special during its install process.

Here are a couple of references that were helpful in diagnosing this problem:

Keywords: VS2008, Eset, Nod32, debugging, F5

Thursday, August 14, 2008

Using Trim() in SSRS2005 - and how it doesn't work

I found a situation today where the Trim() function doesn't work when used in a textbox expression.

I had a cell in a table that was showing the notes associated with a record, so the expression was simply:

= Fields!Notes.Value

While viewing the generated report, it was noticed that some notes had a lot of whitespace either at the leading or trailing end of the note, which caused the whole row in the table to expand in size as the note text wrapped, so I ended up with an effect like this:

This can look pretty ugly on a report. So the simple solution would be to trim the whitespace from the note text as part of the textbox expression:

= Trim(Fields!Notes.Value)

Bzzzzzzzztttttt!!!!!! Wrong answer - it didn't trim the note text at all. OK, time for Plan B, do a specific trim:


But once again, no cigar (and yes, i know that Trim() should just call LTrim() and RTrim() under the hood, but i would be a bad developer if i just went with that assumption and didn't try this option). I'm thinking that this was caused by a failure to implicitly cast the Field!Notes.Value from an object to a string, so the various trim functions failed. So i though of another approach:


and success!!!!!!...... and failure. This expression worked perfectly on rows that had a valid note, but on the ones where the value was null i got the familiar #Error statement in the textbox instead of a blank value:

This meant one more modification was required - i needed to check for a null value before i attempt the ToString().Trim() on the value. I am not a fan of the VB style IIf statement, as it doesn't shortcut (this means every argument in the function gets evaluated, regardless of the result of the expression), this means that the expression:

=IIf(IsNothing(Fields!Notes.Value), Nothing, Fields!Notes.Value.ToString().Trim())

would still error because the false part of the IIf would still get evaluated every time. The easy way round this is to just use a bit of custom code, and this is what i ended up with:

Textbox expression:


and the custom code:

Public Function TrimStringValue(ByVal fieldValue As Object) As Object
    If IsNothing(fieldValue)
        TrimStringValue = Nothing
        TrimStringValue = fieldValue.ToString().Trim()
    End If
End Function

Keywords: ssrs2005, ssrs, trim, ltrim, rtrim, iif

Wednesday, July 23, 2008

Calculate a median on a group in SSRS

Today i needed to do something that turned out to be impossible to do in the conventional manner. I was doing a report on some items that are measured in days, each row in the report was actually a group, and i needed to calculate a median for that group. Step by step, here is how i did it.

First, here is the report layout i started with:

as you can see i have two row groups, with the detail row of the second group hidden (because i don't want to list all the details, there are hundreds of them).

Initially this seemed quite simple - for each detail row i would call a custom function which would add the value to an array, and then in the group footer i could just call another custom function called CalculateMedian() to get the median of the group. But the problem is, when i called CalculateMedian(), my array was empty, so i had no values to calculate with. I double checked, and yes the AddToMedian() function was being called for every detail row, so wtf? After some digging around and some Googling, i found one or two blog posts that mention group header and footer expressions get evaluated before the group detail expressions - this meant i was trying to calculate the median before the values had even been added to the array! What the???!!!! Who the fuck came up with that brilliant design, and why? I can understand processing group headers before the details (for sorting puroses, etc), but surely footers should be done last? I mean, for 99.9% of cases, footers will contain aggregate or summary information, right? (I don't care about the other 0.1% of cases and whatever they are trying to put in the footer, if they want to do non-standard stuff then they should be prepared to feel the pain of getting it to work).

So, i couldn't use the group footer to show my summary information, a different approach was required. To achieve my median calculation, three things need to happen:
- at the start of each category group the array used to accumulate the values needed to be emptied.
- i needed to iterate the detail rows of each category group, as there is no other way of passing the group values through to a custom function.
- at the end of each category group i need to calculate and display the median value.

To begin with, i replaced the table that had the division and category row groups on it with a list container for the division group (call it the DivisionList). Above that i put a rectangle with a textbox for each column title, this replicates what was in the heading of the table:

After that, i nest another list inside the DivisionList container, this has a grouping set to group by category and is called the CategoryList:

Note that immediately inside each list container i have a textbox which shows the name of the grouped item. The CategoryList is now going to perform the same function as each row did in the original table, so i add a few more textboxes to hold the values for that "row":

Note that i have already added the expressions, the important one is the median textbox, which has the expression:


So this leaves the big question: i have the required grouping set up, and my aggregate/summary expressions in place, but how am i going to achieve objectives 1 and 2 from above, which was to reset the array holding the values, and to iterate rows in the category group so that i can place the individual values into the array the median is calculated from?

Hmmm, how can we iterate the values? I know - let's bring back the table, just give it one column:

if you are paying attention, you will see that it is inside the CategoryList container, with all the textboxes. Check out the expression, as the values get placed into the table rows they also get lodged in the array that is used for the median calculation. That's objective #2 resolved, now for objective #1, resetting the array. It's time for sly trick #1, using the knowledge that the table header expressions are processed before the table detail expressions, we can do this:

see what i did? I used an expression in the table header to reset the array. I also set the visibility of the table to hidden - i don't want this to show on the rendered report, remember? Sweet, now i have just one little detail left to take care of - how can i ensure that the table rows are filled before i call CalculateMedian() from my median textbox?

To achieve this, i employ sly trick #2: reports are rendered top to bottom, left to right. I've not seen this documented anywhere, it's just what i've observed, i've depended upon that behaviour in the past and it hasn't let me down yet. So, make the table 0.1cm wide, and make it the same height as the textboxes that are in the CategoryList container:

see that orange kind of spot on the report there? I added some colour to the table just so you could see what i've done. Now that it is resized, i move it to the left and above of the category label (IOW move it to location 0,0 of the CategoryList container, look for the orange spot). This ensures that it gets filled/rendered before the rest of the textboxes in that list container:

And that's all. Now, i just adjust the height of my list containers, and i run the report. This is what the final result looks like:

As you can see, the final result is almost the same as if i had used a table, but with this method i get to calculate the median on grouped rows - what a mission that was :(

For those of you who need it, here is the code for accumulating the median values and calculating them. It's pretty sandard stuff, nothing too exciting.

Dim Public Shared MedianArray(0) As Integer

Public Function ResetMedian()
ReDim MedianArray(0)
End Function

Public Function AddToMedian(fieldValue As Integer)
Dim i As Integer
i = UBound(MedianArray) + 1
ReDim Preserve MedianArray(i)
MedianArray(i) = fieldValue
AddToMedian = fieldValue
End Function

Public Function CalculateMedian() as String
Dim arraySize as Integer
Dim ii as Integer
Dim jj As Integer
Dim itemMoved As Boolean
Dim temp As Integer

'sort it & calculate it
arraySize = UBound(MedianArray)
If arraySize = 1 Then
CalculateMedian = CStr( MedianArray(0) )
Exit Function
Else If arraySize > 1 Then
For ii = 0 To arraySize - 1
itemMoved = false
For jj = LBound(MedianArray) To UBound(MedianArray) - 1
If MedianArray(jj) > MedianArray(jj + 1)
temp = MedianArray(jj)
MedianArray(jj) = MedianArray(jj + 1)
MedianArray(jj + 1) = temp
itemMoved = True
End If
If itemMoved = False Then Exit For

'calculate it
If arraySize Mod 2 = 0 Then
'average the two middle values
CalculateMedian = CStr( (MedianArray(arraySize / 2) + MedianArray((arraySize / 2) + 1)) / 2)
'get the middle value
CalculateMedian = CStr( MedianArray(Floor((arraySize / 2)) + 1) )
End If
End If
End Function

keywords: SSRS, SSRS2005, median, row group, calculate, custom aggregate