table of contents · pdf filevery last page of the book contains version information and a...

127

Upload: dodung

Post on 22-Feb-2018

230 views

Category:

Documents


15 download

TRANSCRIPT

Page 1: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the
Page 2: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

1.1

1.2

1.2.1

1.2.2

1.2.3

1.3

1.3.1

1.3.2

1.3.3

1.3.4

1.3.5

1.3.6

1.3.7

1.4

1.5

1.5.1

1.5.2

1.5.3

1.5.4

1.6

1.6.1

1.6.2

1.7

TableofContentsIntroduction

Yourfirstapplication

GettheSDK

HelloWorldinC#

CreateanASP.NETCoreproject

MVCbasics

Createacontroller

Createmodels

Createaview

Addaserviceclass

Usedependencyinjection

Finishthecontroller

Updatethelayout

Addexternalpackages

Useadatabase

Connecttoadatabase

Updatethecontext

Createamigration

Createanewserviceclass

Addmorefeatures

Addnewto-doitems

Completeitemswithacheckbox

Securityandidentity

2

Page 3: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

1.7.1

1.7.2

1.7.3

1.7.4

1.8

1.8.1

1.8.2

1.9

1.9.1

1.9.2

1.10

Requireauthentication

Usingidentityintheapplication

Authorizationwithroles

Moreresources

Automatedtesting

Unittesting

Integrationtesting

Deploytheapplication

DeploytoAzure

DeploywithDocker

Conclusion

3

Page 4: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

TheLittleASP.NETCoreBookbyNateBarbettini

Copyright©2018.Allrightsreserved.

ISBN:978-1-387-75615-5

ReleasedundertheCreativeCommonsAttribution4.0license.Youarefreetoshare,copy,andredistributethisbookinanyformat,orremixandtransformitforanypurpose(evencommercially).Youmustgiveappropriatecreditandprovidealinktothelicense.

Formoreinformation,visithttps://creativecommons.org/licenses/by/4.0/

IntroductionThanksforpickingupTheLittleASP.NETCoreBook!IwrotethisshortbooktohelpdevelopersandpeopleinterestedinwebprogramminglearnaboutASP.NETCore,anewframeworkforbuildingwebapplicationsandAPIs.

TheLittleASP.NETCoreBookisstructuredasatutorial.You'llbuildanapplicationfromstarttofinishandlearn:

ThebasicsoftheMVC(Model-View-Controller)patternHowfront-endcode(HTML,CSS,JavaScript)workstogetherwithback-endcodeWhatdependencyinjectionisandwhyit'susefulHowtoreadandwritedatatoadatabaseHowtoaddlog-in,registration,andsecurityHowtodeploytheapplicationtotheweb

Introduction

4

Page 5: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

Don'tworry,youdon'tneedtoknowanythingaboutASP.NETCore(oranyoftheabove)togetstarted.

BeforeyoubeginThecodeforthefinishedversionoftheapplicationyou'llbuildisavailableonGitHub:

https://www.github.com/nbarbettini/little-aspnetcore-todo

Feelfreetodownloaditifyouwanttoseethefinishedproduct,orcompareasyouwriteyourowncode.

Thebookitselfisupdatedfrequentlywithbugfixesandnewcontent.Ifyou'rereadingaPDF,e-book,orprintversion,checktheofficialwebsite(littleasp.net/book)toseeifthere'sanupdatedversionavailable.Theverylastpageofthebookcontainsversioninformationandachangelog.

Readinginyourownlanguage

Thankstosomefantasticmultilingualcontributors,theLittleASP.NETCoreBookhasbeentranslatedintootherlanguages:

Turkish:https://sahinyanlik.gitbooks.io/kisa-asp-net-core-kitabi/

Chinese:https://windsting.github.io/little-aspnetcore-book/book/

WhothisbookisforIfyou'renewtoprogramming,thisbookwillintroduceyoutothepatternsandconceptsusedtobuildmodernwebapplications.You'lllearnhowtobuildawebapp(andhowthebigpiecesfittogether)by

Introduction

5

Page 6: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

buildingsomethingfromscratch!Whilethislittlebookwon'tbeabletocoverabsolutelyeverythingyouneedtoknowaboutprogramming,it'llgiveyouastartingpointsoyoucanlearnmoreadvancedtopics.

IfyoualreadycodeinabackendlanguagelikeNode,Python,Ruby,Go,orJava,you'llnoticealotoffamiliarideaslikeMVC,viewtemplates,anddependencyinjection.ThecodewillbeinC#,butitwon'tlooktoodifferentfromwhatyoualreadyknow.

Ifyou'reanASP.NETMVCdeveloper,you'llfeelrightathome!ASP.NETCoreaddssomenewtoolsandreuses(andsimplifies)thethingsyoualreadyknow.I'llpointoutsomeofthedifferencesbelow.

Nomatterwhatyourpreviousexperiencewithwebprogramming,thisbookwillteachyoueverythingyouneedtocreateasimpleandusefulwebapplicationinASP.NETCore.You'lllearnhowtobuildfunctionalityusingbackendandfrontendcode,howtointeractwithadatabase,andhowtodeploytheapptotheworld.

WhatisASP.NETCore?ASP.NETCoreisawebframeworkcreatedbyMicrosoftforbuildingwebapplications,APIs,andmicroservices.ItusescommonpatternslikeMVC(Model-View-Controller),dependencyinjection,andarequestpipelinecomprisedofmiddleware.It'sopen-sourceundertheApache2.0license,whichmeansthesourcecodeisfreelyavailable,andthecommunityisencouragedtocontributebugfixesandnewfeatures.

ASP.NETCorerunsontopofMicrosoft's.NETruntime,similartotheJavaVirtualMachine(JVM)ortheRubyinterpreter.YoucanwriteASP.NETCoreapplicationsinanumberoflanguages(C#,VisualBasic,F#).C#isthemostpopularchoice,andit'swhatI'lluseinthisbook.YoucanbuildandrunASP.NETCoreapplicationsonWindows,Mac,andLinux.

Introduction

6

Page 7: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

Whydoweneedanotherwebframework?Therearealotofgreatwebframeworkstochoosefromalready:Node/Express,Spring,RubyonRails,Django,Laravel,andmanymore.WhatadvantagesdoesASP.NETCorehave?

Speed.ASP.NETCoreisfast.Because.NETcodeiscompiled,itexecutesmuchfasterthancodeininterpretedlanguageslikeJavaScriptorRuby.ASP.NETCoreisalsooptimizedformultithreadingandasynchronoustasks.It'scommontoseea5-10xspeedimprovementovercodewritteninNode.js.

Ecosystem.ASP.NETCoremaybenew,but.NEThasbeenaroundforalongtime.TherearethousandsofpackagesavailableonNuGet(the.NETpackagemanager;thinknpm,Rubygems,orMaven).TherearealreadypackagesavailableforJSONdeserialization,databaseconnectors,PDFgeneration,oralmostanythingelseyoucanthinkof.

Security.TheteamatMicrosofttakessecurityseriously,andASP.NETCoreisbuilttobesecurefromthegroundup.Ithandlesthingslikesanitizinginputdataandpreventingcross-siterequestforgery(CSRF)attacks,soyoudon'thaveto.Youalsogetthebenefitofstatictypingwiththe.NETcompiler,whichislikehavingaveryparanoidlinterturnedonatalltimes.Thismakesithardertodosomethingyoudidn'tintendwithavariableorchunkofdata.

.NETCoreand.NETStandardThroughoutthisbook,you'llbelearningaboutASP.NETCore(thewebframework).I'lloccasionallymentionthe.NETruntime,thesupportinglibrarythatruns.NETcode.IfthisalreadysoundslikeGreektoyou,just

Introduction

7

Page 8: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

skiptothenextchapter!

Youmayalsohearabout.NETCoreand.NETStandard.Thenaminggetsconfusing,sohere'sasimpleexplanation:

.NETStandardisaplatform-agnosticinterfacethatdefinesfeaturesandAPIs.It'simportanttonotethat.NETStandarddoesn'trepresentanyactualcodeorfunctionality,justtheAPIdefinition.Therearedifferent"versions"orlevelsof.NETStandardthatreflecthowmanyAPIsareavailable(orhowwidetheAPIsurfaceareais).Forexample,.NETStandard2.0hasmoreAPIsavailablethan.NETStandard1.5,whichhasmoreAPIsthan.NETStandard1.0.

.NETCoreisthe.NETruntimethatcanbeinstalledonWindows,Mac,orLinux.ItimplementstheAPIsdefinedinthe.NETStandardinterfacewiththeappropriateplatform-specificcodeoneachoperatingsystem.Thisiswhatyou'llinstallonyourownmachinetobuildandrunASP.NETCoreapplications.

Andjustforgoodmeasure,.NETFrameworkisadifferentimplementationof.NETStandardthatisWindows-only.Thiswastheonly.NETruntimeuntil.NETCorecamealongandbrought.NETtoMacandLinux.ASP.NETCorecanalsorunonWindows-only.NETFramework,butIwon'ttouchonthistoomuch.

Ifyou'reconfusedbyallthisnaming,noworries!We'llgettosomerealcodeinabit.

AnotetoASP.NET4developersIfyouhaven'tusedapreviousversionofASP.NET,skipaheadtothenextchapter.

Introduction

8

Page 9: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

ASP.NETCoreisacompleteground-uprewriteofASP.NET,withafocusonmodernizingtheframeworkandfinallydecouplingitfromSystem.Web,IIS,andWindows.IfyourememberalltheOWIN/KatanastufffromASP.NET4,you'realreadyhalfwaythere:theKatanaprojectbecameASP.NET5whichwasultimatelyrenamedtoASP.NETCore.

BecauseoftheKatanalegacy,theStartupclassisfrontandcenter,andthere'snomoreApplication_StartorGlobal.asax.Theentirepipelineisdrivenbymiddleware,andthere'snolongerasplitbetweenMVCandWebAPI:controllerscansimplyreturnviews,statuscodes,ordata.Dependencyinjectioncomesbakedin,soyoudon'tneedtoinstallandconfigureacontainerlikeStructureMaporNinjectifyoudon'twantto.Andtheentireframeworkhasbeenoptimizedforspeedandruntimeefficiency.

Alright,enoughintroduction.Let'sdiveintoASP.NETCore!

Introduction

9

Page 10: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

YourfirstapplicationReadytobuildyourfirstwebappwithASP.NETCore?You'llneedtogatherafewthingsfirst:

Yourfavoritecodeeditor.YoucanuseAtom,Sublime,Notepad,orwhatevereditoryoupreferwritingcodein.Ifyoudon'thaveafavorite,giveVisualStudioCodeatry.It'safree,cross-platformcodeeditorthathasrichsupportforwritingC#,JavaScript,HTML,andmore.Justsearchfor"downloadvisualstudiocode"andfollowtheinstructions.

Ifyou'reonWindows,youcanalsouseVisualStudiotobuildASP.NETCoreapplications.You'llneedVisualStudio2017version15.3orlater(thefreeCommunityEditionisfine).VisualStudiohasgreatcodecompletionandrefactoringsupportforC#,althoughVisualStudioCodeisclosebehind.

The.NETCoreSDK.Regardlessoftheeditororplatformyou'reusing,you'llneedtoinstallthe.NETCoreSDK,whichincludestheruntime,baselibraries,andcommandlinetoolsyouneedforbuildingASP.NETCoreapplications.TheSDKcanbeinstalledonWindows,Mac,orLinux.

Onceyou'vedecidedonaneditor,you'llneedtogettheSDK.

Yourfirstapplication

10

Page 11: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

GettheSDKSearchfor"download.netcore"andfollowtheinstructionsonMicrosoft'sdownloadpagetogetthe.NETCoreSDK.AftertheSDKhasfinishedinstalling,openuptheTerminal(orPowerShellonWindows)andusethedotnetcommandlinetool(alsocalledaCLI)tomakesureeverythingisworking:

dotnet--version

2.1.104

Youcangetmoreinformationaboutyourplatformwiththe--infoflag:

dotnet--info

.NETCommandLineTools(2.1.104)

ProductInformation:

Version:2.1.104

CommitSHA-1hash:48ec687460

RuntimeEnvironment:

OSName:MacOSX

OSVersion:10.13

(moredetails...)

Ifyouseeoutputliketheabove,you'rereadytogo!

GettheSDK

11

Page 12: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

HelloWorldinC#BeforeyoudiveintoASP.NETCore,trycreatingandrunningasimpleC#application.

Youcandothisallfromthecommandline.First,openuptheTerminal(orPowerShellonWindows).Navigatetothelocationyouwanttostoreyourprojects,suchasyourDocumentsdirectory:

cdDocuments

Usethedotnetcommandtocreateanewproject:

dotnetnewconsole-oCsharpHelloWorld

Thedotnetnewcommandcreatesanew.NETprojectinC#bydefault.Theconsoleparameterselectsatemplateforaconsoleapplication(aprogramthatoutputstexttothescreen).The-oCsharpHelloWorldparametertellsdotnetnewtocreateanewdirectorycalledCsharpHelloWorldforalltheprojectfiles.Moveintothisnewdirectory:

cdCsharpHelloWorld

dotnetnewconsolecreatesabasicC#programthatwritesthetextHelloWorld!tothescreen.Theprogramiscomprisedoftwofiles:aprojectfile(witha.csprojextension)andaC#codefile(witha.csextension).Ifyouopentheformerinatextorcodeeditor,you'llseethis:

CsharpHelloWorld.csproj

<ProjectSdk="Microsoft.NET.Sdk">

HelloWorldinC#

12

Page 13: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

<PropertyGroup>

<OutputType>Exe</OutputType>

<TargetFramework>netcoreapp2.0</TargetFramework>

</PropertyGroup>

</Project>

TheprojectfileisXML-basedanddefinessomemetadataabouttheproject.Later,whenyoureferenceotherpackages,thosewillbelistedhere(similartoapackage.jsonfilefornpm).Youwon'thavetoeditthisfilebyhandveryoften.

Program.cs

usingSystem;

namespaceCsharpHelloWorld

{

classProgram

{

staticvoidMain(string[]args)

{

Console.WriteLine("HelloWorld!");

}

}

}

staticvoidMainistheentrypointmethodofaC#program,andbyconventionit'splacedinaclass(atypeofcodestructureormodule)calledProgram.Theusingstatementatthetopimportsthebuilt-inSystemclassesfrom.NETandmakesthemavailabletothecodeinyourclass.

Frominsidetheprojectdirectory,usedotnetruntoruntheprogram.You'llseetheoutputwrittentotheconsoleafterthecodecompiles:

dotnetrun

HelloWorldinC#

13

Page 14: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

HelloWorld!

That'sallittakestoscaffoldandruna.NETprogram!Next,you'lldothesamethingforanASP.NETCoreapplication.

HelloWorldinC#

14

Page 15: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

CreateanASP.NETCoreprojectIfyou'restillinthedirectoryyoucreatedfortheHelloWorldsample,movebackuptoyourDocumentsorhomedirectory:

cd..

Next,createanewdirectorytostoreyourentireproject,andmoveintoit:

mkdirAspNetCoreTodo

cdAspNetCoreTodo

Next,createanewprojectwithdotnetnew,thistimewithsomeextraoptions:

dotnetnewmvc--authIndividual-oAspNetCoreTodo

cdAspNetCoreTodo

Thiscreatesanewprojectfromthemvctemplate,andaddssomeadditionalauthenticationandsecuritybitstotheproject.(I'llcoversecurityintheSecurityandidentitychapter.)

YoumightbewonderingwhyyouhaveadirectorycalledAspNetCoreTodoinsideanotherdirectorycalledAspNetCoreTodo.Thetop-levelor"root"directorycancontainoneormoreprojectdirectories.Therootdirectoryissometimescalledasolutiondirectory.Later,you'lladdmoreprojectdirectoriesside-by-sidewiththeAspNetCoreTodoprojectdirectory,allwithinasinglerootsolutiondirectory.

CreateanASP.NETCoreproject

15

Page 16: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

You'llseequiteafewfilesshowupinthenewprojectdirectory.Onceyoucdintothenewdirectory,allyouhavetodoisruntheproject:

dotnetrun

Nowlisteningon:http://localhost:5000

Applicationstarted.PressCtrl+Ctoshutdown.

Insteadofprintingtotheconsoleandexiting,thisprogramstartsawebserverandwaitsforrequestsonport5000.

Openyourwebbrowserandnavigatetohttp://localhost:5000.You'llseethedefaultASP.NETCoresplashpage,whichmeansyourprojectisworking!Whenyou'redone,pressCtrl-Cintheterminalwindowtostoptheserver.

ThepartsofanASP.NETCoreproject

Thedotnetnewmvctemplategeneratesanumberoffilesanddirectoriesforyou.Herearethemostimportantthingsyougetoutofthebox:

TheProgram.csandStartup.csfilessetupthewebserverandASP.NETCorepipeline.TheStartupclassiswhereyoucanaddmiddlewarethathandlesandmodifiesincomingrequests,andservesthingslikestaticcontentorerrorpages.It'salsowhereyouaddyourownservicestothedependencyinjectioncontainer(moreonthislater).

TheModels,Views,andControllersdirectoriescontainthecomponentsoftheModel-View-Controller(MVC)architecture.You'llexploreallthreeinthenextchapter.

CreateanASP.NETCoreproject

16

Page 17: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

ThewwwrootdirectorycontainsstaticassetslikeCSS,JavaScript,andimagefiles.Filesinwwwrootwillbeservedasstaticcontent,andcanbebundledandminifiedautomatically.

Theappsettings.jsonfilecontainsconfigurationsettingsASP.NETCorewillloadonstartup.Youcanusethistostoredatabaseconnectionstringsorotherthingsthatyoudon'twanttohard-code.

TipsforVisualStudioCode

Ifyou'reusingVisualStudioCodeforthefirsttime,hereareacoupleofhelpfultipstogetyoustarted:

Opentheprojectrootfolder:InVisualStudioCode,chooseFile-OpenorFile-OpenFolder.OpentheAspNetCoreTodofolder(therootdirectory),nottheinnerprojectdirectory.IfVisualStudioCodepromptsyoutoinstallmissingfiles,clickYestoaddthem.

F5torun(anddebugbreakpoints):Withyourprojectopen,pressF5toruntheprojectindebugmode.Thisisthesameasdotnetrunonthecommandline,butyouhavethebenefitofsettingbreakpointsinyourcodebyclickingontheleftmargin:

Lightbulbtofixproblems:Ifyourcodecontainsredsquiggles(compilererrors),putyourcursoronthecodethat'sredandlookforthelightbulbiconontheleftmargin.Thelightbulbmenuwillsuggest

CreateanASP.NETCoreproject

17

Page 18: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

commonfixes,likeaddingamissingusingstatementtoyourcode:

Compilequickly:UsetheshortcutCommand-Shift-BorControl-Shift-BtoruntheBuildtask,whichdoesthesamethingasdotnetbuild.

ThesetipsapplytoVisualStudio(notCode)onWindowstoo.Ifyou'reusingVisualStudio,you'llneedtoopenthe.csprojprojectfiledirectly.VisualStudiowilllaterpromptyoutosavetheSolutionfile,whichyoushouldsaveintherootdirectory(thefirstAspNetCoreTodofolder).YoucanalsocreateanASP.NETCoreprojectdirectlywithinVisualStudiousingthetemplatesinFile-NewProject.

AnoteaboutGit

IfyouuseGitorGitHubtomanageyoursourcecode,nowisagoodtimetodogitinitandinitializeaGitrepositoryintheprojectrootdirectory:

cd..

gitinit

Makesureyouadda.gitignorefilethatignoresthebinandobjdirectories.TheVisualStudiotemplateonGitHub'sgitignoretemplaterepo(https://github.com/github/gitignore)worksgreat.

CreateanASP.NETCoreproject

18

Page 19: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

There'splentymoretoexplore,solet'sdiveinandstartbuildinganapplication!

CreateanASP.NETCoreproject

19

Page 20: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

MVCbasicsInthischapter,you'llexploretheMVCsysteminASP.NETCore.MVC(Model-View-Controller)isapatternforbuildingwebapplicationsthat'susedinalmosteverywebframework(RubyonRailsandExpressarepopularexamples),plusfrontendJavaScriptframeworkslikeAngular.MobileappsoniOSandAndroiduseavariationofMVCaswell.

Asthenamesuggests,MVChasthreecomponents:models,views,andcontrollers.Controllershandleincomingrequestsfromaclientorwebbrowserandmakedecisionsaboutwhatcodetorun.Viewsaretemplates(usuallyHTMLplusatemplatinglanguagelikeHandlebars,Pug,orRazor)thatgetdataaddedtothemandthenaredisplayedtotheuser.Modelsholdthedatathatisaddedtoviews,ordatathatisenteredbytheuser.

AcommonpatternforMVCcodeis:

ThecontrollerreceivesarequestandlooksupsomeinformationinadatabaseThecontrollercreatesamodelwiththeinformationandattachesittoaviewTheviewisrenderedanddisplayedintheuser'sbrowserTheuserclicksabuttonorsubmitsaform,whichsendsanewrequesttothecontroller,andthecyclerepeats

Ifyou'veworkedwithMVCinotherlanguages,you'llfeelrightathomeinASP.NETCoreMVC.Ifyou'renewtoMVC,thischapterwillteachyouthebasicsandwillhelpgetyoustarted.

Whatyou'llbuild

MVCbasics

20

Page 21: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

The"HelloWorld"exerciseofMVCisbuildingato-dolistapplication.It'sagreatprojectsinceit'ssmallandsimpleinscope,butittoucheseachpartofMVCandcoversmanyoftheconceptsyou'duseinalargerapplication.

Inthisbook,you'llbuildato-doappthatletstheuseradditemstotheirto-dolistandcheckthemoffoncecomplete.Morespecifically,you'llbecreating:

Awebapplicationserver(sometimescalledthe"backend")usingASP.NETCore,C#,andtheMVCpatternAdatabasetostoretheuser'sto-doitemsusingtheSQLitedatabaseengineandasystemcalledEntityFrameworkCoreWebpagesandaninterfacethattheuserwillinteractwithviatheirbrowser,usingHTML,CSS,andJavaScript(calledthe"frontend")Aloginformandsecuritycheckssoeachuser'sto-dolistiskeptprivate

Soundgood?Let'sbuiltit!Ifyouhaven'talreadycreatedanewASP.NETCoreprojectusingdotnetnewmvc,followthestepsinthepreviouschapter.Youshouldbeabletobuildandruntheprojectandseethedefaultwelcomescreen.

MVCbasics

21

Page 22: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

CreateacontrollerTherearealreadyafewcontrollersintheproject'sControllersdirectory,includingtheHomeControllerthatrendersthedefaultwelcomescreenyouseewhenyouvisithttp://localhost:5000.Youcanignorethesecontrollersfornow.

Createanewcontrollerfortheto-dolistfunctionality,calledTodoController,andaddthefollowingcode:

Controllers/TodoController.cs

usingSystem;

usingSystem.Collections.Generic;

usingSystem.Linq;

usingSystem.Threading.Tasks;

usingMicrosoft.AspNetCore.Mvc;

namespaceAspNetCoreTodo.Controllers

{

publicclassTodoController:Controller

{

//Actionsgohere

}

}

Routesthatarehandledbycontrollersarecalledactions,andarerepresentedbymethodsinthecontrollerclass.Forexample,theHomeControllerincludesthreeactionmethods(Index,About,andContact)whicharemappedbyASP.NETCoretotheserouteURLs:

localhost:5000/Home->Index()

localhost:5000/Home/About->About()

localhost:5000/Home/Contact->Contact()

Createacontroller

22

Page 23: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

Thereareanumberofconventions(commonpatterns)usedbyASP.NETCore,suchasthepatternthatFooControllerbecomes/Foo,andtheIndexactionnamecanbeleftoutoftheURL.Youcancustomizethisbehaviorifyou'dlike,butfornow,we'llsticktothedefaultconventions.

AddanewactioncalledIndextotheTodoController,replacingthe//Actionsgoherecomment:

publicclassTodoController:Controller

{

publicIActionResultIndex()

{

//Getto-doitemsfromdatabase

//Putitemsintoamodel

//Renderviewusingthemodel

}

}

Actionmethodscanreturnviews,JSONdata,orHTTPstatuscodeslike200OKand404NotFound.TheIActionResultreturntypegivesyoutheflexibilitytoreturnanyofthesefromtheaction.

It'sabestpracticetokeepcontrollersaslightweightaspossible.Inthiscase,thecontrollerwillberesponsibleforgettingtheto-doitemsfromthedatabase,puttingthoseitemsintoamodeltheviewcanunderstand,andsendingtheviewbacktotheuser'sbrowser.

Beforeyoucanwritetherestofthecontrollercode,youneedtocreateamodelandaview.

Createacontroller

23

Page 24: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

CreatemodelsTherearetwoseparatemodelclassesthatneedtobecreated:amodelthatrepresentsato-doitemstoredinthedatabase(sometimescalledanentity),andthemodelthatwillbecombinedwithaview(theMVinMVC)andsentbacktotheuser'sbrowser.Becausebothofthemcanbereferredtoas"models",I'llrefertothelatterasaviewmodel.

First,createaclasscalledTodoItemintheModelsdirectory:

Models/TodoItem.cs

usingSystem;

usingSystem.ComponentModel.DataAnnotations;

namespaceAspNetCoreTodo.Models

{

publicclassTodoItem

{

publicGuidId{get;set;}

publicboolIsDone{get;set;}

[Required]

publicstringTitle{get;set;}

publicDateTimeOffset?DueAt{get;set;}

}

}

Thisclassdefineswhatthedatabasewillneedtostoreforeachto-doitem:anID,atitleorname,whethertheitemiscomplete,andwhattheduedateis.Eachlinedefinesapropertyoftheclass:

Createmodels

24

Page 25: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

TheIdpropertyisaguid,oragloballyuniqueidentifier.Guids(orGUIDs)arelongstringsoflettersandnumbers,like43ec09f2-7f70-4f4b-9559-65011d5781bb.Becauseguidsarerandomandareextremelyunlikelytobeaccidentallyduplicated,theyarecommonlyusedasuniqueIDs.Youcouldalsouseanumber(integer)asadatabaseentityID,butyou'dneedtoconfigureyourdatabasetoalwaysincrementthenumberwhennewrowsareaddedtothedatabase.Guidsaregeneratedrandomly,soyoudon'thavetoworryaboutauto-incrementing.

TheIsDonepropertyisaboolean(true/falsevalue).Bydefault,itwillbefalseforallnewitems.Lateryou'llusewritecodetoswitchthispropertytotruewhentheuserclicksanitem'scheckboxintheview.

TheTitlepropertyisastring(textvalue).Thiswillholdthenameordescriptionoftheto-doitem.The[Required]attributetellsASP.NETCorethatthisstringcan'tbenullorempty.

TheDueAtpropertyisaDateTimeOffset,whichisaC#typethatstoresadate/timestampalongwithatimezoneoffsetfromUTC.Storingthedate,time,andtimezoneoffsettogethermakesiteasytorenderdatesaccuratelyonsystemsindifferenttimezones.

Noticethe?questionmarkaftertheDateTimeOffsettype?ThatmarkstheDueAtpropertyasnullable,oroptional.Ifthe?wasn'tincluded,everyto-doitemwouldneedtohaveaduedate.TheIdandIsDonepropertiesaren'tmarkedasnullable,sotheyarerequiredandwillalwayshaveavalue(oradefaultvalue).

StringsinC#arealwaysnullable,sothere'snoneedtomarktheTitlepropertyasnullable.C#stringscanbenull,empty,orcontaintext.

Createmodels

25

Page 26: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

Eachpropertyisfollowedbyget;set;,whichisashorthandwayofsayingthepropertyisread/write(or,moretechnically,ithasagetterandsettermethods).

Atthispoint,itdoesn'tmatterwhattheunderlyingdatabasetechnologyis.ItcouldbeSQLServer,MySQL,MongoDB,Redis,orsomethingmoreexotic.ThismodeldefineswhatthedatabaseroworentrywilllooklikeinC#soyoudon'thavetoworryaboutthelow-leveldatabasestuffinyourcode.Thissimplestyleofmodelissometimescalleda"plainoldC#object"orPOCO.

Theviewmodel

Often,themodel(entity)youstoreinthedatabaseissimilarbutnotexactlythesameasthemodelyouwanttouseinMVC(theviewmodel).Inthiscase,theTodoItemmodelrepresentsasingleiteminthedatabase,buttheviewmightneedtodisplaytwo,ten,orahundredto-doitems(dependingonhowbadlytheuserisprocrastinating).

Becauseofthis,theviewmodelshouldbeaseparateclassthatholdsanarrayofTodoItems:

Models/TodoViewModel.cs

namespaceAspNetCoreTodo.Models

{

publicclassTodoViewModel

{

publicTodoItem[]Items{get;set;}

}

}

Nowthatyouhavesomemodels,it'stimetocreateaviewthatwilltakeaTodoViewModelandrendertherightHTMLtoshowtheusertheirto-dolist.

Createmodels

26

Page 27: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

Createmodels

27

Page 28: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

CreateaviewViewsinASP.NETCorearebuiltusingtheRazortemplatinglanguage,whichcombinesHTMLandC#code.(Ifyou'vewrittenpagesusingHandlebarsmoustaches,ERBinRubyonRails,orThymeleafinJava,you'vealreadygotthebasicidea.)

MostviewcodeisjustHTML,withtheoccasionalC#statementaddedintopulldataoutoftheviewmodelandturnitintotextorHTML.TheC#statementsareprefixedwiththe@symbol.

TheviewrenderedbytheIndexactionoftheTodoControllerneedstotakethedataintheviewmodel(asequenceofto-doitems)anddisplayitinanicetablefortheuser.Byconvention,viewsareplacedintheViewsdirectory,inasubdirectorycorrespondingtothecontrollername.Thefilenameoftheviewisthenameoftheactionwitha.cshtmlextension.

CreateaTododirectoryinsidetheViewsdirectory,andaddthisfile:

Views/Todo/Index.cshtml

@modelTodoViewModel

@{

ViewData["Title"]="Manageyourtodolist";

}

<divclass="panelpanel-defaulttodo-panel">

<divclass="panel-heading">@ViewData["Title"]</div>

<tableclass="tabletable-hover">

<thead>

<tr>

<td>&#x2714;</td>

<td>Item</td>

<td>Due</td>

Createaview

28

Page 29: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

</tr>

</thead>

@foreach(variteminModel.Items)

{

<tr>

<td>

<inputtype="checkbox"class="done-checkbox">

</td>

<td>@item.Title</td>

<td>@item.DueAt</td>

</tr>

}

</table>

<divclass="panel-footeradd-item-form">

<!--TODO:Additemform-->

</div>

</div>

Attheverytopofthefile,the@modeldirectivetellsRazorwhichmodeltoexpectthisviewtobeboundto.ThemodelisaccessedthroughtheModelproperty.

Assumingthereareanyto-doitemsinModel.Items,theforeachstatementwillloopovereachto-doitemandrenderatablerow(<tr>element)containingtheitem'snameandduedate.Acheckboxisalsorenderedthatwilllettheusermarktheitemascomplete.

Thelayoutfile

YoumightbewonderingwheretherestoftheHTMLis:whataboutthe<body>tag,ortheheaderandfooterofthepage?ASP.NETCoreusesalayoutviewthatdefinesthebasestructurethateveryotherviewisrenderedinsideof.It'sstoredinViews/Shared/_Layout.cshtml.

Createaview

29

Page 30: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

ThedefaultASP.NETCoretemplateincludesBootstrapandjQueryinthislayoutfile,soyoucanquicklycreateawebapplication.Ofcourse,youcanuseyourownCSSandJavaScriptlibrariesifyou'dlike.

Customizingthestylesheet

ThedefaulttemplatealsoincludesastylesheetwithsomebasicCSSrules.Thestylesheetisstoredinthewwwroot/cssdirectory.AddafewnewCSSstylerulestothebottomofthesite.cssfile:

wwwroot/css/site.css

div.todo-panel{

margin-top:15px;

}

tabletr.done{

text-decoration:line-through;

color:#888;

}

YoucanuseCSSruleslikethesetocompletelycustomizehowyourpageslookandfeel.

ASP.NETCoreandRazorcandomuchmore,suchaspartialviewsandserver-renderedviewcomponents,butasimplelayoutandviewisallyouneedfornow.TheofficialASP.NETCoredocumentation(athttps://docs.asp.net)containsanumberofexamplesifyou'dliketolearnmore.

Createaview

30

Page 31: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

AddaserviceclassYou'vecreatedamodel,aview,andacontroller.Beforeyouusethemodelandviewinthecontroller,youalsoneedtowritecodethatwillgettheuser'sto-doitemsfromadatabase.

Youcouldwritethisdatabasecodedirectlyinthecontroller,butit'sabetterpracticetokeepyourcodeseparate.Why?Inabig,real-worldapplication,you'llhavetojugglemanyconcerns:

Renderingviewsandhandlingincomingdata:thisiswhatyourcontrolleralreadydoes.Performingbusinesslogic,orcodeandlogicthat'srelatedtothepurposeand"business"ofyourapplication.Inato-dolistapplication,businesslogicmeansdecisionslikesettingadefaultduedateonnewtasks,oronlydisplayingtasksthatareincomplete.Otherexamplesofbusinesslogicincludecalculatingatotalcostbasedonproductpricesandtaxrates,orcheckingwhetheraplayerhasenoughpointstolevelupinagame.Savingandretrievingitemsfromadatabase.

Again,it'spossibletodoallofthesethingsinasingle,massivecontroller,butthatquicklybecomestoohardtomanageandtest.Instead,it'scommontoseeapplicationssplitupintotwo,three,ormore"layers"ortiersthateachhandleone(andonlyone)concern.Thishelpskeepthecontrollersassimpleaspossible,andmakesiteasiertotestandchangethebusinesslogicanddatabasecodelater.

Separatingyourapplicationthiswayissometimescalledamulti-tierorn-tierarchitecture.Insomecases,thetiers(layers)areisolatedincompletelyseparateprojects,butothertimesitjustreferstohowthe

Addaserviceclass

31

Page 32: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

classesareorganizedandused.Theimportantthingisthinkingabouthowtosplityourapplicationintomanageablepieces,andavoidhavingcontrollersorbloatedclassesthattrytodoeverything.

Forthisproject,you'llusetwoapplicationlayers:apresentationlayermadeupofthecontrollersandviewsthatinteractwiththeuser,andaservicelayerthatcontainsbusinesslogicanddatabasecode.Thepresentationlayeralreadyexists,sothenextstepistobuildaservicethathandlesto-dobusinesslogicandsavesto-doitemstoadatabase.

Mostlargerprojectsusea3-tierarchitecture:apresentationlayer,aservicelogiclayer,andadatarepositorylayer.Arepositoryisaclassthat'sonlyfocusedondatabasecode(nobusinesslogic).Inthisapplication,you'llcombinetheseintoasingleservicelayerforsimplicity,butfeelfreetoexperimentwithdifferentwaysofarchitectingthecode.

Createaninterface

TheC#languageincludestheconceptofinterfaces,wherethedefinitionofanobject'smethodsandpropertiesisseparatefromtheclassthatactuallycontainsthecodeforthosemethodsandproperties.Interfacesmakeiteasytokeepyourclassesdecoupledandeasytotest,asyou'llseehere(andlaterintheAutomatedtestingchapter).You'lluseaninterfacetorepresenttheservicethatcaninteractwithto-doitemsinthedatabase.

Byconvention,interfacesareprefixedwith"I".CreateanewfileintheServicesdirectory:

Services/ITodoItemService.cs

usingSystem;

usingSystem.Collections.Generic;

usingSystem.Threading.Tasks;

usingAspNetCoreTodo.Models;

Addaserviceclass

32

Page 33: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

namespaceAspNetCoreTodo.Services

{

publicinterfaceITodoItemService

{

Task<TodoItem[]>GetIncompleteItemsAsync();

}

}

NotethatthenamespaceofthisfileisAspNetCoreTodo.Services.Namespacesareawaytoorganize.NETcodefiles,andit'scustomaryforthenamespacetofollowthedirectorythefileisstoredin(AspNetCoreTodo.ServicesforfilesintheServicesdirectory,andsoon).

Becausethisfile(intheAspNetCoreTodo.Servicesnamespace)referencestheTodoItemclass(intheAspNetCoreTodo.Modelsnamespace),itneedstoincludeausingstatementatthetopofthefiletoimportthatnamespace.Withouttheusingstatement,you'llseeanerrorlike:

Thetypeornamespacename'TodoItem'couldnotbefound(areyou

missingausingdirectiveoranassemblyreference?)

Sincethisisaninterface,thereisn'tanyactualcodehere,justthedefinition(ormethodsignature)oftheGetIncompleteItemsAsyncmethod.ThismethodrequiresnoparametersandreturnsaTask<TodoItem[]>.

Ifthissyntaxlooksconfusing,think:"aTaskthatcontainsanarrayofTodoItems".

TheTasktypeissimilartoafutureorapromise,andit'susedherebecausethismethodwillbeasynchronous.Inotherwords,themethodmaynotbeabletoreturnthelistofto-doitemsrightawaybecauseitneedstogotalktothedatabasefirst.(Moreonthislater.)

Createtheserviceclass

Addaserviceclass

33

Page 34: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

Nowthattheinterfaceisdefined,you'rereadytocreatetheactualserviceclass.I'llcoverdatabasecodeindepthintheUseadatabasechapter,sofornowyou'lljustfakeitandalwaysreturntwohard-codeditems:

Services/FakeTodoItemService.cs

usingSystem;

usingSystem.Collections.Generic;

usingSystem.Threading.Tasks;

usingAspNetCoreTodo.Models;

namespaceAspNetCoreTodo.Services

{

publicclassFakeTodoItemService:ITodoItemService

{

publicTask<TodoItem[]>GetIncompleteItemsAsync()

{

varitem1=newTodoItem

{

Title="LearnASP.NETCore",

DueAt=DateTimeOffset.Now.AddDays(1)

};

varitem2=newTodoItem

{

Title="Buildawesomeapps",

DueAt=DateTimeOffset.Now.AddDays(2)

};

returnTask.FromResult(new[]{item1,item2});

}

}

}

ThisFakeTodoItemServiceimplementstheITodoItemServiceinterfacebutalwaysreturnsthesamearrayoftwoTodoItems.You'llusethistotestthecontrollerandview,andthenaddrealdatabasecodeinUseadatabase.

Addaserviceclass

34

Page 35: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

Addaserviceclass

35

Page 36: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

UsedependencyinjectionBackintheTodoController,addsomecodetoworkwiththeITodoItemService:

publicclassTodoController:Controller

{

privatereadonlyITodoItemService_todoItemService;

publicTodoController(ITodoItemServicetodoItemService)

{

_todoItemService=todoItemService;

}

publicIActionResultIndex()

{

//Getto-doitemsfromdatabase

//Putitemsintoamodel

//Passtheviewtoamodelandrender

}

}

SinceITodoItemServiceisintheServicesnamespace,you'llalsoneedtoaddausingstatementatthetop:

usingAspNetCoreTodo.Services;

ThefirstlineoftheclassdeclaresaprivatevariabletoholdareferencetotheITodoItemService.ThisvariableletsyouusetheservicefromtheIndexactionmethodlater(you'llseehowinaminute).

ThepublicTodoController(ITodoItemServicetodoItemService)linedefinesaconstructorfortheclass.Theconstructorisaspecialmethodthatiscalledwhenyouwanttocreateanewinstanceofaclass(the

Usedependencyinjection

36

Page 37: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

TodoControllerclass,inthiscase).ByaddinganITodoItemServiceparametertotheconstructor,you'vedeclaredthatinordertocreatetheTodoController,you'llneedtoprovideanobjectthatmatchestheITodoItemServiceinterface.

Interfacesareawesomebecausetheyhelpdecouple(separate)thelogicofyourapplication.SincethecontrollerdependsontheITodoItemServiceinterface,andnotonanyspecificclass,itdoesn'tknoworcarewhichclassit'sactuallygiven.ItcouldbetheFakeTodoItemService,adifferentonethattalkstoalivedatabase,orsomethingelse!Aslongasitmatchestheinterface,thecontrollercanuseit.Thismakesitreallyeasytotestpartsofyourapplicationseparately.I'llcovertestingindetailintheAutomatedtestingchapter.

NowyoucanfinallyusetheITodoItemService(viatheprivatevariableyoudeclared)inyouractionmethodtogetto-doitemsfromtheservicelayer:

publicIActionResultIndex()

{

varitems=await_todoItemService.GetIncompleteItemsAsync();

//...

}

RememberthattheGetIncompleteItemsAsyncmethodreturnedaTask<TodoItem[]>?ReturningaTaskmeansthatthemethodwon'tnecessarilyhavearesultrightaway,butyoucanusetheawaitkeywordtomakesureyourcodewaitsuntiltheresultisreadybeforecontinuingon.

TheTaskpatterniscommonwhenyourcodecallsouttoadatabaseoranAPIservice,becauseitwon'tbeabletoreturnarealresultuntilthedatabase(ornetwork)responds.Ifyou'veusedpromisesorcallbacksin

Usedependencyinjection

37

Page 38: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

JavaScriptorotherlanguages,Taskisthesameidea:thepromisethattherewillbearesult-sometimeinthefuture.

Ifyou'vehadtodealwith"callbackhell"inolderJavaScriptcode,you'reinluck.Dealingwithasynchronouscodein.NETismucheasierthankstothemagicoftheawaitkeyword!awaitletsyourcodepauseonanasyncoperation,andthenpickupwhereitleftoffwhentheunderlyingdatabaseornetworkrequestfinishes.Inthemeantime,yourapplicationisn'tblocked,becauseitcanprocessotherrequestsasneeded.Thispatternissimplebuttakesalittlegettingusedto,sodon'tworryifthisdoesn'tmakesenserightaway.Justkeepfollowingalong!

TheonlycatchisthatyouneedtoupdatetheIndexmethodsignaturetoreturnaTask<IActionResult>insteadofjustIActionResult,andmarkitasasync:

publicasyncTask<IActionResult>Index()

{

varitems=await_todoItemService.GetIncompleteItemsAsync();

//Putitemsintoamodel

//Passtheviewtoamodelandrender

}

You'realmostthere!You'vemadetheTodoControllerdependontheITodoItemServiceinterface,butyouhaven'tyettoldASP.NETCorethatyouwanttheFakeTodoItemServicetobetheactualservicethat'susedunderthehood.ItmightseemobviousrightnowsinceyouonlyhaveoneclassthatimplementsITodoItemService,butlateryou'llhavemultipleclassesthatimplementthesameinterface,sobeingexplicitisnecessary.

Usedependencyinjection

38

Page 39: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

Declaring(or"wiringup")whichconcreteclasstouseforeachinterfaceisdoneintheConfigureServicesmethodoftheStartupclass.Rightnow,itlookssomethinglikethis:

Startup.cs

publicvoidConfigureServices(IServiceCollectionservices)

{

//(...somecode)

services.AddMvc();

}

ThejoboftheConfigureServicesmethodisaddingthingstotheservicecontainer,orthecollectionofservicesthatASP.NETCoreknowsabout.Theservices.AddMvclineaddstheservicesthattheinternalASP.NETCoresystemsneed(asanexperiment,trycommentingoutthisline).AnyotherservicesyouwanttouseinyourapplicationmustbeaddedtotheservicecontainerhereinConfigureServices.

AddthefollowinglineanywhereinsidetheConfigureServicesmethod:

services.AddSingleton<ITodoItemService,FakeTodoItemService>();

ThislinetellsASP.NETCoretousetheFakeTodoItemServicewhenevertheITodoItemServiceinterfaceisrequestedinaconstructor(oranywhereelse).

AddSingletonaddsyourservicetotheservicecontainerasasingleton.ThismeansthatonlyonecopyoftheFakeTodoItemServiceiscreated,andit'sreusedwhenevertheserviceisrequested.Later,whenyouwriteadifferentserviceclassthattalkstoadatabase,you'lluseadifferentapproach(calledscoped)instead.I'llexplainwhyintheUseadatabasechapter.

Usedependencyinjection

39

Page 40: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

That'sit!WhenarequestcomesinandisroutedtotheTodoController,ASP.NETCorewilllookattheavailableservicesandautomaticallysupplytheFakeTodoItemServicewhenthecontrollerasksforanITodoItemService.Becausetheservicesare"injected"fromtheservicecontainer,thispatterniscalleddependencyinjection.

Usedependencyinjection

40

Page 41: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

FinishthecontrollerThelaststepistofinishthecontrollercode.Thecontrollernowhasalistofto-doitemsfromtheservicelayer,anditneedstoputthoseitemsintoaTodoViewModelandbindthatmodeltotheviewyoucreatedearlier:

Controllers/TodoController.cs

publicasyncTask<IActionResult>Index()

{

varitems=await_todoItemService.GetIncompleteItemsAsync();

varmodel=newTodoViewModel()

{

Items=items

};

returnView(model);

}

Ifyouhaven'talready,makesuretheseusingstatementsareatthetopofthefile:

usingAspNetCoreTodo.Services;

usingAspNetCoreTodo.Models;

Ifyou'reusingVisualStudioorVisualStudioCode,theeditorwillsuggesttheseusingstatementswhenyouputyourcursoronaredsquigglyline.

Testitout

Finishthecontroller

41

Page 42: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

Tostarttheapplication,pressF5(ifyou'reusingVisualStudioorVisualStudioCode),orjusttypedotnetrunintheterminal.Ifthecodecompileswithouterrors,theserverwillstartuponport5000bydefault.

Ifyourwebbrowserdidn'topenautomatically,openitandnavigatetohttp://localhost:5000/todo.You'llseetheviewyoucreated,withthedatapulledfromyourfakedatabase(fornow).

Althoughit'spossibletogodirectlytohttp://localhost:5000/todo,itwouldbenicertoaddanitemcalledMyto-dostothenavbar.Todothis,youcaneditthesharedlayoutfile.

Finishthecontroller

42

Page 43: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

UpdatethelayoutThelayoutfileatViews/Shared/_Layout.cshtmlcontainsthe"base"HTMLforeachview.Thisincludesthenavbar,whichisrenderedatthetopofeachpage.

Toaddanewitemtothenavbar,findtheHTMLcodefortheexistingnavbaritems:

Views/Shared/_Layout.cshtml

<ulclass="navnavbar-nav">

<li><aasp-area=""asp-controller="Home"asp-action="Index">

Home

</a></li>

<li><aasp-area=""asp-controller="Home"asp-action="About">

About

</a></li>

<li><aasp-area=""asp-controller="Home"asp-action="Contact">

Contact

</a></li>

</ul>

AddyourownitemthatpointstotheTodocontrollerinsteadofHome:

<li>

<aasp-controller="Todo"asp-action="Index">Myto-dos</a>

</li>

Theasp-controllerandasp-actionattributesonthe<a>elementarecalledtaghelpers.Beforetheviewisrendered,ASP.NETCorereplacesthesetaghelperswithrealHTMLattributes.Inthiscase,aURLtothe/Todo/Indexrouteisgeneratedandaddedtothe<a>element

Updatethelayout

43

Page 44: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

asanhrefattribute.Thismeansyoudon'thavetohard-codetheroutetotheTodoController.Instead,ASP.NETCoregeneratesitforyouautomatically.

Ifyou'veusedRazorinASP.NET4.x,you'[email protected]()togeneratealinktoanaction,taghelpersarenowtherecommendedwaytocreatelinksinyourviews.Taghelpersareusefulforforms,too(you'llseewhyinalaterchapter).Youcanlearnaboutothertaghelpersinthedocumentationathttps://docs.asp.net.

Updatethelayout

44

Page 45: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

AddexternalpackagesOneofthebigadvantagesofusingamatureecosystemlike.NETisthatthenumberofthird-partypackagesandpluginsishuge.Justlikeotherpackagesystems,youcandownloadandinstall.NETpackagesthathelpwithalmostanytaskorproblemyoucanimagine.

NuGetisboththepackagemanagertoolandtheofficialpackagerepository(athttps://www.nuget.org).YoucansearchforNuGetpackagesontheweb,andinstallthemfromyourlocalmachinethroughtheterminal(ortheGUI,ifyou'reusingVisualStudio).

InstalltheHumanizerpackageAttheendofthelastchapter,theto-doapplicationdisplayedto-doitemslikethis:

Theduedatecolumnisdisplayingdatesinaformatthat'sgoodformachines(calledISO8601),butclunkyforhumans.Wouldn'titbenicerifitsimplyread"Xdaysfromnow"?

YoucouldwritecodeyourselfthatconvertedanISO8601dateintoahuman-friendlystring,butfortunately,there'safasterway.

TheHumanizerpackageonNuGetsolvesthisproblembyprovidingmethodsthatcan"humanize"orrewritealmostanything:dates,times,durations,numbers,andsoon.It'safantasticandusefulopen-source

Addexternalpackages

45

Page 46: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

projectthat'spublishedunderthepermissiveMITlicense.

Toaddittoyourproject,runthiscommandintheterminal:

dotnetaddpackageHumanizer

IfyoupeekattheAspNetCoreTodo.csprojprojectfile,you'llseeanewPackageReferencelinethatreferencesHumanizer.

UseHumanizerintheviewTouseapackageinyourcode,youusuallyneedtoaddausingstatementthatimportsthepackageatthetopofthefile.

SinceHumanizerwillbeusedtorewritedatesrenderedintheview,youcanuseitdirectlyintheviewitself.First,adda@usingstatementatthetopoftheview:

Views/Todo/Index.cshtml

@modelTodoViewModel

@usingHumanizer

//...

Then,updatethelinethatwritestheDueAtpropertytouseHumanizer'sHumanizemethod:

<td>@item.DueAt.Humanize()</td>

Nowthedatesaremuchmorereadable:

Addexternalpackages

46

Page 47: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

TherearepackagesavailableonNuGetforeverythingfromparsingXMLtomachinelearningtopostingtoTwitter.ASP.NETCoreitself,underthehood,isnothingmorethanacollectionofNuGetpackagesthatareaddedtoyourproject.

TheprojectfilecreatedbydotnetnewmvcincludesasinglereferencetotheMicrosoft.AspNetCore.Allpackage,whichisaconvenient"metapackage"thatreferencesalloftheotherASP.NETCorepackagesyouneedforatypicalproject.Thatway,youdon'tneedtohavehundredsofpackagereferencesinyourprojectfile.

Inthenextchapter,you'lluseanothersetofNuGetpackages(asystemcalledEntityFrameworkCore)towritecodethatinteractswithadatabase.

Addexternalpackages

47

Page 48: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

UseadatabaseWritingdatabasecodecanbetricky.Unlessyoureallyknowwhatyou'redoing,it'sabadideatopasterawSQLquerystringsintoyourapplicationcode.Anobject-relationalmapper(ORM)makesiteasiertowritecodethatinteractswithadatabasebyaddingalayerofabstractionbetweenyourcodeandthedatabaseitself.HibernateinJavaandActiveRecordinRubyaretwowell-knownORMs.

ThereareanumberofORMsfor.NET,includingonebuiltbyMicrosoftandincludedinASP.NETCorebydefault:EntityFrameworkCore.EntityFrameworkCoremakesiteasytoconnecttoanumberofdifferentdatabasetypes,andletsyouuseC#codetocreatedatabasequeriesthataremappedbackintoC#models(POCOs).

Rememberhowcreatingaserviceinterfacedecoupledthecontrollercodefromtheactualserviceclass?EntityFrameworkCoreislikeabiginterfaceoveryourdatabase.YourC#codecanstaydatabase-agnostic,andyoucanswapoutdifferentprovidersdependingontheunderlyingdatabasetechnology.

EntityFrameworkCorecanconnecttorelationaldatabaseslikeSQLServer,PostgreSQL,andMySQL,andalsoworkswithNoSQL(document)databaseslikeMongo.Duringdevelopment,you'lluseSQLiteinthisprojecttomakethingseasytosetup.

Useadatabase

48

Page 49: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

ConnecttoadatabaseThereareafewthingsyouneedtouseEntityFrameworkCoretoconnecttoadatabase.SinceyouuseddotnetnewandtheMVC+IndividualAuthtemplatetosetyourproject,you'vealreadygotthem:

TheEntityFrameworkCorepackages.TheseareincludedbydefaultinallASP.NETCoreprojects.

Adatabase(naturally).Theapp.dbfileintheprojectrootdirectoryisasmallSQLitedatabasecreatedforyoubydotnetnew.SQLiteisalightweightdatabaseenginethatcanrunwithoutrequiringyoutoinstallanyextratoolsonyourmachine,soit'seasyandquicktouseindevelopment.

Adatabasecontextclass.ThedatabasecontextisaC#classthatprovidesanentrypointintothedatabase.It'showyourcodewillinteractwiththedatabasetoreadandsaveitems.AbasiccontextclassalreadyexistsintheData/ApplicationDbContext.csfile.

Aconnectionstring.Whetheryouareconnectingtoalocalfiledatabase(likeSQLite)oradatabasehostedelsewhere,you'lldefineastringthatcontainsthenameoraddressofthedatabasetoconnectto.Thisisalreadysetupforyouintheappsettings.jsonfile:theconnectionstringfortheSQLitedatabaseisDataSource=app.db.

EntityFrameworkCoreusesthedatabasecontext,togetherwiththeconnectionstring,toestablishaconnectiontothedatabase.YouneedtotellEntityFrameworkCorewhichcontext,connectionstring,anddatabaseprovidertouseintheConfigureServicesmethodoftheStartupclass.Here'swhat'sdefinedforyou,thankstothetemplate:

services.AddDbContext<ApplicationDbContext>(options=>

Connecttoadatabase

49

Page 50: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

options.UseSqlite(

Configuration.GetConnectionString("DefaultConnection")));

ThiscodeaddstheApplicationDbContexttotheservicecontainer,andtellsEntityFrameworkCoretousetheSQLitedatabaseprovider,withtheconnectionstringfromconfiguration(appsettings.json).

Asyoucansee,dotnetnewcreatesalotofstuffforyou!Thedatabaseissetupandreadytobeused.However,itdoesn'thaveanytablesforstoringto-doitems.InordertostoreyourTodoItementities,you'llneedtoupdatethecontextandmigratethedatabase.

Connecttoadatabase

50

Page 51: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

UpdatethecontextThere'snotawholelotgoingoninthedatabasecontextyet:

Data/ApplicationDbContext.cs

publicclassApplicationDbContext

:IdentityDbContext<ApplicationUser>

{

publicApplicationDbContext(

DbContextOptions<ApplicationDbContext>options)

:base(options)

{

}

protectedoverridevoidOnModelCreating(ModelBuilderbuilder)

{

base.OnModelCreating(builder);

//...

}

}

AddaDbSetpropertytotheApplicationDbContext,rightbelowtheconstructor:

publicApplicationDbContext(

DbContextOptions<ApplicationDbContext>options)

:base(options)

{

}

publicDbSet<TodoItem>Items{get;set;}

//...

Updatethecontext

51

Page 52: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

ADbSetrepresentsatableorcollectioninthedatabase.BycreatingaDbSet<TodoItem>propertycalledItems,you'retellingEntityFrameworkCorethatyouwanttostoreTodoItementitiesinatablecalledItems.

You'veupdatedthecontextclass,butnowthere'sonesmallproblem:thecontextanddatabasearenowoutofsync,becausethereisn'tactuallyanItemstableinthedatabase.(Justupdatingthecodeofthecontextclassdoesn'tchangethedatabaseitself.)

Inordertoupdatethedatabasetoreflectthechangeyoujustmadetothecontext,youneedtocreateamigration.

Ifyoualreadyhaveanexistingdatabase,searchthewebfor"scaffold-dbcontextexistingdatabase"andreadMicrosoft'sdocumentationonusingtheScaffold-DbContexttooltoreverse-engineeryourdatabasestructureintotheproperDbContextandmodelclassesautomatically.

Updatethecontext

52

Page 53: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

CreateamigrationMigrationskeeptrackofchangestothedatabasestructureovertime.Theymakeitpossibletoundo(rollback)asetofchanges,orcreateaseconddatabasewiththesamestructureasthefirst.Withmigrations,youhaveafullhistoryofmodificationslikeaddingorremovingcolumns(andentiretables).

Inthepreviouschapter,youaddedanItemssettothecontext.Sincethecontextnowincludesaset(ortable)thatdoesn'texistinthedatabase,youneedtocreateamigrationtoupdatethedatabase:

dotnetefmigrationsaddAddItems

ThiscreatesanewmigrationcalledAddItemsbyexamininganychangesyou'vemadetothecontext.

IfyougetanerrorlikeNoexecutablefoundmatchingcommand"dotnet-ef",makesureyou'reintherightdirectory.Thesecommandsmustberunfromtheprojectrootdirectory(wheretheProgram.csfileis).

IfyouopenuptheData/Migrationsdirectory,you'llseeafewfiles:

Createamigration

53

Page 54: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

Thefirstmigrationfile(withanamelike00_CreateIdentitySchema.cs)wascreatedandappliedforyouwaybackwhenyourandotnetnew.YournewAddItemmigrationisprefixedwithatimestampwhenyoucreateit.

Youcanseealistofmigrationswithdotnetefmigrationslist.

Ifyouopenyourmigrationfile,you'llseetwomethodscalledUpandDown:

Data/Migrations/_AddItems.cs

protectedoverridevoidUp(MigrationBuildermigrationBuilder)

{

//(...somecode)

migrationBuilder.CreateTable(

name:"Items",

columns:table=>new

{

Id=table.Column<Guid>(nullable:false),

DueAt=table.Column<DateTimeOffset>(nullable:true),

IsDone=table.Column<bool>(nullable:false),

Title=table.Column<string>(nullable:true)

},

constraints:table=>

{

table.PrimaryKey("PK_Items",x=>x.Id);

});

//(somecode...)

}

protectedoverridevoidDown(MigrationBuildermigrationBuilder)

{

//(...somecode)

migrationBuilder.DropTable(

name:"Items");

//(somecode...)

}

Createamigration

54

Page 55: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

TheUpmethodrunswhenyouapplythemigrationtothedatabase.SinceyouaddedaDbSet<TodoItem>tothedatabasecontext,EntityFrameworkCorewillcreateanItemstable(withcolumnsthatmatchaTodoItem)whenyouapplythemigration.

TheDownmethoddoestheopposite:ifyouneedtoundo(rollback)themigration,theItemstablewillbedropped.

WorkaroundforSQLitelimitations

TherearesomelimitationsofSQLitethatgetinthewayifyoutrytorunthemigrationas-is.Untilthisproblemisfixed,usethisworkaround:

CommentoutorremovethemigrationBuilder.AddForeignKeylinesintheUpmethod.CommentoutorremoveanymigrationBuilder.DropForeignKeylinesintheDownmethod.

Ifyouuseafull-fledgedSQLdatabase,likeSQLServerorMySQL,thiswon'tbeanissueandyouwon'tneedtodothis(admittedlyhackish)workaround.

Applythemigration

Thefinalstepaftercreatingone(ormore)migrationsistoactuallyapplythemtothedatabase:

dotnetefdatabaseupdate

ThiscommandwillcauseEntityFrameworkCoretocreatetheItemstableinthedatabase.

Createamigration

55

Page 56: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

Ifyouwanttorollbackthedatabase,youcanprovidethenameofthepreviousmigration:dotnetefdatabaseupdateCreateIdentitySchemaThiswillruntheDownmethodsofanymigrationsnewerthanthemigrationyouspecify.

Ifyouneedtocompletelyerasethedatabaseandstartover,rundotnetefdatabasedropfollowedbydotnetefdatabaseupdatetore-scaffoldthedatabaseandbringituptothecurrentmigration.

That'sit!Boththedatabaseandthecontextarereadytogo.Next,you'llusethecontextinyourservicelayer.

Createamigration

56

Page 57: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

CreateanewserviceclassBackintheMVCbasicschapter,youcreatedaFakeTodoItemServicethatcontainedhard-codedto-doitems.Nowthatyouhaveadatabasecontext,youcancreateanewserviceclassthatwilluseEntityFrameworkCoretogettherealitemsfromthedatabase.

DeletetheFakeTodoItemService.csfile,andcreateanewfile:

Services/TodoItemService.cs

usingSystem;

usingSystem.Collections.Generic;

usingSystem.Linq;

usingSystem.Threading.Tasks;

usingAspNetCoreTodo.Data;

usingAspNetCoreTodo.Models;

usingMicrosoft.EntityFrameworkCore;

namespaceAspNetCoreTodo.Services

{

publicclassTodoItemService:ITodoItemService

{

privatereadonlyApplicationDbContext_context;

publicTodoItemService(ApplicationDbContextcontext)

{

_context=context;

}

publicasyncTask<TodoItem[]>GetIncompleteItemsAsync()

{

returnawait_context.Items

.Where(x=>x.IsDone==false)

.ToArrayAsync();

}

}

}

Createanewserviceclass

57

Page 58: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

You'llnoticethesamedependencyinjectionpatternherethatyousawintheMVCbasicschapter,exceptthistimeit'stheApplicationDbContextthat'sgettinginjected.TheApplicationDbContextisalreadybeingaddedtotheservicecontainerintheConfigureServicesmethod,soit'savailableforinjectionhere.

Let'stakeacloserlookatthecodeoftheGetIncompleteItemsAsyncmethod.First,itusestheItemspropertyofthecontexttoaccessalltheto-doitemsintheDbSet:

varitems=await_context.Items

Then,theWheremethodisusedtofilteronlytheitemsthatarenotcomplete:

.Where(x=>x.IsDone==false)

TheWheremethodisafeatureofC#calledLINQ(languageintegratedquery),whichtakesinspirationfromfunctionalprogrammingandmakesiteasytoexpressdatabasequeriesincode.Underthehood,EntityFrameworkCoretranslatestheWheremethodintoastatementlikeSELECT*FROMItemsWHEREIsDone=0,oranequivalentquerydocumentinaNoSQLdatabase.

Finally,theToArrayAsyncmethodtellsEntityFrameworkCoretogetalltheentitiesthatmatchedthefilterandreturnthemasanarray.TheToArrayAsyncmethodisasynchronous(itreturnsaTask),soitmustbeawaitedtogetitsvalue.

Tomakethemethodalittleshorter,youcanremovetheintermediateitemsvariableandjustreturntheresultofthequerydirectly(whichdoesthesamething):

publicasyncTask<TodoItem[]>GetIncompleteItemsAsync()

Createanewserviceclass

58

Page 59: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

{

returnawait_context.Items

.Where(x=>x.IsDone==false)

.ToArrayAsync();

}

Updatetheservicecontainer

BecauseyoudeletedtheFakeTodoItemServiceclass,you'llneedtoupdatethelineinConfigureServicesthatiswiringuptheITodoItemServiceinterface:

services.AddScoped<ITodoItemService,TodoItemService>();

AddScopedaddsyourservicetotheservicecontainerusingthescopedlifecycle.ThismeansthatanewinstanceoftheTodoItemServiceclasswillbecreatedduringeachwebrequest.Thisisrequiredforserviceclassesthatinteractwithadatabase.

AddingaserviceclassthatinteractswithEntityFrameworkCore(andyourdatabase)withthesingletonlifecycle(orotherlifecycles)cancauseproblems,becauseofhowEntityFrameworkCoremanagesdatabaseconnectionsperrequestunderthehood.Toavoidthat,alwaysusethescopedlifecycleforservicesthatinteractwithEntityFrameworkCore.

TheTodoControllerthatdependsonaninjectedITodoItemServicewillbeblissfullyunawareofthechangeinservicesclasses,butunderthehoodit'llbeusingEntityFrameworkCoreandtalkingtoarealdatabase!

Testitout

Startuptheapplicationandnavigatetohttp://localhost:5000/todo.Thefakeitemsaregone,andyourapplicationismakingrealqueriestothedatabase.Theredoesn'thappentobeanysavedto-doitems,soit's

Createanewserviceclass

59

Page 60: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

blankfornow.

Inthenextchapter,you'lladdmorefeaturestotheapplication,startingwiththeabilitytocreatenewto-doitems.

Createanewserviceclass

60

Page 61: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

AddmorefeaturesNowthatyou'veconnectedtoadatabaseusingEntityFrameworkCore,you'rereadytoaddsomemorefeaturestotheapplication.First,you'llmakeitpossibletoaddnewto-doitemsusingaform.

Addmorefeatures

61

Page 62: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

Addnewto-doitemsTheuserwilladdnewto-doitemswithasimpleformbelowthelist:

Addingthisfeaturerequiresafewsteps:

AddingaformtotheviewCreatinganewactiononthecontrollertohandletheformAddingcodetotheservicelayertoupdatethedatabase

Addaform

TheViews/Todo/Index.cshtmlviewhasaplaceholderfortheAddItemform:

<divclass="panel-footeradd-item-form">

<!--TODO:Additemform-->

</div>

Tokeepthingsseparateandorganized,you'llcreatetheformasapartialview.Apartialviewisasmallpieceofalargerviewthatlivesinaseparatefile.

CreateanAddItemPartial.cshtmlview:

Views/Todo/AddItemPartial.cshtml

Addnewto-doitems

62

Page 63: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

@modelTodoItem

<formasp-action="AddItem"method="POST">

<labelasp-for="Title">Addanewitem:</label>

<inputasp-for="Title">

<buttontype="submit">Add</button>

</form>

Theasp-actiontaghelpercangenerateaURLfortheform,justlikewhenyouuseitonan<a>element.Inthiscase,theasp-actionhelpergetsreplacedwiththerealpathtotheAddItemrouteyou'llcreate:

<formaction="/Todo/AddItem"method="POST">

Addinganasp-taghelpertothe<form>elementalsoaddsahiddenfieldtotheformcontainingaverificationtoken.Thisverificationtokencanbeusedtopreventcross-siterequestforgery(CSRF)attacks.You'llverifythetokenwhenyouwritetheaction.

Thattakescareofcreatingthepartialview.Now,referenceitfromthemainTodoview:

Views/Todo/Index.cshtml

<divclass="panel-footeradd-item-form">

@awaitHtml.PartialAsync("AddItemPartial",newTodoItem())

</div>

Addanaction

WhenauserclicksAddontheformyoujustcreated,theirbrowserwillconstructaPOSTrequestto/Todo/AddItemonyourapplication.Thatwon'tworkrightnow,becausethereisn'tanyactionthatcanhandlethe/Todo/AddItemroute.Ifyoutryitnow,ASP.NETCorewillreturna404NotFounderror.

Addnewto-doitems

63

Page 64: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

You'llneedtocreateanewactioncalledAddItemontheTodoController:

[ValidateAntiForgeryToken]

publicasyncTask<IActionResult>AddItem(TodoItemnewItem)

{

if(!ModelState.IsValid)

{

returnRedirectToAction("Index");

}

varsuccessful=await_todoItemService.AddItemAsync(newItem);

if(!successful)

{

returnBadRequest("Couldnotadditem.");

}

returnRedirectToAction("Index");

}

NoticehowthenewAddItemactionacceptsaTodoItemparameter?ThisisthesameTodoItemmodelyoucreatedintheMVCbasicschaptertostoreinformationaboutato-doitem.Whenit'susedhereasanactionparameter,ASP.NETCorewillautomaticallyperformaprocesscalledmodelbinding.

Modelbindinglooksatthedatainarequestandtriestointelligentlymatchtheincomingfieldswithpropertiesonthemodel.Inotherwords,whentheusersubmitsthisformandtheirbrowserPOSTstothisaction,ASP.NETCorewillgrabtheinformationfromtheformandplaceitinthenewItemvariable.

The[ValidateAntiForgeryToken]attributebeforetheactiontellsASP.NETCorethatitshouldlookfor(andverify)thehiddenverificationtokenthatwasaddedtotheformbytheasp-actiontaghelper.Thisisanimportantsecuritymeasuretopreventcross-siterequestforgery

Addnewto-doitems

64

Page 65: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

(CSRF)attacks,whereyouruserscouldbetrickedintosubmittingdatafromamalicioussite.Theverificationtokenensuresthatyourapplicationisactuallytheonethatrenderedandsubmittedtheform.

TakealookattheAddItemPartial.cshtmlviewoncemore.The@modelTodoItemlineatthetopofthefiletellsASP.NETCorethattheviewshouldexpecttobepairedwiththeTodoItemmodel.Thismakesitpossibletouseasp-for="Title"onthe<input>tagtoletASP.NETCoreknowthatthisinputelementisfortheTitleproperty.

Becauseofthe@modelline,thepartialviewwillexpecttobepassedaTodoItemobjectwhenit'srendered.PassingitanewTodoItemviaHtml.PartialAsyncinitializestheformwithanemptyitem.(Tryappending{Title="hello"}andseewhathappens!)

Duringmodelbinding,anymodelpropertiesthatcan'tbematchedupwithfieldsintherequestareignored.SincetheformonlyincludesaTitleinputelement,youcanexpectthattheotherpropertiesonTodoItem(theIsDoneflag,theDueAtdate)willbeemptyorcontaindefaultvalues.

InsteadofreusingtheTodoItemmodel,anotherapproachwouldbetocreateaseparatemodel(likeNewTodoItem)that'sonlyusedforthisactionandonlyhasthespecificproperties(Title)youneedforaddinganewto-doitem.Modelbindingisstillused,butthiswayyou'veseparatedthemodelthat'susedforstoringato-doiteminthedatabasefromthemodelthat'susedforbindingincomingrequestdata.Thisissometimescalledabindingmodeloradatatransferobject(DTO).Thispatterniscommoninlarger,morecomplexprojects.

Afterbindingtherequestdatatothemodel,ASP.NETCorealsoperformsmodelvalidation.Validationcheckswhetherthedataboundtothemodelfromtheincomingrequestmakessenseorisvalid.Youcan

Addnewto-doitems

65

Page 66: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

addattributestothemodeltotellASP.NETCorehowitshouldbevalidated.

The[Required]attributeontheTitlepropertytellsASP.NETCore'smodelvalidatortoconsiderthetitleinvalidifitismissingorblank.TakealookatthecodeoftheAddItemaction:thefirstblockcheckswhethertheModelState(themodelvalidationresult)isvalid.It'scustomarytodothisvalidationcheckrightatthebeginningoftheaction:

if(!ModelState.IsValid)

{

returnRedirectToAction("Index");

}

IftheModelStateisinvalidforanyreason,thebrowserwillberedirectedtothe/Todo/Indexroute,whichrefreshesthepage.

Next,thecontrollercallsintotheservicelayertodotheactualdatabaseoperationofsavingthenewto-doitem:

varsuccessful=await_todoItemService.AddItemAsync(newItem);

if(!successful)

{

returnBadRequest(new{error="Couldnotadditem."});

}

TheAddItemAsyncmethodwillreturntrueorfalsedependingonwhethertheitemwassuccessfullyaddedtothedatabase.Ifitfailsforsomereason,theactionwillreturnanHTTP400BadRequesterroralongwithanobjectthatcontainsanerrormessage.

Finally,ifeverythingcompletedwithouterrors,theactionredirectsthebrowsertothe/Todo/Indexroute,whichrefreshesthepageanddisplaysthenew,updatedlistofto-doitemstotheuser.

Addaservicemethod

Addnewto-doitems

66

Page 67: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

Ifyou'reusingacodeeditorthatunderstandsC#,you'llseeredsquiggelylinesunderAddItemAsyncbecausethemethoddoesn'texistyet.

Asalaststep,youneedtoaddamethodtotheservicelayer.First,addittotheinterfacedefinitioninITodoItemService:

publicinterfaceITodoItemService

{

Task<TodoItem[]>GetIncompleteItemsAsync();

Task<bool>AddItemAsync(TodoItemnewItem);

}

Then,theactualimplementationinTodoItemService:

publicasyncTask<bool>AddItemAsync(TodoItemnewItem)

{

newItem.Id=Guid.NewGuid();

newItem.IsDone=false;

newItem.DueAt=DateTimeOffset.Now.AddDays(3);

_context.Items.Add(newItem);

varsaveResult=await_context.SaveChangesAsync();

returnsaveResult==1;

}

ThenewItem.TitlepropertyhasalreadybeensetbyASP.NETCore'smodelbinder,sothismethodonlyneedstoassignanIDandsetthedefaultvaluesfortheotherproperties.Then,thenewitemisaddedtothedatabasecontext.Itisn'tactuallysaveduntilyoucallSaveChangesAsync().Ifthesaveoperationwassuccessful,SaveChangesAsync()willreturn1.

Tryitout

Addnewto-doitems

67

Page 68: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

Runtheapplicationandaddsomeitemstoyourto-dolistwiththeform.Sincetheitemsarebeingstoredinthedatabase,they'llstillbethereevenafteryoustopandstarttheapplicationagain.

Asanextrachallenge,tryaddingadatepickerusingHTMLandJavaScript,andlettheuserchoosean(optional)datefortheDueAtproperty.Then,usethatdateinsteadofalwaysmakingnewtasksthatareduein3days.

Addnewto-doitems

68

Page 69: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

CompleteitemswithacheckboxAddingitemstoyourto-dolistisgreat,buteventuallyyou'llneedtogetthingsdone,too.IntheViews/Todo/Index.cshtmlview,acheckboxisrenderedforeachto-doitem:

<inputtype="checkbox"class="done-checkbox">

Clickingthecheckboxdoesn'tdoanything(yet).Justlikethelastchapter,you'lladdthisbehaviorusingformsandactions.Inthiscase,you'llalsoneedatinybitofJavaScriptcode.

Addformelementstotheview

First,updatetheviewandwrapeachcheckboxwitha<form>element.Then,addahiddenelementcontainingtheitem'sID:

Views/Todo/Index.cshtml

<td>

<formasp-action="MarkDone"method="POST">

<inputtype="checkbox"class="done-checkbox">

<inputtype="hidden"name="id"value="@item.Id">

</form>

</td>

Whentheforeachlooprunsintheviewandprintsarowforeachto-doitem,acopyofthisformwillexistineachrow.Thehiddeninputcontainingtheto-doitem'sIDmakesitpossibleforyourcontrollercodetotellwhichboxwaschecked.(Withoutit,you'dbeabletotellthatsomeboxwaschecked,butnotwhichone.)

Completeitemswithacheckbox

69

Page 70: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

Ifyourunyourapplicationrightnow,thecheckboxesstillwon'tdoanything,becausethere'snosubmitbuttontotellthebrowsertocreateaPOSTrequestwiththeform'sdata.Youcouldaddasubmitbuttonundereachcheckbox,butthatwouldbeasillyuserexperience.Ideally,clickingthecheckboxshouldautomaticallysubmittheform.YoucanachievethatbyaddingsomeJavaScript.

AddJavaScriptcode

Findthesite.jsfileinthewwwroot/jsdirectoryandaddthiscode:

wwwroot/js/site.js

$(document).ready(function(){

//WireupallofthecheckboxestorunmarkCompleted()

$('.done-checkbox').on('click',function(e){

markCompleted(e.target);

});

});

functionmarkCompleted(checkbox){

checkbox.disabled=true;

varrow=checkbox.closest('tr');

$(row).addClass('done');

varform=checkbox.closest('form');

form.submit();

}

ThiscodefirstusesjQuery(aJavaScripthelperlibrary)toattachsomecodetotheclickevenofallthecheckboxesonthepagewiththeCSSclassdone-checkbox.Whenacheckboxisclicked,themarkCompleted()functionisrun.

ThemarkCompleted()functiondoesafewthings:

Completeitemswithacheckbox

70

Page 71: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

Addsthedisabledattributetothecheckboxsoitcan'tbeclickedagainAddsthedoneCSSclasstotheparentrowthatcontainsthecheckbox,whichchangesthewaytherowlooksbasedontheCSSrulesinstyle.cssSubmitstheform

Thattakescareoftheviewandfrontendcode.Nowit'stimetoaddanewaction!

Addanactiontothecontroller

Asyou'veprobablyguessed,youneedtoaddanactioncalledMarkDoneintheTodoController:

[ValidateAntiForgeryToken]

publicasyncTask<IActionResult>MarkDone(Guidid)

{

if(id==Guid.Empty)

{

returnRedirectToAction("Index");

}

varsuccessful=await_todoItemService.MarkDoneAsync(id);

if(!successful)

{

returnBadRequest("Couldnotmarkitemasdone.");

}

returnRedirectToAction("Index");

}

Let'sstepthrougheachlineofthisactionmethod.First,themethodacceptsaGuidparametercalledidinthemethodsignature.UnliketheAddItemaction,whichusedamodelandmodelbinding/validation,theidparameterisverysimple.Iftheincomingrequestdataincludesa

Completeitemswithacheckbox

71

Page 72: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

fieldcalledid,ASP.NETCorewilltrytoparseitasaguid.Thisworksbecausethehiddenelementyouaddedtothecheckboxformisnamedid.

Sinceyouaren'tusingmodelbinding,there'snoModelStatetocheckforvalidity.Instead,youcanchecktheguidvaluedirectlytomakesureit'svalid.Ifforsomereasontheidparameterintherequestwasmissingorcouldn'tbeparsedasaguid,idwillhaveavalueofGuid.Empty.Ifthat'sthecase,theactiontellsthebrowsertoredirectto/Todo/Indexandrefreshthepage.

Next,thecontrollerneedstocalltheservicelayertoupdatethedatabase.ThiswillbehandledbyanewmethodcalledMarkDoneAsyncontheITodoItemServiceinterface,whichwillreturntrueorfalsedependingonwhethertheupdatesucceeded:

varsuccessful=await_todoItemService.MarkDoneAsync(id);

if(!successful)

{

returnBadRequest("Couldnotmarkitemasdone.");

}

Finally,ifeverythinglooksgood,thebrowserisredirectedtothe/Todo/Indexactionandthepageisrefreshed.

Withtheviewandcontrollerupdated,allthat'sleftisaddingthemissingservicemethod.

Addaservicemethod

First,addMarkDoneAsynctotheinterfacedefinition:

Services/ITodoItemService.cs

Task<bool>MarkDoneAsync(Guidid);

Completeitemswithacheckbox

72

Page 73: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

Then,addtheconcreteimplementationtotheTodoItemService:

Services/TodoItemService.cs

publicasyncTask<bool>MarkDoneAsync(Guidid)

{

varitem=await_context.Items

.Where(x=>x.Id==id)

.SingleOrDefaultAsync();

if(item==null)returnfalse;

item.IsDone=true;

varsaveResult=await_context.SaveChangesAsync();

returnsaveResult==1;//Oneentityshouldhavebeenupdated

}

ThismethodusesEntityFrameworkCoreandWhere()tofindanitembyIDinthedatabase.TheSingleOrDefaultAsync()methodwilleitherreturntheitemornullifitcouldn'tbefound.

Onceyou'resurethatitemisn'tnull,it'sasimplematterofsettingtheIsDoneproperty:

item.IsDone=true;

ChangingthepropertyonlyaffectsthelocalcopyoftheitemuntilSaveChangesAsync()iscalledtopersistthechangebacktothedatabase.SaveChangesAsync()returnsanumberthatindicateshowmanyentitieswereupdatedduringthesaveoperation.Inthiscase,it'lleitherbe1(theitemwasupdated)or0(somethingwentwrong).

Tryitout

Completeitemswithacheckbox

73

Page 74: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

Runtheapplicationandtrycheckingsomeitemsoffthelist.Refreshthepageandthey'lldisappearcompletely,becauseoftheWhere()filterintheGetIncompleteItemsAsync()method.

Rightnow,theapplicationcontainsasingle,sharedto-dolist.It'dbeevenmoreusefulifitkepttrackofindividualto-dolistsforeachuser.Inthenextchapter,you'lladdloginandsecurityfeaturestotheproject.

Completeitemswithacheckbox

74

Page 75: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

SecurityandidentitySecurityisamajorconcernofanymodernwebapplicationorAPI.It'simportanttokeepyouruserorcustomerdatasafeandoutofthehandsofattackers.Thisisaverybroadtopic,involvingthingslike:

SanitizingdatainputtopreventSQLinjectionattacksPreventingcross-domain(CSRF)attacksinformsUsingHTTPS(connectionencryption)sodatacan'tbeinterceptedasittravelsovertheInternetGivingusersawaytosecurelysigninwithapasswordorothercredentialsDesigningpasswordreset,accountrecovery,andmulti-factorauthenticationflows

ASP.NETCorecanhelpmakeallofthiseasiertoimplement.Thefirsttwo(protectionagainstSQLinjectionandcross-domainattacks)arealreadybuilt-in,andyoucanaddafewlinesofcodetoenableHTTPSsupport.Thischapterwillmainlyfocusontheidentityaspectsofsecurity:handlinguseraccounts,authenticating(loggingin)youruserssecurely,andmakingauthorizationdecisionsoncetheyareauthenticated.

Authenticationandauthorizationaredistinctideasthatareoftenconfused.Authenticationdealswithwhetherauserisloggedin,whileauthorizationdealswithwhattheyareallowedtodoaftertheylogin.Youcanthinkofauthenticationasaskingthequestion,"DoIknowwhothisuseris?"Whileauthorizationasks,"DoesthisuserhavepermissiontodoX?"

Securityandidentity

75

Page 76: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

TheMVC+IndividualAuthenticationtemplateyouusedtoscaffoldtheprojectincludesanumberofclassesbuiltontopofASP.NETCoreIdentity,anauthenticationandidentitysystemthat'spartofASP.NETCore.Outofthebox,thisaddstheabilitytologinwithanemailandpassword.

WhatisASP.NETCoreIdentity?ASP.NETCoreIdentityistheidentitysystemthatshipswithASP.NETCore.LikeeverythingelseintheASP.NETCoreecosystem,it'sasetofNuGetpackagesthatcanbeinstalledinanyproject(andarealreadyincludedifyouusethedefaulttemplate).

ASP.NETCoreIdentitytakescareofstoringuseraccounts,hashingandstoringpasswords,andmanagingrolesforusers.Itsupportsemail/passwordlogin,multi-factorauthentication,socialloginwithproviderslikeGoogleandFacebook,aswellasconnectingtootherservicesusingprotocolslikeOAuth2.0andOpenIDConnect.

TheRegisterandLoginviewsthatshipwiththeMVC+IndividualAuthenticationtemplatealreadytakeadvantageofASP.NETCoreIdentity,andtheyalreadywork!Tryregisteringforanaccountandloggingin.

Securityandidentity

76

Page 77: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

RequireauthenticationOftenyou'llwanttorequiretheusertologinbeforetheycanaccesscertainpartsofyourapplication.Forexample,itmakessensetoshowthehomepagetoeveryone(whetheryou'reloggedinornot),butonlyshowyourto-dolistafteryou'veloggedin.

Youcanusethe[Authorize]attributeinASP.NETCoretorequirealogged-inuserforaparticularaction,oranentirecontroller.TorequireauthenticationforallactionsoftheTodoController,addtheattributeabovethefirstlineofthecontroller:

Controllers/TodoController.cs

[Authorize]

publicclassTodoController:Controller

{

//...

}

Addthisusingstatementatthetopofthefile:

usingMicrosoft.AspNetCore.Authorization;

Tryrunningtheapplicationandaccessing/todowithoutbeingloggedin.You'llberedirectedtotheloginpageautomatically.

The[Authorize]attributeisactuallydoinganauthenticationcheckhere,notanauthorizationcheck(despitethenameoftheattribute).Later,you'llusetheattributetocheckbothauthenticationandauthorization.

Requireauthentication

77

Page 78: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

Requireauthentication

78

Page 79: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

UsingidentityintheapplicationTheto-dolistitemsthemselvesarestillsharedbetweenallusers,becausethestoredto-doentitiesaren'ttiedtoaparticularuser.Nowthatthe[Authorize]attributeensuresthatyoumustbeloggedintoseetheto-doview,youcanfilterthedatabasequerybasedonwhoisloggedin.

First,injectaUserManager<ApplicationUser>intotheTodoController:

Controllers/TodoController.cs

[Authorize]

publicclassTodoController:Controller

{

privatereadonlyITodoItemService_todoItemService;

privatereadonlyUserManager<ApplicationUser>_userManager;

publicTodoController(ITodoItemServicetodoItemService,

UserManager<ApplicationUser>userManager)

{

_todoItemService=todoItemService;

_userManager=userManager;

}

//...

}

You'llneedtoaddanewusingstatementatthetop:

usingMicrosoft.AspNetCore.Identity;

TheUserManagerclassispartofASP.NETCoreIdentity.YoucanuseittogetthecurrentuserintheIndexaction:

publicasyncTask<IActionResult>Index()

Usingidentityintheapplication

79

Page 80: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

{

varcurrentUser=await_userManager.GetUserAsync(User);

if(currentUser==null)returnChallenge();

varitems=await_todoItemService

.GetIncompleteItemsAsync(currentUser);

varmodel=newTodoViewModel()

{

Items=items

};

returnView(model);

}

ThenewcodeatthetopoftheactionmethodusestheUserManagertolookupthecurrentuserfromtheUserpropertyavailableintheaction:

varcurrentUser=await_userManager.GetUserAsync(User);

Ifthereisalogged-inuser,theUserpropertycontainsalightweightobjectwithsome(butnotall)oftheuser'sinformation.TheUserManagerusesthistolookupthefulluserdetailsinthedatabaseviatheGetUserAsync()method.

ThevalueofcurrentUsershouldneverbenull,becausethe[Authorize]attributeispresentonthecontroller.However,it'sagoodideatodoasanitycheck,justincase.YoucanusetheChallenge()methodtoforcetheusertologinagainiftheirinformationismissing:

if(currentUser==null)returnChallenge();

Sinceyou'renowpassinganApplicationUserparametertoGetIncompleteItemsAsync(),you'llneedtoupdatetheITodoItemServiceinterface:

Services/ITodoItemService.cs

Usingidentityintheapplication

80

Page 81: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

publicinterfaceITodoItemService

{

Task<TodoItem[]>GetIncompleteItemsAsync(

ApplicationUseruser);

//...

}

SinceyouchangedtheITodoItemServiceinterface,youalsoneedtoupdatethesignatureoftheGetIncompleteItemsAsync()methodintheTodoItemService:

Services/TodoItemService

publicasyncTask<TodoItem[]>GetIncompleteItemsAsync(

ApplicationUseruser)

Thenextstepistoupdatethedatabasequeryandaddafiltertoshowonlytheitemscreatedbythecurrentuser.Beforeyoucandothat,youneedtoaddanewpropertytothedatabase.

Updatethedatabase

You'llneedtoaddanewpropertytotheTodoItementitymodelsoeachitemcan"remember"theuserthatownsit:

Models/TodoItem.cs

publicstringUserId{get;set;}

Sinceyouupdatedtheentitymodelusedbythedatabasecontext,youalsoneedtomigratethedatabase.Createanewmigrationusingdotnetefintheterminal:

dotnetefmigrationsaddAddItemUserId

Usingidentityintheapplication

81

Page 82: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

ThiscreatesanewmigrationcalledAddItemUserIdwhichwilladdanewcolumntotheItemstable,mirroringthechangeyoumadetotheTodoItemmodel.

Usedotnetefagaintoapplyittothedatabase:

dotnetefdatabaseupdate

Updatetheserviceclass

Withthedatabaseandthedatabasecontextupdated,youcannowupdatetheGetIncompleteItemsAsync()methodintheTodoItemServiceandaddanotherclausetotheWherestatement:

Services/TodoItemService.cs

publicasyncTask<TodoItem[]>GetIncompleteItemsAsync(

ApplicationUseruser)

{

returnawait_context.Items

.Where(x=>x.IsDone==false&&x.UserId==user.Id)

.ToArrayAsync();

}

Ifyouruntheapplicationandregisterorlogin,you'llseeanemptyto-dolistonceagain.Unfortunately,anyitemsyoutrytoadddisappearintotheether,becauseyouhaven'tupdatedtheAddItemactiontobeuser-awareyet.

UpdatetheAddItemandMarkDoneactions

You'llneedtousetheUserManagertogetthecurrentuserintheAddItemandMarkDoneactionmethods,justlikeyoudidinIndex.

Herearebothupdatedmethods:

Usingidentityintheapplication

82

Page 83: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

Controllers/TodoController.cs

[ValidateAntiForgeryToken]

publicasyncTask<IActionResult>AddItem(TodoItemnewItem)

{

if(!ModelState.IsValid)

{

returnRedirectToAction("Index");

}

varcurrentUser=await_userManager.GetUserAsync(User);

if(currentUser==null)returnChallenge();

varsuccessful=await_todoItemService

.AddItemAsync(newItem,currentUser);

if(!successful)

{

returnBadRequest("Couldnotadditem.");

}

returnRedirectToAction("Index");

}

[ValidateAntiForgeryToken]

publicasyncTask<IActionResult>MarkDone(Guidid)

{

if(id==Guid.Empty)

{

returnRedirectToAction("Index");

}

varcurrentUser=await_userManager.GetUserAsync(User);

if(currentUser==null)returnChallenge();

varsuccessful=await_todoItemService

.MarkDoneAsync(id,currentUser);

if(!successful)

{

returnBadRequest("Couldnotmarkitemasdone.");

}

returnRedirectToAction("Index");

Usingidentityintheapplication

83

Page 84: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

}

BothservicemethodsmustnowacceptanApplicationUserparameter.UpdatetheinterfacedefinitioninITodoItemService:

Task<bool>AddItemAsync(TodoItemnewItem,ApplicationUseruser);

Task<bool>MarkDoneAsync(Guidid,ApplicationUseruser);

Andfinally,updatetheservicemethodimplementationsintheTodoItemService.InAddItemAsyncmethod,settheUserIdpropertywhenyouconstructanewTodoItem:

publicasyncTask<bool>AddItemAsync(

TodoItemnewItem,ApplicationUseruser)

{

newItem.Id=Guid.NewGuid();

newItem.IsDone=false;

newItem.DueAt=DateTimeOffset.Now.AddDays(3);

newItem.UserId=user.Id;

//...

}

TheWhereclauseintheMarkDoneAsyncmethodalsoneedstocheckfortheuser'sID,soarogueusercan'tcompletesomeoneelse'sitemsbyguessingtheirIDs:

publicasyncTask<bool>MarkDoneAsync(

Guidid,ApplicationUseruser)

{

varitem=await_context.Items

.Where(x=>x.Id==id&&x.UserId==user.Id)

.SingleOrDefaultAsync();

//...

}

Usingidentityintheapplication

84

Page 85: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

Alldone!Tryusingtheapplicationwithtwodifferentuseraccounts.Theto-doitemsstayprivateforeachaccount.

Usingidentityintheapplication

85

Page 86: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

AuthorizationwithrolesRolesareacommonapproachtohandlingauthorizationandpermissionsinawebapplication.Forexample,it'scommontocreateanAdministratorrolethatgivesadminusersmorepermissionsorpowerthannormalusers.

Inthisproject,you'lladdaManageUserspagethatonlyadministratorscansee.Ifnormaluserstrytoaccessit,they'llseeanerror.

AddaManageUserspage

First,createanewcontroller:

Controllers/ManageUsersController.cs

usingSystem;

usingSystem.Linq;

usingSystem.Threading.Tasks;

usingMicrosoft.AspNetCore.Mvc;

usingMicrosoft.AspNetCore.Authorization;

usingMicrosoft.AspNetCore.Identity;

usingAspNetCoreTodo.Models;

usingMicrosoft.EntityFrameworkCore;

namespaceAspNetCoreTodo.Controllers

{

[Authorize(Roles="Administrator")]

publicclassManageUsersController:Controller

{

privatereadonlyUserManager<ApplicationUser>

_userManager;

publicManageUsersController(

UserManager<ApplicationUser>userManager)

{

_userManager=userManager;

}

Authorizationwithroles

86

Page 87: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

publicasyncTask<IActionResult>Index()

{

varadmins=(await_userManager

.GetUsersInRoleAsync("Administrator"))

.ToArray();

vareveryone=await_userManager.Users

.ToArrayAsync();

varmodel=newManageUsersViewModel

{

Administrators=admins,

Everyone=everyone

};

returnView(model);

}

}

}

SettingtheRolespropertyonthe[Authorize]attributewillensurethattheusermustbeloggedinandassignedtheAdministratorroleinordertoviewthepage.

Next,createaviewmodel:

Models/ManageUsersViewModel.cs

usingSystem.Collections.Generic;

usingAspNetCoreTodo.Models;

namespaceAspNetCoreTodo.Models

{

publicclassManageUsersViewModel

{

publicApplicationUser[]Administrators{get;set;}

publicApplicationUser[]Everyone{get;set;}

}

}

Authorizationwithroles

87

Page 88: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

Finally,createaViews/ManageUsersfolderandaviewfortheIndexaction:

Views/ManageUsers/Index.cshtml

@modelManageUsersViewModel

@{

ViewData["Title"]="Manageusers";

}

<h2>@ViewData["Title"]</h2>

<h3>Administrators</h3>

<tableclass="table">

<thead>

<tr>

<td>Id</td>

<td>Email</td>

</tr>

</thead>

@foreach(varuserinModel.Administrators)

{

<tr>

<td>@user.Id</td>

<td>@user.Email</td>

</tr>

}

</table>

<h3>Everyone</h3>

<tableclass="table">

<thead>

<tr>

<td>Id</td>

<td>Email</td>

</tr>

</thead>

@foreach(varuserinModel.Everyone)

Authorizationwithroles

88

Page 89: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

{

<tr>

<td>@user.Id</td>

<td>@user.Email</td>

</tr>

}

</table>

Startuptheapplicationandtrytoaccessthe/ManageUsersroutewhileloggedinasanormaluser.You'llseethisaccessdeniedpage:

That'sbecauseusersaren'tassignedtheAdministratorroleautomatically.

Createatestadministratoraccount

Forobvioussecurityreasons,itisn'tpossibleforanyonetoregisteranewadministratoraccountthemselves.Infact,theAdministratorroledoesn'tevenexistinthedatabaseyet!

YoucanaddtheAdministratorroleplusatestadministratoraccounttothedatabasethefirsttimetheapplicationstartsup.Addingfirst-timedatatothedatabaseiscalledinitializingorseedingthedatabase.

CreateanewclassintherootoftheprojectcalledSeedData:

Authorizationwithroles

89

Page 90: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

SeedData.cs

usingSystem;

usingSystem.Threading.Tasks;

usingAspNetCoreTodo.Models;

usingMicrosoft.AspNetCore.Identity;

usingMicrosoft.EntityFrameworkCore;

usingMicrosoft.Extensions.DependencyInjection;

namespaceAspNetCoreTodo

{

publicstaticclassSeedData

{

publicstaticasyncTaskInitializeAsync(

IServiceProviderservices)

{

varroleManager=services

.GetRequiredService<RoleManager<IdentityRole>>();

awaitEnsureRolesAsync(roleManager);

varuserManager=services

.GetRequiredService<UserManager<ApplicationUser>>(

);

awaitEnsureTestAdminAsync(userManager);

}

}

}

TheInitializeAsync()methodusesanIServiceProvider(thecollectionofservicesthatissetupintheStartup.ConfigureServices()method)togettheRoleManagerandUserManagerfromASP.NETCoreIdentity.

AddtwomoremethodsbelowtheInitializeAsync()method.First,theEnsureRolesAsync()method:

privatestaticasyncTaskEnsureRolesAsync(

RoleManager<IdentityRole>roleManager)

{

varalreadyExists=awaitroleManager

.RoleExistsAsync(Constants.AdministratorRole);

Authorizationwithroles

90

Page 91: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

if(alreadyExists)return;

awaitroleManager.CreateAsync(

newIdentityRole(Constants.AdministratorRole));

}

ThismethodcheckstoseeifanAdministratorroleexistsinthedatabase.Ifnot,itcreatesone.Insteadofrepeatedlytypingthestring"Administrator",createasmallclasscalledConstantstoholdthevalue:

Constants.cs

namespaceAspNetCoreTodo

{

publicstaticclassConstants

{

publicconststringAdministratorRole="Administrator";

}

}

Ifyouwant,youcanupdatetheManageUsersControllertousethisconstantvalueaswell.

Next,writetheEnsureTestAdminAsync()method:

SeedData.cs

privatestaticasyncTaskEnsureTestAdminAsync(

UserManager<ApplicationUser>userManager)

{

vartestAdmin=awaituserManager.Users

.Where(x=>x.UserName=="[email protected]")

.SingleOrDefaultAsync();

if(testAdmin!=null)return;

testAdmin=newApplicationUser

{

Authorizationwithroles

91

Page 92: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

UserName="[email protected]",

Email="[email protected]"

};

awaituserManager.CreateAsync(

testAdmin,"NotSecure123!!");

awaituserManager.AddToRoleAsync(

testAdmin,Constants.AdministratorRole);

}

Ifthereisn'[email protected],thismethodwillcreateoneandassignatemporarypassword.Afteryouloginforthefirsttime,youshouldchangetheaccount'spasswordtosomethingsecure!

Next,youneedtotellyourapplicationtorunthislogicwhenitstartsup.ModifyProgram.csandupdateMain()tocallanewmethod,InitializeDatabase():

Program.cs

publicstaticvoidMain(string[]args)

{

varhost=BuildWebHost(args);

InitializeDatabase(host);

host.Run();

}

Then,addthenewmethodtotheclassbelowMain():

privatestaticvoidInitializeDatabase(IWebHosthost)

{

using(varscope=host.Services.CreateScope())

{

varservices=scope.ServiceProvider;

try

{

SeedData.InitializeAsync(services).Wait();

}

Authorizationwithroles

92

Page 93: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

catch(Exceptionex)

{

varlogger=services

.GetRequiredService<ILogger<Program>>();

logger.LogError(ex,"ErroroccurredseedingtheDB.");

}

}

}

Addthisusingstatementtothetopofthefile:

usingMicrosoft.Extensions.DependencyInjection;

ThismethodgetstheservicecollectionthatSeedData.InitializeAsync()needsandthenrunsthemethodtoseedthedatabase.Ifsomethinggoeswrong,anerrorislogged.

BecauseInitializeAsync()returnsaTask,theWait()methodmustbeusedtomakesureitfinishesbeforetheapplicationstartsup.You'dnormallyuseawaitforthis,butfortechnicalreasonsyoucan'tuseawaitintheProgramclass.Thisisarareexception.Youshoulduseawaiteverywhereelse!

Whenyoustarttheapplicationnext,theadmin@todo.localaccountwillbecreatedandassignedtheAdministratorrole.Trylogginginwiththisaccount,andnavigatingtohttp://localhost:5000/ManageUsers.You'llseealistofallusersregisteredfortheapplication.

Asanextrachallenge,tryaddingmoreadministrationfeaturestothispage.Forexample,youcouldaddabuttonthatgivesanadministratortheabilitytodeleteauseraccount.

Checkforauthorizationinaview

Authorizationwithroles

93

Page 94: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

The[Authorize]attributemakesiteasytoperformanauthorizationcheckinacontrolleroractionmethod,butwhatifyouneedtocheckauthorizationinaview?Forexample,itwouldbenicetodisplaya"Manageusers"linkinthenavigationbarifthelogged-inuserisanadministrator.

YoucaninjecttheUserManagerdirectlyintoaviewtodothesetypesofauthorizationchecks.Tokeepyourviewscleanandorganized,createanewpartialviewthatwilladdanitemtothenavbarinthelayout:

Views/Shared/_AdminActionsPartial.cshtml

@usingMicrosoft.AspNetCore.Identity

@usingAspNetCoreTodo.Models

@injectSignInManager<ApplicationUser>signInManager

@injectUserManager<ApplicationUser>userManager

@if(signInManager.IsSignedIn(User))

{

varcurrentUser=awaitUserManager.GetUserAsync(User);

varisAdmin=currentUser!=null

&&awaituserManager.IsInRoleAsync(

currentUser,

Constants.AdministratorRole);

if(isAdmin)

{

<ulclass="navnavbar-navnavbar-right">

<li>

<aasp-controller="ManageUsers"

asp-action="Index">

ManageUsers

</a>

</li>

</ul>

}

}

Authorizationwithroles

94

Page 95: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

It'sconventionaltonamesharedpartialviewsstartingwithan_underscore,butit'snotrequired.

ThispartialviewfirstusestheSignInManagertoquicklydeterminewhethertheuserisloggedin.Iftheyaren't,therestoftheviewcodecanbeskipped.Ifthereisalogged-inuser,theUserManagerisusedtolookuptheirdetailsandperformanauthorizationcheckwithIsInRoleAsync().Ifallcheckssucceedandtheuserisanadminstrator,aManageuserslinkisaddedtothenavbar.

Toincludethispartialinthemainlayout,edit_Layout.cshtmlandadditinthenavbarsection:

Views/Shared/_Layout.cshtml

<divclass="navbar-collapsecollapse">

<ulclass="navnavbar-nav">

<!--existingcodehere-->

</ul>

@awaitHtml.PartialAsync("_LoginPartial")

@awaitHtml.PartialAsync("_AdminActionsPartial")

</div>

Whenyouloginwithanadministratoraccount,you'llnowseeanewitemonthetopright:

Authorizationwithroles

95

Page 96: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

MoreresourcesASP.NETCoreIdentityhelpsyouaddsecurityandidentityfeatureslikeloginandregistrationtoyourapplication.Thedotnetnewtemplatesgiveyoupre-builtviewsandcontrollersthathandlethesecommonscenariossoyoucangetupandrunningquickly.

There'smuchmorethatASP.NETCoreIdentitycando,suchaspasswordresetandsociallogin.Thedocumentationavailableathttp://docs.asp.netisafantasticresourceforlearninghowtoaddthesefeatures.

AlternativestoASP.NETCoreIdentity

ASP.NETCoreIdentityisn'ttheonlywaytoaddidentityfunctionality.Anotherapproachistouseacloud-hostedidentityservicelikeAzureActiveDirectoryB2CorOktatohandleidentityforyourapplication.Youcanthinkoftheseoptionsaspartofaprogression:

Do-it-yourselfsecurity:Notrecommended,unlessyouareasecurityexpert!ASP.NETCoreIdentity:Yougetalotofcodeforfreewiththetemplates,whichmakesitprettyeasytogetstarted.You'llstillneedtowritesomecodeformoreadvancedscenarios,andmaintainadatabasetostoreuserinformation.Cloud-hostedidentityservices.Theservicehandlesbothsimpleandadvancedscenarios(multi-factorauthentication,accountrecovery,federation),andsignificantlyreducestheamountofcodeyouneedtowriteandmaintaininyourapplication.Plus,sensitiveuserdataisn'tstoredinyourowndatabase.

Moreresources

96

Page 97: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

Forthisproject,ASP.NETCoreIdentityisagreatfit.Formorecomplexprojects,I'drecommenddoingsomeresearchandexperimentingwithbothoptionstounderstandwhichisbestforyourusecase.

Moreresources

97

Page 98: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

AutomatedtestingWritingtestsisanimportantpartofbuildinganyapplication.Testingyourcodehelpsyoufindandavoidbugs,andmakesiteasiertorefactoryourcodelaterwithoutbreakingfunctionalityorintroducingnewproblems.

Inthischapteryou'lllearnhowtowritebothunittestsandintegrationteststhatexerciseyourASP.NETCoreapplication.Unittestsaresmallteststhatmakesureasinglemethodorchunkoflogicworksproperly.Integrationtests(sometimescalledfunctionaltests)arelargerteststhatsimulatereal-worldscenariosandtestmultiplelayersorpartsofyourapplication.

Automatedtesting

98

Page 99: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

UnittestingUnittestsaresmall,shortteststhatcheckthebehaviorofasinglemethodorclass.Whenthecodeyou'retestingreliesonothermethodsorclasses,unittestsrelyonmockingthoseotherclassessothatthetestonlyfocusesononethingatatime.

Forexample,theTodoControllerclasshastwodependencies:anITodoItemServiceandtheUserManager.TheTodoItemService,inturn,dependsontheApplicationDbContext.(TheideathatyoucandrawalinefromTodoController>TodoItemService>ApplicationDbContextiscalledadependencygraph).

Whentheapplicationrunsnormally,theASP.NETCoreservicecontaineranddependencyinjectionsysteminjectseachofthoseobjectsintothedependencygraphwhentheTodoControllerortheTodoItemServiceiscreated.

Whenyouwriteaunittest,ontheotherhand,youhavetohandlethedependencygraphyourself.It'stypicaltoprovidetest-onlyor"mocked"versionsofthosedependencies.Thismeansyoucanisolatejustthelogicintheclassormethodyouaretesting.(Thisisimportant!Ifyou'retestingaservice,youdon'twanttoalsobeaccidentallywritingtoyourdatabase.)

Createatestproject

It'sabestpracticetocreateaseparateprojectforyourtests,sotheyarekeptseparatefromyourapplicationcode.Thenewtestprojectshouldliveinadirectorythat'snextto(notinside)yourmainproject'sdirectory.

Unittesting

99

Page 100: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

Ifyou'recurrentlyinyourprojectdirectory,cduponelevel.(ThisrootdirectorywillalsobecalledAspNetCoreTodo).Thenusethiscommandtoscaffoldanewtestproject:

dotnetnewxunit-oAspNetCoreTodo.UnitTests

xUnit.NETisapopulartestframeworkfor.NETcodethatcanbeusedtowritebothunitandintegrationtests.Likeeverythingelse,it'sasetofNuGetpackagesthatcanbeinstalledinanyproject.Thedotnetnewxunittemplatealreadyincludeseverythingyouneed.

Yourdirectorystructureshouldnowlooklikethis:

AspNetCoreTodo/

AspNetCoreTodo/

AspNetCoreTodo.csproj

Controllers/

(etc...)

AspNetCoreTodo.UnitTests/

AspNetCoreTodo.UnitTests.csproj

Sincethetestprojectwillusetheclassesdefinedinyourmainproject,you'llneedtoaddareferencetotheAspNetCoreTodoproject:

dotnetaddreference../AspNetCoreTodo/AspNetCoreTodo.csproj

DeletetheUnitTest1.csfilethat'sautomaticallycreated.You'rereadytowriteyourfirsttest.

Ifyou'reusingVisualStudioCode,youmayneedtocloseandreopentheVisualStudioCodewindowtogetcodecompletionworkinginthenewproject.

Writeaservicetest

Unittesting

100

Page 101: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

TakealookatthelogicintheAddItemAsync()methodoftheTodoItemService:

publicasyncTask<bool>AddItemAsync(

TodoItemnewItem,ApplicationUseruser)

{

newItem.Id=Guid.NewGuid();

newItem.IsDone=false;

newItem.DueAt=DateTimeOffset.Now.AddDays(3);

newItem.UserId=user.Id;

_context.Items.Add(newItem);

varsaveResult=await_context.SaveChangesAsync();

returnsaveResult==1;

}

Thismethodmakesanumberofdecisionsorassumptionsaboutthenewitem(inotherwords,performsbusinesslogiconthenewitem)beforeitactuallysavesittothedatabase:

TheUserIdpropertyshouldbesettotheuser'sIDNewitemsshouldalwaysbeincomplete(IsDone=false)ThetitleofthenewitemshouldbecopiedfromnewItem.TitleNewitemsshouldalwaysbedue3daysfromnow

ImagineifyouorsomeoneelserefactoredtheAddItemAsync()methodandforgotaboutpartofthisbusinesslogic.Thebehaviorofyourapplicationcouldchangewithoutyourealizingit!Youcanpreventthisbywritingatestthatdouble-checksthatthisbusinesslogichasn'tchanged(evenifthemethod'sinternalimplementationchanges).

Itmightseemunlikelynowthatyoucouldintroduceachangeinbusinesslogicwithoutrealizingit,butitbecomesmuchhardertokeeptrackofdecisionsandassumptionsinalarge,complexproject.Thelargeryourprojectis,themoreimportantitistohaveautomatedchecksthatmakesurenothinghaschanged!

Unittesting

101

Page 102: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

TowriteaunittestthatwillverifythelogicintheTodoItemService,createanewclassinyourtestproject:

AspNetCoreTodo.UnitTests/TodoItemServiceShould.cs

usingSystem;

usingSystem.Threading.Tasks;

usingAspNetCoreTodo.Data;

usingAspNetCoreTodo.Models;

usingAspNetCoreTodo.Services;

usingMicrosoft.EntityFrameworkCore;

usingXunit;

namespaceAspNetCoreTodo.UnitTests

{

publicclassTodoItemServiceShould

{

[Fact]

publicasyncTaskAddNewItemAsIncompleteWithDueDate()

{

//...

}

}

}

Therearemanydifferentwaysofnamingandorganizingtests,allwithdifferentprosandcons.IlikepostfixingmytestclasseswithShouldtocreateareadablesentencewiththetestmethodname,butfeelfreetouseyourownstyle!

The[Fact]attributecomesfromthexUnit.NETpackage,anditmarksthismethodasatestmethod.

TheTodoItemServicerequiresanApplicationDbContext,whichisnormallyconnectedtoyourdatabase.Youwon'twanttousethatfortests.Instead,youcanuseEntityFrameworkCore'sin-memorydatabaseproviderinyourtestcode.Sincetheentiredatabaseexistsinmemory,

Unittesting

102

Page 103: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

it'swipedouteverytimethetestisrestarted.And,sinceit'saproperEntityFrameworkCoreprovider,theTodoItemServicewon'tknowthedifference!

UseaDbContextOptionsBuildertoconfigurethein-memorydatabaseprovider,andthenmakeacalltoAddItemAsync():

varoptions=newDbContextOptionsBuilder<ApplicationDbContext>()

.UseInMemoryDatabase(databaseName:"Test_AddNewItem").Options;

//Setupacontext(connectiontothe"DB")forwriting

using(varcontext=newApplicationDbContext(options))

{

varservice=newTodoItemService(context);

varfakeUser=newApplicationUser

{

Id="fake-000",

UserName="[email protected]"

};

awaitservice.AddItemAsync(newTodoItem

{

Title="Testing?"

},fakeUser);

}

Thelastlinecreatesanewto-doitemcalledTesting?,andtellstheservicetosaveittothe(in-memory)database.

Toverifythatthebusinesslogicrancorrectly,writesomemorecodebelowtheexistingusingblock:

//Useaseparatecontexttoreaddatabackfromthe"DB"

using(varcontext=newApplicationDbContext(options))

{

varitemsInDatabase=awaitcontext

.Items.CountAsync();

Assert.Equal(1,itemsInDatabase);

Unittesting

103

Page 104: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

varitem=awaitcontext.Items.FirstAsync();

Assert.Equal("Testing?",item.Title);

Assert.Equal(false,item.IsDone);

//Itemshouldbedue3daysfromnow(giveortakeasecond)

vardifference=DateTimeOffset.Now.AddDays(3)-item.DueAt;

Assert.True(difference<TimeSpan.FromSeconds(1));

}

Thefirstassertionisasanitycheck:thereshouldneverbemorethanoneitemsavedtothein-memorydatabase.Assumingthat'strue,thetestretrievesthesaveditemwithFirstAsyncandthenassertsthatthepropertiesaresettotheexpectedvalues.

BothunitandintegrationteststypicallyfollowtheAAA(Arrange-Act-Assert)pattern:objectsanddataaresetupfirst,thensomeactionisperformed,andfinallythetestchecks(asserts)thattheexpectedbehavioroccurred.

Assertingadatetimevalueisalittletricky,sincecomparingtwodatesforequalitywillfailifeventhemillisecondcomponentsaredifferent.Instead,thetestchecksthattheDueAtvalueislessthanasecondawayfromtheexpectedvalue.

Runthetest

Ontheterminal,runthiscommand(makesureyou'restillintheAspNetCoreTodo.UnitTestsdirectory):

dotnettest

Thetestcommandscansthecurrentprojectfortests(markedwith[Fact]attributesinthiscase),andrunsallthetestsitfinds.You'llseeoutputsimilarto:

Startingtestexecution,pleasewait...

Unittesting

104

Page 105: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

Discovering:AspNetCoreTodo.UnitTests

Discovered:AspNetCoreTodo.UnitTests

Starting:AspNetCoreTodo.UnitTests

Finished:AspNetCoreTodo.UnitTests

Totaltests:1.Passed:1.Failed:0.Skipped:0.

TestRunSuccessful.

Testexecutiontime:1.9074Seconds

YounowhaveonetestprovidingtestcoverageoftheTodoItemService.Asanextrachallenge,trywritingunitteststhatensure:

TheMarkDoneAsync()methodreturnsfalseifit'spassedanIDthatdoesn'texistTheMarkDoneAsync()methodreturnstruewhenitmakesavaliditemascompleteTheGetIncompleteItemsAsync()methodreturnsonlytheitemsownedbyaparticularuser

Unittesting

105

Page 106: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

IntegrationtestingComparedtounittests,integrationtestsaremuchlargerinscope.exercisethewholeapplicationstack.Insteadofisolatingoneclassormethod,integrationtestsensurethatallofthecomponentsofyourapplicationareworkingtogetherproperly:routing,controllers,services,databasecode,andsoon.

Integrationtestsareslowerandmoreinvolvedthanunittests,soit'scommonforaprojecttohavelotsofsmallunittestsbutonlyahandfulofintegrationtests.

Inordertotestthewholestack(includingcontrollerrouting),integrationteststypicallymakeHTTPcallstoyourapplicationjustlikeawebbrowserwould.

TowriteintegrationteststhatmakeHTTPrequests,youcouldmanuallystartyourapplicationandtestsatthesametime,andwriteyourteststomakerequeststohttp://localhost:5000.ASP.NETCoreprovidesanicerwaytohostyourapplicationfortesting,however:theTestServerclass.TestServercanhostyourapplicationforthedurationofthetest,andthenstopitautomaticallywhenthetestiscomplete.

Createatestproject

Ifyou'recurrentlyinyourprojectdirectory,cduponeleveltotherootAspNetCoreTododirectory.Usethiscommandtoscaffoldanewtestproject:

dotnetnewxunit-oAspNetCoreTodo.IntegrationTests

Yourdirectorystructureshouldnowlooklikethis:

Integrationtesting

106

Page 107: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

AspNetCoreTodo/

AspNetCoreTodo/

AspNetCoreTodo.csproj

Controllers/

(etc...)

AspNetCoreTodo.UnitTests/

AspNetCoreTodo.UnitTests.csproj

AspNetCoreTodo.IntegrationTests/

AspNetCoreTodo.IntegrationTests.csproj

Ifyouprefer,youcankeepyourunittestsandintegrationtestsinthesameproject.Forlargeprojects,it'scommontosplitthemupsoit'seasytorunthemseparately.

Sincethetestprojectwillusetheclassesdefinedinyourmainproject,you'llneedtoaddareferencetothemainproject:

dotnetaddreference../AspNetCoreTodo/AspNetCoreTodo.csproj

You'llalsoneedtoaddtheMicrosoft.AspNetCore.TestHostNuGetpackage:

dotnetaddpackageMicrosoft.AspNetCore.TestHost

DeletetheUnitTest1.csfilethat'screatedbydotnetnew.You'rereadytowriteanintegrationtest.

Writeanintegrationtest

Thereareafewthingsthatneedtobeconfiguredonthetestserverbeforeeachtest.Insteadofclutteringthetestwiththissetupcode,youcankeepthissetupinaseparateclass.CreateanewclasscalledTestFixture:

Integrationtesting

107

Page 108: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

AspNetCoreTodo.IntegrationTests/TestFixture.cs

usingSystem;

usingSystem.Collections.Generic;

usingSystem.IO;

usingSystem.Net.Http;

usingMicrosoft.AspNetCore.Hosting;

usingMicrosoft.AspNetCore.TestHost;

usingMicrosoft.Extensions.Configuration;

namespaceAspNetCoreTodo.IntegrationTests

{

publicclassTestFixture:IDisposable

{

privatereadonlyTestServer_server;

publicHttpClientClient{get;}

publicTestFixture()

{

varbuilder=newWebHostBuilder()

.UseStartup<AspNetCoreTodo.Startup>()

.ConfigureAppConfiguration((context,config)=>

{

config.SetBasePath(Path.Combine(

Directory.GetCurrentDirectory(),

"..\\..\\..\\..\\AspNetCoreTodo"));

config.AddJsonFile("appsettings.json");

});

_server=newTestServer(builder);

Client=_server.CreateClient();

Client.BaseAddress=newUri("http://localhost:8888");

}

publicvoidDispose()

{

Client.Dispose();

_server.Dispose();

}

}

}

Integrationtesting

108

Page 109: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

ThisclasstakescareofsettingupaTestServer,andwillhelpkeeptheteststhemselvescleanandtidy.

Nowyou're(really)readytowriteanintegrationtest.CreateanewclasscalledTodoRouteShould:

AspNetCoreTodo.IntegrationTests/TodoRouteShould.cs

usingSystem.Net;

usingSystem.Net.Http;

usingSystem.Threading.Tasks;

usingXunit;

namespaceAspNetCoreTodo.IntegrationTests

{

publicclassTodoRouteShould:IClassFixture<TestFixture>

{

privatereadonlyHttpClient_client;

publicTodoRouteShould(TestFixturefixture)

{

_client=fixture.Client;

}

[Fact]

publicasyncTaskChallengeAnonymousUser()

{

//Arrange

varrequest=newHttpRequestMessage(

HttpMethod.Get,"/todo");

//Act:requestthe/todoroute

varresponse=await_client.SendAsync(request);

//Assert:theuserissenttotheloginpage

Assert.Equal(

HttpStatusCode.Redirect,

response.StatusCode);

Assert.Equal(

"http://localhost:8888/Account"+

Integrationtesting

109

Page 110: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

"/Login?ReturnUrl=%2Ftodo",

response.Headers.Location.ToString());

}

}

}

Thistestmakesananonymous(not-logged-in)requesttothe/todorouteandverifiesthatthebrowserisredirectedtotheloginpage.

Thisscenarioisagoodcandidateforanintegrationtest,becauseitinvolvesmultiplecomponentsoftheapplication:theroutingsystem,thecontroller,thefactthatthecontrollerismarkedwith[Authorize],andsoon.It'salsoagoodtestbecauseitensuresyouwon'teveraccidentallyremovethe[Authorize]attributeandmaketheto-doviewaccessibletoeveryone.

RunthetestRunthetestintheterminalwithdotnettest.Ifeverything'sworkingright,you'llseeasuccessmessage:

Startingtestexecution,pleasewait...

Discovering:AspNetCoreTodo.IntegrationTests

Discovered:AspNetCoreTodo.IntegrationTests

Starting:AspNetCoreTodo.IntegrationTests

Finished:AspNetCoreTodo.IntegrationTests

Totaltests:1.Passed:1.Failed:0.Skipped:0.

TestRunSuccessful.

Testexecutiontime:2.0588Seconds

Wrapup

Integrationtesting

110

Page 111: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

Testingisabroadtopic,andthere'smuchmoretolearn.Thischapterdoesn'ttouchonUItestingortestingfrontend(JavaScript)code,whichprobablydeserveentirebooksoftheirown.Youshould,however,havetheskillsandbaseknowledgeyouneedtolearnmoreabouttestingandtopracticewritingtestsforyourownapplications.

TheASP.NETCoredocumentation(https://docs.asp.net)andStackOverflowaregreatresourcesforlearningmoreandfindinganswerswhenyougetstuck.

Integrationtesting

111

Page 112: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

DeploytheapplicationYou'vecomealongway,butyou'renotquitedoneyet.Onceyou'vecreatedagreatapplication,youneedtoshareitwiththeworld!

BecauseASP.NETCoreapplicationscanrunonWindows,Mac,orLinux,thereareanumberofdifferentwaysyoucandeployyourapplication.Inthischapter,I'llshowyouthemostcommon(andeasiest)waystogolive.

DeploymentoptionsASP.NETCoreapplicationsaretypicallydeployedtooneoftheseenvironments:

ADockerhost.AnymachinecapableofhostingDockercontainerscanbeusedtohostanASP.NETCoreapplication.CreatingaDockerimageisaveryquickwaytogetyourapplicationdeployed,especiallyifyou'refamiliarwithDocker.(Ifyou'renot,don'tworry!I'llcoverthestepslater.)

Azure.MicrosoftAzurehasnativesupportforASP.NETCoreapplications.IfyouhaveanAzuresubscription,youjustneedtocreateaWebAppanduploadyourprojectfiles.I'llcoverhowtodothiswiththeAzureCLIinthenextsection.

Linux(withNginx).Ifyoudon'twanttogotheDockerroute,youcanstillhostyourapplicationonanyLinuxserver(thisincludesAmazonEC2andDigitalOceanvirtualmachines).It'stypicaltopairASP.NETCorewiththeNginxreverseproxy.(MoreaboutNginxbelow.)

Deploytheapplication

112

Page 113: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

Windows.YoucanusetheIISwebserveronWindowstohostASP.NETCoreapplications.It'susuallyeasier(andcheaper)tojustdeploytoAzure,butifyouprefermanagingWindowsserversyourself,it'llworkjustfine.

KestrelandreverseproxiesIfyoudon'tcareaboutthegutsofhostingASP.NETCoreapplicationsandjustwantthestep-by-stepinstructions,feelfreetoskiptooneofthenexttwosections.

ASP.NETCoreincludesafast,lightweightwebservercalledKestrel.It'stheserveryou'vebeenusingeverytimeyourandotnetrunandbrowsedtohttp://localhost:5000.Whenyoudeployyourapplicationtoaproductionenvironment,it'llstilluseKestrelbehindthescenes.However,it'srecommendedthatyouputareverseproxyinfrontofKestrel,becauseKestreldoesn'tyethaveloadbalancingandotherfeaturesthatmorematurewebservershave.

OnLinux(andinDockercontainers),youcanuseNginxortheApachewebservertoreceiveincomingrequestsfromtheinternetandroutethemtoyourapplicationhostedwithKestrel.Ifyou'reonWindows,IISdoesthesamething.

Ifyou'reusingAzuretohostyourapplication,thisisalldoneforyouautomatically.I'llcoversettingupNginxasareverseproxyintheDockersection.

Deploytheapplication

113

Page 114: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

DeploytoAzureDeployingyourASP.NETCoreapplicationtoAzureonlytakesafewsteps.YoucandoitthroughtheAzurewebportal,oronthecommandlineusingtheAzureCLI.I'llcoverthelatter.

Whatyou'llneed

Git(usegit--versiontomakesureit'sinstalled)TheAzureCLI(followtheinstallinstructionsathttps://github.com/Azure/azure-cli)AnAzuresubscription(thefreesubscriptionisfine)Adeploymentconfigurationfileinyourprojectroot

Createadeploymentconfigurationfile

Sincetherearemultipleprojectsinyourdirectorystructure(thewebapplication,andtwotestprojects),Azurewon'tknowwhichonetopublish.Tofixthis,createafilecalled.deploymentattheverytopofyourdirectorystructure:

.deployment

[config]

project=AspNetCoreTodo/AspNetCoreTodo.csproj

Makesureyousavethefileas.deploymentwithnootherpartstothename.(OnWindows,youmayneedtoputquotesaroundthefilename,like".deployment",topreventa.txtextensionfrombeingadded.)

Ifyoulsordirinyourtop-leveldirectory,youshouldseetheseitems:

DeploytoAzure

114

Page 115: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

.deployment

AspNetCoreTodo

AspNetCoreTodo.IntegrationTests

AspNetCoreTodo.UnitTests

SetuptheAzureresources

IfyoujustinstalledtheAzureCLIforthefirsttime,run

azlogin

andfollowthepromptstologinonyourmachine.Then,createanewResourceGroupforthisapplication:

azgroupcreate-lwestus-nAspNetCoreTodoGroup

ThiscreatesaResourceGroupintheWestUSregion.Ifyou'relocatedfarawayfromthewesternUS,useazaccountlist-locationstogetalistoflocationsandfindoneclosertoyou.

Next,createanAppServiceplaninthegroupyoujustcreated:

azappserviceplancreate-gAspNetCoreTodoGroup-nAspNetCoreTodo

Plan--skuF1

F1isthefreeappplan.Ifyouwanttouseacustomdomainnamewithyourapp,usetheD1($10/month)planorhigher.

NowcreateaWebAppintheAppServiceplan:

azwebappcreate-gAspNetCoreTodoGroup-pAspNetCoreTodoPlan-nM

yTodoApp

DeploytoAzure

115

Page 116: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

Thenameoftheapp(MyTodoAppabove)mustbegloballyuniqueinAzure.Oncetheappiscreated,itwillhaveadefaultURLintheformat:http://mytodoapp.azurewebsites.net

DeployyourprojectfilestoAzure

YoucanuseGittopushyourapplicationfilesuptotheAzureWebApp.Ifyourlocaldirectoryisn'talreadytrackedasaGitrepo,runthesecommandstosetitup:

gitinit

gitadd.

gitcommit-m"Firstcommit!"

Next,createanAzureusernameandpasswordfordeployment:

azwebappdeploymentuserset--user-namenate

Followtheinstructionstocreateapassword.Thenuseconfig-local-gittospitoutaGitURL:

azwebappdeploymentsourceconfig-local-git-gAspNetCoreTodoGrou

p-nMyTodoApp--outtsv

https://[email protected]/MyTodoApp.git

CopytheURLtotheclipboard,anduseittoaddaGitremotetoyourlocalrepository:

gitremoteaddazure<paste>

Youonlyneedtodothesestepsonce.Now,wheneveryouwanttopushyourapplicationfilestoAzure,checktheminwithGitandrun

DeploytoAzure

116

Page 117: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

gitpushazuremaster

You'llseeastreamoflogmessagesastheapplicationisdeployedtoAzure.

Whenit'scomplete,browsetohttp://yourappname.azurewebsites.nettocheckouttheapp!

DeploytoAzure

117

Page 118: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

DeploywithDockerIfyouaren'tusingaplatformlikeAzure,containerizationtechnologieslikeDockercanmakeiteasytodeploywebapplicationstoyourownservers.Insteadofspendingtimeconfiguringaserverwiththedependenciesitneedstorunyourapp,copyingfiles,andrestartingprocesses,youcansimplycreateaDockerimagethatdescribeseverythingyourappneedstorun,andspinitupasacontaineronanyDockerhost.

Dockercanmakescalingyourappacrossmultipleserverseasier,too.Onceyouhaveanimage,usingittocreate1containeristhesameprocessascreating100containers.

Beforeyoustart,youneedtheDockerCLIinstalledonyourdevelopmentmachine.Searchfor"getdockerfor(mac/windows/linux)"andfollowtheinstructionsontheofficialDockerwebsite.Youcanverifythatit'sinstalledcorrectlywith

dockerversion

AddaDockerfile

Thefirstthingyou'llneedisaDockerfile,whichislikearecipethattellsDockerwhatyourapplicationneedstobuildandrun.

CreateafilecalledDockerfile(noextension)intheroot,top-levelAspNetCoreTodofolder.Openitinyourfavoriteeditor.Writethefollowingline:

FROMmicrosoft/dotnet:2.0-sdkASbuild

DeploywithDocker

118

Page 119: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

ThistellsDockertousethemicrosoft/dotnet:2.0-sdkimageasastartingpoint.ThisimageispublishedbyMicrosoftandcontainsthetoolsanddependenciesyouneedtoexecutedotnetbuildandcompileyourapplication.Byusingthispre-builtimageasastartingpoint,Dockercanoptimizetheimageproducedforyourappandkeepitsmall.

Next,addthisline:

COPYAspNetCoreTodo/*.csproj./app/AspNetCoreTodo/

TheCOPYcommandcopiesthe.csprojprojectfileintotheimageatthepath/app/AspNetCoreTodo/.Notethatnoneoftheactualcode(.csfiles)havebeencopiedintotheimageyet.You'llseewhyinaminute.

WORKDIR/app/AspNetCoreTodo

RUNdotnetrestore

WORKDIRistheDockerequivalentofcd.Thismeansanycommandsexecutednextwillrunfrominsidethe/app/AspNetCoreTododirectorythattheCOPYcommandcreatedinthelaststep.

RunningthedotnetrestorecommandrestorestheNuGetpackagesthattheapplicationneeds,definedinthe.csprojfile.Byrestoringpackagesinsidetheimagebeforeaddingtherestofthecode,Dockerisabletocachetherestoredpackages.Then,ifyoumakecodechanges(butdon'tchangethepackagesdefinedintheprojectfile),rebuildingtheDockerimagewillbesuperfast.

Nowit'stimetocopytherestofthecodeandcompiletheapplication:

COPYAspNetCoreTodo/../AspNetCoreTodo/

RUNdotnetpublish-oout/p:PublishWithAspNetCoreTargetManifest="

false"

DeploywithDocker

119

Page 120: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

Thedotnetpublishcommandcompilestheproject,andthe-ooutflagputsthecompiledfilesinadirectorycalledout.

Thesecompiledfileswillbeusedtoruntheapplicationwiththefinalfewcommands:

FROMmicrosoft/dotnet:2.0-runtimeASruntime

ENVASPNETCORE_URLShttp://+:80

WORKDIR/app

COPY--from=build/app/AspNetCoreTodo/out./

ENTRYPOINT["dotnet","AspNetCoreTodo.dll"]

TheFROMcommandisusedagaintoselectasmallerimagethatonlyhasthedependenciesneededtoruntheapplication.TheENVcommandisusedtosetenvironmentvariablesinthecontainer,andtheASPNETCORE_URLSenvironmentvariabletellsASP.NETCorewhichnetworkinterfaceandportitshouldbindto(inthiscase,port80).

TheENTRYPOINTcommandletsDockerknowthatthecontainershouldbestartedasanexecutablebyrunningdotnetAspNetCoreTodo.dll.Thistellsdotnettostartupyourapplicationfromthecompiledfilecreatedbydotnetpublishearlier.(Whenyoudodotnetrunduringdevelopment,you'reaccomplishingthesamethinginonestep.)

ThefullDockerfilelookslikethis:

Dockerfile

FROMmicrosoft/dotnet:2.0-sdkASbuild

COPYAspNetCoreTodo/*.csproj./app/AspNetCoreTodo/

WORKDIR/app/AspNetCoreTodo

RUNdotnetrestore

COPYAspNetCoreTodo/../

RUNdotnetpublish-oout/p:PublishWithAspNetCoreTargetManifest="

false"

FROMmicrosoft/dotnet:2.0-runtimeASruntime

DeploywithDocker

120

Page 121: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

ENVASPNETCORE_URLShttp://+:80

WORKDIR/app

COPY--from=build/app/AspNetCoreTodo/out./

ENTRYPOINT["dotnet","AspNetCoreTodo.dll"]

Createanimage

MakesuretheDockerfileissaved,andthenusedockerbuildtocreateanimage:

dockerbuild-taspnetcoretodo.

Don'tmissthetrailingperiod!ThattellsDockertolookforaDockerfileinthecurrentdirectory.

Oncetheimageiscreated,youcanrundockerimagestotolistalltheimagesavailableonyourlocalmachine.Totestitoutinacontainer,run

dockerrun--nameaspnetcoretodo_sample--rm-it-p8080:80aspnet

coretodo

The-itflagtellsDockertorunthecontainerininteractivemode(outputtingtotheterminal,asopposedtorunninginthebackground).Whenyouwanttostopthecontainer,pressControl-C.

RemembertheASPNETCORE_URLSvariablethattoldASP.NETCoretolistenonport80?The-p8080:80optiontellsDockertomapport8080onyourmachinetothecontainer'sport80.Openupyourbrowserandnavigatetohttp://localhost:8080toseetheapplicationrunninginthecontainer!

SetupNginx

DeploywithDocker

121

Page 122: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

Atthebeginningofthischapter,ImentionedthatyoushoulduseareverseproxylikeNginxtoproxyrequeststoKestrel.YoucanuseDockerforthis,too.

Theoverallarchitecturewillconsistoftwocontainers:anNginxcontainerlisteningonport80,forwardingrequeststothecontaineryoujustbuiltthathostsyourapplicationwithKestrel.

TheNginxcontainerneedsitsownDockerfile.TokeepitfromconflictingwiththeDockerfileyoujustcreated,makeanewdirectoryinthewebapplicationroot:

mkdirnginx

CreateanewDockerfileandaddtheselines:

nginx/Dockerfile

FROMnginx

COPYnginx.conf/etc/nginx/nginx.conf

Next,createannginx.conffile:

nginx/nginx.conf

events{worker_connections1024;}

http{

server{

listen80;

location/{

proxy_passhttp://kestrel:80;

proxy_http_version1.1;

proxy_set_headerUpgrade$http_upgrade;

proxy_set_headerConnection'keep-alive';

proxy_set_headerHost$host;

proxy_cache_bypass$http_upgrade;

}

DeploywithDocker

122

Page 123: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

}

}

ThisconfigurationfiletellsNginxtoproxyincomingrequeststohttp://kestrel:80.(You'llseewhykestrelworksasahostnameinamoment.)

Whenyoumakedeployyourapplicationtoaproductionenvironment,youshouldaddtheserver_namedirectiveandvalidateandrestrictthehostheadertoknowngoodvalues.Formoreinformation,see:

https://github.com/aspnet/Announcements/issues/295

SetupDockerCompose

There'sonemorefiletocreate.Upintherootdirectory,createdocker-compose.yml:

docker-compose.yml

nginx:

build:./nginx

links:

-kestrel:kestrel

ports:

-"80:80"

kestrel:

build:.

ports:

-"80"

DockerComposeisatoolthathelpsyoucreateandrunmulti-containerapplications.Thisconfigurationfiledefinestwocontainers:nginxfromthe./nginx/Dockerfilerecipe,andkestrelfromthe./Dockerfilerecipe.Thecontainersareexplicitlylinkedtogethersotheycancommunicate.

DeploywithDocker

123

Page 124: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

Youcantryspinninguptheentiremulti-containerapplicationbyrunning:

docker-composeup

Tryopeningabrowserandnavigatingtohttp://localhost(port80,not8080!).Nginxislisteningonport80(thedefaultHTTPport)andproxyingrequeststoyourASP.NETCoreapplicationhostedbyKestrel.

SetupaDockerserver

Specificsetupinstructionsareoutsidethescopeofthisbook,butanymodernflavorofLinux(likeUbuntu)canbeusedtosetupaDockerhost.Forexample,youcouldcreateavirtualmachinewithAmazonEC2,andinstalltheDockerservice.Youcansearchfor"amazonec2setupdocker"(forexample)forinstructions.

IlikeusingDigitalOceanbecausethey'vemadeitreallyeasytogetstarted.DigitalOceanhasbothapre-builtDockervirtualmachine,andin-depthtutorialsforgettingDockerupandrunning(searchfor"digitaloceandocker").

DeploywithDocker

124

Page 125: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

ConclusionThanksformakingittotheendoftheLittleASP.NETCoreBook!Ifthisbookwashelpful(ornot),I'dlovetohearyourthoughts.SendmeyourcommentsviaTwitter:https://twitter.com/nbarbettini

HowtolearnmoreThere'salotmorethatASP.NETCorecandothatcouldn'tfitinthisshortbook,including

BuildingRESTfulAPIsandmicroservicesUsingASP.NETCorewithsingle-pageappslikeAngularandReactRazorPagesBundlingandminifyingstaticassetsWebSocketsandSignalR

Thereareanumberofwaysyoucanlearnmore:

TheASP.NETCoredocumentation.TheofficialASP.NETCoredocumentationathttp://docs.asp.netcontainsanumberofin-depthtutorialscoveringmanyofthesetopics.I'dhighlyrecommendit!

ASP.NETCoreinAction.ThisbookbyAndrewLockisacomprehensive,deepdiveintoASP.NETCore.YoucangetitfromAmazonoralocalbookstore.

CoursesonLinkedInLearningandPluralsight.Ifyoulearnbestfromvideos,therearefantasticcoursesavailableonPluralsightandLinkedInLearning(includingsomebyyourstruly).Ifyoudon'thaveanaccountandneedacoupon,sendmeanemail:[email protected].

Conclusion

125

Page 126: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

Nate'sblog.IalsowriteaboutASP.NETCoreandmoreonmyblogathttps://www.recaffeinate.co.

Happycoding!

AbouttheauthorHey,I'mNate!IwrotetheLittleASP.NETCoreBookinalong,caffeine-fueledweekendbecauseIlovethe.NETcommunityandwantedtogivebackinmyownlittleway.Ihopeithelpedyoulearnsomethingnew!

YoucanstayintouchwithmeonTwitter(@nbarbettini)oronmyblog(https://www.recaffeinate.co)[email protected].

SpecialthanksToJennifer,whoalwayssupportsmycrazyideas.

TothefollowingcontributorswhoimprovedtheLittleASP.NETCoreBook:

0xNFMattWelke

TotheseamazingpolyglotprogrammerswhotranslatedtheLittleASP.NETCoreBook:

sahinyanlik(Turkish)windsting,yuyi(SimplifiedChinese)

Changelog

Conclusion

126

Page 127: Table of Contents · PDF filevery last page of the book contains version information and a changelog. Reading in your own language Thanks to some fantastic multilingual folks, the

Thefull,detailedchangelogisalwaysavailablehere:

https://github.com/nbarbettini/little-aspnetcore-book/releases

1.1.0(2018-05-03):SignificantlyreworkedtheAddmorefeatureschaptertouseMVCthoroughthewholestackandremovetheAJAXpattern.RemovedFacebooklogintosimplifythesecuritychapterandstreamlinetestinganddeployment.UpdatedtheDockerinstructionstoreflectthelatestbestpractices.Fixedtyposandaddedsuggestionsfromreaders.Thebookalsosportsanew,improvedcoverdesign!

1.0.4(2018-01-15):Addedexplanationofservicecontainerlifecycles,clarifiedserverportsandthe-oflag,andremovedsemicolonsafterRazordirectives.CorrectedChinesetranslationauthorcredit.Fixedothersmalltyposandissuesnoticedbyreaders.

1.0.3(2017-11-13):Typofixesandsmallimprovementssuggestedbyreaders.

1.0.2(2017-10-20):Morebugfixesandsmallimprovements.Addedlinktotranslations.

1.0.1(2017-09-23):Bugfixesandsmallimprovements.

1.0.0(2017-09-18):Initialrelease.

Conclusion

127