You are on page 1of 34

Thistutorialispartofaset.FindoutmoreaboutdataaccesswithASP.

NETintheWorkingwith
DatainASP.NET2.0sectionoftheASP.NETsiteathttp://www.asp.net/learn/dataaccess/default.aspx.
WorkingwithDatainASP.NET2.0::Creatinga
DataAccessLayer
Introduction
Aswebdevelopers,ourlivesrevolvearoundworkingwithdata.Wecreatedatabasestostorethedata,codeto
retrieveandmodifyit,andwebpagestocollectandsummarizeit.Thisisthefirsttutorialinalengthyseries
thatwillexploretechniquesforimplementingthesecommonpatternsinASP.NET2.0.We'llstartwithcreating
asoftwarearchitecture composedofaDataAccessLayer(DAL)usingTypedDataSets,aBusinessLogicLayer
(BLL)thatenforcescustombusinessrules,andapresentationlayercomposedofASP.NETpagesthatsharea
commonpagelayout.Oncethisbackendgroundworkhasbeenlaid,we'llmoveintoreporting,showinghowto
display,summarize,collect,andvalidatedatafromawebapplication.Thesetutorialsaregearedtobeconcise
andprovidestepbystepinstructionswithplentyofscreenshotstowalkyouthroughtheprocessvisually.Each
tutorialisavailableinC#andVisualBasicversionsandincludesadownloadofthecompletecodeused.(This
firsttutorialisquitelengthy,buttherestarepresentedinmuchmoredigestiblechunks.)
Forthesetutorialswe'llbeusingaMicrosoftSQLServer2005ExpressEdition versionoftheNorthwind
databaseplacedintheApp_Datadirectory.Inadditiontothedatabasefile,theApp_Datafolderalsocontains
theSQLscriptsforcreatingthedatabase,incaseyouwanttouseadifferentdatabaseversion.Thesescriptscan
bealsobedownloadeddirectlyfromMicrosoft,ifyou'dprefer.IfyouuseadifferentSQLServerversionofthe
Northwinddatabase,youwillneedtoupdatetheNORTHWNDConnectionStringsettingintheapplication's
Web.configfile.ThewebapplicationwasbuiltusingVisualStudio2005ProfessionalEditionasafilesystem
basedWebsiteproject.However,allofthetutorialswillworkequallywellwiththefreeversionofVisual
Studio2005, VisualWebDeveloper.
Inthistutorialwe'llstartfromtheverybeginningandcreatetheDataAccessLayer(DAL),followedby
creatingtheBusinessLogicLayer(BLL)inthesecondtutorial,andworkingonpagelayoutandnavigationin
thethird.Thetutorialsafterthethirdonewillbuilduponthefoundationlaidinthefirstthree.We'vegotalotto
coverinthisfirsttutorial,sofireupVisualStudioandlet'sgetstarted!
Step1:CreatingaWebProjectandConnectingto
theDatabase
BeforewecancreateourDataAccessLayer(DAL),wefirstneedtocreateawebsiteandsetupourdatabase.
StartbycreatinganewfilesystembasedASP.NETwebsite.Toaccomplishthis,gototheFilemenuand
chooseNewWebSite,displayingtheNewWebSitedialogbox.ChoosetheASP.NETWebSitetemplate,set
theLocationdropdownlisttoFileSystem,chooseafoldertoplacethewebsite,andsetthelanguagetoC#.
1 of34
Figure1:CreateaNewFileSystemBasedWebSite
ThiswillcreateanewwebsitewithaDefault.aspxASP.NETpageandanApp_Datafolder.
Withthewebsitecreated,thenextstepistoaddareferencetothedatabaseinVisualStudio'sServerExplorer.
ByaddingadatabasetotheServerExploreryoucanaddtables,storedprocedures,views,andsoonallfrom
withinVisualStudio.Youcanalsoviewtabledataorcreateyourownquerieseitherbyhandorgraphicallyvia
theQueryBuilder.Furthermore,whenwebuildtheTypedDataSetsfortheDALwe'llneedtopointVisual
StudiotothedatabasefromwhichtheTypedDataSetsshouldbeconstructed.Whilewecanprovidethis
connectioninformationatthatpointintime,VisualStudioautomaticallypopulatesadropdownlistofthe
databasesalreadyregisteredintheServerExplorer.
ThestepsforaddingtheNorthwinddatabasetotheServerExplorerdependonwhetheryouwanttousethe
SQLServer2005ExpressEditiondatabaseintheApp_DatafolderorifyouhaveaMicrosoftSQLServer2000
or2005databaseserversetupthatyouwanttouseinstead.
UsingaDatabaseintheApp_DataFolder
IfyoudonothaveaSQLServer2000or2005databaseservertoconnectto,oryousimplywanttoavoid
havingtoaddthedatabasetoadatabaseserver,youcanusetheSQLServer2005ExpressEditionversionof
theNorthwinddatabasethatislocatedinthedownloadedwebsite'sApp_Datafolder(NORTHWND.MDF).
AdatabaseplacedintheApp_DatafolderisautomaticallyaddedtotheServerExplorer.Assumingyouhave
SQLServer2005ExpressEditioninstalledonyourmachineyoushouldseeanodenamedNORTHWND.MDF
intheServerExplorer,whichyoucanexpandandexploreitstables,views,storedprocedure,andsoon(see
Figure2).
TheApp_DatafoldercanalsoholdMicrosoftAccess.mdbfiles,which,liketheirSQLServercounterparts,are
automaticallyaddedtotheServerExplorer.Ifyoudon'twanttouseanyoftheSQLServeroptions,youcan
alwaysdownloadaMicrosoftAccessversionoftheNorthwinddatabasefileanddropintotheApp_Data
directory.Keepinmind,however,thatAccessdatabasesaren'tasfeaturerichasSQLServer,andaren't
designedtobeusedinwebsitescenarios.Furthermore,acoupleofthe35+tutorialswillutilizecertain
databaselevelfeaturesthataren'tsupportedbyAccess.
2 of34
ConnectingtotheDatabaseinaMicrosoftSQL
Server2000or2005DatabaseServer
Alternatively,youmayconnecttoaNorthwinddatabaseinstalledonadatabaseserver.Ifthedatabaseserver
doesnotalreadyhavetheNorthwinddatabaseinstalled,youfirstmustaddittodatabaseserverbyrunningthe
installationscriptincludedinthistutorial'sdownloadorbydownloadingtheSQLServer2000versionof
NorthwindandinstallationscriptdirectlyfromMicrosoft'swebsite.
Onceyouhavethedatabaseinstalled,gototheServerExplorerinVisualStudio,rightclickontheData
Connectionsnode,andchooseAddConnection.Ifyoudon'tseetheServerExplorergototheView/Server
Explorer,orhitCtrl+Alt+S.ThiswillbringuptheAddConnectiondialogbox,whereyoucanspecifythe
servertoconnectto,theauthenticationinformation,andthedatabasename.Onceyouhavesuccessfully
configuredthedatabaseconnectioninformationandclickedtheOKbutton,thedatabasewillbeaddedasanode
underneaththeDataConnectionsnode.Youcanexpandthedatabasenodetoexploreitstables,views,stored
procedures,andsoon.
Figure2:AddaConnectiontoYourDatabaseServer'sNorthwindDatabase
Step2:CreatingtheDataAccessLayer
Whenworkingwithdataoneoptionistoembedthedataspecificlogicdirectlyintothepresentationlayer(ina
webapplication,theASP.NETpagesmakeupthepresentationlayer).Thismaytaketheformofwriting
ADO.NETcodeintheASP.NETpage'scodeportionorusingtheSqlDataSourcecontrolfromthemarkup
portion.Ineithercase,thisapproachtightlycouplesthedataaccesslogicwiththepresentationlayer.The
recommendedapproach,however,istoseparatethedataaccesslogicfromthepresentationlayer.Thisseparate
layerisreferredtoastheDataAccessLayer,DALforshort,andistypicallyimplementedasaseparateClass
3 of34
Libraryproject.Thebenefitsofthislayeredarchitecturearewelldocumented(seethe"FurtherReadings"
sectionattheendofthistutorialforinformationontheseadvantages)andistheapproachwewilltakeinthis
series.
Allcodethatisspecifictotheunderlyingdatasource suchascreatingaconnectiontothedatabase,issuing
SELECT,INSERT,UPDATE,andDELETEcommands,andsoonshouldbelocatedintheDAL.Thepresentation
layershouldnotcontainanyreferencestosuchdataaccesscode,butshouldinsteadmakecallsintotheDALfor
anyandalldatarequests.DataAccessLayerstypicallycontainmethodsforaccessingtheunderlyingdatabase
data.TheNorthwinddatabase,forexample,hasProductsandCategoriestablesthatrecordtheproductsfor
saleandthecategoriestowhichtheybelong.InourDALwewillhavemethodslike:
l GetCategories(), whichwillreturninformationaboutallofthecategories
l GetProducts(),whichwillreturninformationaboutalloftheproducts
l GetProductsByCategoryID(categoryID),whichwillreturnallproductsthatbelongtoaspecified
category
l GetProductByProductID(productID),whichwillreturninformationaboutaparticularproduct
Thesemethods,wheninvoked,willconnecttothedatabase,issuetheappropriatequery,andreturntheresults.
Howwereturntheseresultsisimportant.ThesemethodscouldsimplyreturnaDataSetorDataReader
populatedbythedatabasequery,butideallytheseresultsshouldbereturnedusingstronglytypedobjects.A
stronglytypedobjectisonewhoseschemaisrigidlydefinedatcompiletime,whereastheopposite,aloosely
typedobject,isonewhoseschemaisnotknownuntilruntime.
Forexample,theDataReaderandtheDataSet(bydefault)arelooselytypedobjectssincetheirschemais
definedbythecolumnsreturnedbythedatabasequeryusedtopopulatethem.Toaccessaparticularcolumn
fromalooselytypedDataTableweneedtousesyntaxlike:DataTable.Rows[index]["columnName"].The
DataTable'sloosetypinginthisexampleisexhibitedbythefactthatweneedtoaccessthecolumnnameusing
astringorordinalindex.AstronglytypedDataTable,ontheotherhand,willhaveeachofitscolumns
implementedasproperties,resultingincodethatlookslike:DataTable.Rows[index].columnName.
Toreturnstronglytypedobjects,developerscaneithercreatetheirowncustombusinessobjectsoruseTyped
DataSets.Abusinessobjectisimplementedbythedeveloperasaclasswhosepropertiestypicallyreflectthe
columnsoftheunderlyingdatabasetablethebusinessobjectrepresents.ATypedDataSetisaclassgenerated
foryoubyVisualStudiobasedonadatabaseschemaandwhosemembersarestronglytypedaccordingtothis
schema.TheTypedDataSetitselfconsistsofclassesthatextendtheADO.NETDataSet,DataTable,and
DataRowclasses.InadditiontostronglytypedDataTables,TypedDataSetsnowalsoincludeTableAdapters,
whichareclasseswithmethodsforpopulatingtheDataSet'sDataTablesandpropagatingmodificationswithin
theDataTablesbacktothedatabase.
Note:FormoreinformationontheadvantagesanddisadvantagesofusingTypedDataSetsversuscustom
businessobjects,refertoDesigningDataTierComponentsandPassingDataThroughTiers.
We'llusestronglytypedDataSetsforthesetutorials'architecture.Figure3illustratestheworkflowbetweenthe
differentlayersofanapplicationthatusesTypedDataSets.
4 of34
Figure3:AllDataAccessCodeisRelegatedtotheDAL
CreatingaTypedDataSetandTableAdapter
TobegincreatingourDAL,westartbyaddingaTypedDataSettoourproject.Toaccomplishthis,rightclick
ontheprojectnodeintheSolutionExplorerandchooseAddaNewItem.SelecttheDataSetoptionfromthelist
oftemplatesandnameitNorthwind.xsd.
Figure4:ChoosetoAddaNewDataSettoYourProject
AfterclickingAdd,whenpromptedtoaddtheDataSettotheApp_Codefolder,chooseYes.TheDesignerfor
theTypedDataSetwillthenbedisplayed,andtheTableAdapterConfigurationWizardwillstart,allowingyou
toaddyourfirstTableAdaptertotheTypedDataSet.
ATypedDataSetservesasastronglytypedcollectionofdataitiscomposedofstronglytypedDataTable
instances,eachofwhichisinturncomposedofstronglytypedDataRowinstances.Wewillcreateastrongly
typedDataTableforeachoftheunderlyingdatabasetablesthatweneedtoworkwithinthistutorialsseries.
Let'sstartwithcreatingaDataTablefortheProductstable.
KeepinmindthatstronglytypedDataTablesdonotincludeanyinformationonhowtoaccessdatafromtheir
underlyingdatabasetable.InordertoretrievethedatatopopulatetheDataTable,weuseaTableAdapterclass,
whichfunctionsasourDataAccessLayer.ForourProductsDataTable,theTableAdapterwillcontainthe
methodsGetProducts(),GetProductByCategoryID(categoryID),andsoonthatwe'llinvokefromthe
5 of34
presentationlayer.TheDataTable'sroleistoserveasthestronglytypedobjectsusedtopassdatabetweenthe
layers.
TheTableAdapterConfigurationWizardbeginsbypromptingyoutoselectwhichdatabasetoworkwith.The
dropdownlistshowsthosedatabasesintheServerExplorer.IfyoudidnotaddtheNorthwinddatabasetothe
ServerExplorer,youcanclicktheNewConnectionbuttonatthistimetodoso.
Figure5:ChoosetheNorthwindDatabasefromtheDropDownList
AfterselectingthedatabaseandclickingNext,you'llbeaskedifyouwanttosavetheconnectionstringinthe
Web.configfile.Bysavingtheconnectionstringyou'llavoidhavingithardcodedintheTableAdapterclasses,
whichsimplifiesthingsiftheconnectionstringinformationchangesinthefuture.Ifyouopttosavethe
connectionstringintheconfigurationfileit'splacedinthe<connectionStrings>section,whichcanbe
optionallyencryptedforimprovedsecurityormodifiedlaterthroughthenewASP.NET2.0PropertyPage
withintheIISGUIAdminTool,whichismoreidealforadministrators.
6 of34
Figure6:SavetheConnectionStringtoWeb.config
Next,weneedtodefinetheschemaforthefirststronglytypedDataTableandprovidethefirstmethodforour
TableAdaptertousewhenpopulatingthestronglytypedDataSet.Thesetwostepsareaccomplished
simultaneouslybycreatingaquerythatreturnsthecolumnsfromthetablethatwewantreflectedinour
DataTable.Attheendofthewizardwe'llgiveamethodnametothisquery.Oncethat'sbeenaccomplished,this
methodcanbeinvokedfromourpresentationlayer.Themethodwillexecutethedefinedqueryandpopulatea
stronglytypedDataTable.
TogetstarteddefiningtheSQLquerywemustfirstindicatehowwewanttheTableAdaptertoissuethequery.
WecanuseanadhocSQLstatement,createanewstoredprocedure,oruseanexistingstoredprocedure.For
thesetutorialswe'lluseadhocSQLstatements.RefertoBrianNoyes'sarticle,BuildaDataAccessLayerwith
theVisualStudio2005DataSetDesigner foranexampleofusingstoredprocedures.
7 of34
Figure7:QuerytheDataUsinganAdHocSQLStatement
AtthispointwecantypeintheSQLquerybyhand.WhencreatingthefirstmethodintheTableAdapteryou
typicallywanttohavethequeryreturnthosecolumnsthatneedtobeexpressedinthecorrespondingDataTable.
WecanaccomplishthisbycreatingaquerythatreturnsallcolumnsandallrowsfromtheProductstable:
Figure8:EntertheSQLQueryIntotheTextbox
Alternatively,usetheQueryBuilderandgraphicallyconstructthequery,asshowninFigure9.
8 of34
Figure9:CreatetheQueryGraphically,throughtheQueryEditor
Aftercreatingthequery,butbeforemovingontothenextscreen,clicktheAdvancedOptionsbutton.InWeb
SiteProjects,"GenerateInsert,Update,andDeletestatements"istheonlyadvancedoptionselectedbydefault
ifyourunthiswizardfromaClassLibraryoraWindowsProjectthe"Useoptimisticconcurrency"optionwill
alsobeselected.Leavethe"Useoptimisticconcurrency"optionuncheckedfornow.We'llexamineoptimistic
concurrencyinfuturetutorials.
Figure10:SelectOnlythe"GenerateInsert,Update,andDeletestatements"Option
Afterverifyingtheadvancedoptions,clickNexttoproceedtothefinalscreen.Hereweareaskedtoselect
whichmethodstoaddtotheTableAdapter.Therearetwopatternsforpopulatingdata:
l FillaDataTable withthisapproachamethodiscreatedthattakesinaDataTableasaparameterand
9 of34
populatesitbasedontheresultsofthequery.TheADO.NETDataAdapterclass,forexample,
implementsthispatternwithitsFill()method.
l ReturnaDataTablewiththisapproachthemethodcreatesandfillstheDataTableforyouandreturns
itasthemethodsreturnvalue.
YoucanhavetheTableAdapterimplementoneorbothofthesepatterns.Youcanalsorenamethemethods
providedhere.Let'sleavebothcheckboxeschecked,eventhoughwe'llonlybeusingthelatterpattern
throughoutthesetutorials.Also,let'srenametherathergenericGetDatamethodtoGetProducts.
Ifchecked,thefinalcheckbox,"GenerateDBDirectMethods,"createsInsert(),Update(),andDelete()
methodsfortheTableAdapter.Ifyouleavethisoptionunchecked,allupdateswillneedtobedonethroughthe
TableAdapter'ssoleUpdate()method,whichtakesintheTypedDataSet,aDataTable,asingleDataRow,oran
arrayofDataRows.(Ifyou'veuncheckedthe"GenerateInsert,Update,andDeletestatements"optionfromthe
advancedpropertiesinFigure9thischeckbox'ssettingwillhavenoeffect.)Let'sleavethischeckboxselected.
Figure11:ChangetheMethodNamefromGetDatatoGetProducts
CompletethewizardbyclickingFinish.AfterthewizardcloseswearereturnedtotheDataSetDesignerwhich
showstheDataTablewejustcreated.YoucanseethelistofcolumnsintheProductsDataTable(ProductID,
ProductName,andsoon),aswellasthemethodsoftheProductsTableAdapter(Fill()andGetProducts
()).
10 of34
Figure12:TheProductsDataTableandProductsTableAdapterhavebeenAddedtotheTypedDataSet
AtthispointwehaveaTypedDataSetwithasingleDataTable(Northwind.Products)andastronglytyped
DataAdapterclass(NorthwindTableAdapters.ProductsTableAdapter)withaGetProducts()method.
Theseobjectscanbeusedtoaccessalistofallproductsfromcodelike:
NorthwindTableAdapters.ProductsTableAdapterproductsAdapter=
newNorthwindTableAdapters.ProductsTableAdapter()
Northwind.ProductsDataTableproducts
products=productsAdapter.GetProducts()
foreach(Northwind.ProductsRowproductRowinproducts)
Response.Write("Product:"+productRow.ProductName+"<br/>")
Thiscodedidnotrequireustowriteonebitofdataaccessspecificcode.Wedidnothavetoinstantiateany
ADO.NETclasses,wedidn'thavetorefertoanyconnectionstrings,SQLqueries,orstoredprocedures.Instead,
theTableAdapterprovidesthelowleveldataaccesscodeforus.
Eachobjectusedinthisexampleisalsostronglytyped,allowingVisualStudiotoprovideIntelliSenseand
compiletimetypechecking.AndbestofalltheDataTablesreturnedbytheTableAdaptercanbeboundto
ASP.NETdataWebcontrols,suchastheGridView,DetailsView,DropDownList,CheckBoxList,andseveral
others.ThefollowingexampleillustratesbindingtheDataTablereturnedbytheGetProducts()methodtoa
GridViewinjustascantthreelinesofcodewithinthePage_Loadeventhandler.
AllProducts.aspx
<%@PageLanguage="C#"AutoEventWireup="true"CodeFile="AllProducts.aspx.cs"
Inherits="AllProducts"%>
<!DOCTYPEhtmlPUBLIC"//W3C//DTDXHTML1.0Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1transitional.dtd">
<htmlxmlns="http://www.w3.org/1999/xhtml">
<headrunat="server">
<title>ViewAllProductsinaGridView</title>
<linkhref="Styles.css"rel="stylesheet"type="text/css"/>
11 of34
</head>
<body>
<formid="form1"runat="server">
<div>
<h1>
AllProducts</h1>
<p>
<asp:GridViewID="GridView1"runat="server"
CssClass="DataWebControlStyle">
<HeaderStyleCssClass="HeaderStyle"/>
<AlternatingRowStyleCssClass="AlternatingRowStyle"/>
</asp:GridView>
</p>
</div>
</form>
</body>
</html>
AllProducts.aspx.cs
usingSystem
usingSystem.Data
usingSystem.Configuration
usingSystem.Collections
usingSystem.Web
usingSystem.Web.Security
usingSystem.Web.UI
usingSystem.Web.UI.WebControls
usingSystem.Web.UI.WebControls.WebParts
usingSystem.Web.UI.HtmlControls
usingNorthwindTableAdapters
publicpartialclassAllProducts:System.Web.UI.Page
{
protectedvoidPage_Load(objectsender,EventArgse)
{
ProductsTableAdapterproductsAdapter=new
ProductsTableAdapter()
GridView1.DataSource=productsAdapter.GetProducts()
GridView1.DataBind()
}
}
12 of34
Figure13:TheListofProductsisDisplayedinaGridView
WhilethisexamplerequiredthatwewritethreelinesofcodeinourASP.NETpage'sPage_Loadeventhandler,
infuturetutorialswe'llexaminehowtousetheObjectDataSourcetodeclarativelyretrievethedatafromthe
DAL.WiththeObjectDataSourcewe'llnothavetowriteanycodeandwillgetpagingandsortingsupportas
well!
Step3:AddingParameterizedMethodstotheData
AccessLayer
AtthispointourProductsTableAdapterclasshasbutonemethod,GetProducts(),whichreturnsallofthe
productsinthedatabase.Whilebeingabletoworkwithallproductsisdefinitelyuseful,therearetimeswhen
we'llwanttoretrieveinformationaboutaspecificproduct,orallproductsthatbelongtoaparticularcategory.
ToaddsuchfunctionalitytoourDataAccessLayerwecanaddparameterizedmethodstotheTableAdapter.
Let'saddtheGetProductsByCategoryID(categoryID)method.ToaddanewmethodtotheDAL,returnto
theDataSetDesigner,rightclickintheProductsTableAdaptersection,andchooseAddQuery.
13 of34
Figure14:RightClickontheTableAdapterandChooseAddQuery
WearefirstpromptedaboutwhetherwewanttoaccessthedatabaseusinganadhocSQLstatementoranewor
existingstoredprocedure.Let'schoosetouseanadhocSQLstatementagain.Next,weareaskedwhattypeof
SQLquerywe'dliketouse.Sincewewanttoreturnallproductsthatbelongtoaspecifiedcategory,wewantto
writeaSELECTstatementwhichreturnsrows.
14 of34
Figure15:ChoosetoCreateaSELECTStatementWhichReturnsRows
ThenextstepistodefinetheSQLqueryusedtoaccessthedata.Sincewewanttoreturnonlythoseproducts
thatbelongtoaparticularcategory,IusethesameSELECTstatementfromGetProducts(),butaddthe
followingWHEREclause:WHERECategoryID=@CategoryID.The@CategoryIDparameterindicatestothe
TableAdapterwizardthatthemethodwe'recreatingwillrequireaninputparameterofthecorrespondingtype
(namely,anullableinteger).
Figure16:EnteraQuerytoOnlyReturnProductsinaSpecifiedCategory
15 of34
Inthefinalstepwecanchoosewhichdataaccesspatternstouse,aswellascustomizethenamesofthemethods
generated.FortheFillpattern,let'schangethenametoFillByCategoryIDandforthereturnaDataTable
returnpattern(theGetXmethods),let'suseGetProductsByCategoryID.
Figure17:ChoosetheNamesfortheTableAdapterMethods
Aftercompletingthewizard,theDataSetDesignerincludesthenewTableAdaptermethods.
Figure18:TheProductsCanNowbeQueriedbyCategory
TakeamomenttoaddaGetProductByProductID(productID)methodusingthesametechnique.
TheseparameterizedqueriescanbetesteddirectlyfromtheDataSetDesigner.Rightclickonthemethodinthe
16 of34
TableAdapterandchoosePreviewData.Next,enterthevaluestousefortheparametersandclickPreview.
Figure19:ThoseProductsBelongingtotheBeveragesCategoryareShown
WiththeGetProductsByCategoryID(categoryID)methodinourDAL,wecannowcreateanASP.NETpage
thatdisplaysonlythoseproductsinaspecifiedcategory.Thefollowingexampleshowsallproductsthatarein
theBeveragescategory,whichhaveaCategoryIDof1.
Beverages.aspx
<%@PageLanguage="C#"AutoEventWireup="true"CodeFile="Beverages.aspx.cs"
Inherits="Beverages"%>
<!DOCTYPEhtmlPUBLIC"//W3C//DTDXHTML1.0Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1transitional.dtd">
<htmlxmlns="http://www.w3.org/1999/xhtml">
<headrunat="server">
<title>UntitledPage</title>
<linkhref="Styles.css"rel="stylesheet"type="text/css"/>
</head>
<body>
<formid="form1"runat="server">
<div>
<h1>Beverages</h1>
<p>
<asp:GridViewID="GridView1"runat="server"
CssClass="DataWebControlStyle">
<HeaderStyleCssClass="HeaderStyle"/>
<AlternatingRowStyleCssClass="AlternatingRowStyle"/>
</asp:GridView>
</p>
</div>
</form>
</body>
</html>
17 of34
Beverages.aspx.cs
usingSystem
usingSystem.Data
usingSystem.Configuration
usingSystem.Collections
usingSystem.Web
usingSystem.Web.Security
usingSystem.Web.UI
usingSystem.Web.UI.WebControls
usingSystem.Web.UI.WebControls.WebParts
usingSystem.Web.UI.HtmlControls
usingNorthwindTableAdapters
publicpartialclassBeverages:System.Web.UI.Page
{
protectedvoidPage_Load(objectsender,EventArgse)
{
ProductsTableAdapterproductsAdapter=new
ProductsTableAdapter()
GridView1.DataSource=
productsAdapter.GetProductsByCategoryID(1)
GridView1.DataBind()
}
}
Figure20:ThoseProductsintheBeveragesCategoryareDisplayed
Step4:Inserting,Updating,andDeletingData
Therearetwopatternscommonlyusedforinserting,updating,anddeletingdata.Thefirstpattern,whichI'llcall
thedatabasedirectpattern,involvescreatingmethodsthat,wheninvoked,issueanINSERT,UPDATE,orDELETE
commandtothedatabasethatoperatesonasingledatabaserecord.Suchmethodsaretypicallypassedina
seriesofscalarvalues(integers,strings,Booleans,DateTimes,andsoon)thatcorrespondtothevaluestoinsert,
update,ordelete.Forexample,withthispatternfortheProductstablethedeletemethodwouldtakeinan
integerparameter,indicatingtheProductIDoftherecordtodelete,whiletheinsertmethodwouldtakeina
stringfortheProductName,adecimalfortheUnitPrice,anintegerfortheUnitsOnStock,andsoon.
18 of34
Figure21:EachInsert,Update,andDeleteRequestisSenttotheDatabaseImmediately
Theotherpattern,whichI'llrefertoasthebatchupdatepattern,istoupdateanentireDataSet,DataTable,or
collectionofDataRowsinonemethodcall.Withthispatternadeveloperdeletes,inserts,andmodifiesthe
DataRowsinaDataTableandthenpassesthoseDataRowsorDataTableintoanupdatemethod.Thismethod
thenenumeratestheDataRowspassedin,determineswhetherornotthey'vebeenmodified,added,ordeleted
(viatheDataRow'sRowStateproperty value),andissuestheappropriatedatabaserequestforeachrecord.
Figure22:AllChangesareSynchronizedwiththeDatabaseWhentheUpdateMethodisInvoked
TheTableAdapterusesthebatchupdatepatternbydefault,butalsosupportstheDBdirectpattern.Sincewe
selectedthe"GenerateInsert,Update,andDeletestatements"optionfromtheAdvancedPropertieswhen
creatingourTableAdapter,theProductsTableAdaptercontainsanUpdate()method,whichimplementsthe
batchupdatepattern.Specifically,theTableAdaptercontainsanUpdate()methodthatcanbepassedtheTyped
DataSet,astronglytypedDataTable,oroneormoreDataRows.Ifyouleftthe"GenerateDBDirectMethods"
checkboxcheckedwhenfirstcreatingtheTableAdaptertheDBdirectpatternwillalsobeimplementedvia
Insert(),Update(),andDelete()methods.
BothdatamodificationpatternsusetheTableAdapter'sInsertCommand,UpdateCommand,andDeleteCommand
propertiestoissuetheirINSERT,UPDATE,andDELETEcommandstothedatabase.Youcaninspectandmodify
theInsertCommand,UpdateCommand,andDeleteCommandpropertiesbyclickingontheTableAdapterinthe
DataSetDesignerandthengoingtothePropertieswindow.(MakesureyouhaveselectedtheTableAdapter,
andthattheProductsTableAdapterobjectistheoneselectedinthedropdownlistinthePropertieswindow.)
19 of34
Figure23:TheTableAdapterhasInsertCommand,UpdateCommand,andDeleteCommandProperties
Toexamineormodifyanyofthesedatabasecommandproperties,clickontheCommandTextsubproperty,
whichwillbringuptheQueryBuilder.
Figure24:ConfiguretheINSERT,UPDATE,andDELETEStatementsintheQueryBuilder
Thefollowingcodeexampleshowshowtousethebatchupdatepatterntodoublethepriceofallproductsthat
arenotdiscontinuedandthathave25unitsinstockorless:
20 of34
NorthwindTableAdapters.ProductsTableAdapterproductsAdapter=
newNorthwindTableAdapters.ProductsTableAdapter()
//Foreachproduct,doubleitspriceifitisnotdiscontinuedand
//thereare25itemsinstockorless
Northwind.ProductsDataTableproducts=productsAdapter.GetProducts()
foreach(Northwind.ProductsRowproductinproducts)
if(!product.Discontinued&&product.UnitsInStock<=25)
product.UnitPrice*=2
//Updatetheproducts
productsAdapter.Update(products)
ThecodebelowillustrateshowtousetheDBdirectpatterntoprogrammaticallydeleteaparticularproduct,
thenupdateone,andthenaddanewone:
NorthwindTableAdapters.ProductsTableAdapterproductsAdapter=
newNorthwindTableAdapters.ProductsTableAdapter()
//DeletetheproductwithProductID3
productsAdapter.Delete(3)
//UpdateChai(ProductIDof1),settingtheUnitsOnOrderto15
productsAdapter.Update("Chai",1,1,"10boxesx20bags",
18.0m,39,15,10,false,1)
//Addanewproduct
productsAdapter.Insert("NewProduct",1,1,
"12tinspercarton",14.95m,15,0,10,false)
CreatingCustomInsert,Update,andDelete
Methods
TheInsert(),Update(),andDelete()methodscreatedbytheDBdirectmethodcanbeabitcumbersome,
especiallyfortableswithmanycolumns.Lookingatthepreviouscodeexample,withoutIntelliSense'shelpit's
notparticularlyclearwhatProductstablecolumnmapstoeachinputparametertotheUpdate()andInsert()
methods.Theremaybetimeswhenweonlywanttoupdateasinglecolumnortwo,orwantacustomized
Insert()methodthatwill,perhaps,returnthevalueofthenewlyinsertedrecord'sIDENTITY(autoincrement)
field.
Tocreatesuchacustommethod,returntotheDataSetDesigner.RightclickontheTableAdapterandchoose
AddQuery,returningtotheTableAdapterwizard.Onthesecondscreenwecanindicatethetypeofqueryto
create.Let'screateamethodthataddsanewproductandthenreturnsthevalueofthenewlyaddedrecord's
ProductID.Therefore,opttocreateanINSERTquery.
21 of34
Figure25:CreateaMethodtoAddaNewRowtotheProductsTable
OnthenextscreentheInsertCommand'sCommandTextappears.AugmentthisquerybyaddingSELECT
SCOPE_IDENTITY()attheendofthequery,whichwillreturnthelastidentityvalueinsertedintoanIDENTITY
columninthesamescope.(Seethetechnicaldocumentation formoreinformationaboutSCOPE_IDENTITY()
andwhyyouprobablywanttouseSCOPE_IDENTITY()inlieuof@@IDENTITY.)Makesurethatyouend
theINSERTstatementwithasemicolonbeforeaddingtheSELECTstatement.
Figure26:AugmenttheQuerytoReturntheSCOPE_IDENTITY()Value
22 of34
Finally,namethenewmethodInsertProduct.
Figure27:SettheNewMethodNametoInsertProduct
WhenyoureturntotheDataSetDesigneryou'llseethattheProductsTableAdaptercontainsanewmethod,
InsertProduct.Ifthisnewmethoddoesn'thaveaparameterforeachcolumnintheProductstable,chances
areyouforgottoterminatetheINSERTstatementwithasemicolon.ConfiguretheInsertProductmethodand
ensureyouhaveasemicolondelimitingtheINSERTandSELECTstatements.
Bydefault,insertmethodsissuenonquerymethods,meaningthattheyreturnthenumberofaffectedrows.
However,wewanttheInsertProductmethodtoreturnthevaluereturnedbythequery,notthenumberof
rowsaffected.Toaccomplishthis,adjusttheInsertProductmethod'sExecuteModepropertytoScalar.
23 of34
Figure28:ChangetheExecuteModePropertytoScalar
ThefollowingcodeshowsthisnewInsertProductmethodinaction:
NorthwindTableAdapters.ProductsTableAdapterproductsAdapter=
newNorthwindTableAdapters.ProductsTableAdapter()
//Addanewproduct
intnew_productID=Convert.ToInt32(productsAdapter.InsertProduct
("NewProduct",1,1,"12tinspercarton",14.95m,10,0,10,false))
//Onsecondthought,deletetheproduct
productsAdapter.Delete(new_productID)
Step5:CompletingtheDataAccessLayer
NotethattheProductsTableAdaptersclassreturnstheCategoryIDandSupplierIDvaluesfromthe
Productstable,butdoesn'tincludetheCategoryNamecolumnfromtheCategoriestableortheCompanyName
columnfromtheSupplierstable,althoughthesearelikelythecolumnswewanttodisplaywhenshowing
productinformation.WecanaugmenttheTableAdapter'sinitialmethod,GetProducts(),toincludeboththe
CategoryNameandCompanyNamecolumnvalues,whichwillupdatethestronglytypedDataTabletoinclude
thesenewcolumnsaswell.
Thiscanpresentaproblem,however,astheTableAdapter'smethodsforinserting,updating,anddeletingdata
arebasedoffofthisinitialmethod.Fortunately,theautogeneratedmethodsforinserting,updating,and
deletingarenotaffectedbysubqueriesintheSELECTclause.BytakingcaretoaddourqueriestoCategories
andSuppliersassubqueries,ratherthanJOINs,we'llavoidhavingtoreworkthosemethodsformodifying
data.RightclickontheGetProducts()methodintheProductsTableAdapterandchooseConfigure.Then,
adjusttheSELECTclausesothatitlookslike:
SELECTProductID,ProductName,SupplierID,CategoryID,
QuantityPerUnit,UnitPrice,UnitsInStock,UnitsOnOrder,ReorderLevel,Discontinued,
(SELECTCategoryNameFROMCategories
WHERECategories.CategoryID=Products.CategoryID)asCategoryName,
(SELECTCompanyNameFROMSuppliers
WHERESuppliers.SupplierID=Products.SupplierID)asSupplierName
FROMProducts
24 of34
Figure29:UpdatetheSELECTStatementfortheGetProducts()Method
AfterupdatingtheGetProducts()methodtousethisnewquerytheDataTablewillincludetwonewcolumns:
CategoryNameandSupplierName.
Figure30:TheProductsDataTablehasTwoNewColumns
TakeamomenttoupdatetheSELECTclauseintheGetProductsByCategoryID(categoryID)methodaswell.
IfyouupdatetheGetProducts()SELECTusingJOINsyntaxtheDataSetDesignerwon'tbeabletoauto
generatethemethodsforinserting,updating,anddeletingdatabasedatausingtheDBdirectpattern.Instead,
25 of34
you'llhavetomanuallycreatethemmuchlikewedidwiththeInsertProductmethodearlierinthistutorial.
Furthermore,you'llmanuallyhavetoprovidetheInsertCommand,UpdateCommand,andDeleteCommand
propertyvaluesifyouwanttousethebatchupdatingpattern.
AddingtheRemainingTableAdapters
Upuntilnow,we'veonlylookedatworkingwithasingleTableAdapterforasingledatabasetable.However,
theNorthwinddatabasecontainsseveralrelatedtablesthatwe'llneedtoworkwithinourwebapplication.A
TypedDataSetcancontainmultiple,relatedDataTables.Therefore,tocompleteourDALweneedtoadd
DataTablesfortheothertableswe'llbeusinginthesetutorials.ToaddanewTableAdaptertoaTypedDataSet,
opentheDataSetDesigner,rightclickintheDesigner,andchooseAdd/TableAdapter.Thiswillcreateanew
DataTableandTableAdapterandwalkyouthroughthewizardweexaminedearlierinthistutorial.
TakeafewminutestocreatethefollowingTableAdaptersandmethodsusingthefollowingqueries.Notethat
thequeriesintheProductsTableAdapterincludethesubqueriestograbeachproduct'scategoryandsupplier
names.Additionally,ifyou'vebeenfollowingalong,you'vealreadyaddedtheProductsTableAdapterclass's
GetProducts()andGetProductsByCategoryID(categoryID)methods.
l ProductsTableAdapter
GetProducts:
SELECTProductID,ProductName,SupplierID,CategoryID,
QuantityPerUnit,UnitPrice,UnitsInStock,UnitsOnOrder,
ReorderLevel,Discontinued,(SELECTCategoryNameFROM
CategoriesWHERECategories.CategoryID=
Products.CategoryID)asCategoryName,(SELECTCompanyName
FROMSuppliersWHERESuppliers.SupplierID=
Products.SupplierID)asSupplierName
FROMProducts
GetProductsByCategoryID:
SELECTProductID,ProductName,SupplierID,CategoryID,
QuantityPerUnit,UnitPrice,UnitsInStock,UnitsOnOrder,
ReorderLevel,Discontinued,(SELECTCategoryNameFROM
CategoriesWHERECategories.CategoryID=
Products.CategoryID)asCategoryName,
(SELECTCompanyNameFROMSuppliersWHERE
Suppliers.SupplierID=Products.SupplierID)asSupplierName
FROMProducts
WHERECategoryID=@CategoryID
GetProductsBySupplierID
SELECTProductID,ProductName,SupplierID,CategoryID,
QuantityPerUnit,UnitPrice,UnitsInStock,UnitsOnOrder,
ReorderLevel,Discontinued,
(SELECTCategoryNameFROMCategoriesWHERE
Categories.CategoryID=Products.CategoryID)
asCategoryName,(SELECTCompanyNameFROMSuppliers
WHERESuppliers.SupplierID=Products.SupplierID)
asSupplierName
FROMProducts
WHERESupplierID=@SupplierID
GetProductByProductID
SELECTProductID,ProductName,SupplierID,CategoryID,
26 of34
QuantityPerUnit,UnitPrice,UnitsInStock,UnitsOnOrder,
ReorderLevel,Discontinued,(SELECTCategoryName
FROMCategoriesWHERECategories.CategoryID=
Products.CategoryID)asCategoryName,
(SELECTCompanyNameFROMSuppliers
WHERESuppliers.SupplierID=Products.SupplierID)
asSupplierName
FROMProducts
WHEREProductID=@ProductID
l CategoriesTableAdapter
GetCategories
SELECTCategoryID,CategoryName,Description
FROMCategories
GetCategoryByCategoryID
SELECTCategoryID,CategoryName,Description
FROMCategories
WHERECategoryID=@CategoryID
l SuppliersTableAdapter
GetSuppliers
SELECTSupplierID,CompanyName,Address,City,
Country,Phone
FROMSuppliers
GetSuppliersByCountry
SELECTSupplierID,CompanyName,Address,
City,Country,Phone
FROMSuppliers
WHERECountry=@Country
GetSupplierBySupplierID
SELECTSupplierID,CompanyName,Address,
City,Country,Phone
FROMSuppliers
WHERESupplierID=@SupplierID
l EmployeesTableAdapter
GetEmployees
SELECTEmployeeID,LastName,FirstName,
Title,HireDate,ReportsTo,Country
FROMEmployees
GetEmployeesByManager
SELECTEmployeeID,LastName,FirstName,
Title,HireDate,ReportsTo,Country
FROMEmployees
WHEREReportsTo=@ManagerID
GetEmployeeByEmployeeID
SELECTEmployeeID,LastName,FirstName,
Title,HireDate,ReportsTo,Country
FROMEmployees
WHEREEmployeeID=@EmployeeID
27 of34
Figure31:TheDataSetDesignerAftertheFourTableAdaptersHaveBeenAdded
AddingCustomCodetotheDAL
TheTableAdaptersandDataTablesaddedtotheTypedDataSetareexpressedasanXMLSchemaDefinition
file(Northwind.xsd).YoucanviewthisschemainformationbyrightclickingontheNorthwind.xsdfilein
theSolutionExplorerandchoosingViewCode.
28 of34
Figure32:TheXMLSchemaDefinition(XSD)FilefortheNorthwindsTypedDataSet
ThisschemainformationistranslatedintoC#orVisualBasiccodeatdesigntimewhencompiledoratruntime
(ifneeded),atwhichpointyoucanstepthroughitwiththedebugger.Toviewthisautogeneratedcodegoto
theClassViewanddrilldowntotheTableAdapterorTypedDataSetclasses.Ifyoudon'tseetheClassViewon
yourscreen,gototheViewmenuandselectitfromthere,orhitCtrl+Shift+C.FromtheClassViewyoucan
seetheproperties,methods,andeventsoftheTypedDataSetandTableAdapterclasses.Toviewthecodefora
particularmethod,doubleclickthemethodnameintheClassVieworrightclickonitandchooseGoTo
Definition.
Figure33:InspecttheAutoGeneratedCodebySelectingGoToDefinitionfromtheClassView
Whileautogeneratedcodecanbeagreattimesaver,thecodeisoftenverygenericandneedstobecustomized
29 of34
tomeettheuniqueneedsofanapplication.Theriskofextendingautogeneratedcode,though,isthatthetool
thatgeneratedthecodemightdecideit'stimeto"regenerate"andoverwriteyourcustomizations.With.NET
2.0'snewpartialclassconcept,it'seasytosplitaclassacrossmultiplefiles.Thisenablesustoaddourown
methods,properties,andeventstotheautogeneratedclasseswithouthavingtoworryaboutVisualStudio
overwritingourcustomizations.
TodemonstratehowtocustomizetheDAL,let'saddaGetProducts()methodtotheSuppliersRowclass.The
SuppliersRowclassrepresentsasinglerecordintheSupplierstableeachsuppliercanproviderzerotomany
products,soGetProducts()willreturnthoseproductsofthespecifiedsupplier.Toaccomplishthiscreatea
newclassfileintheApp_CodefoldernamedSuppliersRow.csandaddthefollowingcode:
usingSystem
usingSystem.Data
usingNorthwindTableAdapters
publicpartialclassNorthwind
{
publicpartialclassSuppliersRow
{
publicNorthwind.ProductsDataTableGetProducts()
{
ProductsTableAdapterproductsAdapter=
newProductsTableAdapter()
return
productsAdapter.GetProductsBySupplierID(this.SupplierID)
}
}
}
ThispartialclassinstructsthecompilerthatwhenbuildingtheNorthwind.SuppliersRowclasstoincludethe
GetProducts()methodwejustdefined.IfyoubuildyourprojectandthenreturntotheClassViewyou'llsee
GetProducts()nowlistedasamethodofNorthwind.SuppliersRow.
30 of34
Figure34:TheGetProducts()MethodisNowPartoftheNorthwind.SuppliersRowClass
TheGetProducts()methodcannowbeusedtoenumeratethesetofproductsforaparticularsupplier,asthe
followingcodeshows:
NorthwindTableAdapters.SuppliersTableAdaptersuppliersAdapter=
newNorthwindTableAdapters.SuppliersTableAdapter()
//Getallofthesuppliers
Northwind.SuppliersDataTablesuppliers=
suppliersAdapter.GetSuppliers()
//Enumeratethesuppliers
foreach(Northwind.SuppliersRowsupplierinsuppliers)
{
Response.Write("Supplier:"+supplier.CompanyName)
Response.Write("<ul>")
//Listtheproductsforthissupplier
Northwind.ProductsDataTableproducts=supplier.GetProducts()
foreach(Northwind.ProductsRowproductinproducts)
Response.Write("<li>"+product.ProductName+"</li>")
Response.Write("</ul><p></p>")
}
ThisdatacanalsobedisplayedinanyofASP.NET'sdataWebcontrols.ThefollowingpageusesaGridView
controlwithtwofields:
l ABoundFieldthatdisplaysthenameofeachsupplier,and
l ATemplateFieldthatcontainsaBulletedListcontrolthatisboundtotheresultsreturnedbythe
GetProducts()methodforeachsupplier.
We'llexaminehowtodisplaysuchmasterdetailreportsinfuturetutorials.Fornow,thisexampleisdesignedto
illustrateusingthecustommethodaddedtotheNorthwind.SuppliersRowclass.
SuppliersAndProducts.aspx
<%@PageLanguage="C#"CodeFile="SuppliersAndProducts.aspx.cs"
AutoEventWireup="true"Inherits="SuppliersAndProducts"%>
<!DOCTYPEhtmlPUBLIC"//W3C//DTDXHTML1.0Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1transitional.dtd">
<htmlxmlns="http://www.w3.org/1999/xhtml">
<headrunat="server">
<title>UntitledPage</title>
<linkhref="Styles.css"rel="stylesheet"type="text/css"/>
</head>
<body>
<formid="form1"runat="server">
<div>
<h1>
SuppliersandTheirProducts</h1>
<p>
<asp:GridViewID="GridView1"runat="server"
AutoGenerateColumns="False"
CssClass="DataWebControlStyle">
<HeaderStyleCssClass="HeaderStyle"/>
<AlternatingRowStyleCssClass="AlternatingRowStyle"/>
31 of34
<Columns>
<asp:BoundFieldDataField="CompanyName"
HeaderText="Supplier"/>
<asp:TemplateFieldHeaderText="Products">
<ItemTemplate>
<asp:BulletedListID="BulletedList1"
runat="server"DataSource="<%#
((Northwind.SuppliersRow)((System.Data.DataRowView)
Container.DataItem).Row).GetProducts()%>"
DataTextField="ProductName">
</asp:BulletedList>
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
</p>
</div>
</form>
</body>
</html>
SuppliersAndProducts.aspx.cs
usingSystem
usingSystem.Data
usingSystem.Configuration
usingSystem.Collections
usingSystem.Web
usingSystem.Web.Security
usingSystem.Web.UI
usingSystem.Web.UI.WebControls
usingSystem.Web.UI.WebControls.WebParts
usingSystem.Web.UI.HtmlControls
usingNorthwindTableAdapters
publicpartialclassSuppliersAndProducts:System.Web.UI.Page
{
protectedvoidPage_Load(objectsender,EventArgse)
{
SuppliersTableAdaptersuppliersAdapter=new
SuppliersTableAdapter()
GridView1.DataSource=suppliersAdapter.GetSuppliers()
GridView1.DataBind()
}
}
32 of34
Figure35:TheSupplier'sCompanyNameisListedintheLeftColumn,TheirProductsintheRight
Summary
WhenbuildingawebapplicationcreatingtheDALshouldbeoneofyourfirststeps,occurringbeforeyoustart
creatingyourpresentationlayer.WithVisualStudio,creatingaDALbasedonTypedDataSetsisataskthatcan
beaccomplishedin1015minuteswithoutwritingalineofcode.Thetutorialsmovingforwardwillbuildupon
thisDAL.Inthenexttutorial we'lldefineanumberofbusinessrulesandseehowtoimplementthemina
separateBusinessLogicLayer.
HappyProgramming!
FurtherReading
Formoreinformationonthetopicsdiscussedinthistutorial,refertothefollowingresources:
l BuildingaDALusingStronglyTypedTableAdaptersandDataTablesinVS2005andASP.NET2.0
l DesigningDataTierComponentsandPassingDataThroughTiers
l BuildaDataAccessLayerwiththeVisualStudio2005DataSetDesigner
l EncryptingConfigurationInformationinASP.NET2.0Applications
l TableAdapterOverview
l WorkingwithaTypedDataSet
l UsingStronglyTypedDataAccessinVisualStudio2005andASP.NET2.0
l HowtoExtendTableAdapterMethods
33 of34
l RetrievingScalarDatafromaStoredProcedure
AbouttheAuthor
ScottMitchell,authorofsixASP/ASP.NETbooksandfounderof4GuysFromRolla.com,hasbeenworking
withMicrosoftWebtechnologiessince1998.Scottworksasanindependentconsultant,trainer,andwriter,
recentlycompletinghislatestbook, SamsTeachYourselfASP.NET2.0in24Hours.Hecanbereachedat
mitchell@4guysfromrolla.comorviahisblog,whichcanbefoundathttp://ScottOnWriting.NET.
SpecialThanksTo
Thistutorialserieswasreviewedbymanyhelpfulreviewers.LeadreviewersforthistutorialincludeRon
Green,HiltonGiesenow,DennisPatterson,LizShulok,AbelGomez,andCarlosSantos.Interestedin
reviewingmyupcomingMSDNarticles?Ifso,dropmealineatmitchell@4GuysFromRolla.com.
34 of34

You might also like