Problem

The accordion control provided by the Ajax Control Toolkit is a great control to display expandable items in a list. Depending on the amount of data you want to display when an item is expanded, the page that contains the control can quickly become huge because you have to load in the page all the data shown in the expanded version of all the items.

Solution

Here comes the Load On Demand accordion control !

Instead of loading all the data up front, it fetches the data of the selected item only using Ajax.

imageimage

Overview

On your page, you will have two invisible panels, one that contains the html code you want to display whenever you are loading an item, and a second that is wrapped in an UpdatePanel control, that will retrieve the html code of the selected item. When the ajax request of the UpdatePanel finishes, you simply copy the content into the content panel of the selected item.

On the asp.net side

The code I use for the invisible loading panel:

<div id=”loadingPanel” class=”invisible”>
    <img src=’ajax-loader.gif’ /> Loading…
</div>

The code I use for the update panel:

<asp:UpdatePanel ID=”updatePanel” runat=”server”>
    <ContentTemplate>
        <p>Invisible update panel:
        <div class=”invisible”>
            <asp:Panel ID=”panelUpdated” runat=”server”>
               <uc1:DetailedPlayerView ID=”detailedPlayerView” runat=”server” />
            </asp:Panel>
            <asp:HiddenField ID=”hiddenPlayerId” runat=”server”/>
            <asp:Button ID=”btnUpdate” OnClick=”btnUpdate_PlayerView” runat=”server” Text=”Button” class=”invisible”/>
        </div>
        </p>
    </ContentTemplate>
</asp:UpdatePanel>

When a user clicks on the header of an item, a javascript function is called, performing the following actions:

- Save the selected panel in a javascript variable to refer to it when the callback function is called

- Store the selected data item id in an hidden field (to be able to display data specific to that id)

- Copy the content of the loading panel into the content panel of the selected item

- Click the button btnUpdate to update the UpdatePanel.

The javascript code looks like this:

var selectedContentPanel = “”;

function fetchContent(contentPanel, playerID)
{
    // save selected panel
    selectedContentPanel = contentPanel;
    // save playerId value in hidden field
    $get(“<%= hiddenPlayerId.ClientID%>”).value = playerID;
    // show the loading picture
    $get(contentPanel).innerHTML = $get(“loadingPanel”).innerHTML;
    // call postback
    $get(“<%= btnUpdate.ClientID%>”).click();
}

You need to subscribe to the endRequest events to be able to update the content panel based on the UpdatePanel content:

Sys.WebForms.PageRequestManager.getInstance().add_endRequest(EndRequestHandler);

function EndRequestHandler(sender,args)
{
    // update selected panel with postback result
   $get(selectedContentPanel).innerHTML = $get(“<%= panelUpdated.ClientID %>”).innerHTML;
}

On the C# side

You can update the content of the UpdatePanel in the event handler of the Click event of the invisible button in the UpdatePanel

protected void btnUpdate_PlayerView(object sender, EventArgs e)
{
    var playerId = long.Parse(hiddenPlayerId.Value);
    var player = _players.Find(x => x.PlayerId == playerId);
    detailedPlayerView.Player = player;
}

Calling fetchContent

To be able to call the javascript fetchContent method when the header is clicked, you need to add a onClick attribute. Because I want to be able to pass the id of the content panel, I found it easier to add the onClick attribute on the C# side.

I subscribed to the ItemDataBound event of the Accordion control and handled it this way:

protected void accordionPlayers_ItemDataBound(object sender, AccordionItemEventArgs e)
{
    if (e.AccordionItem.ItemType == AccordionItemType.Content)
    {
        var player = (PlayerInfo)e.Item;
        var contentPanel = (Panel)e.AccordionItem.FindControl(“panelContent”);
        var headerPanel = (Panel)e.AccordionItem.Parent.Controls[0].FindControl(“panelHeader”);
        // add to the header panel an onclick attribute that passes the id of the player and the content panel
        var fetchContent = string.Format(“fetchContent(\”{0}\”, {1});”, contentPanel.ClientID, player.PlayerId);
        headerPanel.Attributes.Add(“onclick”, fetchContent);
    }
}

I hope you will find this blog post useful, don’t hesitate to leave a comment!

Cheers

Today I had to undo the pending changes of my user from a different workspace and I used the following command (from a Visual Studio command prompt on the TFS server):

tf undo /workspace:workspacename /server:http://urltotfs:port /recursive “$/projectname”

The fact that sql2005 can host the CLR was a big selling point. I kind of always knew that it was possible to extend and create new SQL functions in C# but never really took the time to do it myself.

What do we want to do?

I’d like to be able to create a function that works like the .NET String.Join static method. Here is a sample query that would demonstrate the use our new aggregate function:

select PizzaRecipeId, dbo.strjoin(IngredientName)
from PizzaRecipe
group by PizzaRecipeId

This query would output something like:

1 Cheese, Chicken, BBQ Sauce
2 Cheese, Ham
3 Cheese, Chicken, Pineapple

Let’s do it :)

The first thing is to write the c# code.
Create a new class library project and add a new class called Concatenate. Our new class must:
- have a serializable attribute
- have the SqlUserDefinedAggregate attribute
- implement IBinarySerialize
- have the following 4 methods (there is no interface for them)
o public void Init()
o public void Accumulate(SqlString value)
o public void Merge(Concatenate other)
o public SqlString Terminate()

The implementation itself is straightforward, using a string builder to store the data:

[Serializable]
[SqlUserDefinedAggregate(Format.UserDefined, MaxByteSize=8000)]
public class Concatenate : IBinarySerialize
{
private StringBuilder sb;

public void Init()
{
sb = new StringBuilder();
}

public void Accumulate(SqlString value)
{
if (sb.Length != 0)
{
sb.Append(“, “);
}

sb.Append(value);
}

public void Merge(Concatenate other)
{
sb.Append(other.sb);
}

public SqlString Terminate()
{
return new SqlString(sb.ToString());
}

#region IBinarySerialize Members

public void Read(BinaryReader r)
{
sb = new StringBuilder(r.ReadString());
}

public void Write(BinaryWriter w)
{
w.Write(sb.ToString());
}

#endregion
}

If the project compiles, let’s continue with the commands that will actually load the dll into sql server and reference the aggregate function:

create assembly clr_integration from ‘C:\dev\learning\sql2005\ \clr_integration\bin\debug\clr_integration.dll’ with permission_set = safe
GO

create aggregate strjoin(@input nvarchar(200)) returns nvarchar(max)
external name clr_integration.Concatenate

The permission_set is safe because the assembly does not perform any interop or contains any unsafe blocks.

Let’s create a table and some dummy data:

if (object_id(‘PizzaRecipe’) is not null) drop table PizzaRecipe
create table PizzaRecipe
(
PizzaRecipeId int NOT NULL,
IngredientName nvarchar(200)
)

insert PizzaRecipe values(1, ‘Cheese’)
insert PizzaRecipe values(1, ‘Chicken’)
insert PizzaRecipe values(1, ‘BBQ Sauce’)
insert PizzaRecipe values(2, ‘Cheese’)
insert PizzaRecipe values(2, ‘Ham’)
insert PizzaRecipe values(3, ‘Cheese’)
insert PizzaRecipe values(3, ‘Chicken’)
insert PizzaRecipe values(3, ‘Pinnaple’)

Everything should now be ready and it’s time to test it :)

Writing the aggregate function was actually simpler than what I imagined. It’s now just a matter of detecting when this type of feature can be used.

TFS: removing _svn folders

October 11, 2007

I recently had the need to clean up a project in TFS. When it was imported, little care had been put in making sure that only the required files would be on source control.

The most noticable folders that were to be removed from TFS were: ‘_svn’, ‘bin’ and ‘obj’.

As I’m not a big fan of doing things manually (yes, even if doing it manualy is faster – well in that case it was probably faster to write the script) I ended up trying to remember how to do those fancy FOR loops in DOS :)

First, build a list all the folders (or files) you want to remove:

dir /s /b _svn bin obj > files_to_delete

Creating that file is handy and you can edit it before running the following query:

for /f “delims=” %g in (files_to_delete.txt) do tf.exe delete “%g” /login:DOMAIN\login,password

note: make sure that tf.exe is in your path, or replace it with the full path.

you can now refresh your pending checkin in visual studio and check it in !

I have read quite a few times that exceptions were slow. I came to wonder about how slow they actually were? Is that big enough to care about it ? After all, hardware is pretty fast nowdays, isn’t it?

Well it turns out that throwing and catching an exception takes about 6ms on my machine (Core Duo, 2ghz)

I you want to know why it takes 6ms, have a look at this fantastic post :)

Now, a few milliseconds, it does not look like a long time, but think about a web server, handling hundreds of requests at the same time. If each request uses a few Int.Parse that fails(or whatever code that may throw exceptions) , that could end up being quite significant.

This is a simplified version of the code I used (removed the loop to get an average value):

private void Execute()
{
    Stopwatch sw = new Stopwatch();
    sw.Start();

    ThrowAndCatchException();

    sw.Stop();

    Console.WriteLine(“Time taken: {0}ms”, sw.ElapsedMilliseconds);
}

private void ThrowAndCatchException()
{
    try
    {
        throw new Exception();
    }
    catch (Exception) { }
    {
    }
}

Have you ever wondered if visual studio can hex edit files ? Well it’s actually possible but not very intuitive…

Go to File -> Open -> file
Select the file that you want to open, the Open button state will change to enable
Click the small drop down list at the right of the Open button and select Open With

You will get a list of additional editors, choose Binary Editor :)

This morning I wanted to share TFS queries that I created in the “My Queries” folder with a team mate. I found that the best way of doing that was to save them as files (.wiq), send them by email and he just had to open them and adding them to his “My Queries” folder.

How to save a query to a file:

1) Right click on the query, select view query.
2) Go to “File” -> “Save QueryName As”
3) Select File

This will create a QueryName.wiq file.

How to add a .wiq file to your “My Queries” folder ?

1) Double click on the .wiq file
2) Go to “File” -> “Save QueryName As”
3) Select “My Query (only visible to me)”

Enjoy :)

I attended the Sydney’s Microsoft Ready Summit today. It was a nice event in the Darling Harbour convention centre, targeted to developers, IT professionals and IT decision makers. The developer sessions were about the new Windows Vista user experience and Office 2007.

I took a few notes about the things I thought were worth remembering. I hope that I will interest some people that did not make it to the event.

The developer keynote was presented by Andrew Coates who is a Microsoft evangelist and most of the other sessions were presented one of my Readify co worker: Mitch Denny.
We started with the Expression Interactive Designer. This is a lot like the Macromedia Flash editor, but Expression will generate XAML files. This new format (at the heart of WPF) should be the common language talked by both developers and interface designers (i.e. the same XAML file can also be opened in Visual Studio). It will hopefully reduce the number of cases where developers have to integrate a full screen bitmap, tiled into 200 tiny pieces J

A short look at PeerNet was the occasion to try finding legal uses of the P2P technology. Vista provides a new communication stack, what can we do with it ? A few examples were discussed : a collaborative meeting where an ad hoc network can be created using auto discovery features, a data synchronisation scenario where a Readify ultra mobile worker would keep all his devices up to date in an efficient way.

The search API was briefly overviewed by talking about the “New York Times reader” that integrates with the Vista search engine.

A humoristic history of icons (starting with the plain old 255 colours win95 style) introduced the “on the fly” generation of the high resolution Vista icons. It’s possible to write a custom preview handler for your own file format. No doubts that end users will quickly expect to be able to view something relevant about the files they are browsing within the Explorer window.

We had a look at the gadgets that can be added to the vista sidebar. They can be created in either HTML or in WPF. Gadget files are in fact just zip files. Anyone can rename a gadget and have a look at it. If you want to create one, it’ll probably be easier to start by modifying another one that does something similar. That’s a good way to learn how they work.

What would Vista be without the wonderful glass feature? Well first, it seems that whole point of the glass effect, beyond the fact that it’s nice looking, is that you get to focus to the important stuff inside the window. We had a look at how to use glass effect in your own application, using winforms or WPF. In both cases, a reference to an unmanaged API is required. Search for DwmExtendFramIntoClientArea and DwmApi.dll for examples. Warning: don’t misuse that functionality! We do not want to see new application using the glass effect everywhere; it would defeat the whole purpose J

A lot of effort has been put into redesigning the standard dialog windows. They are a lot simpler, with most options hidden from the user. The idea is that the dialogs now focus on what’s important. For example, the new open file dialog has got very few options displayed by default. The basic action performed by the dialog is to give a name to the file. Selecting a folder is not the primary concern anymore because the application would have probably set the current path to some meaningful folder and that anyway with the new search and filtering features of vista, saving all the documents in one single place should not be an issue anymore. All the new standard dialogs can as usal be extended (and it is even supposed to be easier than what it used to be). Search for the following keywords if you are interested: IFileDialog, IFileOpenDialog.

Last but not least, the new xml Office file format. The big change is that xml is now the default format when working with Office 2007 documents. Once again the files are just zip archives. You can rename your .docx to .zip and start to have a look inside by yourself. That’s pretty cool! Pictures are stored in their binary format for efficiency reason (base64 would make it 1.33 times bigger). It’s quite cool to be able to play directly with the list of images inside a PowerPoint document. You can manipulate the xml yourself or you can use a set of new dotnet classes (search for System.IO.Packaging). As far as compatibly is concerned, patches that can open/edit/save the new xml formats are already available for the previous versions (up to office 2000). There was a good question: what about password protected documents? The answer is that it is handled automatically by the zip format which supports encryption on a file by file basis.

When using azman, it’s better to reference the following dll, rathing than browsing in the list of registred COM components. You’ll get a much more friendly namespace.

Azman reference path:

C:\WINDOWS\Microsoft.NET\AuthMan\1.2\microsoft.interop.security.azroles.dll

I came across an interesting issue last friday.

There was an empty Page_Init method in some web control. I removed it, thinking that it would not do any harm.

Well, in fact, it was actually hidding the base class Page_Init method which used not to be executed. Of course, after my change, the base method was called and that caused a crash in the application :)

What I have learned from that:

When creating a base class that requires initialization, it’s better to use the OnInit method, rather that the Page_Init.

When using OnInit, the derived class will have to explicitly use the override keyword, which may help other developers avoiding this kind of issues.