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,565
|
Comments: 51,184
Privacy Policy · Terms
filter by tags archive
time to read 61 min | 12194 words

Castle Demo App: Complex Interactions

We have CRUD operations for users and projects. The next step is to add a way to assign / remove users from a project. We can do it both for users and projects, but this is just overly complex. We're going to allow a project edit page to add or remove users, and that is all.

I'll be the first to admit that I'm not very good with JavaScript, and I've practically no knowledge in the Ajax capabilities of MonoRail (which are based on Prototype library). I'm just going to stubmle around in this post, and see if I can make something work. Do ignore any screaming that you may hear in the background, this is just me hating browsers :-)

The reason that I'm so late with this post is that I run into a couple of problem with Ajax that weren't easy to solve (or even find). Just to put things in perspective, I wrote the code for this post in an hour and a half, and that included looking at MonoRail source, finding a bug and fixing it. In contrast, it took me much longer to write / debug / fix the problems in the java script parts. I think that this is mostly my fault, though, for not knowing the DOM and the environment well enough. Anyway, enough chit-chat, we've an application to write!

We start by adding this snipper to the head of Views\Layout\Admin.boo:

<head>

${AjaxHelper.GetJavascriptFunctions()}

<title>Administrator Section</title>

</head>

This simply add the correct javascript refeferences (<script> tag) so we could use the ProtoType functions. What I'm going to do next involve a bit of Ajax and some understanding of the tools that MonoRail gives me. I'll explain everything in a couple of minutes, but for now, here is the new EditProject method:

    public void EditProject([ARFetch("id")]Project project)

    {

        AddAllUsersToPropertyBag();

        ValidateObjectToSendToView("project", "Projects", project);

        User[] allUsers = User.FindAll();

        PropertyBag["assignedUsers"] = new User[0];

        PropertyBag["unassignedUsers"] = allUsers;

        if (project == null)

            return;

        ICollection<User> assignedUsers = project.Users;

        User[] unassignedUsers = Array.FindAll(allUsers, delegate(User user)

        {

            return assignedUsers.Contains(user) == false;

        });

        PropertyBag["assignedUsers"] = assignedUsers;

        PropertyBag["unassignedUsers"] = unassignedUsers;

    }

This requires some explanation, I believe. The first two lines are the same as before, but the rest of it is all new. The idea is to pull all the users from the database, and then filter them based on those not already assigned to the project. We then pass the assigned and unassigned users to the view. The two lines just after User.FindAll() are there because we need to give the view something to display if this is a brand new project.

Now, let's see what we need to do to the view in order to make it support our brand new interface, here we have quite a bit of word, as a matter of fact. Because I made so many changes, I'm going to show each part of the page and then provide explanation on what it does. It's a lot of code, but it is not complicated one:

<?brail

def outputUsersList(usersList, addEnabled, removeEnabled):

     index = 0

     addStyle = ""

     removeStyle =""

     addStyle = 'display:none;' if addEnabled == false

     removeStyle = 'display:none;' if removeEnabled == false

    

     for user in usersList:

           userClient = "user_id[${user.Id}]"

           moveToAssigned = "addToProject($('${userClient}'))"

           moveToUnassigned = "removeFromProject($('${userClient}'))"

           output "<tr id=${userClient} userId='${user.Id}'><td>"

           output AjaxHelper.LinkToFunction('&lt;&lt;', moveToAssigned, {'id':userClient+'.addToProject', 'style':addStyle})

           output "</td><td>${user.Name}</td><td>"

           output AjaxHelper.LinkToFunction('&gt;&gt;', moveToUnassigned, {'id': userClient+'.removeFromProject', 'style':removeStyle})

           output "</td></tr>"

           index++

     end

end

 

name = ""

defaultAssigneeId = -1

id = "0"

if ?project:

   name = project.Name

   defaultAssigneeId = project.DefaultAssignee.Id

   id = project.Id.ToString()

end

 

?>

Notice the outputUsersList() method, this one is one of the reasons I love Brail, it allows me to do Extract Method refactoring for HTML. What this method does is very simple, it gets a list of users, and two flags telling it whatever to show or hide the add / remove functionality. You can see a couple of things here, we are taking advantage of the fact that Boo (which is the base langauge for Brail) has strong inference capabilties, so we don't bother with specifying types, in fact, this method declaration looks very much like the declaration we will see in a moment, in the java script code. (Although I find Boo much easier to work with than Javascript, of course.)

We output some HTML to the client, which isn't very interesting (and vaguely resembles CGI code), but we have a call there to AjaxHelper, which I'm going to assume that you're interested at. The method we use is LinkToFunction(), which just means that MonoRail generates a link for us, and will call a (local) java script function when the link is clicked. The first parameter is the text of the link, the second is the function to call (including its parameters), the third is used to pass a dictionary of attributes for the link (and one of them is display=none, which is used to hide the link if it's not needed.

One thing to notice here is the way I'm using the javascript. First, "$()" is a function call that I get from Prototype, it'll get me the element that I need without needing to do stuff like document.getElementById(), etc. Second, I'm passing this function the id for whole <tr> element. We'll see how it makes our life easier in a couple of minutes, since it means that I can just move stuff around without worrying how it looks. Third, notice that I'm naming all the links the same way, the container id plus "addToProject" or "removeFromProject", this will allow me to get the correct link easily, without traversing the DOM to find the right one. Because I know the user id is unique, I know that both the <tr> id and the links ids will be unique in the page. One other thing that I am doing here is to add a userId attribute to the <tr> element, I'll be using this attribute in the Ajax code, to get the user I'm working on.

I think that the parts after the method require no explanation, we've already seen them before. Before I will show the Javascript, we need to take a look at the page, and see how it's laid out, otherwise you wouldn't be able to make head or tails from the scripts, so here it is:

${HtmlHelper.Form('SaveProject.rails')}

${HtmlHelper.FieldSet('Project Details:')}

${HtmlHelper.InputHidden('project.id', id)}

<table>

     <tr>

           <td>

                ${HtmlHelper.LabelFor('project.Name','Project name:')}

           </td>

           <td>

                ${HtmlHelper.InputText('project.Name',name, 50, 50)}

           </td>

     <tr>

     <tr>

           <td>

      ${HtmlHelper.LabelFor('project.DefaultAssignee.Id','Default Assignee:')}

    </td>

    <td>

           ${HtmlHelper.Select('project.DefaultAssignee.Id')}

           ${HtmlHelper.CreateOptions(allUsers, 'Name','Id', defaultAssigneeId)}}

           ${HtmlHelper.EndSelect()}

    </td>

     </tr>

</table>

<p>Users Assignments:</p>

<div  id='assignedUsersInputs'>

<?brail for user in assignedUsers: ?>

     ${HtmlHelper.InputHidden('project.Users', user.Id)}

<?brail end     ?>        

 </div>

<table>

  <tr>

    <th style="BORDER-BOTTOM: #f96 1px dashed; BORDER-RIGHT: #f96 1px dashed">Users assigned to this project:</th>

    <th style="BORDER-BOTTOM: #f56 1px dashed; BORDER-LEFT: #f56 1px dashed">Users not assigned to this project:</th>

  </tr>

  <tr>

    <td style="BORDER: #f96 1px dotted">

      <table id="assignedUsersTable">

        <tbody>

        <?brail

                outputUsersList(assignedUsers, false, true)

        ?>

        </tbody>

      </table>

    </td>

    <td style="BORDER: #f56 1px dotted">

      <table id="unassignedUsersTable">

           <tbody>

           <?brail

                outputUsersList(unassignedUsers, true,  false)

           ?>

           </tbody>

      </table>

    </td>

  </tr>

</table>

<p>

${HtmlHelper.SubmitButton('Save')}

</p>

${HtmlHelper.EndFieldSet()}

${HtmlHelper.EndForm()}

The first part isn't interesting, since we already seen it before, what we will look at is everything below the "Users Assignments"  paragraph. We have the assignedUsersDiv, where we put all the users assigned to this project in hidden inputs. Notice that all those hidden input fields are named the same: "project.Users", this will become important when we'll save the project.

We then define a table, and use the method outputUsersList() to output the correct list to the page. If we would run the page now, we'll see something like this:

I am not a designer, and the HTML above has some hardocded values instead of using CSS, but I'm not doing this series to show you how to build pretty sites. :-) Do you see the >> and << signs near the users? This will allow us to simply move a user to or from the project.

Assume for a moment that this page was written in ASP.Net, and that a post-back is required to move a user on/off the project. I now want to add three users and take one away, how much time will this take me? About a minute or two, assuming that the connection and server are fast. And that is for doing no processing whatsoever excpet move a text box from one spot to the other.

The way we are doing this is much more user friendly (at the cost of being much less developer friendly, but that is the usual balance). Okay, we've seen the HTML code and the way it looks like, now let's move on to everyone's favoriate buzzward, Ajax:

<script lang="javascript">

     function addToProject(userTag)

     {

           Element.remove(userTag);

           $('assignedUsersTable').tBodies[0].appendChild(userTag);

           toggleLinks(userTag);

           var i =0;

           var hidden = document.createElement('input')

           hidden.type='hidden';

           hidden.value = userTag.getAttribute('userId');

           hidden.id = hidden.name = 'project.Users';   

           $('assignedUsersInputs').appendChild(hidden);

     }

     function removeFromProject(userTag)

     {

           Element.remove(userTag);

           $('unassignedUsersTable').tBodies[0].appendChild(userTag);

           toggleLinks(userTag);

           for(var node = $('assignedUsersInputs').firstChild;node !=null; node = node.nextSibling)

           {

                if(node.nodeType != 1) //input

                     continue;

                if(node.value == userTag.getAttribute('userId'))

                {

                     Element.remove(node);

                     break;

                }

           }

     }

     function toggleLinks(userTag)

     {

           Element.toggle($(userTag.id +'.addToProject'),

                $(userTag.id +'.removeFromProject'));

     }

</script>

We'll start with the simplest function, toggleLinks(), it takes a single element, and using its id, it find the related addToProject and removeFromProject links, and toggle their visibility. The Element class is part of Prototype, and provides many useful methods.

The addToProject() function will remove the element it gets from the page and add it to the assignedUsersTable and switch the links state. Then it creates a hidden input field and stores it in the originally named assignedUsersInputs <div>. This <div> is used merely as a place holder in the DOM for all the hidden fields. One super important thing to remember. An input field you create with javascript must have a name, otherwise it won't be sent to the server! I lost more than a little bit of time over this. (Thanks, Jeff!)

We're setting all the input fields to use the same name and id. The id I don't care much about, but it's important that they'll all have the same name, since we'll use that in our controller code.

The removeFromProject() function does pretty much the same thing, physically move the element from one container to another, toggle the links and then search through the children of assignedUsersInputs for the hidden field that match the user, so it can remove it.

Okay. This is it. We're done with all the client code! I'm thinking about throwing a party just for that...

Now to the SaveProject() method, which should now handle saving the assigned users for this project. How hard do you think this will be? Let's find out:

    public void SaveProject(
        [ARDataBind("project", AutoLoadUnlessKeyIs = 0)]Project project,

        [ARFetch("project.DefaultAssignee.Id",
                       Create = false, Required = true)] User defaultAssigneee,

        [ARFetch("project.Users")]User[] assignedUsers)

    {

        RenderMessage("Back to Projects", "Projects");

        try

        {

            if (project.DefaultAssignee == null)

                project.DefaultAssignee = defaultAssigneee;

            //Remove all assigned users from the project, since the autority

            //on who is assigned to the project is now the assigned users in the page.

            project.Users.Clear();

            Array.ForEach(assignedUsers,

                delegate(User users) { project.Users.Add(users); });

            project.Save();

            Flash["good"] = string.Format("Project {0} was saved successfully.", project.Name);

        }

        catch (Exception e)

        {

            Flash["DataBindErrors"] = project.ValidationErrorMessages;

            Flash["bad"] = e.Message;

            Flash["Debug"] = e.ToString();

        }

    }

That is a lot of attributes, I head you say, and I agree. But it's better than saying, that is a lot of code...  Those attirbutes saves quite a bit of time, and if you don't like them, you can access their functionality without losing anything. Personally, I like this ability, but I know that opinions about this are divergent.

What do we have here, then? We have the project and defaultAssignee parameters, which we had before, and a new one, an array of assignedUsers. I talked about ARFetch in previous posts, it take an id and gives you the matching object. It also works on arrays, so I don't need to do anything to get the list of users the admin assigned to this project. ARFetch knows to do this because all those ids were named project.Users, so it can just grab all the parameters with this name and load them by their ids.

One thing that I have to do manually, however, is to add the assigned users to the project. MonoRail will not do this for me, and that is a Good Thing. MonoRail doesn't know about the semantics of the operation, in this case, I want to remove all the users already assigned, and add the ones the admin assigned now, but what happens when I want something else? MonoRail can't tell, so it's giving us the live objects, and leave the last step for the business logic to handle.

As you can see, it's not much of a burden, we clear the Users collection, then add all the users the admin specified to the project and save the project. That is nearly it. We need to pass the assignedUsers and unassignedUsers to the view from the CreateProject action as well. Since I'm going to be duplicating code here, I'm going to refactor it out and put it in AddAllUsersToPropertyBag(), since this is the common dominator between EditProject() and CreateProject().

Okay, that was a long post. Just a couple of things before I wrap things up, just think of how much work we did in this page. We've an Ajax interface, we load and save complex data to the database, including managing many to many relationships. I may be a partial observer, but I think that we did all that with a minimum of fuss, and not much complexity.

Next, we will talk about some latent bugs and adding some more validation to the application. Then, it'll be time for the Big Security Review. I got another post coming soon, so I'll update the binaries on the site when I post that one, and not for this.

My Projects | BrailOpen Source | CastleProgrammingWebsite
time to read 13 min | 2499 words

Stefan asks What Is Wrong With Javascript, I think that I've at least a partial answer to that. Here are my reasons:

  • Until very recently, Javascript was just too slow by far to consider writing applications with it.
  • Lack of a rich class library.
  • Until recently, lack of good debugging tools.
  • The browser is a really sucky place to develop in.
  • The impression of being a Toy Languague.
  • Lack of strong typing, this bytes me so often that I feel like screaming. I missed a semi column, or misspelled a varaible... go figure when/where this is going to blow (usually silently).
  • The browser is not a nice place to work with.
  • Easily modificable by the users.
  • Easily disabled by the users.
  • While the langauge is mostly standartize, there were (and are) problems with the environments that you work with, which makes life painful.
  • No typing is annoying to me.
  • Did I mention that the browser is not an application platform?

Maybe I'm bitter, but I'm butting my head against the wall in a relatively simple JavaScript problem for a couple of days now, and this is not a new feeling. Everytime that I need to do something with Javascript I approach it with disdain, not because the langauge is bad, I actually enjoy a lot of the things that I can do with Javascript. I don't like it because I know that I'm going to spend quite a few hours chasing stupid stuff all over the place.

For instance, take the most recent puzzle, creating input controls dynamically and sending them to the server:

<script language="javascript" type="text/javascript">

function addUser()

{

    var users = document.getElementById('users');

    var hidden = document.createElement('input');

    hidden.value = 'foo';

    users.appendChild(hidden);

    var i =0;

    for(var node = users.firstChild;node !=null; node = node.nextSibling)

    {

        if(node.nodeType != 1) //input

                continue;

            node.id = 'project.Users['+i+'].Id';

        i++;

    }

}

</script>

And the HTML:

<form id="form1" runat="server">

    <div id="users">

        <input id="Button1" type="button" value="Add User" onclick="javascript:addUser();" />

    </div>

    <asp:TextBox ID="PostBackValues" runat="server" Height="225px" TextMode="MultiLine" Width="596px"></asp:TextBox><br />

    <asp:Button ID="submit" runat="server" Text="submit" OnClick="submit_Click" /><br />

</form>

Now, pressing the add users cause a nice text input to be added to the page, I then hit sumbit, and watch the result from this code-behind:

protected void submit_Click(object sender, EventArgs e)

{

    foreach (string key in Request.Params.AllKeys)

    {

        PostBackValues.Text += string.Format(" {0}: {1}\r\n", key, Request.Params[key]);

    }

}

I don't see the values I just put in. Even more important, I can't see them in the request stream when I'm using Fiddler to check this. What happened to them, I don't know, and all my debugging turned out blank. I know that there is a two line fix for this, and probably a reasonable explanation, but getting to this point wasn't a fun process, and hitting a wall like that isn't uncommon for me when using Javascript. I had similar problems doing some smart UI with tables & colors in Javascript, especially since I wanted to do this on both IE and Firefox.

time to read 43 min | 8580 words

Okay, here we go again. One thing that I forgot to mention in the last post is how you access the pages you created. You simply need to use the following URLs:

Before we continue, a couple of words. When I started this series, I had the intention to make it possible to use this with the latest release of Castle. Unfortantely, while writing this series, I encountered a couple of bugs in Brail (and there has been some other changes to MonoRail) that meant that what I write here is relevant to the newest bits. You can get the bits from the Castle Subversion repository.

Just as a note, I noticed that editing Brail documents in VS.Net isn't much fun. I highly recommend using SharpDevelop to edit the views. I support boo and it has much nicer code highlighting than the one you get from VS.Net. That said, I made some modifications to Brail so you could write valid XML documents using:

<?brail

  import Boo.Lang

 

  for i in Builtins.range(3):

    output i

  end

?>

You can still use the <% and %>, but I will use the <?brail ?> style from now on. Note: you can't mix the two in a single page.

After you finish admiring those lovely page, (and curse me for making you download and compile the latest bits) you'll probably want to add some user interaction to the mix, wouldn't you? Let's add editing support for the users.

First, let's us add an edit link to the users page:

<table border="1" width="70%">

     <tr>

           <td>

                <B>Id</B>

           </td>

           <td>

                <B>Name</B>

           </td>

           <td>

                <B>Email</B></td>

    <td></td>

     </tr>

<?brail

     for user in allUsers:

?>

     <tr>

           <td>

                ${user.Id}

           </td>

           <td>

                ${user.Name}

           </td>

           <td>

                ${user.Email}

           </td>

    <td>

      <?brail

        output HtmlHelper.LinkTo("Edit","admin","EditUser",user.Id)

      ?>

    </td>

     </tr>

<?brail

     end

?>

As you can see, I moved to use the <?brail ?> notation. I bolded the new parts. Refresh the page again (no need to recompile anything). You should see a nice "Edit" link from the users list. It doesn't do anything ( and you'll get an error page if you'll try to follow the link.

We'll start by create the EditUser method on the AdminController class, with a single integer as a parameter:

    public void EditUser(int id)

    {

        User user = User.TryFind(id);

        if (user == null)

        {

            RenderView("NoSuchUser");

            return;

        }

        PropertyBag.Add("User", user);

    }

It's a simple method, it gets the id of the user (this is handled by MonoRail automagically) and then try to pull the user from the database. If it doesn't exists, it redirect to the error page. If it does exist, it add the user to the property bag and we're done with the controller. Now it's time to create the views.

The error view is really simple, it's actually just this:

A user with the id: ${Id} could not be found.

Save it to: Views\Admin\NoSuchUser.boo, and now we create the more complex view at Views\Admin\EditUser.boo:

<?brail

     name = ""

     email = ""

     id = ""

     password = ""  

     if IsDefined("user"):

        name = user.Name

        email = user.Email

        id = user.Id.ToString()

        #remember, no getter for the password

        password = "Dummy Password"

     end

    

?>

${HtmlHelper.Form('SaveUser.rails')}

${HtmlHelper.FieldSet('User Details:')}

${HtmlHelper.InputHidden('id', id)}

<table>

     <tr>

           <td>

                ${HtmlHelper.LabelFor('user.Name','User name:')}

           </td>

           <td>

                ${HtmlHelper.InputText('user.Name',name, 50, 50)}

           </td>

     <tr>

     <tr>

           <td>

      ${HtmlHelper.LabelFor('user.Email','User email:')}

    </td>

    <td>

      ${HtmlHelper.InputText('user.Email',email, 50, 50)}

    </td>

     </tr>

     <tr>

           <td>

      ${HtmlHelper.LabelFor('user.Password','Password:')}

    </td>

    <td>

      ${HtmlHelper.InputPassword('user.Password',password)}

    </td>

     </tr>

</table>

${HtmlHelper.SubmitButton('Save')}

${HtmlHelper.EndFieldSet()}

${HtmlHelper.EndForm()}

I'm using a couple of new things here. The HtmlHelper is just a way to pack a bunch of HTML code into reusable form. You can think of the helper as MonoRail version of the WebForms Controlls, although that isn't 100% accurate.

The part in the beginning require some explanation. Brail is partially late bound language. If it can, it detects types of variables during compilation, but if it's not possible, it will wait for runtime to discover what it can about the variables. Because it's possible that the the controller will not pass the user varaible to the view (and we'll see why in a second), we check to see if the variable exists before using it.

One thing that you should notice is that when ever I use the ${...} syntax and I need to pass a string to a method I use the single quote character '. Trying to use the double quote character " will cause an error.

Pay attention to the naming convention of the items in the form, they all start with "user.", and that will be very helpful down the road.

Okay, now run the page (http://localhost:8080/admin/EditUser.rails?id=1) and see what you get. It's functional, if not pretty. But still we have a problem, we now display the users details in an editable way, but we can't save it. Let's build the SaveUser action.

We have several options we can use:

We can do it the hard way, by manually pulling the data out of the Request.Form (relax, I can hear the groans up to here).  Or, we can allow MonoRail to pull the data and pass it to us using the method parameters. We can even allow MonoRail to construct an object out of the fields. But we'll take the really easy way and let MonoRail do nearly all the work.

We need to do the following in order to make it all work seamlessly:

  • Add reference to: Castle.MonoRail.ActiveRecordSupport.dll
  • Add reference to: Castle.Components.Binder.dll
  • Change the base class of AdminController to be ARSmartDispatcherController.

I present to you, in its entirely, the SaveUser() method:

    public void SaveUser([ARDataBind("user", AutoLoad=true)]User user)

    {

        RenderView("GenericMessage");

        try

        {

            user.Save();

            Flash["good"] = string.Format(

                "User {0} was saved successfully.", user.Name);

        }

        catch (Exception e)

        {

            Flash["bad"] = e.Message;

        }

    }

Okay, we have the ARDataBind attribute for the user parameter, and this tells MonoRail that all the parameters that start with "user" belong to this class, and that it is an Active Record class. Since MonoRail has Active Record integration, it simply pull the correct record from the database and give it to us, with the updated values! Then we can decide what to do with it. Here, I'm just saving it to the database and putting a message to the Flash (which is what we use to put transient messages, it's implemented the same way the PropertyBag is, but things on the Flash are usually error or one time messages, things that can appear and disappear.)

We then choose to render the GenericMessage view, which is located at Views\Admin\GenericMessage.boo and looks like this:

<font color="red">

<?brail

 if IsDefined('bad'):

  output bad

 end

?>

</font>

<font color="green">

<?brail

if IsDefined('good'):

  output good

end

?>

</font>

The CSS purist will go crazy, but this is the quickest way to do it :-) Okay, we're done, let's go home.

Oh, I see that you're still here, well, there are a couple of other things that we need to do before we can move on. We got a couple of problems with this approach. We aren't really verifying string lengths, for instance, and a user can change its name to one that already exists. While we can let the database handle this, I really want to make some check in my code as well (and I'm certainly not going to verify email adresses using the database).

We'll go back to our User class and make a couple of changes.

To start with, we need to change the base class of User from ActiveRecord<User> to ActiveRecordValidationBase<User> Then, we need to add a couple of attributes to the Name and Email properties, here they are:

    [Property]

    [ValidateEmail, ValidateLength(50)]

    public string Email

    {

        get { return _email; }

        set { _email = value; }

    }

 

    [Property]

    [ValidateIsUnique, ValidateLength(50)]

    public string Name

    {

        get { return _name; }

        set { _name = value; }

    }

Now it will automatically validate before save. I'll leave building the EditProject site as an excersize for the reader. I meant to talk about creating / deleting users today, but I fear that I needed to refresh myself with a couple of things (and fix / improve Brail in the process).

I'll do that tomorrow, and then you can see just how much reuse we can reap from today's work.

Tiny IIS Tip

time to read 2 min | 220 words

Yesterday I wrote a long post and lost it because my session has timed out and I had no warning. Today I got a question about this subject, how to move a client browser to a logout page when their client expires.

The solution for this is very easy, actually (but won't help losing work because of a session timeout, unfortantely), you can use this command in the HTML:

<META HTTP-EQUIV="Refresh" CONTENT="1170; URL=/logout.aspx">

This requires work, though, and can get a little tedious after a while. But what I found is that you can set IIS to send it automatically on all requests, so you set it up once, and forget about it. Very simple to setup (Web Site Properties -> Http Headers -> Add), and will work on everything without a single line of code changing.

If you want to prevent users from losing work, you need to use javascript to popup a warning, or make sure that the state is not lost if the client session timed out. (The warning in ati.com site is extremely annoying, for instance).

time to read 1 min | 144 words

I guess the geine has decided that it should extend its reach, now I've got a bug in Google. More spesifically, google analystics.

I registered for a site, put the javascript in the <head>, and waited, and waited, and waited. It's been over 24 hours, and it's still claiming that my site doesn't have the proper javascript installed. What is wrong with it? It's right there, stop denying the truth!

The Google Analytics tracking code has not been detected on your website's home page. For Analytics to function, you or your web administrator must add the code to each page of your website.

 

time to read 1 min | 164 words

Joel has a very interesting article about the reasoning behind spam blogs. As always, it's money that is the cause, but this time the "victim" is Google. And that gives me some hope that it will stop.

A spam blog is usually just an aggeration of links from other sites which usually has a Google AdSense account. Then a zombie network is used to click on the links, and the spammers colleccts the profits. The interesting thing here is that Google is both the target and the supplier here, since BlogSpot, which it own, is one of the bigger targets for scripted-blog-generation. Google is losing money because of this, and that gives them a pretty strong incentive to stop that. Further more, they are losing the trust of the advertisiers. If I know that paying for AdSense would get me 50% visits from some spammer, I'm really going to consider whatever to pay for that or not...

time to read 1 min | 181 words

VS 2005 has a great concept called snippets. It's a wonder it didn't have it before, since it's a very basic item, but at least we got it now. There is a community site where you can get snippets from the community.

All in all, I think that this is a laudable idea, but I find the implementation problematic.

Take a look at the site, it's a very clean UI, and you can see the last 5 entries. This is well and good, but I couldn't figure out how to get anything else. I consider myself pretty handy in computers, and this is just incomprehensible. Maybe they read Scoble's posts about search and took it too far?

I want a list of categories, or even a flat list. I want to browse the site, not search it. I would've been disappointed if they wouldn't have search, but having a search with magic pharses (see "recent 5" on the front page? "recent 50" doesn't work) is just useless.

time to read 1 min | 153 words

I just re-read the license that I've on my site. It started by being the standard BSD license, and I simply removed the ALL CAPS from the no warrany clause. Now, I don't go around reading licenses very often, especially not mine, but I made some (minor) changes to the site, so I was browsing around randomally and happened to read it.

Check it out:

Redistributions of source code must retain the above copyright notice,  this IList of conditions and the following disclaimer.

I know that I didn't put it there in purpose, and I can't find anywhere else that had this mistake. It's fixed now, but it's strange...

time to read 2 min | 347 words

Note: This is an online report, which means that I'm writing this post as I'm upgrading.

I downloaded the binary distribution edited the web.config to point at my database and then copied the directory to the server, overwriting everything there.

In retrospect, I'm not sure this is very wise, since I can't remember how I change the default look and feel of Cuyahoga. OTOH, I have it on a backup, so that is not a big issue.

I'm copying via SmartFTP's threaded queue. One thing to notice there is that there seem to be be problems if you try for really high number of threads (25 and above). The problem doesn't seem to be in SmartFTP itself, but on the server, and it's related to locked files. I'm not sure what is wrong there, but I'm currently using 10 threads to upload, and it seems to be working fine.

This version of Cuyahoga should offer some performance gains (mainly because of the upgraded version of NHibernate) and fixes some problems with regard to over-eager caching.

Hm, it looks like SmartFTP didn't copy the Bin directory. That is strange, I'll copy it manually.

Okay, that worked, and I got the nice installer that upgraded my database. I still got some problems with regard to the positioning, it was a bug in Cuyahoga 0.8.2, which I think has to do with deleting sections, but it should be fixed now. It doesn't automatically fix the problem, though, and I had to go and fix that manually.

There wasn't any problem with my layout, seems that I created a new template instead of overwriting an existing one. The one problem I'd is overwriting the orange RSS image with a blue one, and that took a second to fix.

I needed to upgrade the downloads module manually. There used to be a bug where downloads would sometimes freeze midway, I hope that is fixed.

FUTURE POSTS

No future posts left, oh my!

RECENT SERIES

  1. Production Postmortem (52):
    07 Apr 2025 - The race condition in the interlock
  2. RavenDB (13):
    02 Apr 2025 - .NET Aspire integration
  3. RavenDB 7.1 (6):
    18 Mar 2025 - One IO Ring to rule them all
  4. RavenDB 7.0 Released (4):
    07 Mar 2025 - Moving to NLog
  5. Challenge (77):
    03 Feb 2025 - Giving file system developer ulcer
View all series

RECENT COMMENTS

Syndication

Main feed Feed Stats
Comments feed   Comments Feed Stats
}