Create a “Portal-in-a-Box” - 30 Oct 2009

Using ASP.NET 2.0 Web Parts and Composite Controls to Make a Web-ready Portal

Matt Dinovo

October 30, 2009

12 Min Read
ITPro Today logo in a gray background | ITPro Today

asp:Feature

 

Create a Portal-in-a-Box

Using ASP.NET 2.0 Web Parts and Composite Controls to Makea Web-ready Portal

 

By Matt Dinovo

 

In the world of Web portals sites where a user cancustomize pages for their own content and layout preferences many differentparadigms exist. Microsoft s SharePoint paradigm of what a Web part is and howto manipulate them on a page is what ASP.NET 2.0 s Web part architecture isbased upon. This architecture can be a powerful tool in many business scenarioswhere different audiences need to view different content via a singleapplication. However, the standard ASP.NET 2.0 architecture has severallimitations in its implementation that can be frustrating to a developer new tothe technology.

 

In addition, most developers want to make portals thatoperate like Live.com or My Yahoo, not SharePoint. They want users to be ableto create multiple pages as part of their customizations, as well as to beable to choose from multiple layouts. The ASP.NET Web part architecture doesnot natively allow for such features. In addition, the strong tie to theSqlPersonalizationProvider, limited documentation on PersonalizationState, andthe need for several controls to work in conjunction to make a portal pagefunctional make creating a well designed (and nice looking) portal sitecomplex. But it doesn t have to be like this! In this article, I ll show youhow to develop a portal in a box : a single ASP.NET 2.0 server control that,when dropped on a page, can support any number of tabs of variousconfigurations. This architecture is one that allows developers to consolidateall the plumbing into a single block that one simply drops onto a page for itto function.

 

Creating a Personalization Provider

Because in our new portal a user can not only configurethe Web parts on a page (which we ll call a desktop), but alsoadd/remove/delete individual Tab Pages on this desktop, we must first create apersonalization provider that supports this. Only one personalization provideris included in the .NET Framework, the SqlPersonalizationProvider. Theintention is to use this in conjunction with ASP.NET s membership architecture,intrinsically tied to SQL Server 2005. However, this implementation is rigid inthat it only supports a single personalization per ASPX page. To theSqlPersonalizationProvider, default.aspx?tab=1 is the same asdefault.aspx?tab=2. We want to be able to have an infinite number of tabs perpage, so we must change the behavior of the personalization provider. First, Icreated simple XML serialized entity classes to be our database it willsave an XML file to the root of our Web application that contains thepersonalization information. I did this to show what is actually being stored your personalization provider can save this to any database you would like. (Inthe code samples that accompany this article, the Desktop class contains allthe serialization code for this demo; see end of article for download details).In this demo, we re going to store the actual personalization information as abyte array similar to the way the SqlPersonalizationProvider class stores thisinformation in SQL Server. Therefore, to accomplish our goal of being able tostore multiple personalizations per ASPX page, we re going to create a PersonalizationProviderlike the one shown in Figure 1.

 

Protected Overrides Sub LoadPersonalizationBlobs( _

   ByVal webPartManager AsWebPartManager, _

   ByVal path As String,ByVal userName As String, _

   ByRef sharedDataBlob()As Byte, ByRef userDataBlob() As Byte)

   Dim dt As Desktop =Desktop.Load(userName.Replace(""c, "_"c))

   If Not dt Is NothingThen

       Dim tp As TabPage =dt.Tabs.FindByID(GetPageID())

       If Not tp IsNothing Then

           userDataBlob =tp.Personalization

       End If

   End If

End Sub

Protected Overrides Sub ResetPersonalizationBlob( _

   ByVal webPartManager AsWebPartManager, _

   ByVal path As String,ByVal userName As String)

   Dim usr As String =userName.Replace(""c, "_"c)

   Dim dt As Desktop =Desktop.Load(userName)

   If Not dt Is NothingThen

       Dim tp As TabPage =dt.Tabs.FindByID(GetPageID())

       If Not tp IsNothing Then

           tp.Personalization = Nothing

           dt.Save(userName)

       End If

   End If

End Sub

Protected Overrides Sub SavePersonalizationBlob( _

   ByVal webPartManager AsWebPartManager, _

   ByVal path As String,ByVal userName As String, _

   ByVal dataBlob() AsByte)

   Dim usr As String =userName.Replace(""c, "_"c)

   Dim dt As Desktop =Desktop.Load(usr)

   If dt Is Nothing Thendt = New Desktop()

   Dim tp As TabPage =dt.Tabs.FindByID(GetPageID())

   If (Not tp Is Nothing)Then

       tp.Personalization= dataBlob

   Else

       tp = New TabPage()

       tp.PageId = GetPageID()

       tp.Name ="Home"

       tp.Personalization= dataBlob

       dt.Tabs.Add(tp)

   End If

   dt.Save(usr)

End Sub

Figure 1: Create aPersonalizationProvider like this one.

 

It is important to note that, in this code sample, we reignoring the path parameter and using a method call to GetPageID, which returnsthe current page s tab ID or generates a new one (see Figure 2). This is how weget around the base provider s behavior of only acknowledging the physical pageand not taking into account query string information.

 

Private Shared Function GetPageID() As Guid

   Dim ctx As HttpContext= HttpContext.Current

   Dim userName As String= _

       ctx.User.Identity.Name.Replace(""c, "_"c)

   If Notctx.Request.QueryString("tab") Is Nothing Then

       Return NewGuid(ctx.Request.QueryString("tab"))

   Else

       Dim dt As Desktop =Desktop.Load(userName)

       If Not dt IsNothing Then

           Ifdt.Tabs.Count > 0 Then

               Returndt.Tabs(0).PageId

           End If

       End If

   End If

   Return Guid.NewGuid()

End Function

Figure 2: Ignorethe path parameter and use a method call to GetPageID, which returns thecurrent page s tab ID or generates a new one.

 

Finally, to use this provider instead of the default, youmust configure your Web site by editing the personalization section in theweb.config file (see Figure 3). That s all there is to it. With this in place,all the personalization functions will persist to an XML file with thecurrently logged in user s name. You could stop here, use the includedfunctionality of the ASP.NET Web parts, and be able to have a fully functionalportal. However, let s see how to encapsulate the core portal functionalityinto a single control.

 

 

   

     

       

       

     

   

 

Figure 3: Configureyour Web site by editing the personalization section in the web.config file.

 

Creating the Portal Control

To create a rich portal experience, several servercontrols are required to work in conjunction for everything to function. At aminimum, you must have a WebPartManager that is responsible for all theoperations of the portal (e.g., adding/removing Web parts, etc.), as well asone or more WebPartZones where the Web parts will reside. Web parts can bedeclaratively listed in a WebPartZone s ZoneTemplate; however, there is onemajor drawback to this: declaratively listed Web parts are permanently part ofthe page, meaning a user can only close, but not delete, them. Although thisdoes not sound too catastrophic, closed Web parts remain part of thepersonalization blob and part of the page s control tree. Most users intend toremove a Web part permanently when they close it, so having many hidden Webparts adding overhead is something you should try to avoid if at all possible.

 

So, if we don t want to declaratively list out the Webparts as part of the WebPartZone, we must add a CatalogZone to the mix. If youwant to edit existing Web parts, such as changing the title or border, or evenmanipulating custom properties, you ll need to add an EditorZone. To muddy thewaters further, both the CatalogZone and EditorZone can contain multiple zoneparts that perform a specific function. For example, in a CatalogZone, youcould place a PageCatalogPart that lists all the hidden controls on a page and aDeclarativeCatalogPart that wraps standard ASP.NET server controls in aGenericWebPart fa ade for use in the Web part architecture. There are manymore, but I won t dwell on the specifics here.

 

Obviously, there are quite a few pieces of the portalpuzzle that you must keep track of. You can see how a developer might getconfused as to what to use when. To eliminate some of this confusion, and tohelp further the implementation of our Web-ready portal, let s combine therequired functionality into a single composite control. First, we must createour own CatalogZone and EditorZone that we can programmatically add to ourcontrol. The base CatalogZone and EditorZone implement their parts astemplates declaratively in the page. However, by creating our own zones thatinherit from CatalogZoneBase and EditorZoneBase, we can determine which partsappear at run time (or at the very least, programmatically). In implementationsof CatalogZone and EditorZone, you must define a part collection containingall the catalog or editor parts that appear in a zone. In our example, we aredeclaring a static collection, but you could see how you could add additionalparts in different scenarios. To illustrate this, I added a Boolean propertyonto our custom CatalogZone to indicate whether to display a PageCatalogPart.These implementations look like the examples in Figure 4 and Figure 5.

 

Public NotInheritable Class MyCatalogZone

   InheritsCatalogZoneBase

   Private _pageCatalog AsBoolean

   Public PropertyPageCatalog() As Boolean

       Get

           Return_pageCatalog

       End Get

       Set(ByVal value AsBoolean)

           _pageCatalog =value

       End Set

   End Property

   Friend Sub New()

       'InternalConstructor

   End Sub

   Protected OverridesFunction CreateCatalogParts() _

     As CatalogPartCollection

       Dim parts As NewList(Of CatalogPart)

       Dim catalog As NewMyCatalogPart()

       catalog.ID ="sample_catalog"

       parts.Add(catalog)

       If _pageCatalog Then

           Dim pageCatalogAs New PageCatalogPart()

           pageCatalog.ID= "page_catalog"

           parts.Add(pageCatalog)

       End If

       Return NewCatalogPartCollection(parts)

   End Function

End Class

Figure 4: Add aBoolean property onto our custom CatalogZone to indicate whether to display aPageCatalogPart.

 

Public NotInheritable Class MyEditorZone

   Inherits EditorZoneBase

   Protected OverridesFunction CreateEditorParts() As EditorPartCollection

       Dim parts = New List(OfEditorPart)()

       Dim aep As NewAppearanceEditorPart()

       aep.ID ="appearance_editor"

       parts.Add(aep)

       Return NewEditorPartCollection(parts)

   End Function

End Class

Figure 5: Add aBoolean property onto our custom CatalogZone to indicate whether to display aPageCatalogPart.

 

Note in Figure 4 the use of the MyCatalogPart class. Thisis another custom class that allows us to define programmatically which partsappear in a catalog. You may want to do this for a variety of reasons; forexample, perhaps you wish to display a certain set of parts based on a user srole. The use of a custom CatalogPart allows us to accomplish this.

 

As you can see from Figure 6, the CatalogPart s primaryfunction is to present a WebPartDescriptionCollection to the user so they canselect the Web part(s) to add to a zone. When a user selects a WebPart from thecatalog, the GetWebPart method is called, which actually returns the Web part.In this example (and in the downloadable code), I created a simple Web partthat wraps a standard ASP.NET calendar control.

 

Public Class MyCatalogPart

   Inherits CatalogPart

   Public Sub New()

       MyBase.Title ="Custom Parts"

       MyBase.Description= "Lorem ipsum dolor sit."

   End Sub

   Public OverridesFunction GetAvailableWebPartDescriptions() _

    AsWebPartDescriptionCollection

       Dim descriptions AsNew List(Of WebPartDescription)()

       descriptions.Add(New WebPartDescription(_

     NewMyWebParts.CalendarPart()))

       Return NewWebPartDescriptionCollection(descriptions)

   End Function

   Public OverridesFunction GetWebPart(ByVal description _

     AsWebPartDescription) As WebPart

       Select Casedescription.ID

           CaseMyWebParts.CalendarPart.PARTID

               Return NewMyWebParts.CalendarPart()

           Case Else

               ReturnNothing

       End Select

   End Function

End Class

Figure 6: TheCatalogPart s primary function is to present a WebPartDescriptionCollection tothe user so they can select the Web part(s) to add to a zone.

 

Therefore, even after all of this plumbing, we have notyet actually created the unified portal control. To do this, we are going touse one of the new classes in the 2.0 framework, CompositeControl. The CompositeControlsimplifies creation of a WebControl with the INamingContainer interface, and isdesigned to be used when the server control is simply a container for childcontrols. In this example, we are going to lay out our portal by overriding theCreateChildControls method, as shown in Figure 7.

 

Public Class MyPortal

   InheritsCompositeControl

   Private _template AsControl

   Private _wpm As NewWebPartManager()

   Protected Overrides SubCreateChildControls()

       If (Not DesignMode)Then

           _wpm.ID ="wpm"

           Dim cz = NewMyCatalogZone()

           cz.ID ="cz"

           Dim ez = NewMyEditorZone()

           ez.ID ="ez"

           Me.Controls.Add(_wpm)

           Me.Controls.Add(cz)

           Me.Controls.Add(ez)

           If Not_template Is Nothing Then Me.Controls.Add(_template)

       End If

       MyBase.CreateChildControls()

   End Sub

End Class

Figure 7: Overridethe CreateChildControls method.

 

Looking at this, you can see that we are simply adding aWebPartManager, our CatalogZone, and our EditorZone to the control collectionof the composite control. One might ask, What about the WebPartZones? We wantthe user to be able to choose from multiple templates or layouts ofWebPartZones, so we need to dynamically load the selected template frompersonalization and load that as part of this control. To accomplish this, we lloverride the OnInit method, as shown in Figure 8.

 

Protected Overrides Sub OnInit(ByVal e As EventArgs)

   If Not DesignMode Then

       Dim dt As Desktop =_

          Desktop.Load(Me.Page.User.Identity.Name.Replace(""c,"_"c))

       If Not dt IsNothing Then

           If NotMe.Page.Request.QueryString("tab") Is Nothing Then

               Dim tp AsTabPage = _

                  dt.Tabs.FindByID(NewGuid(Me.Page.Request.QueryString("tab")))

               If Not tpIs Nothing AndAlso _

                  NotString.IsNullOrEmpty(tp.TemplateUrl) Then

                   _template = Me.Page.LoadControl(tp.TemplateUrl)

               End If

           End If

       End If

       If _template IsNothing Then

           _template =Me.Page.LoadControl(Me._defaultTemplate)

       End If

       Me.EnsureChildControls()

   End If

   MyBase.OnInit(e)

End Sub

Figure 8: Overridethe OnInit method.

 

Now we are able to retrieve the virtual path to a usercontrol containing the WebPartZones and dynamically add that to the controltree based on the identifier in the tab query string. One item toparticularly note is that one can only add WebPartZones programmatically in theInit event. If you were to add a WebPartZone any later in the event cycleASP.NET will throw an exception.

 

The Finished Product

So what did all of that buy us? Let s look at a screenshotof the finished product (see Figure 9; to illustrate things a bit better, Iadded images to the zone template of the Web part zones). Here you see the Webparts displayed in a single column.

 


Figure 9: The finished product.

 

The Display Mode dropdown list controls the Web partmanager (which is part of the CompositeControl) and changes the display modeamong Browse, Catalog, and Edit. When one changes the page to Catalog mode(where the list of available Web parts are displayed), you can see the customcatalog zone complete with the dummy calendar Web part created earlier (see Figure10).

 


Figure 10: The custom catalog zonecomplete with the dummy calendar Web part created earlier.

 

To add a new tab, we can enter the name into the textboxand select a template to use. The values of the template dropdown are thevirtual path to available user controls containing the actual Web part zones.When I do this, you can see a second tab appear with a two-column configuration,as in Figure 11.

 


Figure 11: The benefits of thetemplate dropdown.

 

As you can see, we have the same page using a query string tab to indicate which personalized page to show. You can navigate betweenHome and New by changing the Guid identifying the tab.

 

Conclusion

The ASP.NET 2.0 Web part architecture is a flexible andpowerful infrastructure that allows for customized displays of information.While its implementation can be somewhat tricky, the ability to encapsulatemuch of the functionality into a single control helps developers leverage thistechnology while not having to worry about the inner workings. Best of all, onecan extend this design pattern in many different directions with little effort.For example, one could incorporate portal navigation and management (forexample, add/delete/rename functions) directly into the composite control, aswell. Alternatively, you might use a technology like Microsoft s Atlasframework to eliminate the postbacks that occur when changing modes ormanipulating Web parts. I hope this article demystifies some of the buildingblocks of the Web part architecture and encourages you to go out and try someportal development of your own.

 

The source codeaccompanying this article is available for download.

 

Matt Dinovo is aSolution Developer with Avanade (http://www.avanade.com).He has been developing Web applications based on Microsoft technology since1995 for companies like Intel, Compuware, Ernst & Young, and Compaq. Helives in Ohio with his wife andtwo children. You can reach him via his blog at http://mattdinovo.spaces.live.com.

 

 

 

 

Sign up for the ITPro Today newsletter
Stay on top of the IT universe with commentary, news analysis, how-to's, and tips delivered to your inbox daily.

You May Also Like