Sie sind auf Seite 1von 26

Thistutorialispartofaset.FindoutmoreaboutdataaccesswithASP.

NETintheWorkingwithData
inASP.NET2.0sectionoftheASP.NETsiteathttp://www.asp.net/learn/dataaccess/default.aspx.

WorkingwithDatainASP.NET2.0::Buildinga
CustomDatabaseDrivenSiteMapProvider
Introduction
ASP.NET2.0ssitemapfeatureenablesapagedevelopertodefineawebapplicationssitemapinsomepersistent
medium,suchasinanXMLfile.Oncedefined,thesitemapdatacanbeaccessedprogrammaticallythroughthe
SiteMap class inthe System.Web namespaceorthroughavarietyofnavigationWebcontrols,suchasthe
SiteMapPath,Menu,andTreeViewcontrols.Thesitemapsystemusesthe providermodel sothatdifferentsite
mapserializationimplementationscanbecreatedandpluggedintoawebapplication.Thedefaultsitemap
providerthatshipswithASP.NET2.0persistssitemapstructureinanXMLfile.BackintheMasterPagesandSite
Navigation tutorialwecreatedafilenamedWeb.sitemap thatcontainedthisstructureandhavebeenupdatingits
XMLwitheachnewtutorialsection.
ThedefaultXMLbasedsitemapproviderworkswellifthesitemapsstructureisfairlystatic,suchasforthese
tutorials.Inmanyscenarios,however,amoredynamicsitemapisneeded.ConsiderthesitemapshowninFigure
1,whereeachcategoryandproductappearassectionsinthewebsitesstructure.Withthissitemap,visitingthe
webpagecorrespondingtotherootnodemightlistallofthecategories,whereasvisitingaparticularcategorys
webpagewouldlistthatcategorysproductsandviewingaparticularproductswebpagewouldshowthat
productsdetails.

Figure1:TheCategoriesandProductsMakeuptheSiteMapsStructure

Whilethiscategory andproductbasedstructurecouldbehardcodedintotheWeb.sitemap file,thefilewould


needtobeupdatedeachtimeacategoryorproductwasadded,removed,orrenamed.Consequently,thesitemap
maintenancewouldbegreatlysimplifiedifitsstructurewasretrievedfromthedatabaseor,ideally,fromthe
BusinessLogicLayeroftheapplicationsarchitecture.Thatway,asproductsandcategorieswereadded,renamed,
ordeleted,thesitemapwouldautomaticallyupdatetoreflectthesechanges.
SinceASP.NET2.0ssitemapserializationisbuiltatoptheprovidermodel,wecancreateourowncustomsite
mapproviderthatgrabsitsdatafromanalternatedatastore,suchasthedatabaseorarchitecture.Inthistutorial
wellbuildacustomproviderthatretrievesitsdatafromtheBLL.Letsgetstarted!

1 of26

Note:Thecustomsitemapprovidercreatedinthistutorialistightlycoupledtotheapplicationsarchitecture
anddatamodel.JeffProsisesStoringSiteMapsinSQLServerandTheSQLSiteMapProviderYouve
BeenWaitingFor articlesexamineageneralizedapproachtostoringsitemapdatainSQLServer.

Step1:CreatingtheCustomSiteMapProviderWebPages
Beforewestartcreatingacustomsitemapprovider,letsfirstaddtheASP.NETpageswellneedforthistutorial.
StartbyaddinganewfoldernamedSiteMapProvider.Next,addthefollowingASP.NETpagestothatfolder,
makingsuretoassociateeachpagewiththeSite.master masterpage:
Default.aspx
ProductsByCategory.aspx
l ProductDetails.aspx
l
l

AlsoaddaCustomProviders subfoldertotheApp_Code folder.

2 of26

Figure2:AddtheASP.NETPagesfortheSiteMapProviderRelatedTutorials

Sincethereisonlyonetutorialforthissection,wedontneed Default.aspx tolistthesectionstutorials.Instead,


Default.aspx willdisplaythecategoriesinaGridViewcontrol.WelltacklethisinStep2.
Next,updateWeb.sitemap toincludeareferencetotheDefault.aspx page.Specifically,addthefollowing
markupaftertheCaching<siteMapNode>:
<siteMapNode
title="CustomizingtheSiteMap"url="~/SiteMapProvider/Default.aspx"
description="Learnhowtocreateacustomproviderthatretrievesthesitemap
fromtheNorthwinddatabase."/>

AfterupdatingWeb.sitemap,takeamomenttoviewthetutorialswebsitethroughabrowser.Themenuontheleft
3 of26

nowincludesanitemforthesolesitemapprovidertutorial.

Figure3:TheSiteMapNowIncludesanEntryfortheSiteMapProviderTutorial

Thistutorialsmainfocusistoillustratecreatingacustomsitemapproviderandconfiguringawebapplicationto
usethatprovider.Inparticular,wellbuildaproviderthatreturnsasitemapthatincludesarootnodealongwitha
nodeforeachcategoryandproduct,asdepictedinFigure1.Ingeneral,eachnodeinthesitemapmayspecifya
URL.Foroursitemap,therootnodesURLwillbe~/SiteMapProvider/Default.aspx,whichwilllistallofthe
categoriesinthedatabase.EachcategorynodeinthesitemapwillhaveaURLthatpointsto
~/SiteMapProvider/ProductsByCategory.aspx?CategoryID=categoryID,whichwilllistalloftheproductsin
thespecified categoryID.Finally,eachproductsitemapnodewillpointto
~/SiteMapProvider/ProductDetails.aspx?ProductID=productID,whichwilldisplaythespecificproducts
details.
TostartweneedtocreatetheDefault.aspx,ProductsByCategory.aspx,andProductDetails.aspx pages.
ThesepagesarecompletedinSteps2,3,and4,respectively.Sincethethrustofthistutorialisonsitemap
providers,andsincepasttutorialshavecoveredcreatingthesesortsofmultipagemaster/detailreports,wewill
hurrythroughSteps2through4.Ifyouneedarefresheroncreatingmaster/detailreportsthatspanmultiplepages,
referbacktothe Master/DetailFilteringAcrossTwoPagestutorial.

Step2:DisplayingaListofCategories
OpentheDefault.aspx pageintheSiteMapProvider folderanddragaGridViewfromtheToolboxontothe
Designer,settingitsID toCategories.FromtheGridViewssmarttag,bindittoanewObjectDataSourcenamed
CategoriesDataSource andconfigureitsothatitretrievesitsdatausingtheCategoriesBLL classs
GetCategories method.SincethisGridViewjustdisplaysthecategoriesanddoesnotprovidedatamodification
capabilities,setthedropdownlistsintheUPDATE,INSERT,andDELETEtabsto(None).

4 of26

Figure4:ConfiguretheObjectDataSourcetoReturnCategoriesUsingtheGetCategories Method

5 of26

Figure5:SettheDropDownListsintheUPDATE,INSERT,andDELETETabsto(None)

AftercompletingtheConfigureDataSourcewizard,VisualStudiowilladdaBoundFieldforCategoryID,
CategoryName,Description,NumberOfProducts,andBrochurePath.EdittheGridViewsothatitonlycontains
theCategoryName andDescription BoundFieldsandupdatetheCategoryName BoundFieldsHeaderText
propertytoCategory.
Next,addaHyperLinkFieldandpositionitsothatitstheleftmostfield.SettheDataNavigateUrlFields
propertytoCategoryID andtheDataNavigateUrlFormatString propertyto
~/SiteMapProvider/ProductsByCategory.aspx?CategoryID={0}.SettheText propertytoViewProducts.

6 of26

Figure6:AddaHyperLinkFieldtotheCategories GridView

AftercreatingtheObjectDataSourceandcustomizingtheGridViewsfields,thetwocontrols declarativemarkup
willlooklikethefollowing:
<asp:GridViewID="Categories"runat="server"AutoGenerateColumns="False"
DataKeyNames="CategoryID"DataSourceID="CategoriesDataSource"
EnableViewState="False">
<Columns>
<asp:HyperLinkFieldDataNavigateUrlFields="CategoryID"
DataNavigateUrlFormatString=
"~/SiteMapProvider/ProductsByCategory.aspx?CategoryID={0}"
Text="ViewProducts"/>
<asp:BoundFieldDataField="CategoryName"HeaderText="Category"
SortExpression="CategoryName"/>
<asp:BoundFieldDataField="Description"HeaderText="Description"
SortExpression="Description"/>
</Columns>
</asp:GridView>
<asp:ObjectDataSourceID="CategoriesDataSource"runat="server"
OldValuesParameterFormatString="original_{0}"SelectMethod="GetCategories"
TypeName="CategoriesBLL"></asp:ObjectDataSource>

Figure7showsDefault.aspx whenviewedthroughabrowser.ClickingacategorysViewProductslinktakes
youtoProductsByCategory.aspx?CategoryID=categoryID,whichwewillbuildinStep3.

7 of26

Figure7:EachCategoryisListedAlongwithaViewProductsLink

Step3:ListingtheSelectedCategorysProducts
OpentheProductsByCategory.aspx pageandaddaGridView,namingit ProductsByCategory.Fromitssmart
tag,bindtheGridViewtoanewObjectDataSourcenamedProductsByCategoryDataSource.Configurethe
ObjectDataSourcetousetheProductsBLL classsGetProductsByCategoryID(categoryID) methodandsetthe
dropdownliststo(None)intheUPDATE,INSERT,andDELETEtabs.

8 of26

Figure8:UsetheProductsBLL ClasssGetProductsByCategoryID(categoryID) Method

ThefinalstepintheConfigureDataSourcewizardpromptsforaparametersourcefor categoryID.Sincethis
informationispassedthroughthequerystringfieldCategoryID,selectQueryStringfromthedropdownlistand
enter CategoryIDintheQueryStringFieldtextboxasshowninFigure9.ClickFinishtocompletethewizard.

9 of26

Figure9:UsetheCategoryID QuerystringFieldforthecategoryIDParameter

Aftercompletingthewizard,VisualStudiowilladdcorrespondingBoundFieldsandaCheckBoxFieldtothe
GridViewfortheproductdatafields.RemoveallbuttheProductName,UnitPrice,andSupplierName
BoundFields.CustomizethesethreeBoundFieldsHeaderText propertiestoreadProduct, Price, and
Supplier,respectively.FormattheUnitPrice BoundFieldasacurrency.
Next,addaHyperLinkFieldandmoveittotheleftmostposition.SetitsText propertytoViewDetails, its
DataNavigateUrlFields propertytoProductID,anditsDataNavigateUrlFormatString propertyto
~/SiteMapProvider/ProductDetails.aspx?ProductID={0}.

10 of26

Figure10:AddaViewDetailsHyperLinkFieldthatPointstoProductDetails.aspx

Aftermakingthesecustomizations,theGridViewandObjectDataSourcesdeclarativemarkupshouldresemblethe
following:
<asp:GridViewID="ProductsByCategory"runat="server"AutoGenerateColumns="False"
DataKeyNames="ProductID"DataSourceID="ProductsByCategoryDataSource"
EnableViewState="False">
<Columns>
<asp:HyperLinkFieldDataNavigateUrlFields="ProductID"
DataNavigateUrlFormatString=
"~/SiteMapProvider/ProductDetails.aspx?ProductID={0}"
Text="ViewDetails"/>
<asp:BoundFieldDataField="ProductName"HeaderText="Product"
SortExpression="ProductName"/>
<asp:BoundFieldDataField="UnitPrice"DataFormatString="{0:c}"
HeaderText="Price"HtmlEncode="False"
SortExpression="UnitPrice"/>
<asp:BoundFieldDataField="SupplierName"HeaderText="Supplier"
ReadOnly="True"SortExpression="SupplierName"/>
</Columns>
</asp:GridView>
<asp:ObjectDataSourceID="ProductsByCategoryDataSource"runat="server"
OldValuesParameterFormatString="original_{0}"
SelectMethod="GetProductsByCategoryID"TypeName="ProductsBLL">
<SelectParameters>
<asp:QueryStringParameterName="categoryID"

11 of26

QueryStringField="CategoryID"Type="Int32"/>
</SelectParameters>
</asp:ObjectDataSource>

ReturntoviewingDefault.aspx throughabrowserandclickontheViewProductslinkforBeverages.This
willtakeyoutoProductsByCategory.aspx?CategoryID=1,displayingthenames,prices,andsuppliersofthe
productsintheNorthwinddatabasethatbelongtotheBeveragescategory(seeFigure11).Feelfreetofurther
enhancethispagetoincludealinktoreturnuserstothecategorylistingpage(Default.aspx)andaDetailsView
orFormViewcontrolthatdisplaystheselectedcategorysnameanddescription.

Figure11:TheBeverages Names,Prices,andSuppliersareDisplayed

Step4:ShowingaProductsDetails
Thefinalpage,ProductDetails.aspx,displaystheselectedproductsdetails.Open ProductDetails.aspx and
dragaDetailsViewfromtheToolboxontotheDesigner.SettheDetailsViewsID propertytoProductInfo and
clearoutitsHeight andWidth propertyvalues.Fromitssmarttag,bindtheDetailsViewtoanew
ObjectDataSourcenamedProductDataSource,configuringtheObjectDataSourcetopullitsdatafromthe
ProductsBLL classsGetProductByProductID(productID) method.Aswiththepreviouswebpagescreatedin
Steps2and3,setthedropdownlistsintheUPDATE,INSERT,andDELETEtabsto (None).

12 of26

Figure12:ConfiguretheObjectDataSourcetoUsetheGetProductByProductID(productID) Method

ThelaststepoftheConfigureDataSourcewizardpromptsforthesourceofthe productIDparameter.Sincethis
datacomesthroughthequerystringfieldProductID,setthedropdownlisttoQueryStringandthe
QueryStringFieldtextboxto ProductID. Finally,clicktheFinishbuttontocompletethewizard.

13 of26

Figure13:ConfiguretheproductIDParametertoPullitsValuefromtheProductID QuerystringField

AftercompletingtheConfigureDataSourcewizard,VisualStudiowillcreatecorrespondingBoundFieldsanda
CheckBoxFieldintheDetailsViewfortheproductdatafields.RemovetheProductID,SupplierID,and
CategoryID BoundFieldsandconfiguretheremainingfieldsasyouseefit.Afterahandfulofaesthetic
configurations,myDetailsViewandObjectDataSourcesdeclarativemarkuplookedlikethefollowing:
<asp:DetailsViewID="ProductInfo"runat="server"AutoGenerateRows="False"
DataKeyNames="ProductID"DataSourceID="ProductDataSource"
EnableViewState="False">
<Fields>
<asp:BoundFieldDataField="ProductName"HeaderText="Product"
SortExpression="ProductName"/>
<asp:BoundFieldDataField="CategoryName"HeaderText="Category"
ReadOnly="True"SortExpression="CategoryName"/>
<asp:BoundFieldDataField="SupplierName"HeaderText="Supplier"
ReadOnly="True"SortExpression="SupplierName"/>
<asp:BoundFieldDataField="QuantityPerUnit"HeaderText="Qty/Unit"
SortExpression="QuantityPerUnit"/>
<asp:BoundFieldDataField="UnitPrice"DataFormatString="{0:c}"
HeaderText="Price"HtmlEncode="False"
SortExpression="UnitPrice"/>
<asp:BoundFieldDataField="UnitsInStock"HeaderText="UnitsInStock"
SortExpression="UnitsInStock"/>
<asp:BoundFieldDataField="UnitsOnOrder"HeaderText="UnitsOnOrder"
SortExpression="UnitsOnOrder"/>
<asp:BoundFieldDataField="ReorderLevel"HeaderText="ReorderLevel"

14 of26

SortExpression="ReorderLevel"/>
<asp:CheckBoxFieldDataField="Discontinued"HeaderText="Discontinued"
SortExpression="Discontinued"/>
</Fields>
</asp:DetailsView>
<asp:ObjectDataSourceID="ProductDataSource"runat="server"
OldValuesParameterFormatString="original_{0}"
SelectMethod="GetProductByProductID"TypeName="ProductsBLL">
<SelectParameters>
<asp:QueryStringParameterName="productID"
QueryStringField="ProductID"Type="Int32"/>
</SelectParameters>
</asp:ObjectDataSource>

Totestthispage,returntoDefault.aspx andclickonViewProductsfortheBeveragescategory.Fromthe
listingofbeverageproducts,clickonthe ViewDetailslinkforChaiTea.Thiswilltakeyouto
ProductDetails.aspx?ProductID=1,whichshowsaChaiTeasdetails(seeFigure14).

Figure14:ChaiTeasSupplier,Category,Price,andOtherInformationisDisplayed

Step5:UnderstandingtheInnerWorkingsofaSiteMapProvider
ThesitemapisrepresentedinthewebserversmemoryasacollectionofSiteMapNode instancesthatforma
hierarchy.Theremustbeexactlyoneroot,allnonrootnodesmusthaveexactlyoneparentnode,andallnodes
15 of26

mayhaveanarbitrarynumberofchildren.EachSiteMapNode objectrepresentsasectioninthewebsitesstructure
thesesectionscommonlyhaveacorrespondingwebpage.Consequently,theSiteMapNode classhaspropertieslike
Title,Url,andDescription ,whichprovideinformationforthesectiontheSiteMapNode represents.Thereis
alsoaKey propertythatuniquelyidentifieseachSiteMapNode inthehierarchy,aswellaspropertiesusedto
establishthishierarchy ChildNodes,ParentNode,NextSibling,PreviousSibling,andsoforth.
Figure15showsthegeneralsitemapstructurefromFigure1,butwiththeimplementationdetailssketchedoutin
finerdetail.

Figure15:EachSiteMapNode hasPropertiesLikeTitle,Url,Key,andSoOn

ThesitemapisaccessiblethroughtheSiteMap classintheSystem.Web namespace.ThisclasssRootNode


propertyreturnsthesitemapsrootSiteMapNode instanceCurrentNode returnstheSiteMapNode whoseUrl
propertymatchestheURLofthecurrentlyrequestedpage.ThisclassisusedinternallybyASP.NET2.0s
navigationWebcontrols.
WhentheSiteMap classspropertiesareaccessed,itmustserializethesitemapstructurefromsomepersistent
mediumintomemory.However,thesitemapserializationlogicisnothardcodedintotheSiteMap class.Instead,
atruntimetheSiteMap classdetermineswhichsitemapprovidertouseforserialization.Bydefault,the
XmlSiteMapProvider classisused,whichreadsthesitemapsstructurefromaproperlyformattedXMLfile.
However,withalittlebitofworkwecancreateourowncustomsitemapprovider.
AllsitemapprovidersmustbederivedfromtheSiteMapProvider class,whichincludestheessentialmethodsand
propertiesneededforsitemapproviders,butomitsmanyoftheimplementationdetails.Asecondclass,
StaticSiteMapProvider,extendstheSiteMapProvider classandcontainsamorerobustimplementationofthe
neededfunctionality.Internally,theStaticSiteMapProvider storestheSiteMapNode instancesofthesitemapin
aHashtable andprovidesmethodslike AddNode(child,parent),RemoveNode(siteMapNode), andClear()
thataddandremoveSiteMapNodestotheinternalHashtable.XmlSiteMapProvider isderivedfrom
StaticSiteMapProvider.
WhencreatingacustomsitemapproviderthatextendsStaticSiteMapProvider,therearetwoabstractmethods
thatmustbeoverridden:BuildSiteMap andGetRootNodeCore.BuildSiteMap,asitsnameimplies,isresponsible
forloadingthesitemapstructurefrompersistentstorageandconstructingitinmemory.GetRootNodeCore returns
therootnodeinthesitemap.
Beforeawebapplicationcanuseasitemapprovideritmustberegisteredintheapplicationsconfiguration.By
default,theXmlSiteMapProvider classisregisteredusingthenameAspNetXmlSiteMapProvider.Toregister
16 of26

additionalsitemapproviders,addthefollowingmarkuptoWeb.config:
<configuration>
<system.web>
...
<siteMapdefaultProvider="defaultProviderName">
<providers>
<addname="name"type="type"/>
</providers>
</siteMap>
</system.web>
</configuration>

Thename valueassignsahumanreadablenametotheproviderwhiletypespecifiesthefullyqualifiedtypename
ofthesitemapprovider.Wellexploreconcretevaluesforthename andtypevaluesinStep7,afterwevecreated
ourcustomsitemapprovider.
ThesitemapproviderclassisinstantiatedthefirsttimeitisaccessedfromtheSiteMap classandremainsin
memoryforthelifetimeofthewebapplication.Sincethereisonlyoneinstanceofthesitemapproviderthatmay
beinvokedfrommultiple,concurrentwebsitevisitors,itisimperativethattheprovidersmethodsbethreadsafe.
Forperformanceandscalabilityreasons,itsimportantthatwecachetheinmemorysitemapstructureandreturn
thiscachedstructureratherthanrecreatingiteverytimetheBuildSiteMap methodisinvoked.BuildSiteMap may
becalledseveraltimesperpagerequestperuser,dependingonthenavigationcontrolsinuseonthepageandthe
depthofthesitemapstructure.Inanycase,ifwedonotcachethesitemapstructureinBuildSiteMap theneach
timeitisinvokedwewouldneedtoreretrievetheproductandcategoryinformationfromthearchitecture(which
wouldresultinaquerytothedatabase).Aswediscussedinthepreviouscachingtutorials,cacheddatacanbecome
stale.Tocombatthis,wecanuseeithertimeorSQLcachedependencybasedexpiries.
Note:AsitemapprovidermayoptionallyoverridetheInitialize method.Initialize isinvokedwhen
thesitemapproviderisfirstinstantiatedandispassedanycustomattributesassignedtotheproviderin
Web.config inthe <add> elementlike:<addname="name"type="type"customAttribute="value"/>.
Itisusefulifyouwanttoallowapagedevelopertospecifyvarioussitemapproviderrelatedsettings
withouthavingtomodifytheproviderscode.Forexample,ifwewerereadingthecategoryandproducts
datadirectlyfromthedatabaseasopposedtothroughthearchitecture,wedlikelywanttoletthepage
developerspecifythedatabaseconnectionstringthroughWeb.config ratherthanusingahardcodedvaluein
theproviderscode.ThecustomsitemapproviderwellbuildinStep6doesnotoverridethisInitialize
method.ForanexampleofusingtheInitialize method,referto JeffProsisesStoringSiteMapsinSQL
Serverarticle.

Step6:CreatingtheCustomSiteMapProvider
TocreateacustomsitemapproviderthatbuildsthesitemapfromthecategoriesandproductsintheNorthwind
database,weneedtocreateaclassthatextendsStaticSiteMapProvider.InStep1Iaskedyoutoadda
CustomProviders folderintheApp_Code folder addanewclasstothisfoldernamed
NorthwindSiteMapProvider.AddthefollowingcodetotheNorthwindSiteMapProvider class:
usingSystem
usingSystem.Data
usingSystem.Configuration
usingSystem.Web
usingSystem.Web.Security
usingSystem.Web.UI

17 of26

usingSystem.Web.UI.WebControls
usingSystem.Web.UI.WebControls.WebParts
usingSystem.Web.UI.HtmlControls
usingSystem.Web.Caching
publicclassNorthwindSiteMapProvider:StaticSiteMapProvider
{
privatereadonlyobjectsiteMapLock=newobject()
privateSiteMapNoderoot=null
publicconststringCacheDependencyKey=
"NorthwindSiteMapProviderCacheDependency"
publicoverrideSiteMapNodeBuildSiteMap()
{
//Usealocktomakethismethodthreadsafe
lock(siteMapLock)
{
//First,seeifwealreadyhaveconstructedthe
//rootNode.Ifso,returnit...
if(root!=null)
returnroot
//Weneedtobuildthesitemap!
//Clearoutthecurrentsitemapstructure
base.Clear()
//Getthecategoriesandproductsinformationfromthedatabase
ProductsBLLproductsAPI=newProductsBLL()
Northwind.ProductsDataTableproducts=productsAPI.GetProducts()
//CreatetherootSiteMapNode
root=newSiteMapNode(
this,"root","~/SiteMapProvider/Default.aspx","AllCategories")
AddNode(root)
//CreateSiteMapNodesforthecategoriesandproducts
foreach(Northwind.ProductsRowproductinproducts)
{
//AddanewcategorySiteMapNode,ifneeded
stringcategoryKey,categoryName
boolcreateUrlForCategoryNode=true
if(product.IsCategoryIDNull())
{
categoryKey="Category:None"
categoryName="None"
createUrlForCategoryNode=false
}
else
{
categoryKey=string.Concat("Category:",product.CategoryID)
categoryName=product.CategoryName
}
SiteMapNodecategoryNode=FindSiteMapNodeFromKey(categoryKey)
//AddthecategorySiteMapNodeifitdoesnotexist

18 of26

if(categoryNode==null)
{
stringproductsByCategoryUrl=string.Empty
if(createUrlForCategoryNode)
productsByCategoryUrl=
"~/SiteMapProvider/ProductsByCategory.aspx?CategoryID="
+product.CategoryID
categoryNode=newSiteMapNode(
this,categoryKey,productsByCategoryUrl,categoryName)
AddNode(categoryNode,root)
}
//AddtheproductSiteMapNode
stringproductUrl=
"~/SiteMapProvider/ProductDetails.aspx?ProductID="
+product.ProductID
SiteMapNodeproductNode=newSiteMapNode(
this,string.Concat("Product:",product.ProductID),
productUrl,product.ProductName)
AddNode(productNode,categoryNode)
}
//Adda"dummy"itemtothecacheusingaSqlCacheDependency
//ontheProductsandCategoriestables
System.Web.Caching.SqlCacheDependencyproductsTableDependency=
newSystem.Web.Caching.SqlCacheDependency("NorthwindDB","Products")
System.Web.Caching.SqlCacheDependencycategoriesTableDependency=
newSystem.Web.Caching.SqlCacheDependency("NorthwindDB","Categories")
//CreateanAggregateCacheDependency
System.Web.Caching.AggregateCacheDependencyaggregateDependencies=
newSystem.Web.Caching.AggregateCacheDependency()
aggregateDependencies.Add(productsTableDependency,categoriesTableDependency)
//Addtheitemtothecachespecifyingacallbackfunction
HttpRuntime.Cache.Insert(
CacheDependencyKey,DateTime.Now,aggregateDependencies,
Cache.NoAbsoluteExpiration,Cache.NoSlidingExpiration,
CacheItemPriority.Normal,
newCacheItemRemovedCallback(OnSiteMapChanged))

//Finally,returntherootnode
returnroot
}
}
protectedoverrideSiteMapNodeGetRootNodeCore()
{
returnBuildSiteMap()
}
protectedvoidOnSiteMapChanged(stringkey,objectvalue,CacheItemRemovedReasonreason)
{
lock(siteMapLock)
{

19 of26

if(string.Compare(key,CacheDependencyKey)==0)
{
//Refreshthesitemap
root=null
}
}
}
publicDateTime?CachedDate
{
get
{
returnHttpRuntime.Cache[CacheDependencyKey]asDateTime?
}
}
}

LetsstartwithexploringthisclasssBuildSiteMap method,whichstartswithalock statement.Thelock


statementonlyallowsonethreadatatimetoenter,therebyserializingaccesstoitscodeandpreventingtwo
concurrentthreadsfromsteppingononeanotherstoes.
TheclasslevelSiteMapNode variableroot isusedtocachethesitemapstructure.Whenthesitemapis
constructedforthefirsttime,orforthefirsttimeaftertheunderlyingdatahasbeenmodified,root willbenull
andthesitemapstructurewillbeconstructed.Thesitemapsrootnodeisassignedtoroot duringtheconstruction
processsothatthenexttimethismethodiscalled,root willnotbenull.Consequently,solongas root isnot
null thesitemapstructurewillbereturnedtothecallerwithouthavingtorecreateit.
Ifrootisnull,thesitemapstructureiscreatedfromtheproductandcategoryinformation.Thesitemapisbuiltby
creatingtheSiteMapNode instancesandthenformingthehierarchythroughcallstotheStaticSiteMapProvider
classsAddNode method.AddNode performstheinternalbookkeeping,storingtheassortedSiteMapNode instances
inaHashtable.Beforewestartconstructingthehierarchy,westartbycallingtheClear method,whichclearsout
theelementsfromtheinternalHashtable.Next,theProductsBLL classsGetProducts methodandtheresulting
ProductsDataTable arestoredinlocalvariables.
Thesitemapsconstructionbeginsbycreatingtherootnodeandassigningittoroot.Theoverloadofthe
SiteMapNodesconstructor usedhereandthroughoutthis BuildSiteMap ispassedthefollowinginformation:
l
l
l
l

Areferencetothesitemapprovider(this).
TheSiteMapNodesKey.ThisrequiredvaluemustbeuniqueforeachSiteMapNode.
TheSiteMapNodesUrl.Url isoptional,butifprovided,eachSiteMapNodesUrl valuemustbeunique.
TheSiteMapNodesTitle,whichisrequired.

TheAddNode(root) methodcalladdstheSiteMapNoderoot tothesitemapastheroot.Next,eachProductRow


intheProductsDataTable isenumerated.IftherealreadyexistsaSiteMapNode forthecurrentproducts
category,itisreferenced.Otherwise,anewSiteMapNode forthecategoryiscreatedandaddedasachildofthe
SiteMapNoderoot throughthe AddNode(categoryNode,root) methodcall.Aftertheappropriatecategory
SiteMapNode nodehasbeenfoundorcreated,aSiteMapNode iscreatedforthecurrentproductandaddedasa
childofthecategorySiteMapNode viaAddNode(productNode,categoryNode).Notethatthecategory
SiteMapNodesUrl propertyvalueis ~/SiteMapProvider/ProductsByCategory.aspx?
CategoryID=categoryID whiletheproductSiteMapNodesUrl propertyisassigned
~/SiteMapNode/ProductDetails.aspx?ProductID=productID.
Note:ThoseproductsthathaveadatabaseNULL valuefortheirCategoryID aregroupedunderacategory
SiteMapNode whoseTitle propertyissettoNoneandwhoseUrl propertyissettoanemptystring.I
20 of26

decidedtosetUrl toanemptystringsincetheProductBLL classsGetProductsByCategory(categoryID)


methodcurrentlylacksthecapabilitytoreturnjustthoseproductswithaNULLCategoryID value.Also,I
wantedtodemonstratehowthenavigationcontrolsrenderaSiteMapNode thatlacksavalueforitsUrl
property.IencourageyoutoextendthistutorialsothattheNoneSiteMapNodesUrl propertypointsto
ProductsByCategory.aspx,yetonlydisplaystheproductswith NULLCategoryID values.
Afterconstructingthesitemap,anarbitraryobjectisaddedtothedatacacheusingaSQLcachedependencyonthe
Categories andProducts tablesthroughan AggregateCacheDependency object.WeexploredusingSQLcache
dependenciesintheprecedingtutorial, UsingSQLCacheDependencies.Thecustomsitemapprovider,however,
usesanoverloadofthedatacachesInsert methodthatweveyettoexplore.Thisoverloadacceptsasitsfinal
inputparameteradelegatethatiscalledwhentheobjectisremovedfromthecache.Specifically,wepassinanew
CacheItemRemovedCallback delegatethatpointstotheOnSiteMapChanged methoddefinedfurtherdowninthe
NorthwindSiteMapProvider class.
Note:Theinmemoryrepresentationofthesitemapiscachedthroughtheclasslevelvariableroot.Since
thereisonlyoneinstanceofthecustomsitemapproviderclassandsincethatinstanceissharedamongall
threadsinthewebapplication,thisclassvariableservesasacache.TheBuildSiteMap methodalsousesthe
datacache,butonlyasameanstoreceivenotificationwhentheunderlyingdatabasedataintheCategories
orProducts tableschanges.Notethatthevalueputintothedatacacheisjustthecurrentdateandtime.The
actualsitemapdataisnot putinthedatacache.
TheBuildSiteMap methodcompletesbyreturningtherootnodeofthesitemap.
Theremainingmethodsarefairlystraightforward.GetRootNodeCore isresponsibleforreturningtherootnode.
SinceBuildSiteMap returnstheroot,GetRootNodeCore simplyreturnsBuildSiteMapsreturnvalue.The
OnSiteMapChanged methodsetsroot backtonull whenthecacheitemisremoved.Withrootsetbackto null,
thenexttimeBuildSiteMap isinvoked,thesitemapstructurewillberebuilt.Lastly,theCachedDate property
returnsthedateandtimevaluestoredinthedatacache,ifsuchavalueexists.Thispropertycanbeusedbyapage
developertodeterminewhenthesitemapdatawaslastcached.

Step7:RegisteringtheNorthwindSiteMapProvider
InorderforourwebapplicationtousetheNorthwindSiteMapProvider sitemapprovidercreatedinStep6,we
needtoregisteritinthe<siteMap> sectionofWeb.config.Specifically,addthefollowingmarkupwithinthe
<system.web> elementinWeb.config :
<siteMapdefaultProvider="AspNetXmlSiteMapProvider">
<providers>
<addname="Northwind"type="NorthwindSiteMapProvider"/>
</providers>
</siteMap>

Thismarkupdoestwothings:first,itindicatesthatthebuiltin AspNetXmlSiteMapProvider isthedefaultsite


mapprovidersecond,itregistersthecustomsitemapprovidercreatedinStep6withthehumanfriendlyname
Northwind.
Note:ForsitemapproviderslocatedintheapplicationsApp_Code folder,thevalueofthetype attributeis
simplytheclassname.Alternatively,thecustomsitemapprovidercouldhavebeencreatedinaseparate
ClassLibraryprojectwiththecompiledassemblyplacedinthewebapplications/Bin directory.Inthat
case,thetype attributevaluewouldbeNamespace.ClassName, AssemblyName.
AfterupdatingWeb.config,takeamomenttoviewanypagefromthetutorialsinabrowser.Notethatthe

21 of26

navigationinterfaceontheleftstillshowsthesectionsandtutorialsdefinedinWeb.sitemap.Thisisbecausewe
leftAspNetXmlSiteMapProvider asthedefaultprovider.Inordertocreateanavigationuserinterfaceelementthat
usestheNorthwindSiteMapProvider,wellneedtoexplicitlyspecifythattheNorthwindsitemapprovider
shouldbeused.WellseehowtoaccomplishthisinStep8.

Step8:DisplayingSiteMapInformationUsingtheCustomSiteMap
Provider
WiththecustomsitemapprovidercreatedandregisteredinWeb.config,werereadytoaddnavigationcontrolsto
theDefault.aspx,ProductsByCategory.aspx,andProductDetails.aspx pagesintheSiteMapProvider
folder.StartbyopeningtheDefault.aspx pageanddragaSiteMapPath fromtheToolboxontotheDesigner.The
SiteMapPathcontrolislocatedintheNavigationsectionoftheToolbox.

Figure16:AddaSiteMapPathtoDefault.aspx

TheSiteMapPathcontroldisplaysabreadcrumb,indicatingthecurrentpageslocationwithinthesitemap.We
addedaSiteMapPathtothetopofthemasterpagebackintheMasterPagesandSiteNavigation tutorial.
Takeamomenttoviewthispagethroughabrowser.TheSiteMapPathaddedinFigure16usesthedefaultsitemap
provider,pullingitsdatafromWeb.sitemap.Therefore,thebreadcrumbshowsHome>CustomizingtheSite
Map, justlikethebreadcrumbintheupperrightcorner.

22 of26

Figure17:TheBreadcrumbUsestheDefaultSiteMapProvider

TohavetheSiteMapPathaddedinFigure16usethecustomsitemapproviderwecreatedinStep6,setits
SiteMapProvider propertytoNorthwind, thenameweassignedtotheNorthwindSiteMapProvider in
Web.config.Unfortunately,theDesignercontinuestousethedefaultsitemapprovider,butifyouvisitthepage
throughabrowseraftermakingthispropertychangeyoullseethatthebreadcrumbnowusesthecustomsitemap
provider.

Figure18:TheBreadcrumbNowUsestheCustomSiteMapProviderNorthwindSiteMapProvider

TheSiteMapPathcontroldisplaysamorefunctionaluserinterfaceintheProductsByCategory.aspx and
23 of26

ProductDetails.aspx pages.AddaSiteMapPathtothesepages,settingthe SiteMapProvider propertyinboth


toNorthwind. FromDefault.aspx clickontheViewProducts linkforBeverages,andthenontheView

DetailslinkforChaiTea.AsFigure19shows,thebreadcrumbincludesthecurrentsitemapsection(ChaiTea)
anditsancestors:Beveragesand AllCategories.

Figure19:TheBreadcrumbNowUsestheCustomSiteMapProviderNorthwindSiteMapProvider

OthernavigationuserinterfaceelementscanbeusedinadditiontotheSiteMapPath,suchastheMenuand
TreeViewcontrols.TheDefault.aspx,ProductsByCategory.aspx,andProductDetails.aspx pagesinthe
downloadforthistutorial,forexample,allincludeMenucontrols(seeFigure20).SeeExaminingASP.NET2.0s
SiteNavigationFeaturesandtheUsingSiteNavigationControlssectionoftheASP.NET2.0QuickStartsfora
moreindepthlookatthenavigationcontrolsandsitemapsysteminASP.NET2.0.

24 of26

Figure20:TheMenuControlListsEachoftheCategoriesandProducts

Asmentionedearlierinthistutorial,thesitemapstructurecanbeaccessedprogrammaticallythroughtheSiteMap
class.ThefollowingcodereturnstherootSiteMapNode ofthedefaultprovider:
SiteMapNoderoot=SiteMap.RootNode

SincetheAspNetXmlSiteMapProvider isthedefaultproviderforourapplication,theabovecodewouldreturnthe
rootnodedefinedinWeb.sitemap.Toreferenceasitemapproviderotherthanthedefault,usetheSiteMap classs
Providers propertylikeso:
SiteMapNoderoot=SiteMap.Providers["name"].RootNode

Where nameisthenameofthecustomsitemapprovider(Northwind, forourwebapplication).


Toaccessamemberspecifictoasitemapprovider,useSiteMap.Providers["name"] toretrievetheprovider
instanceandthencastittotheappropriatetype.Forexample,todisplaytheNorthwindSiteMapProviders
CachedDate propertyinanASP.NETpage,usethefollowingcode:
NorthwindSiteMapProvidercustomProvider=
SiteMap.Providers["Northwind"]asNorthwindSiteMapProvider
if(customProvider!=null)
{
DateTime?lastCachedDate=customProvider.CachedDate
if(lastCachedDate!=null)
LabelID.Text="Sitemapcachedon:"+lastCachedDate.Value.ToString()
else
LabelID.Text="Thesitemapisbeingreconstructed!"
}

Note:BesuretotestouttheSQLcachedependencyfeature.AftervisitingtheDefault.aspx,

25 of26

ProductsByCategory.aspx,andProductDetails.aspx pages,gotooneofthetutorialsintheEditing,

Inserting,andDeletingsectionandeditthenameofacategoryorproduct.Thenreturntooneofthepagesin
theSiteMapProvider folder.Assumingenoughtimehaspassedforthepollingmechanismtonotethe
changetotheunderlyingdatabase,thesitemapshouldbeupdatedtoshowthenewproductorcategory
name.

Summary
ASP.NET2.0ssitemapfeaturesincludesaSiteMap class,anumberofbuiltinnavigationWebcontrols,anda
defaultsitemapproviderthatexpectsthesitemapinformationpersistedtoanXMLfile.Inordertousesitemap
informationfromsomeothersourcesuchasfromadatabase,theapplicationsarchitecture,oraremoteWeb
service weneedtocreateacustomsitemapprovider.Thisinvolvescreatingaclassthatderives,directlyor
indirectly,fromtheSiteMapProvider class.
Inthistutorialwesawhowtocreateacustomsitemapproviderthatbasedthesitemapontheproductand
categoryinformationculledfromtheapplicationarchitecture.OurproviderextendedtheStaticSiteMapProvider
classandentailedcreatingaBuildSiteMap methodthatretrievedthedata,constructedthesitemaphierarchy,and
cachedtheresultingstructureinaclasslevelvariable.WeusedaSQLcachedependencywithacallbackfunction
toinvalidatethecachedstructurewhentheunderlyingCategories orProducts dataismodified.
HappyProgramming!

FurtherReading
Formoreinformationonthetopicsdiscussedinthistutorial,refertothefollowingresources:
l
l
l
l

StoringSiteMapsinSQLServer andTheSQLSiteMapProviderYouveBeenWaitingFor
ALookatASP.NET2.0sProviderModel
TheProviderToolkit
ExaminingASP.NET2.0sSiteNavigationFeatures

AbouttheAuthor
ScottMitchell,authorofsevenASP/ASP.NETbooksandfounderof4GuysFromRolla.com,hasbeenworkingwith
MicrosoftWebtechnologiessince1998.Scottworksasanindependentconsultant,trainer,andwriter.Hislatest
bookisSamsTeachYourselfASP.NET2.0in24Hours.Hecanbereachedatmitchell@4GuysFromRolla.com. or
viahisblog,whichcanbefoundat http://ScottOnWriting.NET.

SpecialThanksTo
Thistutorialserieswasreviewedbymanyhelpfulreviewers.LeadreviewersforthistutorialwereDaveGardner,
ZackJones,TeresaMurphy,andBernadetteLeigh.InterestedinreviewingmyupcomingMSDNarticles?Ifso,
dropmealineat mitchell@4GuysFromRolla.com.

26 of26

Das könnte Ihnen auch gefallen