Monday, April 30, 2007

Sunday, April 29, 2007

The Portughese restaurant Fado

Update: I've heard that Fado has now been closed. It was nice while it lasted. Too bad I didn't have the chance to go again a few times before it went away.


Today we went with my parents at a Portuguese restaurant called Fado. I have to say I was pleasantly surprised by the cuteness of the place and the service was not bad. The food was bravely innovative, too, and tasty. Actually, it was more on my own taste than most of the restaurants I've been to, including Chinese. Well, I like Chinese more, but I can't cook Chinese, that's what I meant :-P

Anyway, if you happen to search for an intimate restaurant in Bucharest, with interesting food and a waiter that knows suspiciously much about the food, up to the point where he recommends you what is better than your original idea, then Fado is the place for you.

This is a link, in Romanian, with more details: Restaurant Fado

Thursday, April 26, 2007

Saving Page ViewState out of the page!

If you look for solutions to get rid of huge ViewStates from your pages you will get a lot of people telling you to override SavePageStateToPersistenceMedium and LoadPageStateFromPersistenceMedium in your pages and do complicated stuff like keeping the ViewState in the Cache or in a database, calculating strange keys, etc.

No more! Net 2.0 has something called a PageStatePersister. It is an abstract class and every Page has one. In case no override occurs, the default is a HiddenFieldPageStatePersister, but you can also use the provided SessionPageStatePersister like this:
protected override PageStatePersister PageStatePersister
{
get
{
return new SessionPageStatePersister(this);
}
}


And that's it! It works with Ajax and UpdatePanel, too.

However, this is no "Silver Bullet", as the SessionPageStatePersister will have issues with multiple windows open with the same session (like pop up windows) as exampled in this nice article. Also check out this situation when, during Ajax callbacks, a full ViewState is returned due to the ever troublesome ImageButtons.

There is no reason not to create your own PageStatePersister, though. The abstract class is public (not internal and sealed as Microsoft likes their most useful classes) and you can inherit it. You can even store the state in the Cache! :)

A very comprehensive article on ViewState is here.

Wednesday, April 25, 2007

Buggy DataTable Select strikes again: Safe Filtering without Sorting

A while ago I wrote a post about the bug in the DataTable.Select method with columns with comma in their names.

Today I discovered another bug, when using DataTable.Select with an empty sort string, but not an empty filter string, there is an implicit sorting by the first column. Short example: a DataTable with a single column called "words" containing values c,b,a,d , when Selected with a filter like "words is not null" and a null or empty sort string, will return a,b,c,d.

The only solution for this was to drop DataTable.Select entirely and use DataView, with its NET 2.0 method DataView.ToTable. So the code to take a DataTable and return the filtered and sorted table would look like this:
public static DataTable Select(DataTable table, string filter, string sort)
{
    if (table == null) return null;
    var dv=new DataView(table);
    dv.RowStateFilter=DataViewRowState.CurrentRows;
    dv.RowFilter = filter;
    dv.Sort = sort;
    return dv.ToTable();
}

But DataView has the same problem with columns with comma in their names. We solve it in the same way we solved it in the previous post: we change the column names, the sort and filter strings, we select, then we change the column names back:
public static DataTable SelectSafe(this DataTable table, string filter, string sort)
{
    var originalColumnNames = new Dictionary<string, string>();
    
    foreach (DataColumn dc in table.Columns)
    {
        if (dc.ColumnName.IndexOf(',') > -1)
        {
            var columnName = dc.ColumnName;
            var safeColumnName = columnName.Replace(",", ";");
            var  reg = new Regex(Regex.Escape("[" + columnName + "]"), RegexOptions.IgnoreCase);
            dc.ColumnName = safeColumnName;
            if (!String.IsNullOrEmpty(filter)) {
                filter = reg.Replace(filter, "[" + safeColumnName + "]");
            }
            if (!String.IsNullOrEmpty(sort)) {
                sort = reg.Replace(sort, "[" + safeColumnName + "]");
            }
            originalColumnNames[safeColumnName] = columnName;
        }
    }

    var newTable = Select(table, filter, sort);

    foreach (KeyValuePair<string, string> pair in originalColumnNames)
    {
        table.Columns[pair.Key].ColumnName = pair.Value;
        newTable.Columns[pair.Key].ColumnName = pair.Value;
    }
    return newTable;
}

Tuesday, April 24, 2007

Monday, April 23, 2007

Ariel Basescu

I know, the third entry in my blog about Basescu doesn't say much about my IQ or style, yet now I know what I felt I needed to say, but didn't realise what it was. It is the similarity between the case of Traian Basescu and Ariel Constantinof.

It is amazing still that these two similar stories happened in the same time. Indeed, Basescu is like a high school student who is suspended from the school on the basis of his behaviour, without anyone really knowing if it is "kosher" to do so. Do the high school owners have the right to expel Basescu or is this a financially motivated blunder? Both of them are big mouthed smart asses, without being really malevolent, yet being rather naive. Just as Basescu, Ariel gets a lot of sympathy from bloggers and radio hosts (radio bloggers?) everywhere, but without any true support. On each of the blog pages that describe his ordeal there are hundreds of people that offer their moral support, but really not much else. Just as Ariel, Basescu gets only a bunch of people to at least voice their anger to the school directors.

Can the case of Ariel Constantinoff serve as a simulation of what will happen to the country in a few months? I don't know, but I really like Ariel more than Basescu. And at least he has parents, the chance to go to another school. For crying out loud, he's a kid! Teachers, leave the kids alone! But who will find enough sympathy in their hearts for an old, bald, shunned ex president who didn't yet catch on with reality?

That's it! No more Basescu for a while on this blog ;)

Friday, April 20, 2007

Using Menu inside UpdatePanel

A previous post of mine detailed the list of ASP.Net controls that cannot be used with UpdatePanel and ASP.Net Ajax. Since I provided a fix for the validators earlier on, I've decided to try to fix the Menu, as well. And I did! At least for my problem which involved using a two level dynamic menu inside an UpdatePanel.
Here is the code:
<script>
function FixMenu() {
if (typeof(IsMenuFixed)!='undefined') return;
if (!window.Menu_HideItems) return;
window.OldMenu_HideItems=window.Menu_HideItems;
window.Menu_HideItems=function(items) {
try {
OldMenu_HideItems(items);
} catch(ex)
{
if (items && items.id) {
PopOut_Hide(items.id);
}
}
}
IsMenuFixed=true;
}
</script>

Now all you have to do is load it at every page load:
ScriptManager.RegisterStartupScript(this,GetType(),"FixMenu","FixMenu();",true);


Explanation: the error I got was something like "0.cells is null or not an object", so I looked a little in the javascript code, where there was something like " for(i = 0; i < rows[0].cells.length; i++) {" and rows[0] was null. All this in a function called Menu_HideItems.

Solution 1: Copy the entire function (pretty big) and add an extra check for rows[0]==null.

Solution 2: Hijack the function, put it in a Try/Catch block and put the bit of the original function that appeared after the error in the catch. That I did.

Poporul

...which is the Romanian word for "The people". Something a little archaic, like in the old days, when the people were either being oppressed, discontent or revolting. Usually, one uses the word in describing folks living in the country or a people as a whole, but then they are always specifying the country.

These days, right after the Parliament decided to suspend president Basescu, the Romanian Internet went wild. "Poporul" that, "Poporul" this. Blogs everywhere, hailing the great hero of "the people", victim to the vicious plots of traitors and communists and economic interests and so on and so on.

I am not even commenting on the decision to suspend Basescu, although I never liked the guy, but I can and will comment on the reaction of so many people, fellow bloggers, journalists and opinion leaders, all caught in the demagogic and cynical trap of the "hero".

For example, I read that Basescu (single-handedly, no doubt) brought Romania in the EU. Wrong! Most of the job was done by the PSD before, and it would have been inevitable, anyway, since the EU wanted our market. He is also responsible for the transition from the ROL (the leu, the old Romanian coin) to the RON. I might argue that the transition itself is dumb and useless since we will be switching to the Euro in 5 years, but hey... what did the president of the country in order to influence in any way the switch to the RON? Again, he is a great guy, but he did this terrible mistake of putting Tariceanu as prime minister. Did he have any other choice?! He won the elections (barely, I might add) on the back of a coalition of parties, the democrats and the liberals. Being a democrat himself, he had to put a liberal prime minister, whether he wanted or not! What else did he do, this hero of the people, except telling Bill Gates that piracy was a positive thing for Romanians? (which is true, BTW) Publicly fighting with your prime minister is NOT a good thing.

The greatest thing Basescu ever did was impersonate fantastically well a man who fights corruption. But did he? Corruption scandals are everywhere around him, involving him, suggesting political blackmail by use of the Secret Services. Are they based on anything? I don't know, but I do know that the little corruption, the one that I have to face, as a normal person, did not diminish. Quite the opposite! The current government policy seems to be take from the small and give to the big.

But returning to the people, who are these people that like Basescu? The ones that are either sympathizers of the democratic party or not having any political sympathy (as all other parties are now against him). Who are these wonderful people who will fight for the symbol of their freedom and fight against evil (no, not Bush), against the political will of all the parties and the people that support them and against all the TV attacks against the president? Who are these great minds who can think for themselves and make a decision and stick by it? They are not the "popor", that's for sure!

So for you, all these people of the press and Internet blogging bravado, I suggest you speak of yourselves, leaving "the people" alone, since you are not their representatives (as Basescu is not). Leave the illusion of greatness to the dictator-hearted people. Lead by example, not by association. Because you are not of the people, you are a little better.

Thursday, April 19, 2007

Happy Basescu impeachment day!

Well, it sounds better in Romanian, since just a few days ago it was Easter.

Anyway, it seems that the dark force that rules Romania has finally shown its ugly head and took vengeance on president Basescu. You see, some say the country is ruled by corrupt politicians, while others blame economic interest groups. But it's all a scam to hide the real rulers of Romania: the boutique traders!

It all started quite a long while ago, when Basescu was only the mayor of Bucharest. Back then he decided to demolish boutiques that provided cheap and accessible nourishment and drinks to the population and to kill as many dogs as possible. Their plan was long and elaborate, but the boutique people got through all of this without even being noticed. They and their dogs, saved from certain death by their covert operations. Finally, the day has come! Basescu was allowed to go as high as he did only to have a bigger fall in the end.

You might consider this post another tasteless joke from my part, but you will see... some day... when Basescu has lost all his political support, nobody loves him, his beautiful Presidential residence taken from him, feeling defeated, buried in despair... the dogs will get him. Oh, yeah, they will get him. They never forget!

Wednesday, April 18, 2007

Tuesday, April 17, 2007

The web application you are attempting to access on this web server is currently unavailable

You just copied a directory with an ASP.Net web site in your wwwroot directory, you created an ASP.Net application from IIS/Web Sites, yet you get an error, no matter what page you try to access: The web application you are attempting to access on this web server is currently unavailable.
It's an access issue. Give access to the directory to the ASPNET user.

Monday, April 16, 2007

Being a Bicycle Biker in Bucharest

I've wanted to write about this for a long time now. It started with the entry about the ridiculous and diabolical street sweeper machines that make lakes of water at every intersection while "cleaning the streets and fixing the dust". As an added bonus, now entire forests outside Bucharest are being torn down to make way for housing projects. I wonder where the dust is coming from, hmm...
Anyway, I want this entry to be as complete as possible in order to tell anyone considering buying a bicycle to ride in Bucharest what exactly they are getting into.

Step I. Buying the bicycle



Now, being a girl or a slim guy and not having the desire to make bike stunts in the foreseeable future, you might be considering buying a bicycle from a hypermarket chain like Cora. Don't do it!! The bikes there are of bad quality to begin with, but considering the people at a hypermarket don't know anything about bicycles, you will also get a bike that is put together by marketeers.

Let me give you an example: the bike is twice as cheap as anything you saw in a bike shop, it also has a lot of accessories like wheel covers, water bottle support, ringer, bike support leg. Isn't that a good deal? No! The spikes of the wheels are bad Chinese quality, in short time your wheels will turn in an figure 8 like movement; the water support is nothing but a glorified wire, sooner or later you will deform it and it will scratch you or your clothes; The ringer works, but is placed in the middle of the bike horns, so you can't reach it; the wheel covers are cheap plastic and they will always fall on the wheel making loud noises and slowing your bike; the bike support leg will at first not hold your bike, since it is really bad, then it will get its mechanism jammed and you won't be able to expand it.

Still want to buy it from Cora? So where do you buy a bike from? I personally despise franchises. That's why I would not recommend First Bike or any other shop like it. However, not having bought anything from them, I can't express an opinion. I bought both my bikes from Magelan and I am rather satisfied with the bikes and the service, even if I had to pay a little extra. What is nice about Magelan is that they are a full time bike shop. They fix bikes all day long as well as sell them. They taught me about how to maintain my bike, they centered the bike wheels (this being a very important process if you don't want your wheels to deform or the bike to require more effort to ride), etc. They also presented me with bikes of 1000-4000 euros :) Luckily, you won't have to buy anything more expensive than 400 euros.

The things you want in a bike are: wheel covers (see the diabolical street sweepers above), firm brakes, an anti-theft device and a good saddle. Anything else is rather useless. You will need a bicycle pump, but I would recommend the foot pumps that are sold in car shops, not the arm muscle devices the people at Magelan sell as pumps.

Step II. Transporting the bicycle



What do you mean, transport the bike? It should transport you, right? Wrong!. The bicycle will transport you only on the road. Then you have to move it upstairs to your flat or to your office. Sometimes, like when you go shopping, you need to lock your bike and tie it to something. You will soon notice that moving your bike with an elevator is not an easy task, especially if you are tall like me and you bought a big bike. There are also old hags that insist on you cleaning the elevator after you dirty it with your bike wheels. There are blocks without elevators. The first rule about this: don't tie your bike downstairs, hoping it won't get stolen. It will!
If you have the misfortune to live in a block without elevators or with a too small one, you will have to climb the stairs with it. For example I have to do this at the office, but lucky me, it's only at the fourth floor.

Step III. Riding the bike



Oh, this part is going to be long. Take some popcorn and read carefully.
At first you will ride the bike on the sidewalk. It makes sense, since you want to be safe when you make rookie mistakes and you lose your balance. You don't want a big truck to hit you when you stop suddenly and can't keep straight. So, if you are allowed to move on the sidewalk and it's safe, you could always use the sidewalk, right? Wrong!

In a normal situation, where the city hall really thought about you, the citizen, and took measures to keep you happy, you would be OK using the sidewalk. There would be no holes, there would be easy access from the sidewalk to the street, no cars parked on the sidewalk, no cars running on the sidewalk. In Bucharest no one even considers bikes. There are holes in the sidewalk, since you have feet to go around, in or out of holes; sometimes there is a lower sidewalk edge for things like bikes, wheelchairs and child perambulators, but sometimes it is a bit too high for comfort or there is none; when there is, cars are parked in front of them and, at the street crossings, people stop on them, without even moving out of the way when they see you sitting on a bike next to them. That is, if you can move on the sidewalk at all, since cars try to beat the traffic by moving on the sidewalk or they just park there, blocking the way. Not to mention the weird thingies that one meets on the sidewalk: people!

There are three "banes of the biker" when moving on the sidewalk: lover pairs, old people and women with children. So, if you see an elderly couple that seem still in love walking with their nephew, at least go around them on the guy's side. Why are they banes? Lovers like to hold hands, while in the same time keep apart from each other, then suddenly get back together and split again. They are completely clueless, unaware of the world around them and totally unpredictable. Old people are equally unpredictable due to strange and unspeakable pains, aches and mental conditions. They can stop suddenly, zig-zag, turn around unexpectedly and, most of all, get completely freaked out when you pass by them (even at a few meters away). Women with children are firstly women, then they have children. I rest my case.

So, after a while you get annoyed with all this, you decide to move it to the next level: the street. Since the circulation rules regarding bikes are pretty clear, there shouldn't be a problem. Cars will leave the "ecartament" empty, not park there, they will move one meter away from you when they pass by you, the drivers will be civil and respect your legal status of vehicle. Rrrright!

Street "biker banes": women driving new cars, taxi cab drivers and men driving junk cars. Women with new cars usually cheated through their driving licence exam and the car is not even theirs. Someone probably bought it for them. Women in general have a strong sense of law and order, therefore they will expect you to obey every circulation rule they know of and, when faced with real life situations that are not in the bloody book, they panic, freeze, etc. Simplest way to solve this is to always consider the possibility they actually want to hit you and take precautions against it. Taxi cab drivers own the streets. They are afraid of nothing and no one, except other taxi cab drivers. And women in new cars. And men in junk cars. For example, a cab driver will always move on the tramway line and honk at you, explaining in vernacular why he has the right to be there and you don't. But it is understandable... they rarely take Latin. Anyway, the men in junk cars are careless. They know they can buy a car the next day just like the one they are driving. It will be junk, but whatever you hit or hits you, they have to pay more. What could happen in the worst case scenario? Die? They already lost everything. And worse of all, they have a junk car, which is humiliating. They try to cover this shame with loud music, usually bad music, which makes them not only dumb, but also deaf.

When you learn to avoid the banes you still have to fight the street sweepers, to learn to move quickly when the light goes green, as drivers will magically ignore you being there when they turn right and you want to go forward. And most of all, you will have to fight the "good" ideas of the city hall.

Example: they built this bike track (weee!) around the Alexandru Cuza park (formerly known as the I.O.R. park). It is a half a meter wide strip, marked with yellow, right in the middle of the sidewalk, with beautiful images of bikes drawn on it and occasionally some arrows to point out the direction. So, anyone wanting to circle the park in one direction, like a hamster in a wheel, can use this. Of course, last time I saw it, there was a cripple guy walking with dignity on it. At least he respected the direction pointed by the little arrows.

The city hall also plans to build bike lanes. A commendable effort if they didn't do in the same time things that nullify it completely. And I mean the street side garbage bins. These are the bins that drivers can throw stuff in. Of course, they are right on the side of the street, the place where the horns of your bike are supposed to be when you pass through there. Not to mention the remote possibility that any Romanian driver would want to throw things in the bins rather that directly on the street, in which case they would most likely not look behind before putting their hand out. Can you spell "open fracture"?

OK, OK, I don't want to scare you out of it. It's just that there are a lot of morons out there. You need to pay more attention, else you will get hurt. Once you understand that the world is a dark evil place that wants to cause you pain, you will be alright!

Step IV. Taking care of your bike



Your bike needs to have the wheels firmly inflated. A car has the wheel pressure at about 2.4 atmospheres. Your bike will require 3.0-3.2. That means regular pumping or, the lazy man's alternative, fill them up with air at any gas or vulcanization station. Also, if you have a flat tire, all you have to do is pay a guy at the nearest vulcanization 1-2 euros and he will take the wheel out, check the tire, fix it, put it back on. Some would be kind enough to refuse, just go to the next.

After you used it for a while, you need to take it to be checked. Not too often, like once or twice a year (recommended default value). Go to Magelan, no matter where you bought the bike from, and ask them to check it for you. They will do everything there is to be done. The service is really cheap, too.

That was a short chapter.

Step V. Miscellaneous



So you know where to buy it from, what you want on it, how to ride it, what to look out for and how to take care of your bike. What else is there?

Well, first of all, sweat. When on the bike one doesn't usually sweat as the air circulation quickly evaporates the humidity and cools the body. However, stopping is a bitch. All the heat builds up quickly in your system, now used to have external ventilation, and you start to sweat. So, when you get to the office, don't rest, don't catch your breath, take your bike up as soon as possible, go to the bathroom, remove your t-shirt, wash. Use a special towel that you brought with you or that you designated as body towel. You will have a lot of perspiration on your spine and on your chest. Your face will probably be very sweaty, too. Take your time. Your body needs to cool off, so you might need to splash water on you repeatedly.
If you do this, you will need no change clothes and you will be fresh and reasonably not stinky for your colleagues. You will rinse your mouth with water, but don't drink any until you get out of the bathroom and you are reasonably cooled down.

Also, special attention to buses. It is easy to go by them, as they stop often. But they also release those human thingies, sometimes before the bus station, sometimes they start from the station and they stop because they see some guy running after the bus or someone forgot something and they want to get off. Sometimes bus drivers do it on purpose.

The same thing goes for every car stationed at the stop light. The only accident I had with my bike was when a complete idiot opened the car door at the stop without checking if anything is coming. I slammed right into that door. The scary thing is that I also got out of a car at the red light and I realised I didn't even consider checking for a bike. So it happens to everyone. Go slowly by the cars at the stop!

The last thing: etiquette. If you are a girl, not necessarily a stunning image of womanly perfection, you will get honked, whistled at, disrespected, teased. The only ones that will not do that are the women, which, as stated previously, will silently come from behind and kill you in inept panic. Don't get mad, don't get even, just learn to let it go, ignore everything that doesn't have anything to do with you getting to your destination. If you are a guy, you will be challenged at every step of the way. Or is that at every wheel turn? Anyway, drivers will try to bully you, block your way, honk you out of composure. Same advice goes to guys as well: ignore anything that is not about to be in your way or hit you.

What you need to understand is that, in normal Bucharest morning traffic, you are faster than any car there. People that spent their entire economies and still paying huge rates at the bank to have their new car will see you pass right by them, not paying for the car, for the gas or caring about the traffic! There is a lot of frustration there, don't give it a chance to bubble up towards you.

No matter the genre, you will have to either wait for someone to pass on a street crossing and provide you with "cover" or you will have to descend from your bike and walk next to it on the crossing. This is the only rule that one would reason out of, since it it more advantageous for drivers to let you pass on your bike (since you will cross faster) than on foot. Yet 99% of them will ignore you wanting to cross the street while on your bike. It's a pride thing. Bow to the king.

If you got this far, then you probably should get a bike, it means you are committed :) It's not as bad as I portrayed it, either, but pretty close. Danger comes not from what you expect, but from the unexpected. You also expect less when you are not paying attention or are thinking of something else. I don't recommend music listening when you ride the bike, but I do recommend sun glasses and (didn't try it, yet, but soon) a face mask/air filter.

Saturday, April 14, 2007

Thursday, April 12, 2007

Fixing validators in ASP.Net Ajax

Update 2nd of July 2008:
The solution below doesn't always work. For a validator to not work you need to have .NET 2.0 installed without installing Service Pack 1 for it and you also need to add the validator dynamically (so it is not there when the page is loaded, but it appears there during an async postback).

The problem lies in the BaseValidator class itself, after the postback, the validator is not in the Page_Validators array. I've tried rehooking the validators, even enumerating all validators in the page and manually entering them in the Page_Validators array. It does not work. Mainly because the html spans of the validators are not the same thing as the validators and stuff like the id of the control to validate and other details are never rendered.

So you absolutely need to install the .Net 2.0 SP1 in order to use UpdatePanels with validators.
=====

Today I've encountered a strange error, where the validation did work inside an UpdatePanel, but the validator message would not appear. This in a project where I used validation in GridViews inside their own UpdatePanel and they worked!

So I guess part of the reason the error occurs could be any one of these:
  • validated control and validator are both in a UserControl
  • validator is loaded at start of the page, it does not appear during Ajax calls, but it is replaced during Ajax calls
  • validated control is a ListBox, worse, an object inherited from a ListBox


Anyway, the true reason why the validators behaved in this fashion was that they were different from the html node of the validators. In other words, the Javascript array Page_Validators was filled with the validators correctly, did have the evaluation function, did return isvalid=false, but then, when the style.display attribute was changed to inline, the validator was not part of the html page DOM, as it was changed by Ajax.

Solution: a Javascript function that enumerates the validators, gets the validator DOM node by way of document.getElementById, stores the validator properties into this node and then replaces the element in the Page_Validators array.
Problem: where should one load it? Possible solutions include submit button onclick events, form onsubmit events and in the Page_Load method. I would not use onclick, since I should have added onclick events on all possible submit buttons. I would not use RegisterOnSubmitStatement, since it ran the code after checking if the validators are valid or not (even if the validation itself took place afterward; now that is weird). The only solution is to use Page_Load.

There are various ways of doing that, too, since you could use MasterPages, custom made Page objects and even HttpHandlers or HttpModules. You also could have custom controls or objects that don't have a reference to the Ajax ScriptManager object or even to the Ajax library itself. Yes, I know, I am very smart. In my project, all the pages were inherited from a custom Page object, also in the project. So it was relatively easy.

Here is the code:

// in Page_Load
string script=@"
if (typeof(Page_Validators)!='undefined')
for (var i=0; i<Page_Validators.length; i++)
{
// get DOM node
var vld=document.getElementById(Page_Validators[i].id);
if (vld) {
for (var key in Page_Validators[i])
// check if the Page_Validators element has extra attributes
// and add them to the node
if ((vld[key]==null)&&(Page_Validators[i][key]!==null))
vld[key]=Page_Validators[i][key];
// replace the Page_Validators element with the reconstructed validator
Page_Validators[i]=vld;
}
}
"
;
// get current ScriptManager for this page
ScriptManager sm=ScriptManager.GetCurrent(this);
if ((sm!=null)&&(sm.IsInAsyncPostback)) {
// if we did an Ajax postback, fix validators
ScriptManager.RegisterStartupScript(Page, GetType(),"FixValidators",script,true);
}

Tuesday, April 10, 2007

Hot to put a custom object into the ViewState or how to serialize an object

In order to access an object even after postbacks, you need to put it either in the Session or the ViewState. The ViewState is preserved only between postbacks, not between different pages and it is a Page property, so it is more efficient to use it. The problem with this method is that every object you put in the ViewState must be serializable.
So, the quick and dirty path: if you don't have strange custom serializing to do, all you have to do it to decorate the object with the [Serializable] flag, like this:
[Serializable]
public class MyDictionary StateDict:Dictionary<int,bool> {
}

Say you wouldn't have done this, you would have probably met with the "Class is not marked as Serializable". Duh!. However, in this situation above I have inherited from an object that implements ISerializable. I will get an error "The constructor to deserialize an object of type ... was not found". What that means is that the object must have a constructor that accepts two parameters, a SerializationInfo and a StreamingContext object. So we must add it to the object, like this:
[Serializable]
public class MyDictionary StateDict:Dictionary<int,bool> {

public MyDictionary(SerializationInfo info, StreamingContext context) : base(info, context) { }
public MyDictionary() {}

}

I added the second constructor because when adding a parametrized constructor, the default empty one is no longer inherited. So no more new MyDictionary() unless one adds it.

That does it! Please do check out the entire ISerializable interface documentation, since it requires, besides the constructor, a GetObjectData method, with the same parameters as the constructor, which controls the custom serialization of the object.

Thursday, April 05, 2007

Make your own girlfriend!

Now, you might ask yourself what has the girl in the image with making your own girlfriend. Look closer, it's not a girl (and not a nigth elf either) it's a computer generated image, made from scratch.

Wednesday, April 04, 2007

Super Fast and Accurate string distance algorithm: Sift3

Update November 2014: Sift4 is here!! Check out the new improved version of Sift here: Super Fast and Accurate string distance algorithm: Sift4

Update October 6 2014: New stuff, compare Levenstein vs Sift here:
Algorithm:      Levenstein      Sift
String 1:   String 2:  
Result:

Update June 25th 2013: I've decided to play a little with the suggestions in the comments and check for validity. This was spurned by the realization that a lot of people use my algorithm. So, in order to celebrate this, here is the "3B" version of the Sift3 algorithm:
It is made in Javascript, this time, as it was easier to test and has the following extra features:
  • a maxDistance value that tells the algorithm to stop if the strings are already too different.
  • two pointers c1 and c2, rather than a single pointer c and two offsets
  • Instead of dividing to 2 the total length of the strings compared, now I divide it with 1.5. Why? Because this way the value is closer to the Levenshtein distance computed per random strings

Happy usage!
The variant I posted was totally buggy. I removed it. Just use sift3Distance.
Update: the Sift algorithm is now on Codeplex.

A while ago I wrote an entry here about Sift2, an improvement of Sift, the original and silly string distance algorithm. Now I am publishing Sift3, which is way more accurate and even simpler as an algorithm.

I found out that my algorithm is part of a class of algorithms that solve the Longest Common Substring problem, therefore I calculated the LCS, not the distance, then the distance from the LCS. The result is way more robust, easy to understand and closer to the Levenshtein algorithm both on random strings and user databases. Not to mention that there is no goto in this one.

BTW, if you are looking for an algorithm that detects switched words, this is not it :) This just looks for typos and small regional differences between the strings. I mean, you could normalize the strings, so that words are ordered by some mechanism, then it would work because the words wouldn't be switched :)

I promise to work on a word switching algorithm, but not in the near future.
Without further due, here is the code:

The C# code is a method in an object that has a private member maxOffset. As in Sift2 maxOffset should be around 5 and it represents the range in which to try to find a missing character.

public float Distance(string s1, string s2, int maxOffset)
{
  if (String.IsNullOrEmpty(s1))
  {
    return String.IsNullOrEmpty(s2)
             ? 0
             : s2.Length;
  }
  if (String.IsNullOrEmpty(s2))
  {
    return s1.Length;
  }
  int c = 0;
  int offset1 = 0;
  int offset2 = 0;
  int lcs = 0;
  while ((c + offset1 < s1.Length)
         &&
         (c + offset2 < s2.Length))
  {
    if (s1[c + offset1] ==
        s2[c + offset2])
      lcs++;
    else
    {
      offset1 = 0;
      offset2 = 0;
      for (int i = 0;
           i < maxOffset;
           i++)
      {
        if ((c + i < s1.Length)
            &&
            (s1[c + i] == s2[c]))
        {
          offset1 = i;
          break;
        }
        if ((c + i < s2.Length)
            &&
            (s1[c] == s2[c + i]))
        {
          offset2 = i;
          break;
        }
      }
    }
    c++;
  }
  return (s1.Length + s2.Length)/2 - lcs;
}


And here is the T-Sql code. This version is actually an improvement of my original source, gracefully provided by Todd Wolf:

CREATE FUNCTION [DBO].[Sift3distance2]
(
 @s1 NVARCHAR(3999),@s2 NVARCHAR(3999),@maxOffset INT
)
RETURNS FLOAT
AS 
 BEGIN
    DECLARE @s1LEN INT,@s2LEN INT
    
    SELECT @s1LEN=Len(Isnull(@s1,'')),@s2LEN=Len(Isnull(@s2,''))
    
    IF @s1LEN=0 RETURN @s2LEN
    ELSE
    IF @s2LEN=0 RETURN @s1LEN
    
    IF Isnull(@maxOffset,0)=0 SET @maxOffset=5
    
    DECLARE @currPos INT,@matchCnt INT,@wrkPos INT,@s1Offset INT,@s1Char VARCHAR,@s1Pos INT,@s1Dist INT,@s2Offset INT,@s2Char VARCHAR,@s2Pos INT,@s2Dist INT
    
    SELECT @s1Offset=0,@s2Offset=0,@matchCnt=0,@currPos=0
    
    WHILE(@currPos+@s1Offset<@s1LEN AND @currPos+@s2Offset<@s2LEN)
    BEGIN
        SET @wrkPos=@currPos+1
        
        IF(Substring(@s1,@wrkPos+@s1Offset,1)=Substring(@s2,@wrkPos+@s2Offset,1)) SET @matchCnt=@matchCnt+1
        ELSE
        BEGIN
            SET @s1Offset=0
            
            SET @s2Offset=0
            
            SELECT @s1Char=Substring(@s1,@wrkPos,1),@s2Char=Substring(@s2,@wrkPos,1)
            
            SELECT @s1Pos=Charindex(@s2Char,@s1,@wrkPos)-1,@s2Pos=Charindex(@s1Char,@s2,@wrkPos)-1
            
            SELECT @s1Dist=@s1Pos-@currPos,@s2Dist=@s2Pos-@currPos
            
            IF(@s1Pos>0 AND (@s1Dist<=@s2Dist OR @s2Pos<1) AND @s1Dist<@maxOffset) SET @s1Offset=(@s1Pos-@wrkPos)+1
            ELSE
            IF(@s2Pos>0 AND (@s2Dist<@s1Dist OR @s1Pos<1) AND @s2Dist<@maxOffset) SET @s2Offset=(@s2Pos-@wrkPos)+1
        END
        
        SET @currPos=@currPos+1
    END
    
    RETURN(@s1LEN+@s2LEN)/2.0-@matchCnt
END

It doesn't give the same exact results as my own code, yet the result is close enough and the speed is about 20% higher.

And thanks to Diogo Nechtan, the version in PHP:
function sift3Plus($s1, $s2, $maxOffset) {
    $s1Length = strlen($s1); 
    $s2Length = strlen($s2);
    if (empty($s1)) {
        return (empty($s2) ? 0 : $s2Length);
    }
    if (empty($s2)) {
        return $s1Length;
    }
    $c1 = $c2 = $lcs = 0;

    while (($c1 < $s1Length) && ($c2 < $s2Length)) {
        if (($d = $s1{$c1}) == $s2{$c2}) {
            $lcs++;
        } else {
            for ($i = 1; $i < $maxOffset; $i++) {
                if (($c1 + $i < $s1Length) && (($d = $s1{$c1 + $i}) == $s2{$c2})) {
                    $c1 += $i;
                    break;
                }
                if (($c2 + $i < $s2Length) && (($d = $s1{$c1}) == $s2{$c2 + $i})) {
                    $c2 += $i;
                    break;
                }
            }
        }
        $c1++;
        $c2++;
    }
    return (($s1Length + $s2Length) / 2 - $lcs);
}

And thanks to Fernando Jorge Mota, the version in Python:


Also, here is the Javascript version, used in Mailcheck, by Derrick Ko and Wei Lu.

function sift3Distance(s1, s2) {
    if (s1 == null || s1.length === 0) {
        if (s2 == null || s2.length === 0) {
            return 0;
        } else {
            return s2.length;
        }
    }

    if (s2 == null || s2.length === 0) {
        return s1.length;
    }

    var c = 0;
    var offset1 = 0;
    var offset2 = 0;
    var lcs = 0;
    var maxOffset = 5;

    while ((c + offset1 < s1.length) && (c + offset2 < s2.length)) {
        if (s1.charAt(c + offset1) == s2.charAt(c + offset2)) {
            lcs++;
        } else {
            offset1 = 0;
            offset2 = 0;
            for (var i = 0; i < maxOffset; i++) {
                if ((c + i < s1.length) && (s1.charAt(c + i) == s2.charAt(c))) {
                    offset1 = i;
                    break;
                }
                if ((c + i < s2.length) && (s1.charAt(c) == s2.charAt(c + i))) {
                    offset2 = i;
                    break;
                }
            }
        }
        c++;
    }
    return (s1.length + s2.length) / 2 - lcs;
}

Another implementation, this time in Java, by Eclesia:


You might also be interested in a customised version in AutoKey, by Toralf:


Thanks all for your comments and I look forward to more. Just tell me it worked or not and, most important, why. Good luck!

Tuesday, April 03, 2007

Entry 200

Having reached the 200th entry, I really wanted to write something cool, something interesting, something that sticks (and it ain't shit).

I thought of blogging Kartoo, a very nice - albeit slow - visual search engine that shows not only relevant links, but also the context items that link different pages.

But Kartoo is not personal enough, so I switched to YouTube, thought about blogging (yet another) female vocalist nu-metal with goth overtones band like the Italian band ExiliaYouTube. Or something else, like the Turkish band maNgaYouTube, or the Spanish Dead StonedYouTube or DemiurgoYouTube. But this is a blog, not a video/music site.

Then I thought about programming; there must be something in the three projects I am working on worth blogging about, or at least something important like Don't use the .NET Random class when concerned about security. But then again, the blog is full of (I hope) interesting programming hints.

What else is there? Ranting about bycicle lanes the city hall is building on the sidewalk and on which old people are happy to walk (slowly) without losing themselves;
interesting conceptual games like BoomShine, Straight Dice or Stickman Fight and how they can be improved;
the BBC Baghdad Navigator, to show you the distribution and timeline of Baghdad bombings;
the Lilium song for the anime Elfen Lied;
the Coma article on Wikipedia (I didn't write it);
coming improvements in the Sift3 algorithm;
InuYasha manga reaching chapter 500;
the new Google/Kartoo/Wikipedia searches for any selected text in the blog;
how I am reading Il Nome de la Rosa and The Name of the Rose in the same time, trying to grasp more of the Italian language;
Gwoemul, a very nice South Korean film...

No, there is too much to choose and I can't decide. I think I will skip entry 200 entirely.