Skip Navigation Links
Search site. 
Powered by Google
Darren Neimke (Me)

My Book

Readify

">ASP.NET MVP


Interesting Portals 

NetVibes
This portal feels similar to PageFlakes in many ways but I love their gallery. They also have a feature whre certain chrome elements only become visible when you hover over the web part.

Xtra
A New Zealand news portal. I especially liked the content rotator web part at the top of the middle row. Seems like a nice way to allow a user to browse through data.

 

Posts Archive 

Posts for: Apr 2007

Expression and Silverlight videos

There appears to be a lot of awesome video content washing around today in line with the kick-off of MIX07. 

Microsoft Expression Knowledge Center Videos

Dozens of Sliverlight videos from the Silverlight site

A bunch of Silverlight screencasts from Tim Sneath's team

posted on 5/1/2007 9:14:49 AM ( 0 Comments )


Google Reader - Darren's shared items

Google Reader has a cool feature that allows you to mark items in your reading feed as "Shared".  When you mark items as "Shared" they appear on a separate shared items feed: 

Link to Google Reader - Darren's shared items

Subscribing to the feed at that page will allow you to see the coolest things that I read each day! smile_regular

Even cooler is that Google give me a free web part that I can stick on my blog which displays a list of the recent blog posts that I've read:

Google Reader Shared Items Web Part

I love you Google Reader heart

posted on 5/1/2007 8:46:22 AM ( 0 Comments )


Get Real - Managing the Development Process

Recently I started with my new role as manager of the Development Center at Readify.  You can read the announcement of this appointment on the Readify Media Release page (subscribe to the RSS feed while you're there smile_wink)

It's a great role, and one that allows me use the latest technologies to put together solutions for the Readify business and beyond.  Technology aside, I stand by my quote in the media release announcement:

Whilst working with the latest technology always presents unique challenges, the greatest challenge that I see for Readify is having the right processes in place to continually deliver value to our customers...

It's one thing to know the tools and when to use them but you still need to have a proper plan which underpins the things that you do on a day-to-day basis.  Why is this so important?   Consider that you've been charged with managing a development process.  Without a solid plan where will you be after a year?  Sure you may deliver applications but I believe that a good development process should aim to deliver way beyond just the applications that we build.  A healthy development process can lead the way rather than lag behind the business.  A healthy development process is an incubator for professional development skills.  A healthy development process consistently delivers applications that meet the expectations of its customers.  A healthy development process has healthy communication.

A year is a long time in software development.  With 2 developers, every week is 80 hours.  Every month is 350 hours.  Each quarter is 1040 hours.  The year itself totals nearly 4200 hours and then you need to add your own 2100 hours on top of that.

Being given 6300 hours, 2 crack developers, the latest technology, and the needs of a growing organization is a wonderful opportunity and it's the reason I believe it to be so important to build something of significance.  At the end of the year you will look in the mirror and it's then that you'll know whether you've used all of those resources to build something of value or not.  What could be worse than wasting such a rich pool of opportunity!

Some of the best advice that I can offer to date is that prioritization is of the highest importance.  Pick the things that you believe will get you to where you want to be in a year from now, assign them a high priority, and set sail.

posted on 4/30/2007 8:49:26 PM ( 0 Comments )


What happens when the UpdatePanel does its magic?

The UpdatePanel is the nicest server control we've been given in a long while.  As someone who is a relative newcomer to ASP.NET Ajax programming you could be forgiven for thinking that it was some sort of voodoo magic.  The true beauty of the UpdatePanel lies in it's simplicity.  To use it, you wrap the controls that represent the part of the page that you want to get updated asynchronously within the UpdatePanel's ContentTemplate:

<asp:UpdatePanel ID="up1" runat="server" UpdateMode="conditional">
    <ContentTemplate>
        <asp:Label ID="lbl1" runat="server" />
        <asp:Button ID="btn1" runat="server" Text="Smack" OnClick="btn1_Click" />
    </ContentTemplate>
</asp:UpdatePanel>

When this UpdatePanel is embedded within a page, clicking the button will cause only that fragment of the page to be updated.  Now, as a seasoned ASP.NET developer you will know that there's no real magic here, but the question remains... how does all of that goodness happen? 

Underlying the UpdatePanel is a control called the PageRequestManager which carries out all of the out-of-band calls and the marshalling of HTML back onto the UI.  The PageRequestManager has 2 sides to it - there's an internal server-side component that is used as a sort of Strategy implementation by the ScriptManager for the async postback logic.  For example, the implementation of the AsyncPostBackSourceElementID property on the ScriptManager looks something like this:

public string AsyncPostBackSourceElementID {
    get {
        return this.PageRequestManager.AsyncPostBackSourceElementID;
    }
}

The other side of the PageRequestManager is a client-side component that is used to manage partial page updates and to control the lifecycle of a async postback request.  To better understand the PageRequestManager, consider the UpdateProgress control.  The UpdateProgress control can be used on a page to provide feedback to the user about when async callbacks are occurring.  These are typically used to display the little "Updating..." icons to the user while they await the update of an async callback operation resulting from their UpdatePanel posting back to the server.  The UpdateProgress control makes use of some of the client-side lifecycle events of the PageRequestManger to determine when the async request is beginning and when it is complete. 

There's an excellent article on the Ajax docs site which explains how to work with the events of the PageRequestManager:

Working with PageRequestManager Events

In this article I just want to show how you could use those lifecycle events to disable a button when they user clicks it and re-enable it when the async postback completes.  We'll use the UpdatePanel code listed above as the UI code for our scenario. 

We'll need to wire-up event handlers to the beginRequest event and the endRequest event so that we can write our disable/enable code.  We do that from within the Application's Load event like so:

function pageLoad() {
    with(Sys.WebForms.PageRequestManager.getInstance()) {
        add_beginRequest(beginRequestHandler);
        add_endRequest(endRequestHandler);
    }
}

From within our beginRequestHandler function we will receive an instance of the BeginRequestEventArgs class that we can use to determine the button that was clicked:

var _id = null ;

function beginRequestHandler(sender, args) {
    var e = args.get_postBackElement(); // e is an HTML DOM element
    _id = e.id ;
    SetControlEnabled(true) ;
}

function SetControlEnabled(enabled) {
    if(_id != null) {
        $get(_id).disabled = !enabled;
    }
}

Likewise, in our endRequestHandler we will receive an instance of the EndRequestEventArgs class, although we won't be able to use any of it's properties to determine the client element that was clicked - that's why we stored it in a field from within the beginRequestHandler.  So in the endRequestHandler we simply set the control to enabled:

function endRequestHandler(sender, args) {
    SetControlEnabled(false) ;
    _id = null ;
}

All that remains is to detach our event handlers in the Application's Unload event like so:

function pageUnload() {
    with(Sys.WebForms.PageRequestManager.getInstance()) {
        remove_beginRequest(beginRequestHandler);
        remove_endRequest(endRequestHandler);
    }
}

If you run that code you will see that the Button does indeed become disabled when you click on it until the partial page update is complete.

In looking through this code we've been able to see that there is a component called the PageRequestManager which lives on the client (and also on the server) and which creates a lifecycle of a partial page update.  We saw the beginRequest and endRequest events that are exposed by this control which would hopefully provide some understanding of the framework within which the UpdatePanel can operate.  Other significant things to know about events within the PageRequestManager's lifecycle are:

  1. There is also an initializeRequest event that occurs before the beginRequest - just after the Sys.Net.WebRequest instance that will be associated with the callback has been created.
  2. The EventArgs instances all contain an instance of the WebRequest instance
  3. The EndRequestEventArgs contain information about whether any errors occurred so that you could implement custom client-side error notifications
  4. The EndRequestEventArgs contain the raw JSON data that is posted back from the server

All of these provide us with valuable hooks within which to totally customize the user's experience throughout the partial update of an UpdatePanel.

posted on 4/29/2007 10:03:55 PM ( 1 Comments )


paraesthesia: BlogML 2.0 Impressions

I nice write-up about the experience of someone who has just had an experience using BlogML.  Not only does he discuss the positive points of BlogML but also highlights some of the shortcomings. 

my pMachine exporter, though I won't necessarily become a PHP developer for y'all; I'm a .NET guy who's stuck on a PHP platform, not a PHP guy. I'm happy to contribute my pMachine exporter, but I don't think I'll be working on anything for WordPress (nor will

Source: paraesthesia: BlogML 2.0 Impressions

posted on 4/29/2007 4:28:14 PM ( 0 Comments )


Added the Silverlight logo to my blog

I was browsing around over the weekend and came across Dave Campbell's site which has the cool spinny Silverlight logo on it.  It's the same spinny logo that you can see at the very end of the Silverlight ad.  I think that it originally came from Mike Harsh's blog.

Anyway, thanks to Dave for helping me get it up and running on my own blog.  If you'd like to see it for yourself, click here to be taken to my blog.  You should get to see the spinny Silverlight logo in the top right corner. 

Having now downloaded the WPF/E Feb CTP SDK to play around with and re-familiarized myself with a bunch of links to people who are blogging about this stuff fairly regularly, I'm looking forward to getting stuck into the mechanics of it all.

posted on 4/29/2007 3:31:36 PM ( 0 Comments )


Microsoft's Silverlight Site

I'm really looking forward to building "immersive experiences" using Silverlight.  And I love that cool video ad that they've created to show it off smile_regular

Here's what I don't love... why doesn't the Silverlight site light up the RSS button in my browser.  smile_sad  Marketing types create great videos but I'm sure they really don't get how we actually use the web. 

So it's great that they tell me "Coming Soon - New Silverlight Enabled-Site Coming April 30!", but I'm sure that I'll forget to go back and check it out.

posted on 4/28/2007 11:47:06 AM ( 0 Comments )


ASP.NET Ajax - Not your grandfather’s Javascript

As part of a broader series of posts about the ASP.NET 2.0 Web Part framework I've been playing with ASP.NET Ajax to learn more about how web parts and Ajax work together.  In this post I want to show off some of the new syntax that I've learned for working with client-side Javascript in ASP.Net Ajax, in particular the new syntax for working with DOM elements via classes in the Sys.UI Namespace

Sys.UI is a compatability layer for working with DOM elements via an API that abstracts the developer from the vagaries of the different browsers.  The main 2 types in the Sys.UI namespace are DomElement and DomEvent classes.  DomElement is a class which defines a common set of methods and properties for working with DOM elements and likewise, the DomEvent class provides a consistent, cross-browser API for when working with events in the DOM.

Let's start by taking one of the static methods from the Sys.UI.DomElement to see how this works.  Without the use of the ASP.NET Ajax Client Framework we can write code such as this to get a handle to a DOM element from within a page:

var el = document.getElementById("elementID") ;

This will work in all modern browsers such as IE, Firefox, and Safari but might for browsers that don't support the getElementById function - such as pre-IE5 vintage browsers - then getting a specific DOM element would mean walking the DOM tree.  The DomElement class provides a simple method for performing this function which abstracts away from the developer the need to check which browser is being used.  The following syntax shows how to get a handle to a DOM element using ASP.NET Ajax:

var el = Sys.UI.DomElement.getElementById("elementID") ;

Even easier is the fact that the ASP.NET Ajax Client Library provides a shortcut for performing this action by using a global shortcut reference to the Sys.UI.DomElement.getElementById function - $get.  The following syntax is 100% equivalent to the previous snippet of code:

var el = $get("elementID") ;

Similarly, when working with events we can use static helper methods on the Sys.UI.DomEvent class to attach and remove event handlers to events within the DOM.  Using these shortcuts again shields us from the myriad ways that this is done across the range of browsers that exist.  The following snippet of code is for a sample page which shows how to attach and remove an event handling function to a DOM event:

var e = null ;

function pageLoad() {
    e = $get("foo") ;
    $addHandler(e, "mouseover", mouseoverHandler) ;
    $addHandler(e, "mouseout", mouseoutHandler) ;
}

function pageUnload() {
    $removeHandler(e, "mouseover", mouseoverHandler) ;
    $removeHandler(e, "mouseout", mouseoutHandler) ;
    e = null ;
}

function mouseoverHandler(evt) {
    Sys.UI.DomElement.removeCssClass(e, "normal") ;
    Sys.UI.DomElement.addCssClass(e, "hover") ;
}

function mouseoutHandler(evt) {
    Sys.UI.DomElement.removeCssClass(e, "hover") ;
    Sys.UI.DomElement.addCssClass(e, "normal") ;
}

NOTE: Don't forget that for this page to run you must include an asp:ScriptManager control in your page!

In the code we can see that we use the shortcut syntax of pageLoad to handle the load event on the Sys.Application where we then use the $get syntax to get a handle to a DIV element in our page with the ID of "foo".  Once we have a handle to that element we then use the $addHandler syntax - which is another global shortcut method for the Sys.UI.DomEvent.addHandler function - to attach event handlers to the mouseover and mouseout events of our DomElement.  In our handler functions we set the CSS class on our element by using more Sys.UI.DomElement helper functions - removeCssClass and addCssClass.  Finally, in the pageUnload event we clean up our references so that we don't cause memory leaks.

So that's a nice demo for highlighting several of the new client-side programming functions and events and shows how simple it is to perform such a common function as toggling the color of a link when the user hovers over it - who could ever remember that pesky syntax for setting the CSS class on elements for cross browser compatability anyway eh? smile_regular

Rather than holding that global reference to the "e" variable just so that I can use it in each of the other functions I'm going to remove it and get the reference with the functions when I need it.  While performing this refactoring I'm going to make use of the target property of the Sys.UI.DomEvent class that gets passed to our event handling functions.  I'll also make use of the toggleCssClass function to remove another couple of redundant lines of code too:

function pageLoad() {
    var e = $get("foo");
    Sys.UI.DomElement.addCssClass(e, "normal") ; ;
    $addHandler(e, "mouseover", mouseoverHandler) ;
    $addHandler(e, "mouseout", mouseoutHandler) ;
}

function pageUnload() {
    var e = $get("foo");
    $removeHandler(e, "mouseover", mouseoverHandler) ;
    $removeHandler(e, "mouseout", mouseoutHandler) ;
}

function mouseoverHandler(evt) {
    Sys.UI.DomElement.toggleCssClass(evt.target, "hover") ;
}

function mouseoutHandler(evt) {
    Sys.UI.DomElement.toggleCssClass(evt.target, "hover") ;
}

There's another nice refactoring that we can make to this code by using two more helper functions - $addHandlers and $clearHandlers.  These global helper functions map to the Sys.UI.DomEvent.addHandlers and Sys.UI.DomEvent.clearHandlers functions as you would expect, but we are again given this nice shortcut syntax for working with them.  Here is the last snippet of code for our page with our final refactorings in place:

function pageLoad() {
    $addHandlers($get("foo"), {"mouseover":mouseoverHandler, "mouseout":mouseoutHandler}, this)
}

function pageUnload() {
    $clearHandlers($get("foo"))
}

function mouseoverHandler(evt) {
    Sys.UI.DomElement.toggleCssClass(evt.target, "hover") ;
}

function mouseoutHandler(evt) {
    Sys.UI.DomElement.toggleCssClass(evt.target, "hover") ;
}

So that's it.  A long but decent introductory article to working with DomElements and DomEvents with the ASP.NET Ajax client library.  As I said at the beginning of this article, Sys.UI is really a compatability layer for working with the DOM.  I'd really encourage taking a look at the other major sections of the client library to see what other functionality exists:

ASP.NET Ajax - Client Reference

In particular I'd encourage looking into the Sys namespace where you can find classes like the StringBuilder, Debug, and Exceptions.  In addition to that, if you needed any more convincing that this is not your grandfather's JavaScript, take a look at the JavaScript Base Type Exceptions in the Global namespace to see which existing JS types have been extended.  For example with a little spelunking around there you will find some familiar favourites such as String.format:

var obj = {foo:"foo", bar:"bar"} ;
alert(String.format("Foo: {0}, Bar: {1}", obj.foo, obj.bar)) ;

Go for it! smile_regular

posted on 4/27/2007 10:07:10 PM ( 2 Comments )


Running Excel in Elevated (Administrator) Mode

This is just a tip entered for my future self...

To run Excel in Elevated Mode on Vista:

  1. Click the Start Button
  2. Type "Excel"
  3. Press SHIFT + CTRL + Enter

Voila!

posted on 4/26/2007 8:55:08 PM ( 0 Comments )


Google Analytics

I only just hooked up my site to Google Analytics the other day and already it's highlighted a few interesting things to me.  Probably the most interesting thing that I've learned about my site is that the 2nd highest source of referrals to my site are Google Image searches on "Neil Armstrong".  Go ahead and try it out, go to Google, click on the images tab, type "Neil Armstrong" and click on "Search Images": 

Link to neil armstrong - Google Image Search

The link to MarkItUp.com comes up right there on the first page of results!  The funny thing is that I can't even remember what article that image was in relation to smile_regular

posted on 4/25/2007 8:17:21 PM ( 1 Comments )


Dynamic Tabbed Web Part Pages

Part 1 - Creating a Tabbed View
Part 2 - Creating Dynamic Tabs
Part 3 - Dynamic Tabbed Web Part Pages 

Download the code for this article. 


This is the third and final article in the series of how to create a web part page that allows the user to create new pages dynamically.  Remember from previous articles that our aim it to create an effect similar to the dynamic pages feature on the Google portal:

Google Portal with dynamic tabs

 

In the first article we looked at what is involved in creating a tabbed effect.  The following diagram shows the effect that we were interested in creating: 

 

Dragging between tabs

In this picture we can see that the user drags a web part from a zone that is visible when one tab is selected and they can then drag that web part over a second tab which "activates" that tab and exposes the zone that is associated with that tab.  The user is then able to drop the web part onto that zone. 

The second article in the series looked at the problem of creating tabs and pages dynamically.  Unlike in the first article where the tabs and pages were hard-coded into the page, the second article showed how to render tabs and pages dynamically.  The following diagram shows the position of a special tab that allowed the user to create new pages at runtime:

 

Dragging between tabs

The workflow behind the dynamically created pages can be seen in the following diagram:

 

Dragging between tabs

The workflow diagram is quite self-explanatory so I won't go too far into it.  If you'd like to understand it better then I suggest you go back and take a read of the second article

In this article we will take the knowledge gained from the first two articles and use it to combine both features - dynamically adding pages and dragging web parts between tabbed views.  As you will see, it's not a long stretch to get to the solution that we want from where we are, but there are just a couple of technical problems to solve.  The biggest part of the problem that we haven't tackled yet is that we haven't yet shown how to dynamically create the zones that will live within each page and have them properly registered with the WebPartManager at runtime.

Remember from the second article that when the user created the page we stored that information in an XML file.  Now that we'll need to access that information in 2 places - first when we lay out the tabs and second when we dynamically create a zone which represents each page - we can encapsulate the logic for accessing that information in a helper method:

private List<string> _pages ;
public List<string> TabPages {
    get {
        if (_pages == null) {
            SetPages();
        }
        return _pages;
    }
}

private List<string> SetPages() {

    _pages = new List<string>();

    XmlDocument doc = new XmlDocument();
    doc.Load(HttpContext.Current.Server.MapPath("~/SitePages.xml"));
    List<string> pages = new List<string>();

    foreach (XmlNode node in doc.DocumentElement.SelectNodes("site-page")) {
        string pageName = node.Attributes["title"].Value;
        _pages.Add(pageName);
    }
    return _pages;
}

The list of pages can now be accessed from our readonly property TabPages which uses the SetPages helper method to read from the file the first time that it is called.  Ideally we might cache this information and set a FileDependancy on the cached item, but for the purposes of keeping things relatively simple I'll leave that up to the reader to implement.  We can see how this is used to bind to the Repeater to display the tabs in the following snippet of code:

rpt1.DataSource = this.TabPages;
rpt1.DataBind();

Likewise, when it comes to rendering out the zones, we can simply loop through each of the pages in the TabPages property and dynamically render each page:

private void AddPages() {

    int counter = 0;
    foreach (string page in this.TabPages) {

        string visibility = counter == 0 ? "visible" : "hidden"

        PlaceHolder ctl = new PlaceHolder();
        ctl.Controls.Add(new LiteralControl(
        string.Format(
            "<div id=\"tab{0}\" class=\"tabpage_{1}\">", 
            counter,
            visibility
        )));

        WebPartZone zone = new WebPartZone();
        zone.ID = "Zone_" + page;

        ctl.Controls.Add(zone);
        ctl.Controls.Add(new LiteralControl("</div>"));
        dynamicLayout.Controls.Add(ctl);
        counter++;
    }

   

    string arrayName = "tabs"
    string arrayValue = ""
    for (int i = 0; i < counter; i++) {
        arrayValue += string.Format("{1}\"tab{0}\"", i, i > 0 ? "," : "");
    }

    ClientScript.RegisterArrayDeclaration(arrayName, arrayValue);

}

That's quite a meaty chunk of code, so I'll walk through it in 2 parts.  First we have the main foreach loop which spits out the pages. As you can see, the first thing that we do when displaying the pages is to work out the visibility - because we only want the first page to be visible.  Next we can start to build up the control hierarchy for the tabbed page.  At the root of the control hierarchy for each page is a PlaceHolder control and the first control that we add to it's controls collection is an HTML DIV element that will serve as our main handle to the tabbed page.  We give this outer DIV a known ID so that we'll be able to work with it at runtime - these ID's start with "tab" and then have a number at the end which represents the index of the page within the tab collection.  So you will end up with a group of tabbed pages with ID's like: tab0, tab1... tab10, and so on.

Once we have our outer DIV we dynamically create WebPartZone instance, give it a unique ID and add it to our placeholder.  Finally, you can see that we are adding this control collection to a PlaceHolder in the page named dynamicLayout - this just allows us to have control over where the zones will get displayed in the page.

Outside of the main foreach loop you can see that the last task we perform is to register an array named tabs with the page which contains each of the tab ID's as it's elements.  This array is used by clientside Javascript to match the ordinal position of tabs with their named pages. 

Before we can run our page to view our work there's something important that we must attend to.  The thing about zones is that they need to be added to the page in or before the Init stage of the page lifecycle - this is discussed in detail on pages 100-101 of my book.  This leaves us with an interesting dilemma, take a look at the following diagram:

 

Dragging between tabs

 

As we can see from the diagram, whenever the user clicks on the "Create New Page" function in the page we will postback to the server and handle this event by updating our XML definition file.  But notice where we will be handling the postback - well after the page's Init event!  This means that by the time we handle the event we will already have drawn our tabbed pages with their contained zones and so the new tabbed page will not be present for the user to use.  The only way around this is to call out to a helper page which will update the XML definition file and then redirect back to the page that the user came from.  The mechanics of this are shown in the following diagram:

 

Dragging between tabs

 

So now, when we handle the postback event, we grab the argument that is sent from the client and do a redirect over to our helper page like so:

public void RaisePostBackEvent(string eventArgument) {
    if (eventArgument.StartsWith("pb::")) {
        string arg = eventArgument.Substring(4);
        Response.Redirect(string.Format("PortalPostbackHandler.ashx?pageName={0}", arg));
    } else {
        base.RaisePostBackEvent(this, eventArgument);
    }
}

And when our helper page has updated the XML file, it can transfer back:

public void ProcessRequest(HttpContext context) {
    string arg = context.Request["pageName"];
    if (!string.IsNullOrEmpty(arg)) {
        AddTabPage(arg);
    }

    string referrer = context.Request.UrlReferrer.AbsolutePath;
    context.Response.Redirect(referrer);
}

So that's all there is to creating dynamic tabbed web part pages.  I'd encourage you to download the sample code that comes with this article and have a play with it.

posted on 4/22/2007 3:26:58 PM ( 15 Comments )


Creating Dynamic Tabs

Part 1 - Creating a Tabbed View
Part 2 - Creating Dynamic Tabs
Part 3 - Dynamic Tabbed Web Part Pages 

Download the code for this article. 


In the first article in this series, I discussed how to created a tabbed portal page that allows users to drag web parts between the tabbed views:

Web Parts - How to create a tabbed view

Over the next couple of days I'll be putting together a sample which shows how to make the entire UI dynamic so that not only can the user drag web parts between tabs but that they can also create new tabs on the fly, just like the Google portal home page:

Google Portal home page allows users to create new pages on the fly.

 

 

For our solution we will store the dynamically generated tabs in an XML file and bind to it at some point in the page lifecycle.  After binding to each page from our definition file we'll add an extra tab at the end which allows the user to create a new page on the fly.  From a high level our this is what our solution looks like:

 

Tab architecture.

 

The XML page definition file will be pretty simple at this stage and looks something like this:

<?xml version="1.0"?>
<site-pages>
    <site-page title="Page 1" />
    <site-page title="Page 2" />
    <site-page title="Page 3" />
    <site-page title="Page 4" />
    <site-page title="Page Blah" />
</site-pages>

To display the existing tabs on the page we create a Repeater:

<asp:Repeater ID="rpt1" runat="server">
    <ItemTemplate>
        <span class="tabheader"><%# Container.DataItem %></span>
    </ItemTemplate>
</asp:Repeater>

...and bind our XML node data to it:

private void LoadData() {
    
    XmlDocument doc = new XmlDocument();
    doc.Load(Server.MapPath("~/SitePages.xml")) ;
    List<string> pages = new List<string>();

    foreach (XmlNode node in doc.DocumentElement.SelectNodes("site-page")) {
        pages.Add(node.Attributes["title"].Value);
    }

    rpt1.DataSource = pages;
    rpt1.DataBind();
}

That's all that is required - at this stage - to display our tabs.  Of course you might want to style them properly and to get some ideas for that I'd highly recommend this great resource which shows many different styles of tabbed UI's:

14 Tab-Based Interface Techniques

The last simple thing that I'm going to do now is to add an additional Tab at the end of the databound tabs that will act as the link for users to create a new tab.  To do that I'll simply add a FooterTemplate to my Repeater and add the link in that template.  This will ensure that the link is always displayed as the last tab in the group:

<FooterTemplate>
    <a href="javascript: void(0)" onclick="CreateTab()">Create new tab.</a>
</FooterTemplate>

As you can see, there's a Javascript function called CreateTab in that link which kicks off the process of creating the new page.  Before I go into the logic behind that function, let's take a quick look at the workflow that we require:

 

Google Portal home page allows users to create new pages on the fly.

 

So when the user clicks on the "Create New Tab" link the Javascript function CreateTab will get called and it's job is to ask the user for the name of the new tab and then to execute a postback to the server.  Here's what our CreateTab function looks like:

function CreateTab() {
    var s = prompt("Enter the name of the new tab.") ;
    x = x.replace("@@foo@@", s) ;
    eval(x) ;
}

The first thing that we do in the function is to aske the user for the name of the tab by using the sexy Javascript prompt function smile_regular.  Then we are doing something with a variable called 'x'.  This variable is the postback function and the way that we get that is to inject it into the page from the server side like so:

string bar = ClientScript.GetPostBackEventReference(this, "pb::@@foo@@");
ClientScript.RegisterClientScriptBlock(this.GetType(), "pb", "var x = \"" + bar + "\"", true);

The result of these two lines of server side code is that we will get the following line of Javascript injected into our page:

var x = "__doPostBack('__Page','pb::@@foo@@')"

...and as you can see from our CreateTab function, we can eval this string to force a postback, but before we do, we replace our placeholder string - @@foo@@ - with the name of the tab page.

When the page posts back we can simply implent the RaisePostBackEvent method of the IPostbackEventHandler to intercept the postback, grab our event argument and create a new Tab in the page:

public void RaisePostBackEvent(string eventArgument) {
    if (eventArgument.StartsWith("pb::")) {
        string arg = eventArgument.Substring(4);
        AddTabPage(arg);
    } else {
        base.RaisePostBackEvent(this, eventArgument);
    }
}

Adding the Tab is of course as simple as adding a new node to our XML definition file like so:

private void AddTabPage(string arg) {

    string filePath = Server.MapPath("~/SitePages.xml");

    XmlDocument doc = new XmlDocument();
    doc.Load(filePath);

    XmlElement e = doc.CreateElement("site-page");
    XmlAttribute a = doc.CreateAttribute("title");

    a.Value = arg;
    e.Attributes.Append(a);

    doc.DocumentElement.AppendChild(e);

    XmlTextWriter writer = new XmlTextWriter(filePath, null);
    writer.Formatting = Formatting.Indented;
    doc.Save(writer);
    writer.Close();

}

That's all there is to creating a dynamic tabbed display. 

Now that you've read this article, you should read part 3 where we will put everything that we've learned together to create a fully functioning, dynamic tabbed UI. 

Read Part 3 >>

posted on 4/20/2007 8:51:59 AM ( 3 Comments )


Visual Studio "Orcas" and .NET FX 3.5 Beta1 shipped!

As Soma announced, they've just shipped the Beta 1 release for Visual Studio “Orcas” and .NET FX 3.5.  I also saw, via Kent's blog, that the CTP's for the Orcas Express Edition's have shipped too.  I believe that you can grab them from here:

http://msdn.microsoft.com/vstudio/express/future/

Looks like a nice quick way to get me some of that Orcas ASP.NET goodness! smile_regular

posted on 4/20/2007 8:07:29 AM ( 0 Comments )


Working with Data and ASP.NET 2.0

My 2 biggest role models in the .NET world are Scott Mitchell and Dino Esposito - sure there are others that I have huge respect for such as Mitch Denny, Scott Hanselmann, and Phil Haack, but somehow Scott and Dino both strike a special chord with me.  Both of these guys are a real testament to the fact that through persisting towards a goal you can create great things of real value.  There are so many examples of the rich assets that both of these guys have built up over the years - blogs, books, websites, articles, starter kits, etc.  Here's an example of the latest work that Scott has produced:

Working with Data and ASP.NET 2.0

This is an asset that Scott has produced over the past 15 or so months: more than 50 high quality tutorials.  A great example of what you can do if you stick at something, not lose focus, and give it your all!

posted on 4/19/2007 9:49:50 PM ( 1 Comments )


Imagine if there were no leaders

I've learned a lot about leadership in the 12 months since joining the Readify management team.  In that time I've managed to gain a whole new perspective on leadership and have gained some really valuable insights into the value of leadership.  The lessons that I've learned about leadership have exposed new views to me about leading people and about how leaders view those that they lead.

Imagine if there were no leaders.

Before I continue, I'm certainly not about to write a post which says that you should just accept the leadership of people because they are in management.  There are plenty in management who don't belong there.  For the sake of this post, let's say that 50% of all people in management just shouldn't be there.  Some of those got there for ill-intentioned reasons and some just got promoted to their level of incompetence

Of the other 50% I think that you can break it down into two categories: The Born Leader and The Lighthouse Keeper.

 

Born Leaders

Let's face it, we've all marched underneath one of these at some time.  These are guys who not only have vision but also have that special X-factor which just makes you want to follow them.  These are the best leaders.  In my experience, these are also the most dangerous adversaries if they are not acting in a leadership role.  There's not much to be said about born leaders.  If you are a positive guy (such as a Lighthouse Keeper) then working for a born leader is what life is all about smile_regular.  Nothing will grow you more than working for a born leader!

 

Lighthouse Keepers

Delegation in the business world has always been important but especially in today's fast-paced world with a zillion emails and a TODO list that just never ends.  If you were the developer lead on a software project, you would almost certainly single out the most competent people who "get" the vision and lean on them to do the most important stuff.  It's the same in business. 

Does your organization have major events to organize?  Well you wouldn't give that task to somebody who was inactive or a pessamist would you?

Does your organization have people to hire?  Well you wouldn't give that task to somebody who didn't take the time to buy-in to the companies growth strategy would you?

Good Lighthouse keepers - otherwise known as middle-management - enact the vision of the business.

 

Imagine if there were no leaders.

posted on 4/18/2007 10:53:14 PM ( 0 Comments )


This years biggest geek music CD?

The sample track sounds pretty cool, I can easily imagine this being played through headphones in cubicles all over the world:

Link to Exclusive: Rock out with your Halo out! | Tina Wood | Channel 10

posted on 4/18/2007 8:17:36 AM ( 0 Comments )


Consultants: Giving proper advice

A couple of years ago we packed the house up and headed to Canberra to live and work for a couple of years.  While we were away we retained our house in Adelaide and rented it out.  Luckily the tenants that we had stayed for the entire time that we were gone and so there was no loss of rent and our fees were kept to a minimum.  We also employed a guy to maintain the garden and the pool while we were gone... and so that was one less hassle as well. 

The guy that we got to manage our rental property was an older fella who ran his own business.  He did a pretty good job of communicating with us and ensuring that we were aware of things as new situations arose.  Also, the rental manager always ensured that our rental money was always in our bank accounts on time.

You'd think that with such a peachy story that it was a happy story right?  Wrong!  The experience that I had with the property manager when the time came to finish the rental agreement was so poor that I'd never use him again and wouldn't recommend him either.  Let me explain.

Severing a rental agreement is a stressful activity; doubly so when you are also renting at the other end as you now have 2 contracts to terminate.  Our contracts didn't line up on dates.  This meant that we had to carefully work out the best time to exit the Canberra while ensuring that we lost the least amount of money. We had lots of questions...

  1. When does our lease in Canberra finish?
  2. When does the lease in Adelaide finish?
  3. What's the gap in those leases?
  4. Can we legally ask our tenants in Adelaide to exit early?
  5. How much notice should we give our tenants in Adelaide?
  6. Can they leave whenever they like after we've told them that we are not continuing the lease?
  7. How much notice should we give the agency in Canberra?
  8. Where are we going to live when we get back to Adelaide while our tenants are still there?
  9. Where will we store our furniture when we get back to Adelaide while our tenants are still there?
  10. How much will all of this cost us and what are our legal rights and responsibilities?

Our worst case scenarios were:

  1. Giving our Adelaide tenants too much notice and having them leave way too early, thus leaving us with unfunded repayments of $400 per week.
  2. Having too large a gap between leaving Canberra and having our house available in Adelaide leaving us with surplus costs of interim accomodation and storage of our goods.

Through all of this, our rental manager sternly refused to give us advice - other than to say "here's what the law states".  That's fine in terms of him covering his arse, but as concerned customers we had bigger things on our plate and really could have used some practical and sage advice - after all, this is an area that he deals in daily and we may only have to deal with once or twice in our lives.  Surely he has some "lessons learned" and some "guidance", etc to offer.  But the answer was always a resounding "No".

If this "consultant" had used his knowledge to upsell some "services" to us, not only would he be keeping a customer and gaining referrals, but he'd also be making more direct cash from his idle time.  It's now apparent to me why this guy had a small business - it wasn't the boutique little shop that I had initially imagined, it was a business that had failed to grow through poor attention to a proper growth strategy.

So my advice is this.  Use your knowledge and your experiences and look for opportunities to add value.  Take your IP and grow it.

posted on 4/17/2007 1:13:34 PM ( 0 Comments )


Vista - my last rant about this...

People probably think that I'm insane for ranting about this so much but I'm just gunna write about it one more time.  It sooooo pisses me off when I hear and read Developers whinging about Vista.  Get your head around it!

"It's too slow."
"It's too hard to use."
"My apps don't run on it."
"UAC is annoying."

I hear it all the time!  It's just complaining for the sake of it.  Don't sit around dreaming that you are going to build the next YouTube and have Google come and hand you a billion bucks - because it won't play out that way.  The real road to riches is sitting right there in front of you waiting for you to embrace it - Vista.

Coming back to Adelaide has been refreshing in that I've been able to catch up with many old friends who are not developers.  It's been very refreshing to show Vista and Tablet technology to these guys and then watch their reactions to it.  The people that I've spoken to have ranged from: Students to Pensioners to Key Business Decision Makers at multi-national companies.  In the whole they are very excited about the prospect of the coming Vista applications and the new experiences that they will enable.

The developer people who are busily complaining that Vista is (Slow|Hard|etc) are going to miss out on the opportunity to create these experiences because they are too busy complaining about Vista itself.  So I guess that the people that I'm speaking to will just have to wait for the entrepreneurs and visionaries to finish those Web 2.0 apps that they are working on now until they get the apps that I've promised them.

posted on 4/16/2007 9:25:19 AM ( 2 Comments )


UpdateControls 1.1: Bug Fixes and UpdateAction

 Nikhil has just posted an update to his UpdateControls:

UpdateControls 1.1: Bug Fixes and UpdateAction

Included in this update is a new control called the UpdateAction control which provides a slot for the programmer to have structured access to page controls during a partial postback.  Combined with other controls in Nikhil's UpdateControls, such as the AnimatedUpdatePanel, this is creating an even richer framework for implementing common Ajax patterns.

posted on 4/14/2007 10:20:05 AM ( 0 Comments )


Changing Interests

I remember a few years ago I was a total forum junkie.  I had several thousand posts on ASP Messageboard and I was the most active poster on the ASP.NET forums for the first few months.  During that time I was answering questions so that I could learn about new scenarios for coding and using (primarily) ASP.NET.  One thing that always blew me away was that, no matter how much help you gave, the people in these forums would always want more.  You could literally spend a few hours helping someone, even to the point where you basically wrote all of their code for them and they'd still ask for more. 

After a time I became pretty jaded.  Not so much because of the time but also because of the lack of appreciation that people often show for the help that you provide in forums.  Many are the time that you'd pour a few hours into solving some guys business problem with code and, instead of a heartfelt "thankyou", you'd instead get another ream of (often) indecipherable code placed in front of you.

These days I tend to avoid forums altogether.  Most of my learning is done in the serenity of my office and increasingly through my growing participation as a technical reviewer of books on upcoming technologies.  A much more pleasurable way to grow... for now anyways! smile_regular

posted on 4/14/2007 7:29:22 AM ( 2 Comments )


YouTube - Oasis - Wonderwall

posted on 4/13/2007 9:34:55 PM ( 1 Comments )


Creating Responsive Portals - Client Scaffolding

A while ago I wrote a short blog post about the Page Async attribute and how you could use async tasks to improve the performance of your portal applications:

http://markitup.com/Posts/Post.aspx?postId=1fded0e9-e871-4e4a-a419-c1a770174cf1

Since then I’ve been thinking about a follow up to that approach which might make the feel of the portal even more responsive.  As you can see in the following diagram, the major benefit of async processing is that each of the main processing tasks can be run asynchronously so that you don’t have to wait for the total time of each processing job to elapse before the page is rendered:

Async processing

 

There’s still a slight performance issue here though.  Basically the fastest running control still has to pay the cost of the slowest running control.  You can see this in the following diagram where the execution of control C finishes in 500 milliseconds but, even on an Async page the page cannot even begin to be sent to the browser until control D’s execution completes 4 seconds later.

 

Async processing

 

So the user is left watching a blank screen for 4.5-5 seconds when they could have had the initial page sent down immediately and the first control loaded in under a second.  Ideally, what would be good is if I could construct my portal in such a way that each control sent down a small piece of scaffolding to the client which would then allow it to load asynchronously.  We can see this in the following diagram where each control emits a scaffold which takes 200ms to produce – meaning that we can send out our page almost instantly.  Once the scaffolding is in place an Ajax callback would be sent to the server to get the remaining UI.

 

Async processing

 

In thinking of how I’d might acheive this with ASP.NET Ajax I had a few questions because I wasn’t sure that there is really a good path to head down.  So I decided to investigate two possibilities:

1. Server-centric using Update Panel.
The beauty of this approach is that it simplifies your application significantly because you can code your UI against the server objects.  This makes it simple to do things such as using databinding and maintaining state of controls between postbacks. 

2. Client-centric approach using Ajax client model
It seems that you’d really need to take full control with a scenario such as this because you need the flexibility of queuing requests and sending them off in a controlled manner and then reconciling the responses to the batched requests.  The downside of this approach is that you:

a. Have to write a client-side pooling component
b. Lose all of the benefits of the server-side coding model

I spoke with a few friends about this problem and soon came to the realization that it might not be as doable as I first thought.  The first barrier is that, by default, a browser can only have 2 active connections for a given domain (this includes all requests such as graphics, scripts, images, etc) so you couldn't expect to send of an unlimited set of requests.  This immediately mitigates any big wins because you'd now be paying a cost on the client with your batching which would potentially make the page feel even more sluggish than the straight up async load on the server. 

Another lesser barrier is that, by default, the Update Panel control by default only handles 1 active request.  This means that if 2 UpdatePanels send off a request that only 1 will get through.  Hardly suitable for our approach where we may want to have several controls load this way.

A possible solution

In talking to Glav about this problem he put forward the idea of using Timers to kick off UpdatePanels at staged intervals.  The idea goes something like this, you have an UpdatePanel for each control that you want to load and a Timer for each UpdatePanel.  To begin with you only enable 1 of the Timers and then load the first control in the handler for that Timer.  After you've done the work in that initial Timer, you disable your Timer and then enable the next one - so you get a chaining effect:

<asp:UpdatePanel ID="up1" runat="server" UpdateMode="Conditional">
    <ContentTemplate>
        <asp:Literal ID="ltl1" runat="server" />
    </ContentTemplate>
    <Triggers>
        <asp:AsyncPostBackTrigger ControlID="tmr1" EventName="Tick" />
    </Triggers>
</asp:UpdatePanel>
<asp:Timer ID="tmr1" runat="server" Enabled="true" Interval="1000" OnTick="tmr1_Tick" />

<asp:UpdatePanel ID="up2" runat="server" UpdateMode="Conditional">
    <ContentTemplate>
        <asp:Literal ID="ltl2" runat="server" />
    </ContentTemplate>
    <Triggers>
        <asp:AsyncPostBackTrigger ControlID="tmr2" EventName="Tick" />
    </Triggers>
</asp:UpdatePanel>
<asp:Timer ID="tmr2" runat="server" Enabled="false" Interval="1000" OnTick="tmr2_Tick" />

In the preceding snippet of code we can see that we have our scenario of two UpdatePanels (up1 and up2) and a Timer for each of them (tmr1 and tmr2).  The following piece of code shows what the handling code looks like on the server:

protected void tmr1_Tick(object sender, EventArgs e) {
    Thread.Sleep(2000);
    ltl1.Text = "Loaded<br />"
    tmr1.Enabled = false;
    tmr2.Enabled = true;
}

protected void tmr2_Tick(object sender, EventArgs e) {
    Thread.Sleep(2000);
    ltl2.Text = "Loaded<br />"
    tmr2.Enabled = false;
}

Real World Use

In a real world scenario I wouldn't use this as my standard approach but I might consider it for some controls.  For example, when talking to some web services it's not uncommon for the initial call to take quite a while - especially if you are pulling back large amounts of data.  An approach such as this might be suitable when you have such a control and you don't want to slow the loading of the rest of your page for that 1 control.  Of course, when using other performance techniques properly such as caching and async server processing, the number of times where you need to revert to these style of tactics might be pretty minimal.

posted on 4/12/2007 9:02:44 AM ( 1 Comments )


Another BlogML story

In this blog post the author talks about the need to remember to truncate tables as opposed to merely deleting records from them before re-importing your data via BlogML: 

Link to Doctor, heal thyself

He then goes on to say how, by not using post ID's in the URL he could have saved the angst altogether.

posted on 4/12/2007 6:26:56 AM ( 0 Comments )


ASP.NET Ajax delegates

In this post Bertrand discusses the various techniques for wiring up event handlers in client-side OO code and the slight but important differences between them:

Link to Atlas and more : How to keep some context attached to a JavaScript event handler?

I'd highly recommend cracking open VS and playing around with these to get a good feel for the differences.

posted on 4/11/2007 8:24:07 PM ( 0 Comments )


Drivers and Passengers

Have you sat in the passenger's seat of a car on a journey recently?  I have, and it's a wierd feeling. 

Most of the time when we go places, I drive. Because you are the driver, you have to plan your journey to be certain that you'll get to where you are intending to go.  The result of this is that, at the end of your journey, you know how you got to where you were going.  You remember where you had to turn off of the highway; You remember which side street to exit off of the main road; And you remember that it's the second turn on the right - and not the first.  After a lot of driving you even start to learn other things, such as: What order the traffic lights cycle in; Which trees have been chopped down recently; New road signs that have recently been added.

But sit in the passenger's seat for a while and it's a very different story.  Quickly you get to the point that you simply cannot remember how you got to where you were going.  You know where you are but, try putting you in a car and telling you to drive there and you won't be able to do it.  Even if you've travelled there dozens of times you'll find that, without your driver, you are helpless.

I believe that we see this a lot in IT/.NET development today where many people can get to the end result but they don't really understand how or why they did what they did to get there.

“Give a man a fish; you have fed him for today.  Teach a man to fish; and you have fed him for a lifetime”—Author unknown

I think that a lot of the mass produced training content is similar to this - well at least a lot of the mass produced training content that I've looked at.  I remember horrible experiences of wading through endless pages of training content being told to "paste this", and "paste that", and "copy this", and "do that", then, of course... "Press F5"!

Training content like this is probably good for teaching the masses but definitely not how I like to learn.  If you are only taught the copy/paste approach to learning then what happens when a roadblock is put in your way and you need to find a different solution to the problem.  Well I guess that you'll just hope and pray that you type the right words into Google right?  Good luck with that.

Similar to the driver though, if you studied the map and you learned the right way then you will also know about those alternate paths that can lead you to where you want to go.

Let's hope that in IT and Comp-Sci that we never lose the drivers!

posted on 4/11/2007 10:58:01 AM ( 0 Comments )


Preventing a web part being moved

The WebPartManager has a bunch of events that allow you to write code just before and just after a web part is either: Added, Moved, or Deleted from a zone.  Those events are:

  1. Adding/Added
  2. Closing/Closed
  3. Deleting/Deleted
  4. Moving/Moved

You can override those methods and write code to enfore business rules - such as disallowing a user to move a certain web part into a given zone - or to implement additional logging and tracking code.  So let's say that we have the following web part page:

<asp:WebPartZone ID="zone1" runat="server">
    <ZoneTemplate>
        <asp:TextBox ID="txt1" runat="server" />
        <asp:Calendar ID="cal1" runat="server" />
    </ZoneTemplate>
</asp:WebPartZone>

<asp:WebPartZone ID="zone2" runat="server">
    <ZoneTemplate>
        <asp:TextBox ID="txt2" runat="server" />
    </ZoneTemplate>
</asp:WebPartZone>

In the code listing we can see that we have 2 zones (zone1 and zone2).  Inside zone1 we have 2 generic web parts - a textbox and a calendar - and inside zone 2 we have a textbox generic web part.  So imagine that we want to enforce the rule that each zone can only contain 1 web part of each type.  In this case the user would be allowed to move the calendar from zone 1 to zone 2 but they could not move the textbox because that would result in 2 textboxes in the same zone, and that would violate our rule.

To get the demo started we first switch the page into design mode so that we can move web parts around the page:

protected void Page_Load(object sender, EventArgs e) {
    WebPartManager wpm = WebPartManager.GetCurrentWebPartManager(this);
    wpm.DisplayMode = WebPartManager.DesignDisplayMode;
}

Now we can wire up the Moving event and write the code that will ensure only 1 web part of a given type exists in the target zone:

protected override void OnInit(EventArgs e) {
    base.OnInit(e);

    wpm.WebPartMoving += new WebPartMovingEventHandler(wpm_WebPartMoving);
}

void wpm_WebPartMoving(object sender, WebPartMovingEventArgs e) {

    Type t = e.WebPart.WebBrowsableObject.GetType();

    foreach (WebPart wp in e.Zone.WebParts) {
        if (wp.WebBrowsableObject.GetType() == t) {
            RegisterError();
            e.Cancel = true;
            break;
        }
    }
}

As you can see, our event handler gets the Type of the web part being moved and then compares it to the Type of all the web parts in the target zone.  If a match is found, we call a utililty function named RegisterError which will send an alert to the browser informing the user of the error and then cancel the move.

Finally, here is the code for the RegisterError method:

void RegisterError() {
    string message = "You cannot do that"

    string errorScript = string.Format("alert('{0}')", message);
    Page.ClientScript.RegisterClientScriptBlock(this.GetType(), "foo", errorScript, true);
}

Enjoy!

posted on 4/11/2007 8:51:36 AM ( 6 Comments )


AJAX Apps Ripe Targets for JavaScript Hijacking? Not ASP.NET Ajax!

Recently on an ASP.NET list there was some discussion about this article:

Link to AJAX Apps Ripe Targets for JavaScript Hijacking

In the article - dated April 2, 2007 - it is mentioned that ASP.NET Ajax, along with several other leading AJAX implementations - such as the Google Web Toolkit - has a JavaScript-related security vulnerability.  This article was based on a blog post written by Brian Chess, co-founder and "Chief Scientist" of a company named Fortify Software.

2 days after that article was released, Scott Guthrie posted his own blog article which states that ASP.NET Ajax is not vulnerable to this threat:

http://weblogs.asp.net/scottgu/archive/2007/04/04/json-hijacking-and-how-asp-net-ajax-1-0-mitigates-these-attacks.aspx

It's all interesting reading and makes you thankful to be building atop tested, patchable frameworks such as ASP.NET smile_regular

posted on 4/8/2007 10:38:34 PM ( 1 Comments )


Kent Sharkey re-emerges

Well, after a 6 month blogging blackspot, Kent re-emerges to inform us about what he's been working on: 

Link to What has been occupying my time

Great to hear from you again Kent smile_regular

posted on 4/6/2007 10:01:58 AM ( 0 Comments )


Welcome to Yahoo!

Goodbye Flickr, it was fun while it lasted.

Welcome to Yahoo

posted on 4/2/2007 7:13:51 PM ( 0 Comments )


Learning and Growing metaphors

Way back when, I had a boss who instilled the following, well known saying into my mind:

You reap what you sow.

This saying is often said as:

What you do comes back to you.

And so it is with learning and growing.  If you lay good quality seeds and then don't neglect what has been laid down, then good things will surely come.  When you enter a nice house it is seldom an accident that it came to be that way.  Similarly, when you use a computer application that works well and is a pleasure to use, it's never by accident.  Both the gardener and the programmer got their projects that way after a great deal of growing and learning.

This weekend while 200-odd Code Campers headed off to Wagga Wagga to sow a few seeds of learning, I was home with my family sowing a few of my own. 

First we tidied up around the bridge by pulling weeds, planting new bulbs, and generally cleaning up a bit:

Clean up near the bridge

Then we prepared an area which had previously been barren underneath our carport and planted some new wild grasses there:

Planted new wild grasses underneath the carport

Finally, we purchased a new lemon tree and planted it:

Our new lemon tree!

Whether it's learning new technologies or planting fruit trees that will feed you in the future... you should always plan to learn and grow.

posted on 4/2/2007 10:25:35 AM ( 0 Comments )


Custom Control Authoring - the differences between now and then

So many things were improved in ASP.NET 2.0!  Today I was reminded of some of these improvements and so I wanted to make a bit of a blog "Note to Self" about what they were.

Remember what it was like when you created custom server controls in ASP.NET 1.x?  It was cool, and you could do it, but there were many things that caused pain.  I created lots of custom controls in the 1.x days and packaged many of them for use by external users, so I got a pretty good feel for the "pain points".  This weekend was the first time that I really sat down with the aim of creating a custom server control in ASP.NET 2.0 that I was hoping to package for external users and some of the changes surprised me.

Resources

The first notable improvement was in how resources are handled.  In 1.x you would typically either add resources as embedded resources or, as I saw on many occassions - including with ASP.NET itself - package resources as external files to be copied to the filesystem.  If you took the embedded resources path, here's the type of code that you had to write to extract your scripts and embed them in the page with your control:

Type t = typeof(timer) ;
Assembly ass = t.Assembly ;

using (StreamReader reader = new StreamReader(ass.GetManifestResourceStream(t, "TimerScript.js"))) {

    string script = "<script language='javascript' type='text/javascript' >" + reader.ReadToEnd() + "</script>" ;
    this.Page.RegisterClientScriptBlock(scriptKey, script);
}

There's some tricky, low-level code there - and it get's much more difficult if you want to maximise caching and stuff.  In ASP.NET 2.0 there's a much more "typed" version of how to embed resources.  Nikhil has a couple of great articles which describe how this works:

WebResourceAttribute

Web Resouces - Design Time

Essentially, to embed a JavaScript in a custom control in ASP.NET 2.0 you would do the following:

  1. Add your JavaScript file to your Custom Control library and set it's build action as "Embedded Resource"
  2. Add a WebResource assembly attribute to your assembly to mark your resource as web accessible:

        [assembly: WebResource("Button.gif", ContentType.ImageGif)]
  3. If your resource is an image, you can now simply reference that image like so:

        myButton.ImageUrl = Page.GetWebResourceUrl(typeof(MyCustomControl), "Button.gif");

Nikhil's articles explain all of the other details that you need to know about WebResourceAttribute and how it works.

 

Client Scripts

Even before tMicrosoft Ajax, ASP.NET 2.0 added new support for working with client scripts as a control author.  Most importantly, they moved the script logic from the Page, ie:

Page.RegisterStartupScript(...) ;

to a new ClientScriptManager class:

Page.ScriptManager.RegisterStartupScript(...);

where ScriptManager represents an instance of the ClientScriptManager class.  One of the interesting things about the ClientScriptManager class is that many of it's methods require a "Type" argument to be passed in.  Here's an example of using the Type argument in the RegisterStartupScript method:

scripts.RegisterClientScriptResource(typeof(PageLoad), "MarkItUp.WebControls.PageLoadScripts.js");

Note that, for the first argument I pass in the type of my custom control class (PageLoad).  This argument is used as a key for determining the scope of the resource that will be emitted.  Here's a nice article by Rick Strahl which talks about the Type argument and discusses some gotchas with it:

http://west-wind.com/WebLog/posts/9837.aspx

 

Script Control

With the advent of Ajax, ASP.NET has included much more support for working with scripts and client-side object from within server-side code.  I'd actually forgotten about some of this stuff until today when Glav tidied up some of my "old school" custom control code and replaced it with the shiny new stuff.  Here's an overview of the changes that he made. 

First, he changed my control so that, instead of inheriting from WebControl, it instead inherited from ScriptControl.  This control implements the IScriptControl interface which means that it contains two important methods for control authors: GetScriptDescriptors, and GetScriptReferences.

GetScriptReferences

...allows you to return a collection of each script reference that you wish emit with your control.  The syntax is really simple:

protected override IEnumerable<ScriptReference> GetScriptReferences() {
    ScriptReference script = new ScriptReference("YourNamespace.YourJavascriptFile.js", "Your.Assembly");
    yield return script;
}

GetScriptDescriptors

...is a very useful method which removes a lot of the fiddly and potentially buggy (and non-typed) code that control authors used to use to instantiate client side instances of their control and to set its properties.  There are ScriptComponentDescriptors - for working with Ajax component client types - and ScriptBehaviourDescriptors - for working with Ajax behaviour client types.  Under the hood, ScriptDescriptors map to the Ajax client $create method which is used to instantiate client types at runtime, but this provides a very type-safe and clean way of working with these client-side statements.

As a refresher, this is what the client-side $create Ajax method looks like:

$create(type, properties, events, references, element);

The ASP.NET syntax for creating Ajax types from the server is to use ScriptDescriptors.  To create a type instance, simply create an instance of a descriptor and return it from the GetScriptDescriptors method of your ScriptControl:

protected override IEnumerable<ScriptDescriptor> GetScriptDescriptors() {
    ScriptComponentDescriptor cmp = new ScriptComponentDescriptor("MyNamespace.MyClass");
    yield return cmp;
}

To fill in other arguments in the $create statement, you can use the AddProperty, AddEvent, and AddComponentProperty methods to fill those in.  In the example where you want to instantiate a client type named Samples.SimpleComponent and construct that type by setting it's AProp property to 5000, you can use the AddProperty method to fill in that constructor information like so:

protected override IEnumerable<ScriptDescriptor> GetScriptDescriptors() {
    ScriptComponentDescriptor cmp = new ScriptComponentDescriptor("Samples.SimpleComponent");
    cmp.AddProperty("AProp", 5000);
    cmp.AddProperty("id", this.ID) ;
    yield return cmp;
}

You can see what the Javascript code for the Samples.SimpleComponent looks like here:

http://ajax.asp.net/docs/tutorials/CreatingClientComponentPrototype.aspx

posted on 4/1/2007 10:13:41 PM ( 1 Comments )


Mike Harder's Blog : WebParts and ASP.NET AJAX 1.0

In this post, Mike clears up the often asked question of which Web Parts scenarios are supported with Microsoft Ajax:

Link to Mike Harder's Blog : WebParts and ASP.NET AJAX 1.0

posted on 4/1/2007 12:24:44 PM ( 0 Comments )