Skip Navigation Links / Posts / Posts By Category

Posts for Category: WebParts

Feed for this Category
Just another day at the portal...

A while ago I mentioned the new themes that have been added to Google Portal and I thought that I'd write a quick note to talk what one of my standard days looks like these days...

In the morning I love to get up early and have a coffee with Paul Stovell while I read the paper and check my emails...

Google Portal

 

In the Dev Centre there's plenty of time in the morning for goofing around.  Often Paul and I shoot water cannons at one another before we get stuck into our work...

Google Portal

 

... or practice a bit of karate...

Google Portal

 

... on other mornings it's nice to just kick back with a nice, gentleman-like game of badminton...

Google Portal

 

... the weather in Adelaide has been great so we tend to grab our sandwiches and head outside for lunch...  

Google Portal

 

In the afternoon's Paul's favorite activity is water-sliding... 

Google Portal

 

... but I much prefer a quiet game of croquet (Stovell never beats me at this smile_wink)... 

Google Portal

 

... anyway, when the day is done it's time to start preparing for the night.  It's been a bit cold at night this winter so we generally prepare a bit of a fire...

Google Portal

 

... which, of course leads to marshmallows and Stovell's endless stories about his data binding feats! 

Google Portal

 

.... then it's time to Sleep... 

Google Portal

 

... and Dream - although I've been having some weird one's lately...

Google Portal

 

It's a busy life smile_regular

posted on 6/20/2007 8:32:56 PM ( 0 Comments )


Google, you've done it again!

In the past week Google has announced Google Gears, an offline data storage solution for web applications.  This has been implemented by Google Reader so I can now take my blog reading offline to read offline:

Google Gears

In addition to adding offline reading support for Google Reader, Google have added cool new themes to their portal.  Here you can see that I've added the theme called "Seasonal Scape".  During the daytime the little critters can be seen at play...

Google Gears

 

But at nighttime things are much quieter... smile_regular

Google Gears

posted on 6/4/2007 7:22:24 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 )


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 )


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 )


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 )


CSharpZealot : Swag for the community or sponsorships..

I've given a couple of copies (signed smile_regular) to CSharpZealot who will be giving them away as prizes in competitions:

Link to CSharpZealot : Swag for the community or sponsorships..

Cool stuff!

posted on 3/30/2007 11:51:10 AM ( 1 Comments )


Creating Responsive Portals - Async Tasks

Web portals are the ultimate companion to that cool SOA architecture you've been working on.  With all of those great line-of-business (LOB) services that you've created, web parts seem like the ideal way of allowing your users to interact with that data. 

Getting started with this is real easy.  You write 20-30 lines of code and whoosh!... your LOB data is right up there on your portal.

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

    WeatherType weather = GetWeather();
    img1.ImageUrl = string.Format("~/images/{0}.gif", weather.ToString());
}

In this piece of code we can see that in the PreRender code of our web part that we make our service call - GetWeather() - and use the data that is returned from that call to display a relevant image to the end user.  Pretty typical code really. As part of the processing of the portal page by ASP.NET, your web part will get called upon to render itself.

Control execution within the page lifecycle

 

Of course, as time goes by, you will undoubtedly add many new web parts to your portal page: Calendars, Weather, CRM data, Reports, etc.  And each of these will trundle off to your SOA services, get their data, and come back with their own view for your users to use.  Of course there's a problem here.  Because of the synchronous nature of the ASP.NET web page where controls are loaded and executed in sequence, each control adds time to the overall processing length of the page:

 

Multiple Control execution within the page lifecycle

 

So now you have a page which - with all of those calls to external resources - is taking ages to load... maybe 10-20 seconds for a page with only a few web parts on it.  What to do?

Given that each of these web parts are independent of each other we'd ideally like to run them in parallel so that we don't wear the large time penalty for the whole page.  Something like this:

 

Asynchronous Control execution with the page lifecycle

 

In ASP.NET 2.0 they actually added support for easily creating pages which allow tasks to be run asynchronously in a manner similar to this.  All that you have to do to gain these benefits are to include an Async="true" attribute to your Page directive so that ASP.NET knows to implement the IHttpAsyncHandler in the page, and then write your code so that it executes within asynchronous task blocks.  There's an excellent article by Fritz Onion which discusses this architecture here:

http://msdn.microsoft.com/msdnmag/issues/06/07/ExtremeASPNET/

In the article, Fritz highlights the fact that we can simply use the existing asynchronous interfaces that come with things like ADO.NET and Web Service generated proxies in .NET and plug them into the architecture.  When you need to execute asynchronous tasks for which no asynchronous interfaces already exist - such as reading from the file system - then you will need to queue your own delegate to to that work:

private delegate WeatherType DoStuffDelegate();

IAsyncResult BeginGetWeather(object src, EventArgs e, AsyncCallback cb, object state) {
    DoStuffDelegate d = new DoStuffDelegate(GetWeather);
    return d.BeginInvoke(cb, state);
}

void EndGetWeather(IAsyncResult ar) {
    DoStuffDelegate d = (DoStuffDelegate)((AsyncResult)ar).AsyncDelegate;
    WeatherType weather = d.EndInvoke(ar);
    img1.ImageUrl = string.Format("~/images/{0}.gif", weather.ToString());
}

Notice that in this code, our expensive GetWeather() operation is queued to run asynchronously.  The final thing is to show the pattern that you should implement to ensure that your web parts are ready to take advantage of a hosting page that is set to Async="true":

if (Page.IsAsync) {
    Page.RegisterAsyncTask(
        new PageAsyncTask(
            new BeginEventHandler(BeginGetWeather),
            new EndEventHandler(EndGetWeather),
            null, null, true)
    );

} else {
    WeatherType weather = GetWeather();
    img1.ImageUrl = string.Format("~/images/{0}.gif", weather.ToString());
}

Now our web part will always work, but will allow our users to gain the benefits of asynchronous processing when it is enabled at Page level.

posted on 3/30/2007 11:32:42 AM ( 0 Comments )


My Interview on ASP.NET Podcast is up

Towards the end of last year Wally interviewed me about my book and the interview is now online.  The audio file is available via the following link:

 

ASP.NET Podcast

posted on 3/6/2007 12:01:22 PM ( 5 Comments )


Integrating ASP.NET AJAX with SharePoint - Mike Ammerlaan's Blog

I got this link via Adam Cogan: 

Link to Integrating ASP.NET AJAX with SharePoint - Mike Ammerlaan's Blog

It discusses how to go about enabling your Sharepoint 2007 portal for use with Microsoft Ajax.

posted on 2/23/2007 6:41:49 AM ( 0 Comments )


Polymorphic Podcast : ASP.NET 2.0 Web Parts Interview

Late last year I was interviewed by Craig Shoemaker about Web Parts in ASP.NET 2.0.  During the interview we talk about the book and about the writing process in general.  I also talk about why the portal framework is cool because of the extensibility points and the modularity that is built into the framework.

Click here to download and listen to the Podcast. 

posted on 2/2/2007 5:23:25 AM ( 0 Comments )


Working with Web Parts in ASP.NET 2.0

My interview with SearchVB.com about web parts is now online:

Link to Working with Web Parts in ASP.NET 2.0

posted on 1/26/2007 6:40:42 AM ( 0 Comments )


Review: ASP.NET 2.0 Web Parts in Action: ASP Alliance

My book has been reviewed by Richard Dudley and he's posted the review on ASP Alliance here: 

Link to Review: ASP.NET 2.0 Web Parts in Action: ASP Alliance

Thankfully it was a favourable review smile_regular

posted on 1/16/2007 8:19:52 PM ( 0 Comments )


Web Parts in Action Book Review on DotNetSlackers

I just noticed that DotNetSlackers have posted a review of my book.  It's available to read here:

Link to Book Review: ASP.NET 2.0 Web Parts in Action

I'm very happy with the review and it appears that the reviewer liked the style of the book. 

posted on 12/30/2006 7:46:45 AM ( 0 Comments )


ASP.NET 2.0 Web Parts - Creating an Editor Zone Dialog

Some modern portal sites such as http://my.msn.com/ allow the editing of web parts to be carried out in a dialog window. This article shows the mechanics that are required to allow this to be done.

In my book I showed how to create a Catalog Zone dialog which allowed the web parts catalog zone to be displayed in a separate dialog window above the main page.  On the forums for the book, a user recently asked whether it would be possible to create an Editor Zone dialog - a question that I've also seen on the ASP.NET forums a few times as well.

 

Dialog Editor Zone

 

While creating a catalog zone dialog is a little tricky there's really only one really difficult task - to pass the Type name of the selected web part from the dialog window back to the parent window that opened it.  From there you simply force a postback (__doPostback) and use the IPostbackEventHandler interface to intercept it.  In the handing code you create an instance of the Type that was passed back and add it to a zone; simple! 

Creating an editor zone dialog is much trickier because we need to refer to a single instance of a web part across multiple pages.  The reason that this is so difficult is that the WebPartManager knows what web parts exist of a page and which web parts belong to which page and it does not allow a web part that belongs on one page to be added to another page - you can't even programatically change the page that a web part is connected to.  Luckily the portal framework has a mechanism that allows for web parts to be moved around via a form of serialization known as Importing and Exporting.  Through this mechanism, a web part can be exported from one page into an XML format and then, imported onto a second page using the ImportWebPart method of the WebPartManager.  The following diagrams highlight the steps that I took to get a dialog editor zone solution up and running.

 

Dialog Editor Zone

To launch the dialog I added a special edit verb to each web part which called a client-side javascript function that launched the dialog zone.  To do that I had to create a special base class which added the verb and have all of my web parts inherit from that base class.  The code for adding the special verb looked like this:

 

public override WebPartVerbCollection Verbs {
    get {
        Collection<WebPartVerb> verbs = new Collection<WebPartVerb>();
        HttpContext ctx = HttpContext.Current;

        if (ctx.Request.IsAuthenticated) {

            ClientScriptManager cs = this.Page.ClientScript;
            string postbackReference = cs.GetPostBackEventReference(this, "whatever");

            string js = string.Format("DisplayDialog(\"DialogEditor.aspx?path={0}&wpid={1}&postbackReference={2}\");",
                this.Page.Request.Path,
                this.ID,
                postbackReference
            );

            WebPartVerb editVerb = new WebPartVerb("MyEditVerb", js);
            editVerb.Text = "Edit WebPart"
            verbs.Add(editVerb);
        }

        return new WebPartVerbCollection(verbs);
    }
}

Here you can see that the new edit verb will call a client-side function named DisplayDialog when it is clicked and will pass through the following information:

  1. The path to the dialog editor
  2. The id of the web part to edit
  3. a postback reference that can be used to invoke a postback

The postback reference is special in that we can now implement the IPostbackEventHandler interface on the web part to receive the event notification when the postbackReference is invoked. 

 

Dialog Editor Zone

 

Now that the editor has been launched we need to silently invoke the calling page to get the current web part instance that we'll be editing.  Silently invoking the main page is done using the same technique that I discussed in the post about fixing broken web parts

When the dialog is loaded we first check to see whether a web part ID is passed through and if so, we remove any existing web parts and add the new one that is being edited.  We also set the page to Edit mode:

 

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

    if (!IsPostBack) {
        if (!string.IsNullOrEmpty(this.WebPartID)) {
            RemoveAllWebParts();
            AddWebPart();
            WebPartManager1.DisplayMode = WebPartManager.EditDisplayMode;
            WebPartManager1.BeginWebPartEditing(WebPartZone1.WebParts[0]);
        }
    }
}

The code for the RemoveAllWebParts and AddWebPart methods look like so:

 

void RemoveAllWebParts() {
    int countOfParts = WebPartManager1.WebParts.Count;
    for (int i = 0; i < countOfParts; i++) {
        this.WebPartManager1.DeleteWebPart(WebPartManager1.WebParts[i]);
    }
}

private void AddWebPart() {
    string webPartXML = WebPartHelper.ExportWebPart(this.WebPartID, this.PathToEdit, this.Context);
    WebPart wp = WebPartHelper.ImportWebPart(webPartXML, this);
    wp.ID = this.WebPartID;
    WebPartManager1.AddWebPart(wp, WebPartZone1, 0);
    WebPartManager1.SetDirty();
}

The REALLY IMPORTANT thing to note here is the call to SetDirty on the WebPartManager.  If you check the documentation for the WebPartManager you will not see such a method because mine is a custom WebPartManager class.  The SetDirty method simply calls through to the protected SetPersonalizationDirty method of the WebPartManager:

 

public class CustomWebPartManager : WebPartManager {
    public CustomWebPartManager() { }

    public void SetDirty() {
        this.SetPersonalizationDirty();
    }
}

I added the call to SetDirty because generally Personalization changes will only be automatically saved on POST requests and not on GET's.  Because we got to the dialog editor via a GET request we need to manually mark the personalization data as dirty for our changes to get saved.  Failure to do this will mean that our web part is not properly added to the page and will therefore not be available when we attempt to re-access our changes from the main page later on.

To close the dialog editor I've just stuck a close button on the page which invokes the following javascript:

 

protected void btnClose_Click(object sender, EventArgs e) {
    ClientScriptManager cs = this.ClientScript;
    string script = "<script>CloseEditorDialog(\"" + PostbackReference + "\") ;</script>"
    cs.RegisterStartupScript(this.GetType(), "whatever", script);
}

As you can see, closing the dialog will call a CloseEditorDialog function passing in the original PostbackReference string that was created from the verb on the calling web part.  The CloseEditorDialog function will close the editor dialog and invoke the postback by eval'ing the PostBackReference string:

 

function CloseEditorDialog( postbackReference ) {
    if( window.opener ) {
        window.opener.DoEditorPostBack( postbackReference ) ;
    }
    window.close() ;
}

function DoEditorPostBack( postbackReference ) {
    eval(postbackReference) ;
}

 

 

Dialog Editor Zone

 

When the postback is invoked our RaisePostBackEvent method will get called and we will grab the web part from the dialog window and return it as XML: 

public void RaisePostBackEvent(string eventArgument) {
    if (eventArgument == "whatever") {
        string xml = WebPartHelper.ExportWebPart(this.EditorPath, this.Context);
        if (!string.IsNullOrEmpty(xml)) {
            WebPartHelper.CopyWebPartValues(xml, this);
        }
    }
}

The XML is retrieved from the dialog window using the custom ExportWebPart method which, again, silently invokes the editor dialog to get the instance that was just edited:

 

public static string ExportWebPart(string path, HttpContext context) {

    StringBuilder sb = new StringBuilder();
    Page page = (Page)BuildManager.CreateInstanceFromVirtualPath(path, typeof(Page));

    page.Load += delegate {
        WebPartManager wm = WebPartManager.GetCurrentWebPartManager(page);

        if (wm.WebParts.Count > 0) {
            WebPart part = wm.WebParts[0];
            if (part.ExportMode != WebPartExportMode.None) {
                using (StringWriter sw = new StringWriter(sb))
                using (XmlTextWriter xw = new XmlTextWriter(sw)) {
                    wm.ExportWebPart(part, xw);
                }
            }
        }
    };

    ExecutePage(page, path, context);
    return sb.ToString();
}

private static void ExecutePage(Page page, string path, HttpContext context) {
    string originalPath = context.Request.Path;
    context.RewritePath(path);

    try {
        context.Server.Execute(page, TextWriter.Null, false);
    } catch { }

    context.RewritePath(originalPath);
}

 

Dialog Editor Zone

 

For now I have a very dumb method in my WebPartHelper class to copy the values from the XML that came back from the editor dialog and onto the instance.  The method is basically just a huge switch statement which knows how to grab a string value and convert it to several underlying instance types.  I'm sure that this could be done much better with more reflection code and some type converters but I won't take it any further for now.  The method looks like this:

 

/// <summary>
/// Copies the Browsable property values from one web part to another
/// </summary>
/// <param name="webPartXML">The web part values to copy from</param>
/// <param name="copyTo">The web part to copy the values to</param>

public static void CopyWebPartValues( string webPartXML, WebPart copyTo ) {

    XmlDocument doc = new XmlDocument();
    doc.LoadXml(webPartXML);
    XmlNamespaceManager mgr = new XmlNamespaceManager(doc.NameTable);
    mgr.AddNamespace("wp", "http://schemas.microsoft.com/WebPart/v3");

    foreach (XmlNode node in doc.SelectNodes(@"//wp:data/wp:properties/wp:property", mgr)) {
        string propertyName = node.Attributes["name"].Value;
        string propertyValue = (node.FirstChild == null) ? "" : node.FirstChild.Value;

        PropertyInfo prop = copyTo.GetType().GetProperty(propertyName);

        switch (prop.PropertyType.Name) {
            case "Boolean":
                prop.SetValue(copyTo, Convert.ToBoolean(propertyValue), null);
                break;
            case "Integer":
                int b = 0;
                if( int.TryParse(propertyValue, out b )) {
                    prop.SetValue(copyTo, Convert.ToInt32(propertyValue), null);
                }
                break;
            case "DateTime":
                DateTime d = DateTime.MaxValue;
                if( DateTime.TryParse(propertyValue, out d ) ) {
                    prop.SetValue(copyTo, d, null);
                }
                break;
            case "Unit":
                Unit u = new Unit(propertyValue);
                prop.SetValue(copyTo, u, null);
                break;
            case "Color":
                prop.SetValue(copyTo, Color.FromName(propertyValue), null);
                break;
            case "String":
                prop.SetValue(copyTo, propertyValue, null);
                break;
            default:
                // TODO: Handle these
                // System.Web.UI.WebControls.WebParts.WebPartExportMode ExportMode
                // System.Web.UI.WebControls.WebParts.PartChromeType ChromeType
                // System.Web.UI.WebControls.ContentDirection Direction
                // System.Web.UI.WebControls.WebParts.PartChromeState ChromeState
                // System.Web.UI.WebControls.WebParts.WebPartHelpMode HelpMode
                Debug.WriteLine(prop);
                break;
        }
    }
}

posted on 12/27/2006 10:50:41 AM ( 28 Comments )


Fixing Broken Web Parts

When I was writing my book I got to the 2/3 review phase and I received some feedback that I needed to include a chapter on deployment. smile_eyeroll  Nearly every book that you read seems to have the obligatory chapter on deployment and I didn't want to head down that well documented path - so I put on my thinking cap to ponder how I could satisfy these requests while not rewriting the most written chapter in book history. smile_thinking

As I thought about it, I realized that this was a great opportunity to look at the deployment of an ASP.NET 2.0 site from a different angle.  What I decided to do was to write a chapter on how to create a portal that could be easily supported when it was in production.  This is very different to forcing myself to come up with 10,000 words about what is essentially Copy/Paste. smile_regular

I wrote yesterday that one of the features that I wrote about in Chapter 9 was a significant new feature named Health Monitoring.  In another section of Chapter 9 I wrote about allowing users to remove broken web parts from a web page.  Here's the scenario:

You're running a portal page and over time you have made a significant number of personalization changes to the page.  You've added web parts to it, you've moved them around, and made other customizations too.  One day a new web part appears in the web part gallery and you add it to your page.  This web part has some logic errors in its Render method and upon adding the web part to your page you are presented with the all too familiar error page of death!  At this time the broken web part has been saved with the personalization data for the page and there is no way to display the page to remove the broken part - you're screwed!

In Chapter 9 I took a sample that Mike Harder presented at last year's PDC where he showed how to allow users to self-correct pages that had fallen to this despicable fate.  Mike showed that you could create a specialized Custom Error Page that the user would be directed to that would list all of the web parts on the page that they had come from and allow that user to manually remove web parts from that page.  Here's a picture of what that page looks like:

Repair broken pages from a custom error page

Here you can see that the user can click the button that appears next to each web part to remove web parts from the faulty page.

One of my readers pointed out that I didn't supply a working code demo for how to get this sample working and so I've created a small sample project that you can download and run to play with this scenario to see how it works.  The demo code is located here:

     Web Part Management Sample

posted on 11/26/2006 1:29:04 PM ( 0 Comments )


Health Monitoring Sample for Web Parts in Action

One of my readers wrote to me today to let me know that I hadn't included a sample for the Health and Activity Monitoring stuff that I wrote about in Chapter 9.  Health Monitoring is a really cool feature of ASP.NET 2.0 that allows you to raise events and configure the providers for them.  I've created a working solution of the concepts that are discussed in Chapter 9 which can be downloaded from here:

http://markitup.com/BookCodeSamples/HealthMonitoring.zip

In the sample a Health Event is raised when a web service takes too long to return:

OPMLService.OPMLService service = new OPMLService.OPMLService();
Stopwatch watch = new Stopwatch();

watch.Start();
string message = service.GetOPML();
watch.Stop();


if (watch.ElapsedMilliseconds >= 2000)
{
    ExternalCallWebEvent ev = new ExternalCallWebEvent(
        string.Format(
            "The call to OPMLService took {0} milliseconds to complete.",
            watch.ElapsedMilliseconds
        ),
        this,
        WebEventCodes.WebExtendedBase + 1
        );

    ev.Raise();

}

string xml = "<pre>" + Server.HtmlEncode(message) + "</pre>";
LiteralControl lit = new LiteralControl(xml);
this.Controls.Add(lit);

The ExternalCallWebEvent class is a custom event and it is configured in the Web.config file to be used with the standard Windows Event Log provider:

<healthMonitoring enabled="true">
  <eventMappings>
    <add name="WebServiceCallEvent" type="ExternalCallWebEvent" />
  </eventMappings>
  <rules>
    <add name="ServiceCallRule"
         eventName="WebServiceCallEvent" provider="EventLogProvider" />
  </rules>
</healthMonitoring>

When the page in the sample is run, you can open the Windows Event log to see details of the event:

 

Windows Event Log

posted on 11/25/2006 9:40:41 PM ( 0 Comments )


Creating a Tabbed View

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

Download the code for this article. 


During my podcast interview with Craig I discuss how readers of my book learn how to take the basic components in the portal framework - zones, web parts, etc - and to customize them to suit everyday requirements.  In the book I really strove to show examples that are quite common, and that my readers would undoubtedly be called upon to implement in their own portals.  A couple of these examples were:

  1. A catalog zone which displays in a pop-up dialog.
  2. An editor zone with collapsible/expandible editor parts.
  3. The ability for users to remove web parts that cause a page to crash.

Anyways, today while browsing the ASP.NET forums I saw a request for a common portal feature - tabbed views.  Before I explain how to implement such a feature, take a look at the following 2 images which show tabbed views implemented in the Google and Live portals:

 

Live Portal Pages Google Portal Pages

 

 

Throughout the remainder of this article I will discuss what is required to allow users to have a tabbed view and to add code that allows a user to drag web parts from one tab and onto another tab.  When our page is complete it will look like so:

 

Custom Portal Pages

As you can see, it's from this simple page that a user could drag the Untitled web part from WebPartZone1 (which resides on Tab 1) and onto another zone named WebPartZone2 (which resides on Tab 2).  OK, without further ado, let's get cracking.  First things first, let's create a basic page with some tabs on it...

 

Create a page with Tabs

For our example, we'll create a very simple page with 2 tabs with a zone in each tab.  Each zone will begin with a single web part.  To do this I need to add the following HTML to my web form:

<asp:WebPartManager id="WebPartManager1" runat="server" />

<div id="page">
    <div id="tabs>
        <span class="tabheader" id="tabheader1" onmouseover="DisplayTab(1);">Tab 1</span>
        <span class="tabheader" id="tabheader2" onmouseover="DisplayTab(2);">Tab 2</span>

    </div>
    <div id="tab1" class="tabpage_visible">
        <div class="tabbody">
            <asp:WebPartZone ID="WebPartZone1"runat="server">
            <ZoneTemplate>
                <asp:TextBox ID="TextBox1"runat="server" />
            </ZoneTemplate>
           
</asp:WebPartZoneID>

        </div>
    </div>
    <div id="tab2" class="tabpage_hidden">
        <div class="tabbody">
            <asp:WebPartZone ID="WebPartZone2"runat="server">
            <ZoneTemplate>
                <asp:Calendar ID="Calendar1"runat="server" />
            </ZoneTemplate>
           
</asp:WebPartZoneID>

        </div>
    </div>
</div>

From the HTML we can see that the first control which has been added is the WebPartManager; this control is required on all web part pages as it really is the brains of all of the portal operations.  Next we can see that I've added a DIV element with an ID of "tabs" which will contain the SPAN's for each tab that I want to display.  You could use an HTML UL for the tabs if you want to get a little fancier with the CSS styles but I've used SPAN's for simplicity.  After the tab elements we have DIV's for each page that is associated with a tab.  For example, the DIV with the ID of "tab1" is associated with the SPAN with the ID of "tabheader1".  The idea being that when the user clicks on a tabheader, a piece of javascript will get executed to ensure that the correct tab page is visible.  Before taking a look at the javascript which will handle the clicks on the tab headers, I'll quickly present the CSS that is used to layout the page:

.tabpage_visible {
    height: 300px;
    width: 400px;
    background-color: yellow ;
    display: "";
    position: absolute ;
}

.tabpage_hidden {
    height: 300px;
    width: 400px;
    background-color: yellow ;
    display: none ;
    position: absolute ;
}

.tabheader {
    height: 40px;
    width: 150px;
    background-color: silver ;
    color: black ;
    position: relative ;
}

.tabbody {
    height: 100%;
    background-color: white ;
}

Again, we have some very simple stuff happening here.  The tabpages have a style for their visible and hidden state; theres a style for the tabs in the header; and finally a style which sets some styles for the body of a tab page.  Very simple stuff.  The magic that will allow a user to switch between tab pages is a small piece of javascript which is invoked when the user clicks on tab - it looks like so:

var tabs = ["tab1", "tab2"] ;
var _currentTab = null ;

window.onload = function() {
    DisplayTab(1) ;
}

function DisplayTab( tabNumber ) {

    var newTab = document.getElementById("tab" + tabNumber) ;
    if( newTab == null || newTab == _currentTab )
        return ;

    for( var i=0; i<tabs.length; i++ ) { var tab = document.getElementById(tabs[i]) ;
        tab.className = (tab == newTab) ? "tabpage_visible" : "tabpage_hidden" ;
    }
    _currentTab = newTab ;
}

Two global variables - tabs and _currentTab - are used to store references to the tabs and to keep track of which tab is the one that is currently being displayed to the user.  When window loads, we preset the currently loaded tab to be tab1; this is achieved by calling our DisplayTab helper function and passing in the number of the tab that we want to display.  This is the same helper function which gets called when the user clicks on a tab link.

So that's the end of our simple tabbed page and at this point you can run it and click between the tab pages. 

 

Enabling dragging between tabs

Now that we've seen how to create a very simple tabbed interface we need to work out how to allow the user to drag web parts between separate tabs.  The way that we'll be doing that in this article is by listening to drag events and then writing some custom logic to determine whether the drag is occurring over one of our pages.  To do this we'll attach an event handler to the ondragover event of the document using the attachEvent method - also we must remember to clean up after ourselves by calling detachEvent when the page unloads:

window.onload = function() {
    DisplayTab(1) ;
    document.body.attachEvent("ondragover", CheckTabs);
}

document.onunload = function() {
    document.body.detachEvent("ondragover", CheckTabs);
}

Here we see that the attach/detach event methods have been added to the code which will handle the page loading and unloading.  Now whenever the user is dragging web parts around on their page, the method that we've wired up to the ondragover event of the document will be run - the CheckTabs method which looks like so:

function CheckTabs() {
    var eventLocation = __wpGetPageEventLocation(window.event, true);
   
    for( var i=0; i<tabs.length; i++ ) {
        var tab = document.getElementById(tabs[i]) ;

var headerId = "tabheader" + (i+1) ;
        var tabHeader = document.getElementById(headerId) ;
        if (IntersectsWith(eventLocation, tabHeader)) {
            DisplayTab( i+1 ) ;
            break ;
        }
    }
}


function IntersectsWith(location, el) {
    var withinX = (location.x < (el.offsetLeft + el.offsetWidth)) && (location.x > el.offsetLeft)
    var withinY = (location.y < (el.offsetTop + el.offsetHeight)) && (location.y > el.offsetTop)
    return withinY && withinX ;
}

The first thing that you'll notice about the CheckTabs function is that it calls a helper function named __wpGetPageEventLocation to determine the location of the dragover event which has just taken place.  The wpGetPageEventLocation function is a private function that is contained within the resources of the ASP.NET .dll and probably isn't really designed to be called from your own code smile_regular  It doesn't really do anything overly special and so you could just include your own logic for getting the location of the event - but I'm going to use their here for brevity.

As we progress down through the CheckTabs method you will see that what we are doing is to cycle through each of the tabs and using another custom function named IntersectsWith to check whether the event location (the drag) is over a tab - if so, we call our handy DisplayTab function to display that tab.

That's really all there is to being able to drag web parts between pages and at this point you should run your page to note that when you drag a web part from one zone, and drag it up over a tab, that the tab page becomes the active page and you will be able to view the other zone.

Gotcha!

For those of you who have made it this far and run the example, you will notice that while you can drag a web part and have it switch pages, you are not actually able to drop that web part into the zone on the other page.  When I first created this sample today and I saw this behavior I was stumped - in fact it took me a couple of hours to debug the issue.  It turns out that positioning logic on the previously hidden zone is a little screwy and that, whenever we switch between tab pages we must call another private ASP.NET javascript function to make things right smile_omg.  So, provided you are happy enough calling a couple of undocumented ASP.NET javascript functions I will now show you which function to call smile_regular:

function DisplayTab( tabNumber ) {

    var newTab = document.getElementById("tab" + tabNumber) ;
    if( newTab == null || newTab == _currentTab )
        return ;

    for( var i=0; i<tabs.length; i++ ) {

var tab = document.getElementById(tabs[i]) ;
        tab.className = (tab == newTab) ? "tabpage_visible" : "tabpage_hidden" ;
    }
    _currentTab = newTab ;
    __wpm.UpdatePositions() ;
}

The function in question is highlighted in red and has been inserted at the end of the DisplayTab helper function.  This function cycles through the zones contained within the web part manager and ensures that the left, right, top, and bottom measurements are correctly reflected in the in-memory instances of all zone and web part variables. 

That's it, re-run your page now and you should have a perfectly working prototype that allows you to drag web parts from one tab page to another.

Now that you've read this article, you should read part 2 where we will look at how to make the tabs dynamic.

Read Part 2 >>

 

posted on 11/11/2006 5:34:41 PM ( 37 Comments )


Podcast with Craig Shoemaker on Web Parts

This morning I had a podcast interview with Craig Shoemaker on his Polymorphic Podcast where we discussed web parts and the portal framework:

Link to Craig Shoemaker : Podcasting (Web) Parts

 

The podcast will hopefully go up sometime around the new year so I will blog again when it does.  I was very grateful for the opportunity to discuss the portal framework in such detail because I'm sure that it's still a widely misunderstood and underutilized piece of the application development puzzle.

Craig's podcast is a unique format and I've been listening to it for a while - in fact it's the only technical podcast that I listen to regularly.  The style of Craig's podcast is extremely conversational and the speakers have always been excellent.  Craig's recent interviews with Miguel (creating extensible architectures) and Rob Howard were especially enjoyable.

posted on 11/9/2006 12:09:58 PM ( 0 Comments )


The portal framework

As I mentioned, my book is now fully released and is ready to ship from Amazon or the Manning site. 

Web Parts in Action

Now that the book is "out there" I'm hoping to really get behind it and to support it by doing a bit more blogging about the portal framework and also spending time answering questions in the ASP.NET web parts forum.

I'm particularly keen to start writing more code against the portal framework because that's the part that I really enjoyed the most when I was writing my book.  In Web Parts in Action I show how to create such cool portal extensions as:

  • A CatalogZone which displays in a pop-up dialog
  • Expandible/Collapsible EditorParts in the EditorZone
  • One-click editing of WebParts
  • Recovery page (which I talk about here)
  • and more

I was really "chuffed" to see a review here about my book which said:

"There are numerous functional areas discussed where I said out loud: "I didn't know it could do that!"."

So now I'd like to start creating more killer extensions for the portal framework because I love working with it.  As I get ideas about what sort of extensions to build from the forums or from comments I'll prototype them and then blog about them.  Some of them will probably get rolled into SUB too!

posted on 10/28/2006 12:05:07 PM ( 0 Comments )


Permissions for ASP.NET DB Services

This is a reproduction of a post that I made here earlier today.

You'll notice that when you install the ASPNETDB that there are a bunch of application roles which get created in that database.  There are groups of roles for:

  • membership

  • roles

  • profile

  • personalization

and for each of those, there is a "basic", "full", and "reporting" role.  So, for example, with personalization role there exists:

  • aspnet_Personalization_basic

  • aspnet_Personalization_full

  • aspnet_Personalization_reporting

in order to "enable" the web parts functionality, you should only have to grant your database user access to the aspnet_Personalization_basic role.  Likewise, if you want to use membership, then grant access to the aspnet_Membership_basic role.

Of course that's not the full story, if you want to learn about the exact difference between each of those roles, take a peek at this article:

http://msdn2.microsoft.com/en-us/library/ms164596.aspx

posted on 10/28/2006 5:44:18 AM ( 0 Comments )


How to get around internal and private members :-)

As I read Scott's reply to Paul's post I was thinking about the ingenuity of developers and how they will always find a way around such trivial things as access modifiers when I remembered a concrete example of it smile_wink

Fredrik Normen (http://fredrik.nsquared2.com) was one of the real pioneers with the ASP.NET Web Part stuff and I have him to thank for a lot of the stuff that I learned leading up to my book on the subject. This blog post is in no way a slight on Fredrik but instead serves to highlight his ingenuity smile_wink

One of his most popular creations was his Templated Web Part Chrome:

     http://fredrik.nsquared2.com/viewpost.aspx?PostID=248

I wanted to learn how he had managed to recreate the verbs on web parts because rendering verbs is a really tricky exercise. You have to take into account server side verbs, client side verbs, and also different browser types. As I had started to roll my own logic for custom rendering of verbs I just gave up - because it's way too hard.  Spelunking the code through Reflector told me that I was up for many hundreds of lines of code.  So I lolled over to Fredrik's site to see what he had done.  I looked at the tutorial here:

    http://fredrik.nsquared2.com/viewpost.aspx?PostID=...

And then downloaded the code project from here:

    http://www.gotdotnet.com/workspaces/workspace.aspx...

I really had to laugh - mostly because I no longer felt so silly smile_teeth when I saw how Fredrik had implemented it:

 

 

private string RenderVerbs(WebPart webPart) {
    TextWriter writer = new StringWriter();
    HtmlTextWriter htmlWriter = new HtmlTextWriter(writer);

    htmlWriter.RenderBeginTag(HtmlTextWriterTag.Table);

    typeof(WebPartChrome).GetMethod(
        "RenderVerbsInTitleBar",
        System.Reflection.BindingFlags.NonPublic |
        System.Reflection.BindingFlags.Instance).Invoke(this, new object[]         { htmlWriter, webPart, 1 });

    htmlWriter.RenderEndTag();

    TextReader stringReader = new StringReader(writer.ToString());
    return stringReader.ReadToEnd();
}

posted on 10/23/2006 9:38:52 PM ( 1 Comments )


Off to the printers!

I started writing my book in October last year.  Whenever I finished writing a chapter I would send it off to Betsey - the Technical Editor on the project.  Typically, over the period of about a week, Betsey would make changes to my grammar also check that the length of titles and captions were good.  Whenever I completed a third of the book it had to be bundled up with code samples too, and sent off to a team of reviewers who provided feedback.  These reviewers tended to be people from within the industry such as MVP's and so-forth.  Between Betsey and I, we pretty much had the book written by the end of March.

When I completed the book it then had to undergo another round of technical reviewing and a final, more rigorous examination from another person within the industry.

The good news is that all reviewing is now done.  All chapters have been sent off to the printers and so, with any luck, my book will be available by the end of the month - I can't wait!  Of course you can pre-order my book from Amazon and it will be delivered when it arrives:

Book Cover

posted on 7/31/2006 8:35:44 AM ( 0 Comments )


My Web Part book now available for purchase

Manning Press - the publishers of my book - have now started releasing chapters via their MEAP (Manning Early Access Program) facility.  MEAP allows readers to get access to the books' chapters as they are completed but before they have been printed.  This is the basic blurb about MEAP:

Manning Early Access chapters are released before final editing to encourage readers to participate in the most sensitive, final piece of the publishing cycle, by offering feedback to the author at Manning’s Author Online forum.

In addition to getting the digital copies as they are ready for printing you can also opt to receive a final copy when it is available from the printers.  So head off to the site and purchase a copy via the MEAP and let me know what you think via the forums for the book.

     http://www.manning.com/neimke/

posted on 6/24/2006 9:31:27 AM ( 6 Comments )


Webparts, Gadgets, Oh My!

In a comment to a recent post, Prasad asks about Gadgets and Web Parts and wonders how they differ.  He also asks whether I cover Gadgets in my upcoming book so I thought that I'd post the answer here.

Hi Prasad, Gadgets are considered as web parts but they are different from the standard ASP.NET web parts or Sharepoint web parts.  From Sharepoint 2007 onwards, Sharepoint will be actually using ASP.NET web parts - so creating a web part using ASP.NET can be shared with your own custom websites and Sharepoint.

Live.com Gadgets use a different model and authoring them requires you to understand the Live.com Gadget API.

My book devotes a Chapter to explaining Live.com Gadets and takes the reader through writing and deploying their own Gadget using Atlas.

posted on 5/29/2006 3:18:39 PM ( 0 Comments )


ASP.NET 2.0 Web Parts in Action

After 8 months of planning, writing, and fine-tuning my book has finally set sail for production!  You can see the home page for the book here: http://www.manning.com/neimke/

ASP.NET 2.0 Web Parts in Action

If everything goes according to plan the book will be available in bookstores from mid-August although the PDF version should be available prior to that.  The picture on the cover is of a Pirate and I chose it because my eldest son - Harrison (5) - is very much into pirates right now :-)

posted on 5/25/2006 1:54:11 AM ( 6 Comments )


Google Reader now with friendly mobile interface

I posted recently about the cool new Google Reader for managing blog subscriptions.  If you are using the custom Google Reader web part and browsing your feeds from your portal home page you will now benefit from the fact that they've given it a friendlier mobile interface.  Here's a screenshot:

Google Reader displays feeds nicely on a mobile device

Originally found here.

posted on 5/19/2006 12:15:00 PM ( 0 Comments )


More Google Portal Goodness

The Google Personalized Home Page and Google Reader totally rock!  To use Google them you must first create a GMail account and then sign in to the Google.com home page to start personalizing it to your own liking.  Unlike Live.com, Google have gone for the kind of simple elegance that has made Google a market leader in web technology.  One of the great recent additions to the portal has been the tight integration between it and the Google Reader (click here to learn about Google Reader).

Today I noticed another really cool thing about Google Reader.  Click on this button to see it for yourself (and then return to the article):

   Add to Google

Notice that, by adding that link to your blog you can take users to a page that allows them to add your blog to either their default portal view or to your subscriptions. 

Google Reader impresses me and I'm sure that as soon as Google iron out a few bugs in their UI that it is going to gain a very important slice of the feed subscription market.  I'm not sure whether Google Reader plan to open up an API to access feed stores but I'll be watching this space with a great deal of interest.

posted on 5/11/2006 3:59:20 PM ( 0 Comments )


Providing users with a self-service way of fixing broken pages

Chapter 9 is all about looking at various ways to manage a web portal an my favourite sample from that chapter is a feature that the ASP.NET team showed off at PDC in LA last year.  In that session Mike Harder, who is a Software Design Engineer on the ASP.NET team for the web parts feature, showed us how to create a page that displays all of the web parts for another page and then allows individual web parts to be deleted from the personalization data for that page.  To do this, we dynamically execute the target page "silently" and then handle the PreInit event off of it and then grab the web parts from the WebPartManager instance on the page. 

To dynamically execute another page and attach an event handler we use the following snippet of code:


Page page = (Page)BuildManager.CreateInstanceFromVirtualPath(path, typeof(Page));

WebPartCollection webParts = null;

page.PreLoad += delegate {
    webParts = WebPartManager.
        GetCurrentWebPartManager(page).WebParts;
    };

ExecutePage(page, path, context);


Here we instantiate a page dynamically by using CreateInstanceFromVirtualPath method of the BuildManager class.  This method takes the path of the ASP page that we wish to create an instance of and also a type argument which indicates what the base type for the page class is.

At this point you might be wondering why you would want to create a separate page to delete web parts.  Consider what would happen if you added a web part to your page and that web part had some logic error which caused it to throw an exception from within its RenderContents method.  The answer to this is that the web part would get added to the personalization data for the page and then that page would not be able to be displayed again - because of the exception that is thrown from the web part.  This means that you cannot access the page to remove that web part and fix the problem.  Now consider what would happen if that happened on the home page of a public internet site that you have just deployed!

Now I'm sure that you can see why it's important to provide a separate page so that we can recover from just such a case.


In our example, the helper method for executing the page looks is shown in the following snippet:

 

private static void ExecutePage(Page page, string path,
                                HttpContext context) {
    string originalPath = context.Request.Path;
    context.RewritePath(path);

    try {
        context.Server.Execute(page, TextWriter.Null, false);
    } catch {}

    context.RewritePath(originalPath);
}


You can see here that a null TextWriter is passed to the Execute method so that the page is not rendered anywhere.  Once the page is executed, our anonymous method in the first snippet will get invoked and the web parts will be popped-off into our local webParts variable.  This variable can then be bound to a GridView and displayed to the user.  When the user chooses which part they would like to remove we run through a similar set of steps to when we loaded the web parts initially only our anonymous method uses the DeleteWebPart method on the WebPartManager to remove that web part like so:


page.PreLoad += delegate {

    WebPartManager webPartManager =
        WebPartManager.GetCurrentWebPartManager(page);

    WebPart webPart = webPartManager.WebParts[ID];
    webPartManager.DeleteWebPart(webPart);
};


In the book I go into more depth and there's also a working example of how to hook this up as the standard handling mechanism for errors that occur in a portal application so that users are given an immediate self-service option for fixing their broken page.

Thanks Mike, now I know that I won't get a call from the boss at midnight asking me to fix his broken portal page!

posted on 3/13/2006 9:30:01 PM ( 0 Comments )


Managing Personalization Data with PersonalizationAdministration

I'm just finishing off Chapter 9 in my book which is a chapter on how to manage a web portal and in that chapter I take some time to look at the PersonalizationAdministration class to show what it has to offer.  The PersonaliztionAdministration class has static properties and methods that allow us to query personalization data.  For example, using this class you can run a query to view all stale personalization data - that is personalization data that has not been accessed for longer than a given time.  You can also run queries that allow you to view all personalization data for a given user or users.  Most of the queries return a PersonalizationStateInfoCollection which represents the personalization data that matches your query.  Here's an example of a query that is used to return all personalization data that is older than 200 days:


DateTime inactiveSince = DateTime.Now.AddDays(-200) ;

PersonalizationStateInfoCollection inactiveUserResult =
   PersonalizationAdministration.GetAllInactiveUserState(inactiveSince) ;


Running this type of query would be useful for a large, highly active site that did not wish to keep personalization data for users who no longer used the site.  In such a case the site administrator could run this query and then make decisions about which users to delete the personalization information for.  To assist with such a task the administrator could write another query which allowed them to view all personalization data for a specific user before they deleted the data for that user.  The following picture shows what these queries might look like when presented as a couple of administration web parts.

 

Two web parts that are used to view the results of personalization data queries.

 

The code for finding the personalization data for a specific user looks like this:


PersonalizationStateInfoCollection userResult =
    PersonalizationAdministration.FindUserState(null, UserNameTextBox.Text);


When the administrator has decided to delete the personalization data for a specific user they can simply call the ResetUserState method of the PersonalizationAdministration class like so ;


PersonalizationAdministration.ResetUserState(null, UserNameTextBox.Text);


Finally, the PersonalizationStateInfoCollection class contains a collection of PersonalizationStateInfo instances.  The PersonalizationStateInfo class is an abstract class and so each of the items will actually be an instance of either the UserPersonalizationStateInfo class or the SharedPersonalizationStateInfo class.  The PersonalizationStateInfo class itself only contains 3 properties that are useful to us which are: LastUpdatedDate, Path, and Size.  When looping through a collection of personalization data from a query such as GetAllInactiveUserState we must therefore cast each item to a UserPersonalizationStateInfo object before we are able to get at the Username of the user that is associated with the personalization data.  The following code snippet shows an example of the properties that can be found on a PersonalizationStateInfo item:

 

inactiveUserResult[0].LastUpdatedDate;  
inactiveUserResult[0].Path ;
inactiveUserResult[0].Size ;

((UserPersonalizationStateInfo)inactiveUserResult[0]).Username;
((UserPersonalizationStateInfo)inactiveUserResult[0]).LastActivityDate;

posted on 3/13/2006 7:31:41 AM ( 6 Comments )


Live.com Gadgets - Come all ye Component Builders?

I was fortunate enough to see a preview of the Live.com platform last night in Sydney.  At the event I saw the Live applications and heard about the future of the platform.  I have to say that I walked away with a head full of ideas.

My main interest in Live.com centers around gadgets and so I'm very interested to see whether  "Live" exposes an open set of services that could act as a bedrock for building rich Web 2.0 mash-up style applications.  My hope is that at some point developers will be able to tap in to the Live services to create gadgets that are highly personalized and which target the end-user.

I haven't done a lot of research into gadgets at this stage but I'm very keen to learn more about where Microsoft is heading with this stuff.   If the Live services are open then this is going to be an amazing time to be a component builder in the web apps space.

 

posted on 3/10/2006 7:14:50 PM ( 0 Comments )


New version of SUB

I'm currently right in the middle of preparing a new release of SUB and I should have the source code up on ProjectDistributor later tonight.  If you are running an existing build of SUB remember from yesterdays post that the XML index files that used to live in the \data folder now live in the \App_Data folder although all of the post content folders still live under the old \data path.

I just wanted to mention three of the new features that I've added in this release that I'm real happy with.

1) Posts Archive Web Part

If you look on my website you will notice that, on the left side of the page there is now a web part that lists each month and shows how many posts exist for the month.  Clicking on a month takes you to a new page which displays the entries for that month.

2) OPML Web Part

Take a look at the following image:

OPML Web Part

 

This is a web part that allows you to specify the URL of an OPML feed and an optional path to an XSLT and then displays a list of all of the bloggers on the OPML.

 

3) Catalog Dialog

Take a look at the following image:

Catalog Dialog

The pop-up window that you can see with three tabs is called the CatalogDialog.  Basically, this is a Catalog Gallery that reads a list of web parts from an XML configuration file and displays them within a category.  You launch the CatalogDialog by clicking a button which is rendered at the head of each WebPartZone if you are authenticated.  When you click on one of the web part links in the CatalogDialog that web part is added to the zone in the main window that you launched the CatalogDialog from. 

Because this feature reads from an XML configuration file you can easily upload assemblies that contain web parts and then add a configuration entry to have web parts that are in the assembly that you uploaded appear within the catalog.  This makes it extremely easy to add your own web parts without having to redeploy the site. 

I created the CatalogDialog for the WebParts book that I'm currently writing and which will be finished next month!

 

posted on 3/1/2006 2:31:19 AM ( 0 Comments )


SSO Needed for Portals

To provide a full service, Web 2.0 sites need to be able connect to external sources - such as the PageFlakes portal which exposed a Gmail web part that I showed the other day.  Other external sources might include:

  • Map data from Google
  • Wishlist information from Amazon
  • Photo's from Flickr


To be able to connect to these services requires authentication information to be passed around and so it is likely that Identity will become a major topic of discussion sooner rather than later so that we can achieve a form of single sign on across the web.  The buzzwords in this space at the moment are: Web 2.0, Identity 2.0, LAMP, SAML, Passport, and Infocard.

If you'd like to learn more about the issues surrounding Identity 2.0 I'd behoove you to download the following presentation and watch it:

    http://www.identity20.com/media/OSCON2005/

The format of the presentation has been talked about quite a bit throughout the blogsphere and so it's probably worth watching it just for that :-)


Security Quote of the Day

Your identity is your most valuable possession.  Protect it.  And if anything goes wrong, use your powers.

Elastigirl
Elastigirl

posted on 2/12/2006 3:43:12 PM ( 1 Comments )


Google Web Portal

I've used http://Google.com/ig as my home page now for some time and have become used to seeing all of my favourite information in one place whenever I fire-up a browser.  I can see my Gmail, USA Today headlines, and the weather for Canberra, Seattle, and Adelaide all on one page.   The cool thing is that you can open up the web part Catalog by clicking on "Add Content" to add new web parts to the page.

Just recently I noticed the "More Content" link within the web parts catalog which let's you get at loads of additional content that I never knew existed.  From there I grabbed two cool new web parts and added them to my page.  The first one is a set of eyes that follow your mouse around the page, watching your every move and the second web part is a part which shows a random Flickr image. 

This next figure shows the "Google Eyes" web part watching my cursor as it hovers near the "More Content" link within the catalog.

Google Eyes web part

posted on 2/12/2006 12:28:56 AM ( 0 Comments )


This blog is now running SUBV2

If this message has popped into your aggregator then I've most certainly done something right because today I changed my blog over to a brand new version of my SingleUserBlog blogging application which I affectionately refer to as SUBV2.  SUBV2 is a significant jump from the previous version in that it's built using ASP.NET 2.0.

Built using ASP.NET 2.0

Running on top of ASP.NET 2.0 has allowed me to introduce some extremely advanced features while possibly reducing the overall size of the codebase.  Some of the new features include MasterPages and Themes which are used to give the site its look and feel.  Another of the significant changes that have come through using ASP.NET 2.0 are Web Parts and Personalization.  By using these "portal" features I have been able to implement almost all of the content that is visible on this page by using web parts.  To get a feel for how web parts are used on this page, consider the following:

  • The Hosted By Orcsweb image is a web part
  • The Google Search Form is a web part
  • The Posting Categories list is a web part
  • Each of the Link Categories on the right are web parts
  • The Recent Posts listing is a web part
  • The Horizontal Rule just above the Recent Posts is a web part

As you can see, the web parts that are included with SUBV2 are a mixture of application web parts (Posting Categories and Recent Posts) as well as functional web parts that allow me to add any arbitrary image, link, or even HTML to the page at will.  The power of web parts is that they can easily be personalized at runtime.  This means that I can move any of the web parts around by simply dragging them with my mouse and placing them to almost anywhere on the page.  I can also add new web parts too.  As an example, I could easily add a new HTML web part that displayed a welcome message to visitors of my blog and place it at the top of each page on the site.  I could even add my own CSS or Javascript too!

Advanced Portal Framework Features

In addition to the "out-of-the-box" web part features, this blog also uses some pretty advanced portal customizations - such as a CatalogZone that is displayed in a pop-up dialog (ala Sharepoint V3).  The popup CatalogZone will be used to display web parts that are housed in custom web service galleries.
 
The blog also features:

  • Custom WebPartManager to implement common authorization filter logic
  • Custom EditorZone to display editor parts in expandible/collapsible regions
  • Custom WebPartZones to perform custom rendering on web part chrome and to add custom zone verbs to web parts
  • Custom PersonalizationProvider so that all personalization is stored against a common key (not the page url) so that changes made to a web part are applied right across the portal site


Migrating my content

Migrating the content from the previous version of my blog was simple and took me less than twenty minutes.  To migrate the content I opened the administration section of the previous version and saved the blog to BlogML format; then I opened the administration section of this version and imported it.  That's the power of BlogML!  The content that migrated included all of my internal links, attachments and images - so no content was lost at all.

Making SUBV2 publicly available

Over the weekend Paul Stovell - who is currently running SUBV1 for his blog - will be migrating that blog over to use SUBV2.  During that migration he will write a blog article that explains the steps that are involved in setting up SUBV2 and customizing it with your own MasterPage and Theme.  When he has posted that article I'll upload the source code for SUBV2 to ProjectDistributor so that it is publicly available.  In addition to Paul's article I'll be posting some tutorials to my blog that explain some of the features that are there.

Over time I'm hoping to build a small community around SUBV2 so that we can share Themes and WebParts.

posted on 1/14/2006 3:40:40 PM ( 2 Comments )


Making Web Parts easier to manage

Adding Dynamic Content

I mentioned yesterday that I've been creating numerous different web parts throughout the course of writing my book on the ASP.NET 2.0 Portal Framework.  The web part that I showed in yesterday's article was a part that allowed arbitrary chunks of HTML to be added as content.

Many of the web parts that are included in the book were inspired from web parts that I needed for the next version of SingleUserBlog - which is built on top of the portal framework.  Having web parts for all of the dynamic content in SingleUserBlog really gives me a lot of flexibility to add dynamic content to my blog and position it wherever I want.
 

Hyperlinks WebPart

It would be fair to say that most bloggers have many links that are displayed on their blogs.  Heck, even on my simple little blog I have the following groups of links:

My Websites
Blogs I Read
Blogs Using SUB
My Articles
Tools I Use

For other bloggers that I know of it's common to have as many as twenty different categories of links displayed somewhere on the page of their blog.  For this reason, one of the first web parts that I built was a web part that allowed me to add hyperlinks and have them displayed on the page.  Here's a picture of the web part with links for the blogs that I read:

A web part that displays links to the blogs that I read.

Custom EditorPart for the HyperlinksWebPart

When I created the HyperlinksWebPart I decided that the data type for storing the custom content should be a custom class that provided a high level of fidelity over how the links are handled.  For example, a user should be able to specify some of following attributes for each hyperlink that they add:

  • Display Title
  • Url
  • Description (Optional)
  • Display Order
  • Whether or not to open in a new page

Using a complex data type to store the personalization sure provides an easy way to store the data about each hyperlink but complex types do not, by default, get displayed within the property editor when editing a web part.  Thankfully the portal framework makes it very simple to associate custom user interface elements with a web part by simply overriding a method named CreateEditorParts and returning a class that knows how to create an editing interface for a particular type of web part.  By doing this you are able to provide a totally custom solution for managing the data for a web part.  For example, you could display a visual tool such as a map to allow a user to choose which region to display weather information for in a weather web part instead of having to manually type a location name.

The following image shows the user interface that I created for the Hyperlinks web part so that I could easily add new links or delete existing ones:

The custom EditorPart for managing hyperlink data

The custom EditorPart that I created is titled "Manage Links" and sits within the existing EditorZone.  All of the existing EditorParts that ship with ASP.NET 2.0 can also be displayed within the EditorZone to allow users to manage more "generic" web part properties such as its title, height, width, and chrome type to use.

As you can see, providing custom EditorParts to manage the data in web parts is a great way to make web parts easier for users to manage by providing fields for each property on the custom data type and allowing users to easily delete existing links.

Within the next day or so I'll upload a recent build of SUB V2 and supply the authorization credentials so that people can login and have a play with the new web parts to see how they work for real.

posted on 1/7/2006 9:31:32 AM ( 5 Comments )


Encrypting Personalization Data

It is an important feature of modern portals such as Sharepoint and Live.com that they have a rich set of web parts that allow users to view and work with data.  For example, Sharepoint contains the ContentEditor web part that allows users to render arbitrary chunks of HTML within any page.  As a part of my web part book I decided that I wanted the reader to walk away with an equally impressive set of web parts that they could use and build upon in the future of their portal development.

One of the web parts that the user gets to build is a part that allows them to enter arbitrary chunks of textual content and have it persisted in a secure manner.  The web part is called the TaskNote web part and the idea is that it would be used in a similar manner to sticky notes in that users could quickly and simply jot things down and have them appear on their portal pages.  The following image shows the TaskNote web part with some content displayed within its body section that has been created by a user:

The TaskNote Web Part allows users to enter abritrary text and have it stored securely.


Because of the nature of the TaskNote web part, users may choose to store sensitive information - such a credit card numbers or telephone numbers - as the content.  For this reason the TaskNote web part stores its content in a secure manner within the personalization system by implementing IPersonalizable and encrypting the data before saving it.

While writing this web part I came up with the following helper functions that can be used to encrypt and decrypt the web part content before saving and after loading the personalization data from the data store:

public static string EncryptData(string data) {

    string encryptedData = "";

    if (!string.IsNullOrEmpty(data)) {

        byte[] receivedBytes = Encoding.Unicode.GetBytes(data);
        byte[] encryptedBytes =
        _crypto.CreateEncryptor().TransformFinalBlock(
                receivedBytes,
                0,
                receivedBytes.GetLength(0)
                );

        encryptedData = Convert.ToBase64String(encryptedBytes);
    }
    return encryptedData;
}


public static string DecryptData(string encryptedData) {

    string decryptedData = "";

    if (!string.IsNullOrEmpty(encryptedData)) {
        byte[] base64 = Convert.FromBase64String(encryptedData);
        byte[] bytes = _crypto.CreateDecryptor().TransformFinalBlock(
            base64,
            0,
            base64.GetLength(0)
            );

        decryptedData = Encoding.Unicode.GetString(bytes);
    }

    return decryptedData;
}

posted on 1/6/2006 8:29:52 AM ( 1 Comments )


Progress on my Web Parts book

During the Christmas break I managed to get two of the chapters for my book on ASP.NET 2.0 Web Parts written.  The chapters that I wrote were chapter five (Zones) and chapter six (Personalization) and were the last of what I refer to as the "building block" chapters.  In a nutshell, the building-block chapters are designed to provide the reader with a thorough understanding of the key, heavy-lifting members of the portal framework

By the end of chapter six the reader has a very good understanding of each of these members of the framework and understands the key points of extensibility for each of them.  The remaining four chapters are designed to be task based tutorials that build upon the knowledge gained in the building block chapters to build application services over the portal framework.  I refer to these chapters as the "Application Services" chapters.  Tasks that are included in the Application Services section include things such as:

  • Managing the web portal
  • Displaying content in dialog windows
  • Creating workflow solutions using the portal framework
  • Implementing AJAX-style behaviors in the portal framework

Many of the features that are implemented Application Services section are build to mimic features that can be found on popular modern portals such as:

    http://Live.com
    http://Google.com/ig
    Sharepoint V3

If everything goes according to plan I should finish the writing process by early March.

posted on 1/6/2006 2:35:23 AM ( 0 Comments )


Inside GenericWebPart - IAttributeAccessor

Within the web part framework there is a powerful feature that allows us to turn ordinary web controls into web parts simply by dropping them onto a zone.  The real beauty of this is that we can even treat the web control as a web part in markup.  The following snippet of code shows a Label being assigned an ExportMode property value:

<asp:Label id="Label1" runat="server" ExportMode="NonSensitiveData" />

Clearly the Label control has no such property so how is it that this property can be added in markup and inferred to the underlying web part.  The secret lies in 2 things: a property on GenericWebPart named ChildControl and the IAttributeAccessor interface. 

The ChildControl holds onto the instance of real underlying control.  This control, by virtue of the fact that it is a WebControl, implements the IAttributeAccessor interface.  This interface has 2 members of interest - GetAttribute and SetAttribute - which allow controls to accept arbitrary attribute values.  These are referred to as expando properties.  In the example above, the ExportMode value for the Label would have been stored away and retrieved from these expando properties.

When you attempt to set these properties at runtime they will not exist on the underlying controls so the thing to do is to get the actual GenericWebControl instance and set them from there.  Here is an example of setting the ExportMode of the Label at runtime:

GenericWebPart gwp =
    WebPartManager1.GetGenericWebPart(this.Label1) ;

gwp.ExportMode = WebPartExportMode.NonSensitiveData;

If you need to implement expando properties in your own controls then consider implemeting the IAttributeAccessor interface.

posted on 11/22/2005 10:48:03 AM ( 0 Comments )


SUB V2 First Web Part - Post Categories

Today I worked on the "Post Categories" web part which can be seen in the following image:

 First Web Part for SUB V2 - Post Categories

The picture shows me dragging the web part to display it elsewhere on the page.

This web part is a pretty simple beastie, in fact here is the actual web part code (all of it):

<asp:TreeView ID="TreeView1" runat="server" SkinID="CategoryTree"
    EnableViewState="False" DataSourceID="TreeNodeDataSource" >
    <DataBindings>
        <asp:TreeNodeBinding DataMember="treenode" NavigateUrlField="navigateUrl" TextField="text" />
        <asp:TreeNodeBinding DataMember="treenodes" Text="Post Categories" Value="Post Categories" />
    </DataBindings>
</asp:TreeView>


<asp:XmlDataSource ID="TreeNodeDataSource"
    runat="server"
    DataFile="~/Data/Categories.xml"
    TransformFile="~/Data/CategoriesToTreeNodes.xsl" />

The control uses the existing Categories xml file and an XSL transform to create a hierarchical XML document which is then bound to a TreeView control via the XmlDataSource control. 

Not bad for a fully functional, totally dynamic control that can be dragged around the place at runtime eh?

posted on 11/12/2005 7:03:22 AM ( 1 Comments )


Web Parts in SUB V2

The other day I mentioned that I had plans to integrating some portal features into the new version of SingleUserBlog and today I'd like to share some of that vision.  At the moment I'm actually writing a book on the topic of portals and web parts so, I'm actually using some of the samples that I've prepared for the book in SUB - it's been a great way to get some useful, real work examples and the two activities form a nice, symbiotic relationship.

A couple of the features that have been asked for involve exposing additional data about site metrics - such as popular pages, recent comments, etc.  In the new version I'm building some new indexes to cater for the storage of this information and will be exposing each individual "report" as a web part.  This means that I will start out with things such as:

  • Most Popular Posts Web Part
  • Recent Comments Web Part
  • Post Count By Month Web Part
  • Post List By Month Web Part (connects to Post Count By Month Web Part)
  • Daily Links Web Part
  • etc

The web parts would sit in a custom Gallery (CatalogZone) named "Site Tools".  This means that the site owner can dynamically choose which of these parts to display (and where!) at any given time.  Parts can be added to or removed from a page at runtime to change the information that is displayed to visitors of the site.

The "Site Tools" gallery is extensible in that it also "probes" the filesystem and adds parts that are added - think of this as being similar to having a plug-in architecture.  What this means is that it becomes very, very simple to dynamically develop and upload new web parts.

You can view the current list of feature requests and add your own items to the comments of my original post about the new blog engine which is located here.

posted on 11/11/2005 1:23:51 PM ( 0 Comments )


Pop Quiz: WebPart.AuthorizationFilter

In ASP.NET 2.0 you can assign a string called an AuthorizationFilter to a WebPart, then inspect it's value at runtime to decide whether or not to display a web part for a given user.

If I create a static web part like so...

<asp:WebPartZone ID="WebPartZone1" runat="server">
        <ZoneTemplate>
        <wp:CustomWeatherPart
            id="CustomWeatherPart1"
            AuthorizationFilter="DomainUser"
            runat="server"
            title="Today's Weather" />
    </ZoneTemplate>
</asp:WebPartZone>

And add a dynamic part like this:

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

    CustomWeatherPart part = new CustomWeatherPart();
    part.AuthorizationFilter = "Administrator";
    // Adds a custom web part to zone 1...
    WebPartManager1.AddWebPart(
        part,
        WebPartZone1,
        WebPartZone1.WebParts.Count
        );
}

And then wire up the AuthorizeWebPart event handler like so:

protected override void OnInit(EventArgs e) {
    base.OnInit(e);
    WebPartManager1.AuthorizeWebPart +=
        new WebPartAuthorizationEventHandler(WebPartManager1_AuthorizeWebPart);
}

How many times will my event handler get called for 1 page request?  0, 1, or 2?  Why?

What about if I move the event handling wire-up code back to OnPreInit?

posted on 11/2/2005 3:04:07 PM ( 0 Comments )


ASP.NET 2.0 Transformers

Transformers are controls in the ASP.NET V2 portal framework that allow you to connect web parts that expose incompatable endpoints.

The ASP.NET V2 control-set exposes two standard transformers - RowToFieldTransformer and RowToParametersTransformer.

These baked-in transformers allow your web part connections to work automatically for parts that are dealing with IWebPartRow on the provider side and either IWebPartField, or IWebPartParameters on the consumer side.

It therefore makes quite a bit of sense to use these interfaces when writing your own web part controls for added extensibility.  For example, if you are exposing your connection endpoints based on a custom interface then what are the chances that a third-party web part vendor would implement that interface?  None right?  But the chances that they would be implementing one of the standard interfaces are quite high.  So, by deduction, if you are not using those standard interfaces then your parts will probably never be able to work with third-party web parts.

Transformers expose a piece of custom user interface that allow you to manage the property mapping between the connection provider and the connection consumer.  As an example, you may have an AddressWebPart that exposes a row of data with the following column definitions:

{Street1, Street2, City, State, Postcode, Country}

And a WeatherWebPart that consumes the following data:

{Country, Region}

In this case your AddressWebPart would expose data based on IWebPartRow and the WeatherWebPart would consumer data based on IWebPartParameters (because it is more than one field). 

Because of these endpoints, the ASP.NET framework "just knows" that they can be connected using the RowToParametersTransformer to assist with the configuration mapping.  When you are configuring the transformer, you would use the custom user interface provided by it to map the columns from AddressWebPart to the correct parameters on the WeatherWebPart:

{Country -> Country}
{State -> Region}

posted on 10/30/2005 1:16:44 AM ( 0 Comments )


Custom Zones

All web part controls live in Zones. WebParts live in a WebPartZone, Catalog parts live in a CatalogZone and Editor parts - such as AppearanceEditorPart, BehaviorEditPart, etc - live in an EditorZone. If you've played with either the ASP.NET web parts or Sharepoint web parts then you will probably recognize zones as the things that you can drag web parts between. Zones can be used for much more than as simple, dumb containers though.

When you create a custom zone - by inheriting from one of the base zone classes - you get the ability to override some important methods. The two that I'm going to write about here are: CreateWebPartChrome and OnCreateVerbs. These 2 methods allow you to add a common verbs, remove common verbs, and apply custom chrome settings to all parts contained within your zone. Take a look at the following image for a moment and think about how that might be useful.

Windows Task View

You can override CreateWebPartChrome to return your own custom Chrome; this would then be the Chrome that is used for all web parts within your zone.  A reason to do this would be to then use the chrome to do custom rendering. This provides you with way to give parts that are within your zone a very distinctive look regardless of the Themeing settings or how the person that designed the web part styled it. In the case of the above image you could create a special "TaskZone" and then create some custom Chrome to display web parts with that look and feel regardless of how web parts are displayed on other areas within your application.

OnCreateVerbs is a method that allows you to add custom verbs to all parts within your zone, which get added as "zone verbs". Again, this gives you the ability to have a very customized panel for web parts. As an example you might create an "AdministrationZone" which added a "Contact Administrator" verb to each part that is contained within it.

In a future entry I'll discuss how to create the various custom zones.

posted on 9/26/2005 11:05:19 PM ( 2 Comments )


Web Part Pages - DisplayModes

The DisplayMode of a web parts page is an interesting thing and, when you look at the syntax for changing it you'll probably wonder why an enum wasn't used. Here's an example showing the code that is required to change the mode to "catalog" mode:

WebPartManager wpm = WebPartManager.GetCurrentWebPartManager(this) ;
wpm.DisplayMode = WebPartManager.CatalogDisplayMode;

If you've done any amount of .NET programming then you would most likely have expected to have seen something which looks a bit more like this:

WebPartManager wpm = WebPartManager.GetCurrentWebPartManager(this) ;
wpm.DisplayMode = DisplayModes.CatalogDisplayMode;

There are actually 5 "standard" display modes in total - BrowseDisplayMode, CatalogDisplayMode, ConnectDisplayMode, DesignDisplayMode, and EditDisplayMode - and each of those has an associated property of the same name which hangs off of the WebPartManager instance. These properties are actually instances of a class which inherits from an abstract base class named WebPartDisplayMode. This is done because the mode changing actually requires certain logic and state which is held within this class. For example, because of the impact of things such as personalization, each mode may behave slightly differently on a per-user basis.  The initial values of the display mode instance are set by the WebPartManager as you'd expect.

The 5 "standard" display modes are:

BrowseDisplayMode
This is just as it suggests - browse mode. Browse mode is the default mode for a page and is the mode that you you'd be in if you were just browsing a web parts page. No special permissions are required to enter this mode.

CatalogDisplayMode
Switching the page into "catalog mode" displays the catalog user interface so that the user can add new web parts to the page at runtime. When you attempt to change to this mode there is a check to ensure that you have a CatalogZone control on the page before the mode is changed. If you have not added a CatalogZone control to the page then that mode will not be supported for the page and an exception will be thrown.

ConnectDisplayMode
Before you can switch the page into "connect" mode you will need to have a ConnectionsZone on the page and you will need to be authenticated. When in "connect" mode users can manage dynamic connections by displaying the ConnectionsZone. When in this mode, each part that is configured for connections will automatically have a "Connect" verb added to its verbs collection; to display the ConnectionsZone you click on the connect verb for a specific part and the ConnectionsZone will allow you to manage the dynamic connections for that part.

DesignDisplayMode
"Design" mode allows users to drag web parts between zones. When you switch into this mode each zone the user interface for each zone changes appearance to indicate that web parts can be dragged onto it.

EditDisplayMode
In "edit" mode users can move web parts between zones just as they can when in "design" mode. Switching the page into "edit" mode displays the editor user interface so that the user can add eidt web parts properties at runtime. When you attempt to change to this mode there is a check to ensure that you have an EditorZone control on the page before the mode is changed. If you have not added a EditorZone control to the page then that mode will not be supported for the page and an exception will be thrown.

posted on 9/25/2005 3:45:59 AM ( 5 Comments )


Good portals require good gallery customization

A little while ago I wrote about a session that I was giving at TechEd where I was to show how you can "dress-up" the default CatalogZone in ASP.NET.  During the course of that session I showed the extensibility points that you could get into to make the CatalogZone look like the Sharepoint "gallery", as a reminder, here's some pictures to show what I'm talking about:

Sharepoint Gallery Out-of-the-box ASP.NET Gallery Simple Custom ASP.NET Gallery
The Sharepoint Gallert The out-of-the-box ASP.NET Gallery A simple, customized ASP.NET gallery


Notice that, the standard ASP.NET gallery has a very limited range of categories and lacks some of the nice "eye-candy" that the Sharepoint gallery has - such as styled gallery "button links" and images associated to web parts.  In the cusomized ASP.NET gallery, I've added a very simple implementation for those things.

"Portal" architecture is an extremely exciting medium for content delivery because of the sheer volume of information that we have to wade through to get the knowledge to make critical decisions.  The idea is that your browsers "home page" be configured as a dashboard of web parts supplying your daily informational needs. 

A good portal will have a gallery which offers a wide range of web parts that let you view your required information in a single page.  The important thing here is that all of this information becomes just a single click away and is flashed up before your eyes at regular intervals while you are performing standard actions - such as browsing the web.  This range of parts will hopefully contain a range that will cater for, not only your business needs - such as a part showing which staff members have annual leave coming up - but also your spiritual, informational and even personal growth.

A good portal requires quite a bit of customization as can be seen from the following images of the galleries on the http://Start.com and http://Google.com/ig portals:

Start.com Google

Start

Google Portal Gallery

 

These are the 2 internet portals that I know of that allow users to add and remove web parts dynamically and each of these portals have very useable galleries that are friendly to look at, fast to load - using AJAX technologies to implement a lazy loading strategy - and easy to access.  They also have their parts laid out into categories to make choosing common parts much simpler.  In a business sense these categories might be categories that align to business streams - such as finance, manufacturing, HR or sales.

How will you choose to customize your galleries?   What other features should a highly useable gallery have?

posted on 9/19/2005 12:46:00 PM ( 1 Comments )