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
October 30, 2009
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.
About the Author
You May Also Like