• No results found

Lektion 6 MVC 3 - Övningar

N/A
N/A
Protected

Academic year: 2022

Share "Lektion 6 MVC 3 - Övningar"

Copied!
15
0
0

Loading.... (view fulltext now)

Full text

(1)

Lektion 6 – MVC 3 - Övningar

Git/Mitt Repo:

En zip för mitt repo innan uppgift 1 kan hämtas ut här:

https://github.com/awt2gbg2012/Lektion-6New/zipball/v0.0

Ett bättre Alternativ är att du forkar mitt repo och kör ”git checkout v0.0” i ditt lokala repo – då kommer du gå tillbaka till commiten jag taggat med v0.0 – vilket är precis innan uppgift 1.

För att skapa en ny branch med start från v0.0 så skriver du därefter ”git checkout –b

branch_namn_du_satter_sjalv”. Därefter kan du jobba med commits, etc precis som vanligt (men du kommer nu commita till den nya branchen och inte till master-branchen).

Om du gör såhär kan du lätt byta mellan branches. För att t.ex. tillfälligt kolla min uppgift 45 (5 st.

commits tidigare än den senaste commiten). Så kan du skriva ”git checkout master~5” Där ~5 alltså innebär att du vill stega tillbaka 5 commits från min senaste commit (Visual Studio kommer nu fråga ifall du vill uppdatera dina filer – svara Yes to All – nu har du uppe min lösning för uppgift 45). Efter du kollat klart så skriver du ”git checkout branchnamn_du_satte_tidigare”, svarar Yes to All i VS igen, så är du tillbaka i din branch igen.

Ett par notes om min branch och v0.0-versionen:

 Du bör använda den senaste ”/App_Data/NotUser/Extra för lab.txt”-filen – jag gjorde lite tillägg i den under resans gång (plocka den ur master-branchens HEAD – alt. Gå in på mitt repo -> commits -> Uppgift 32-33 + Extra CSS -> View File på ”Extra för lab.txt” och kopiera in hela den texten i din textfil).

 Du bör ta bort mappen ”ViewModels” och allt innehåll från hårddisken (Denna mapp är inte med i projektet och syns därmed inte i VS. Du kommer lägga till denna själv senare och kommer få ett felmeddelande ifall mappen finns där).

 Du bör även bort mappen ”Users” i Views från hårddisken – samma anledning som ovan.

Utgångspunkt

Standardmallen för MVC 3 Internet application.

En bild ”avatar.png” som ligger i /Content/Images/

Lite extra CSS (ligger under ett eget stycke sist i Site.CSS) De tidigare Klasserna: User, Post

De nya klasserna News, Thread (Kommer ej användas idag, men kommer användas i Lab 2)

(2)

Våra Data-objekt-klasser implementerar Interfacet IEntity – det enda interfacet kräver är att en property ID skall implementeras. Vi använder oss av detta för att i Repository kunna begränsa vilka objekt som kan användas i vår Repository-klass.

En Modifierad Repository-klass:

Tidigare: Repository Repo = new Repository(); List<User> users = Repo.GetUsers();

Nu: List<User> users = Repository.Instance.All<User>();

Tidigare: Repository Repo = new Repository();User user = Repo.GetUser(id);

Nu: User user = Repository.Instance.Get<User>(id);

Tidigare: Repository Repo = new Repository(); Repo.AddUser(user);

Nu: Repository.Instance.Save<User>(user);

Tidigare: Repository Repo = new Repository(); User users = Repo.RemoveUser(user);

Nu: Repository.Instance.Remove<User>(user);

Dvs.

Repository.Instance är static – du behöver inte instansiera Repository (Repository Repo = new Repository();)

Denna version av Repository är Generic – Den fungerar på User, Post, News, Thread.

Precis som att en klass kan vara generic (T.ex. List<string> som innebär ”Jag vill använda en variant av List<> som hanterar strängar”) så kan en metod vara generic, (T.ex. .All<User>() som innebär ”Jag vill använda en variant av metoden All<>() som hanterar Users”).

T.ex. Används ”List<Post> posts = Repository.Instance.All<Post>();” för att hämta alla poster

Målbild

Till hemsidan som är vår utgångspunkt så skall vi lägga till följande:

Login

Vi skall implementera tillräckligt av en login/signoff-lösning för att man skall kunna logga in, logga av och för att applikationen skall veta ifall den nuvarande användaren är inloggad eller inte.

User List sida

Vi skall göra en Users-sida som listar de 10 första användarna i bokstavsordning och som visar lite minimal info för varje user.

Varje user skall ha en länk som leder till en sida med detaljer för en user. Ifall användaren som klickar på länken är inloggad eller är den aktuella användaren skall samma länk istället gå till en sida för att editera en user.

(3)

Om användaren är inloggad som admin, så skall det finnas en länk som leder till en sida för att skapa en ny användare.

Vid tid: gör så att det går att bläddra fram och tillbaka mellan alla användare, 10 användare i taget i listan.

User Details sida

På User Details-sidan skall en användare, en avatar-bild, samt dess 5 senaste posts visas (Här skulle kunna länka till en Post-details sida, men vi kommer inte skapa en sådan sida idag – ni skulle lätt kunna göra det med infon från dagens föreläsning dock).

User Create sida

På user create sidan skall du kunna fylla i UserName, FirstName, LastName och skapa en användare.

Sidan skall validera att UserName är satt.

User Edit sida

På user edit sidan skall du kunna ändra UserName, FirstName, LastName och uppdatera en användare. Sidan skall validera att UserName är satt.

Implementation/Hjälpmedel

Vi kommer göra allt detta ”från scratch” – Vi kan sätta upp motsvarande lösning (med en riktig databas i botten, skapad för att passa vår modell) på några minuter med MVC 3s scaffolding-mallar (jag gissar att ni jobbat på detta sätt i php). Problemet med scaffolding är att det är en generisk lösning som aldrig passar perfekt för det man tänkt sig – dvs Efter man scaffoldat en site behöver man gå in och göra justeringar och då behöver man veta vilka komponenter som man behöver justeras och hur de skall justeras. Scaffolding är ett utmärkt arbetssätt som kan spara väldigt mycket tid – men för att använda det så behöver man också kunna grunderna.

Om man funderar vidare lite kring funktionaliteten som beskrivs i målbilden så inser man att man kan komma väldigt långt med denna funktionalitet – många lite större weblösningar går att fixa med denna funktionalitet om vi bara hade haft en databas i botten (Om du kommer på exempel som kanske skulle fungera, men som känns som de skulle vara kantiga med det vi gått igenom så beror det förmodligen på att man skulle lösa problemet med Ajax eller Ajax + Service-anrop på en riktig site)

Vilka ”större” puzzelbitar saknar vi efter vi kan göra detta?

Databas (Torsdag).

Ajax + Service-anrop (Kursvecka 4).

Deployment (kursvecka 5?).

Plugga in 3:e parts komponenter (kursvecka 4/5?)

+ fördjupande kunskap MVC 3 (Routing, Helpers, Etc – kursvecka 3 och frammåt) (Putsa på HTML + CSS – men detta har ni gjort tidigare).

(4)

(Säkerhet – kursvecka 4 eller tidigt under projektet) (Optimeringar – under projektet)

(Integration med externa API:er – under projektet)

Arbetsgång för övningar

Jag kommer gå igenom dessa övningar på föreläsningen och checka in till mitt repository för varje uppgift. När föreläsningen är klar så pushar jag till githib. Under övningstiden skall ni sjäva utföra dessa övningar.

För er som känner er mer säkra så rekommenderar jag att ni utför dem utan att titta på mina lösningar – när ni stöter på problem – försök hitta en lösning på egen hand innan ni tittar på mina lösningar.

För er som känner att det var för mycket att ta till sig på en gång – arbeta er igenom övningarna genom att iprincip kopiera in mina lösningar från github – försök förstå lösningarna (försök göra detta ganska snabbt – lägg inte för mycket tid på detta). Därefter vill jag att ni gör övningarna igen, fast den här gången försöker ni själva först innan ni sneglar på mina lösningar. Fortsätt med detta tills ni kan lösa merparten av övningarna på egen hand (Login-övningarna är inte så viktiga om ni inte kan lösa på egen hand)

Övningar

Tips: Om du vill slippa att VS öppnar en ny sida varje gång du kör projektet: Project Settings -> Web ->

Don’t open a Page. Du kan sedan öppna websidan genom att högerklicka på ASP.NET Development Server och ta ”Open in Web browser”. ASP.NET Development Server ligger i din systems tray när du kör projektet.

Ifall du har problem med att referera till typen User – kontrollera vilken typ det är du refererar till (det finns User i andra dll:er också) – i värsta fall får du referera till Lektion6.Models.Entities.User istället. Sådant här kan kringås genom att inte använda biblioteken som använder User, eller att namnge vår User t.ex. Lektion6User istället.

Ifall du tycker någon uppgift är otydligt formulerad eller ifall du känner att det saknas några steg – maila, så kan jag förklara eller omformulera uppgiften. Tänk också på att du har tillgång till mitt Repository dä

Bli inte skrämd av det stora antalet uppgifter. De flesta uppgifterna går väldigt snabbt att utföra. Om ni kikar på koden i tillagda vys/kontrollers så kommer ni se att det är ganska lite kod tillagt i

slutresultatet.

Login

Uppgifterna för login är inget du behöver ha stenkoll på. Jag vill däremot att ni har sett hur det implementeras så att det inte känns som magi. Dessutom får ni lite övning i att skriva C#.

(5)

Vi kommer fejka lösenordet för en användare genom att skapa en property, ”Password”. Denna property kommer returnera en hash av användarens UserName som vi kommer hantera som ett lösenord. Normalt sett så är ju lösenordet någon form av hash som hämtas från en databas – så principen är den samma i en riktig lösning för lösenord.

1. Skapa ett lösenordsproperty för user. För att göra det enkelt så skapar vi en property

”Password” som är en SHA1-hash av UserName.

Detta gör vi med följande (du behöver bara kunna använda detta, ej förstå det):

// Hashfunktionen behöver en Byte-array som input var data = Encoding.Unicode.GetBytes(UserName);

// Här skapar vi vår hash

var hashData = new SHA1Managed().ComputeHash(data);

// Vi vill returnera en strängrepresentation av hashen return Convert.ToBase64String(hashData);

Snabb förklaring av hash:

När man hanterar lösenord så har man oftast sparat en hash av lösenordet i databasen. När någon sedan loggar in så skapar man en hash av lösenordet som användaren angett vid login – ifall denna hash stämmer överrens med den hash som sparats i databasen så loggas användaren in.

En funktion för att skapa en hash är en funktion som från en input av variabel längd skapar en output med bestämd längd, där det ej skall gå att återskapa input från output på ett enkelt sätt.

Ett enkelt exempel på en Hash-funktion för en sträng är att vi tilldelar alla tecken ett värde (A = 1, B = 2, etc). Vi översätter alla tecken till deras värde och adderar dem (Tex. ”password” => (16 + 1 + 19 + 19 +23 + 15 + 18 + 4 = 115)). För att få en output med bestämd längd kapar vi sedan alla värdesiffror för hundra eller större (115 => 15).

Resultatet är vår hash (Och ifall någon kommer över vår hash så vet de inte att lösenordet är ”password”, det skulle likagärna vara t.ex. ”abbai” eller en stor mängd andra lösenord som resulterar i samma hash – Den här hashen har dock en annan brist: Den är för kort! Det finns bara 100 olika värden i hashen, dvs. en angripare beöver bara prova som mest 100 lösenord för att ta sig in på en site med denna hash.

Exemplet är en 7-bitars hash, vanligtvis är en hash hundratals bitar för att undvika detta problem).

De Hashfunktioner man använder i verkligeheten är mer komplicerade, men principen är den samma (SHA1 är ett exempel på en sådan Hashfunktion, men det finns fler).

OBS! Ifall ni skall göra en egen lösenordslösning så bör ni dels överväga vilken hash- funktion ni skall använda och dels bör ni minst även använda en salt för lösenordet – Vi kommer inte ta upp detta i kursen - läs mer om detta på nätet. Om det är möjligt

(6)

bör ni dock undvika att skapa egna lösenords-lösningar. Använd t.ex. MS membership eller GoogleID eller liknande.

2. Skapa en SessionManager som har till uppgift att sköta logiken för login och SignOut.

SessionManager läggs i ”Models/Managers/SessionManager.cs”. SessionManager skall även hålla reda på den nuvarande användaren, CurrentUser.

3. Implementera ValidateUser, SignOut och CurrentUser i SessionManager, alla 3 skall vara static. ValidateUser skall ta ett användarnamn och lösenord och kontrollera att användaren existerar och att lösenordet stämmer för användaren – om så inte är fallet skall false

returneras. Ifall användaren och lösenordet är korrekta sätts CurrentUser till det User-objekt som användarnamnet motsvarar och metoden returnerar true.

(Tänk på att hasha lösenordet som kommer in i ValidateUser innan det jämförs med ett User- objekts lösenord – om vi jämför en sträng med hash:en av samma sträng kommer vi få false.

Vi kan använda samma metod för att hasha det inkommande lösenordet som vi använde i Password-propertien för User i uppgift 1).

SignOut skall sätta CurrentUser till false.

4. Använd SessionManager i Accounts-controller istället för den nuvarande lösningen för login/SignOut (Ersätt de nuvarande ValidateUser och SignOut-funktionerna med dina funktioner i SessionManager. Ta samtidigt bort raden

FormsAuthentication.SetAuthCookie(…). Denna rad sätter en cookie som gör att sidan kommer ihåg om en användare har loggat in – vi hanterar inte borttagning av cookies i vår SignOff, så därför tar vi helt enkelt bort raden där cookien sätts).

User List sida

Nu skall vi börja bygga vår User List sida. Den generella arbetsgången för att skapa en ny sida är att först fundera över vad det är du vill visa på sidan och sedan börja arbeta med vår Controller. När vi har grundläggande funktionalitet i Controllern så skapar vi en vy. Vi vet att vi vill visa en lista av användare på Sidan, så vi behöver även kunna skicka data från vår Controller till vyn. Denna data kommer sedan formateras och visas på vyn mha HTML/CSS och @Razor-syntax (som är ett sätt att skriva C#-uttryck direkt i vyn).

Vi börjar med att skapa en UsersController. Controllers bör ges namn som slutar på –Controller för att vyer som hör ihop med denna kontroller skall hittas automatiskt, etc.

5. Skapa en empty UsersController (Om du högerklickar i Controllers-mappen så kommer du ha alternativet ”New -> Controller”).

I det här fallet så är målet en sida som visar en lista över användare. Det verkar rimligt att kalla denna sida Index och att url:en ”http://[domän]/Users/Index” skall räcka för att visa listan - dvs. vi behöver inga in-parametrar som t.ex. (”http://[domän]/Users/Index/FormattedList”) i grundutförandet. Från

(7)

detta kan vi utläsa att vi kommer behöva en Action-method ”Index” i UsersController, att denna metod inte skall ta några in-parametrar och att metoden skall returnera en View().

6. Lägg till en ActionMethod för Index i UserController. Index skall inte ta några paramtrar och den skall Returnera ett ActionResult (Denna är ev. redan skapad åt dig)

7. Returnera View() från metoden. (denna är ev. redan skapad åt dig)

Nu behöver vi data till controllern.

Vi kommer använda repository-metoder för att plocka ut data till våra sidor. De flesta av dessa data- förfrågningar mot repository kommer resultera i att vi får skapa kedjor av Linq-uttryck. Vi vill undvika att skriva den logiken i Controller – eftersom vi vill hålla logiken i Controller till ren input/output- hantering (skinny controller/fat model) och det mycket väl kan tänkas att vi vill återanvända flera av frågorna på andra ställen i applikationen.

8. Vi behöver en metod för att plocka ut listor av användare. För att slippa skriva flera sådana så bygger vi in lite extra funktionalitet från början – i det här fallet, skip och take. Lägg till en metod, GetSortedUsers(int take, int skip) i Repository. Den skall returnera så många användare som anges i take, med början på användaren med index skip + 1.

9. Använd GetSortedUsers för att plocka ut de 10 första användarna i Index-metoden i UserController. Tilldela dessa till ett List<User>-objekt kallat users.

Nu behöver vi föra över datan i Controllern till vyn på något sätt. Vi kommer göra detta initialt med ViewBag-objektet. Båda Controllern och dess motsvarande Vy har tillgång till detta objekt. Detta objekt är ett dynamiskt-objekt . Detta innebär att du kan ”hänga på” extra information på objektet lite hur du vill. Om du t.ex. skriver ”ViewBag.MyInteger = 3;” så kan du sedan komma åt värdet med

”ViewBad.MyInteger”, trots att .MyInteger inte fanns på ViewBag-objektet från början.

10. Tilldela users till ViewBag.Users

Nu är det dags att skapa vår vy för Index-metoden i UsersControll. Konventionen är att en metods vy ligger under ”Views/[ControllerPrefix]/[ActionMethod].cshtml” i projektet. Dvs i vårt fall:

”Views/Users/Index.cshtml”. När man skriver ”return View();” i en actionmethod så är det i denna mapp som Controllern kommer leta efter sin vy först. En .cshtml fil är en fil som innehåller C#-kod och html-mixat. Webservern kommer utifrån denna fil generera en ren html-sida och presentera denna när en http-request kommer (sidorna kommer kompileras en gång och sedan cachas – så webbservern behöver inte kompilera om sidorna varje gång någon frågar efter en sida).

11. Skapa en vy för Index genom att höger-klicka i någonstanns i Index-metoden och välja ”New View” (Denna vy kommer läggas i /Views/Users/Index.cshtml)

12. I Index-vyn för Users: Lägg till ett @using-direktiv i vyn för att slippa skriva in namespace för User då du använder typen i @Razor-uttryck i vyn.

(8)

13. Plocka ut användarna ur ViewBag.Users och lägg dem i en List<User> users i det befintliga

@Razor-blocket: @{…} på sidan.

Vi vill ha en länk till vår users-sida från start-sidan i appplikationen. De nuvarande länkarna till

”Home” och ”About” definieras i filen /Views/Shared/_Layout.cshtml – denna sida är ”master-sidan”

för applikationen – här kommer Header, Footer, ToppMeny, ev. Sidomenyer, etc skapas – dvs. allt sådant som visas på en sida i webbbrowsern, men som är gemensamt för hela applikationen. För att skapa länkar använder vi en s.k. Html-helper. Html-helpers är små funktioner (Extension Methods) som hjälper oss med att skapa allt från element på sidan till att validera input mm. För att skapa en länk används Html.ActionLink(…) ActionLink har flera olika utföranden, den vi är ute efter tar 3 parametrar, där den första är Texten som skall visas för länken på hemsidan, den andra parametern tar namnet på den ActionMethod som skall hantera ett klick på länken och den tredje parametern är vilken kontroller (utan ändelsen –Controller) som . Eftersom vi kommer använda uttrycket utanför ett Razor-block (@{…}) lägger vi till @ innan Html.ActionLink för att markera att det är ett C#-uttryck (@Html.ActionLink(”Users”, ”Index”, ”Users”) – inget semikolon behövs här).

14. Lägg till ett meny-alternativ för users i ”_Layout.cshtml” på liknande sätt som de befintliga länkarna – vi vill dock att denna länk skall gå till en UserControllers Index-metod. (Inte till metoder i en HomeController som för de befintliga länkarna).

Nu skall vi fortsätta jobba med vår vy – dags att presentera data för användare!

15. I Index.cshtml för Users: Skapa ett html-table och använd Razor-syntax @foreach på users för att lägga till alla användare i ditt table. Gör två kolumner, en för FullName och en för

UserName. Du kan skriva ut info för varje användare i foreach-loopen med t.ex.

@user.FullName (om du plockar ut user ur users i foreach-loopen, annars får du ersätta user med det variabelnamn du använder).

(Om du känner stor avsky inför HTML-table så får du givetvis lösa det med en list eller liknande – undvik HTML-table generellt, men för tabulär-data är den ok ;)

16. Ändra UserName så att det istället är en Länk till en Details-sida med

@Html.ActionLink(item.UserName, "Details", "Users", new { id = item.U serName}, null)

item är det user-objekt du tar ut i foreach-loopen. Details är vilken Action länken skall leda till, User är vilken Controller den skall leda till. New { id = item.ID} är en parameter vi

använder i länken för att se till att hamnar på rätt users Details-sida. Den sista parametern är ev. html-attritbut som vi lägger till på den resulterande anchor-taggen (ex. för att använda i en javascript-funktion).

User Details sida

17. Skapa en GetUserByUserName(string UserName) i repository som returnerar en user baserat på dess UserName.

(9)

När vi nu skall föra över UserName från vyn till kontrollern så gör vi det genom att länken vi skapade i uppg. 11 går till en viss url i vår applikation. Närmare bestämt kommer vi utnyttja en routing för att överföra UserName som en id-parameter i vår routing-path.

Routing-pathen hittar du i Global.asax i funktionen RegisterRoutes. Den standard routing-pathen som är skapad i template-projektet vi utgick ifrån ser ut som följer: ”{Controller}/{Action}/{id}”. Denna innebär att på domänen för vår applikation så kan vi föra på en 3-delad path som talar om för vår applikation vilken controller och action-method som skall ansvara för requesten. Utöver detta kan vi även haka på ett id som parameter. Vi vill kunna visa en details-sida för en user genom att gå till adressen http://[domän]/Users/Details/[UserName]. Där [UserName] är UserName för den aktuella användaren.

Om vi tittar på routing-pathen så ser vi att den sista parametern heter ”id”. Det är därför viktigt att input parametern i vår action-method heter ”id” – annars kommer vi inte få någon in-parameter till metoden. Det är inte nödvändigt att ange parametern som id i vyn, men det är en bra idé att göra det ändå, så att man snabbt kan se vilken parameter i vyn som hör ihop med vilken parameter i controllerns action-metod.

18. Skapa en ny Action I UserController, ”Details”. Denna action skall ta string id som in- parameter.

19. Skapa ett User-objekt, ”user” och hämta det med GetUserByUserName (UserName) baserat på det id som kommer som in-parameter.

För User List sidan så skickade vi data till vyn med ViewBag-objektet. Eftersom detta objektet är dynamiskt så innebär det flera nackdelar – vi vet inte vad objektet innehåller förrän vi använder det (Dvs när sidan körs), detta kan leda till buggar. T.ex. så kanske vi stavat fel på det vi försöker plocka ut i vyn – vi har lagt till ViewBag.Users i Controllern, men försöker plocka ut ViewBag.Ussre ur ViewBag-objektet i vyn (Detta ges ej felmeddelanden för då projektet kompileras eftersom objektet är dynamic). Eftersom vi inte vet vilka typer ViewBag innehåller kan vi behöva göra massa casts i vår

@Razor-kod för att använda objekten. Vidare finns det en stor mängd hjälpmetoder som snabbar upp allt från Display till Validering som inte fungerar på ViewBag-objekt.

För att undvika att använda ViewBag så kommer vi istället använda vad som kallas för en ”Strongly Typed View” som innebär att datan som skickas från Controller till vy har en fastställd typ (t.ex. User).

Detta innebär också att vi kommer ha tillgång till intellisense i visual studio för objektet som skickas från Controller till View. För att åstadkomma detta så ger vi objektet som parameter till den View() som vi returnerar i Controller (med t.ex. ”return View(user);” för ett user-objekt vi har definierat längre upp i actionmetoden). Vi anger längst upp på vy-sidan vilken typ objektet som skickas till vyn har, med @model-direktivet (T.ex. genom @model User). Lägg märke till att @model är litet m. Du kommer sedan åt objektet i din Razor-syntax med ”Model”. Så för ett User-objekt kan vi t.ex. plocka ut UserName med ”Model.UserName”, etc.

20. Returnera View(user) från metoden.

21. Skapa en ny vy, ”Details” genom att högerklicka någonstans i Action-metoden och välj New View

(10)

22. I vyn: Gör vyn starkt typad, med User som modell. Använd ”@model User”

Nu skall vi jobba lite mer med @Html-helpers.

Ett vanligt problem då man visar innehåll baserat på klass-properties eller databas-fält är att det enda man har tillgång till är namnet på databas-fältet, som är t.ex. ”FullName” medans det man vill visa för användaren är ”Full name” (med mellanslag). Ett annat exempel är booleans, där vissa använder en konvention att man alltid inleder namn på boolean properties och variabler med ”Is”

eller ”Has” för att lätt känna igen en bool i koden. Så en bool för en användare som är gift skulle vara

”IsMarried”, men vi vill visa detta på hemsidan som en kryssruta med labeln ”Married”.

Denna formatering är inget vi vill göra manuellt, utan vi vill säga att ”FullName” alltid skall visas som

”Full name” på vår hemsida och sedan sköts den formateringen automatiskt. Till vår hjälp har vi

@Html-helpers och DataAnnotations. DataAnnotations är ett sätt att dekorera properties i våra klasser för att t.ex. ha ett annat Display-name än deras faktiska namn. Vi använder @Html.LabelFor(i

=> i.FullName) på alla de ställen där vi skriver skapar en label för FullName .cshtml-filerna. Därefter ändrar vi DisplayName för FullName till ”Full name” och ändringen slår igenom för alla labels för FullName. Om vi senare vill ändra ”Full Name” till bara ”Name” överallt på vår hemsidan så ändrar vi DisplayName på ett ställe i koden till ”Name” och plötsligt så står det Name överallt på vår hemsida där det tidigare stod Full Name.

Ofta är den exakta benämningen på fält på en hemsida väldigt viktigt för en ägare till en hemsida och det är inte ovanligt att man av en eller annan anledning ändrar benämningar – om då alla

benämningar är hårdkodade i källkoden på flera hundra sidor så kan en ändring som borde ta sekunder plötsligt ta flera timmar. Gör det därför till en vana att använda

@Html.DisplayName/@Html.LabelFor/@Html.EditorFor när ni skriver ut labels och fält för dataklasser (detta har även andra fördelar som vi kommer kika på senare i kursen).

För att sätta DisplayName på ett fält i en klass så skriver man t.ex. ”[DisplayName(”Full name”)]”

precis ovanför deklarationen av propertyn. DisplayName är ett exempel på en DataAnnotation (För att använda DataAnnotation för DisplayName behöver du ett using-direktiv för

System.ComponentModel).

23. I Vyn: Skriv ut lämplig användarinformation i en div (En HTML-snutt för detta finns i

”App_Data/NotUsed/extra för lab.txt”). Fyll på den med de Razor-uttryck som behövs.

(Använd @Html.LabelFor()/@Html.DisplayFor)

24. Vi skriver ut FirstName som label för FirstName-propertyn – ändra så att ”First name” visas istället mha [DisplayName(”First name”)] precis ovanför FirstName i User-klassen. Gör samma sak med LastName.

Nu tänker vi oss att denna användarinfo div kommer återanvändas på flera ställen i applikationen, t.ex. på en användares profil sida. Eller ifall vi senare skall lägga till möjligheten att vissa Artiklar så vill vi kunna visa denna div som information för vem författaren av artikeln var. Det går att lösa det genom att använda Copy-Paste och klistra in en kopia av diven i de olika vyerna där infon skall visas.

(11)

Men detta är en dålig lösning då vi vid ett senare tillfälle kanske vill lägga till ett extra fält för ”email” i div:en och då måste ändra html/@razor-syntax på ett flertal olika sidor.

Lösningen på detta är Partial-Views. En PartialView är helt enkelt en del-vy som är tänkt att återanvändas som en del av flera andra vyer. Namngivningskonventionen är att man namnger PartialViews med ”_” ibörjan av namnet.

25. Flytta div:en för användarinfo och dess innehåll till en Partial View,

”_UserDetailsPartial.cshtml” (Skapa ny vy i Views/Users/-mappen – i dialogrutan för att skapa en ny vy så finns alternativet ”Create as Partial View”). Din PartialView skall vara starkt typad med ett modell-objekt av typen User.

26. Använd ”_UserDetailsPartial.cshtml” för att visa användar-informationen igen på vår Details- sida mha @Html.Partial(”_UserDetailsPartial”, Model);

Vi vill även lista de 5 senaste posterna för en användare på Details-sidan. För det vill vi använda en ViewModel. ViewModel är ett slags model-objekt som är anpassat för att skicka lite mer komplicerad eller specialanpassad information från Controller till View. I grund och botten är ViewModel en helt vanlig klass, som vi använder för att skicka data mellan Controller och vy. I det här fallet vill vi t.ex.

skicka både en användare och en lista med dess 5 senaste poster till vyn. Vi har inget Model-objekt som kan innehålla bägge dessa objekt. För att lösa detta skapar vi en klass som innehåller en User och en List<Post> och skickar med ett objekt av denna klass till vyn.

27. Skapa en mapp ”ViewModels”.

28. Skapa en ny klass UserDetailsViewModel i ”ViewModels”-mappen.

29. UserDetailsViewModel skall innehålla ett User-objekt och en lista med Post.

30. Skapa en ny Repository-metod som kan plocka ut de 5 senaste posterna för en User.

31. Initiera och Använd denna UserDetailsViewModel som modell för Details-vyn (ändra i Controller) istället för den tidigare modellen ”User” (Ändra också model i View)

När vi listar posterna vill vi inte repetera vad vi gjorde på Index-sidan för att lista alla användare. Vi tänker oss att vi kommer ha listor av poster på flera ställen i programmet och vill snabbt kunna bygga dessa listor så att de ser likadana ut. Vi skulle kunna lösa detta med en partial view likt hur vi löste User Details. Det finns dock ett annat sätt att lösa detta – DisplayTemplates (Som också är partial views, men används lite annorlunda). I slutändan vill vi kunna skriva @Html.DisplayFor(m => m.Posts) för att få en färdig lista med poster, där m.Posts är en lista på poster.

Vi skall börja med att göra några justeringar av Post-klassen för att hantera väldigt breda titles och bodies.

(12)

32. Lägg till en property TitleShort som returnerar hela Title ifall den är under 21 tecken och de första 20 tecknen ifall title är längre än 20 tecken

33. Gör samma sak för BodyShort och Body

Nu skall vi skapa vår DisplayTemplate för Post. När VS ser en DisplayFor för en typ som den inte känner igen så kommer den leta i Views/Shared/DisplayTemplates efter en fil som heter [TypNamn].cshtml för att ta reda på hur den skall visas.

34. Skapa en ny Mapp ”DisplayTemplates” under mappen ”Shared” i ”Views”.

35. Lägg till en partial view ”Post.cshtml” i DisplayTemplates.

36. Lägg till @model Post (med ev. using-direktiv) i Post.cshtml

37. I Post.cshtml: Visa info för TitleShort för en post (med ActionLink till ”/Posts/Details/ID” – även om vi inte byggt den kontrollera/action-metoden ännu – Ifall du använder DisplayFor på Title i ActionLink – titta på vad ActionLink kräver och vad DisplayFor returnerar för typ och justera därefter).

Visa även BodyShort och createdate (med DisplayFor) . (Det finns lite CSS/HTML för detta i /App_Data/NotUsed/Extra för lab.txt som är incheckad till mitt repo med Uppgift 32-33 ifall du inte vill göra HTML/CSS själv).

Nu har vi skapat en DisplayTemplate för Post. Dags att använda den!

38. I Details.cshtml för Post: Använd @Html.DisplayFor(m => m.Posts) – detta kommer lista alla dina poster enligt vad du angett i din DisplayTemplate. Ifall det ser konstigt ut beror det förmodligen på den HTML/CSS du använt – se mitt exempel på Post.cshtml (som jag iofs inte kontrollerat mer än som hastigast ;)).

Nu känns det förmodligen som att DisplayTemplates och PartialViews är i stort sett samma sak.

Skillnaden är att man generellt sett använder DisplayTemplates när man skall generera HTML för ett dataobjekt (Dvs. vår UserDetailsPartial är eg. mer logisk att göra till en DisplayTemplate), medans partial views används för t.ex. en kalender-widget, en socialmedia-panel eller en login-box – dvs.

saker som inte är direkt relaterade till en tabell i en databas eller ett dataobjekt. Anledningen till att vi använder DisplayTemplates för dataobjekt är att de renderas av @Html.DisplayFor(..) som tar hänsyn till t.ex. DataAnnotations av det slag vi använde i uppgift 24.

User Create sida

Nu vill vi skapa en sida för att skapa nya användare. Sidan kommer visa ett formulär med fält för den informationen vi vill att man skall kunna fylla i för en användare. Vidare skall det finnas en knapp som postar formuläret till vår Controller.

(13)

39. Först vill vi skapa en länk till Create sidan från Index sidan. Ifall användaren är inloggad som Admin så skall det finnas en länk för ”Create New User” ovanför Listan över användare.

Använd Razor-syntax @if (SessionManager.CurrentUser.Type == …).

Du har tidigare skapat länkar på User List sidan – utgå från dem och modifiera dem så att du får en länk till Create Action för UserControllern

(Behöver vi några parametrar? – Vad händer om CurrentUser är null?)

40. Skapa en ny Action för Create i UserControllern. Denna skall inte ta några parametrar, och returnera en View utan att skicka med några parametrar till vyn. Varför kommer metoden se ut såhär?

41. Skapa en ny Vy för Create. Vi vill att vyn skall vara starkt typad, så att VS automatiskt förstår att det som skall skickas med när vi postar formuläret är en User.

För att skicka data från vår Create-sida till vår UsersController så skapar vi ett HTML-formulär som fylls med data och en submit-knapp som skickar datan som en http-post till en lämplig Controller. Vi använder @using (Html.BeginForm()) {[Formulär-HTML/@Razor här]} för att skapa ett formulär.

BeginForm kan ta ett antal parametrar – bl.a så kan vi specificera vilken Controllers och vilken Action vi vill att formuläret skall postas till. Ifall vi inte anger några parametrar så kommer vi posta

formuläret till samma Controller igen. En fördel med att använda @using (Html.BeginForm()) {}

framför att använda html-taggar för <form method=”post” action=”/Controller/Action”></form> är att vi kommer få ett kompileringsfel om vi glömmer att stänga formuläret med @BeginForm() {} – vilket inte gäller för <form>-taggen.

42. Använd @using (Html.BeginForm ()) {…[html/razor i formuläret]…} med en

<input type="submit" value="Create" /> i formuläret för att skapa ett formulär som kan postas.

43. Använd @Html.LabelFor() och @Html.EditorFor() i vyn där du visar labels och Edit-fält för användarinfo (Det räcker med UserName som ett minimum av info som krävs för en ny användare – ifall du vill kan du lägga till fler fält).

Nu skall vi hantera att användaren klickar på ”Submit”-knappen för att posta formulärdata. Som standard så hanterar en actionmetod bara HttpGet-Requests (Denna typ av requests är vad du normalt får då du skriver in en webbadress i en webbläsare och trycker enter). Ifall ett hemside- formulär submittas så sker det normalt sett med en HttpPost-Request. Detta gör att vi kan skriva ytterligare en Action-method som hantera formulärdata från Create-sidan och kalla även denna Method För Create, med skillnaden att denna nya metod endast kommer hantera HttpPost-Requests.

Innan vi skapar den nya action metoden så skall vi skapa en valideringsfunktion för User.

44. Vi vill ha validering på UserName – skapa en metod .Validate() i User-klassen, som validerar att användarnamn är satt (!string.IsNullOrEmpty(UserName)).

(14)

Metoden skall returnera true eller false

(OBS! Detta är inte en bra lösning – i Uppgift 46-47 + sidenote kikar vi på en lösning som validerar både client side och server side automatiskt, utan att behöva skriva validerings- metoder för alla properties du använder).

Ifall användaren som kommer med formulärdata inte går att validera så vill vi visa samma sida igen – det gör vi som bekant med return View(); Om användaren går att validera så vill vi istället spara användaren och sedan redirecta till Index-sidan för users. Det finns flera olika sätt att redirecta på – ett av dem är return RedirectToAction(”Index”, ”Users”) – som alltså tar namnen på en

ActionMethod och en Controller som parametrar.

45. Skapa en ny Actionmethod för ”Create” med [HttpPost]-attribut (detta skrivs in på raden direkt ovanför definitionen för metoden och innebär att metoden endast kommer svara på post-requests).

Denna skall ta in-parametern ”User user” och returnera View(user) ifall användaren som kommer som in-parameter inte går att validera. Om användaren går att validera så skall användaren sparas i repositoriet och sedan skall det returneras en redirect till Index- metoden för UserControllern (return RedirectToAction(”Index”, ”Users”);).

Hur kommer det sig att vi kan skapa två metoder som heter samma sak?

Ifall vi nu försöker posta en användere som inte valideras så kommer vi tillbaka till samma sida, vilket är bra – men inga felmeddelanden syns! För att råda bot på detta använder vi DataAnnotations i User-klassen (Denna typ av DataAnnotations kallas för validation attributes ifall du vill söka på fler varianter). Precis som de tidigare DataAnnotations du använt, så använder vi dem inom

hakparanteser precis ovanför det vi vill dekorera med ett validation attribut. Det finns en rad andra validerings-regler man kan använda till exempel [MinLength(2)] (Andra är: Compare, DataType, MaxLength, Range, RegularExpression, StringLength och Custom – Experimentera!).

46. Sätt [Required(ErrorMessage=”Required!”)] precis ovanför UserName i User-klassen (Kom ihåg ctrl+. ifall du saknar using-direktiv).

47. Fortfarande inget felmeddelande! Vi saknar ett fält som visar felmeddelanden! Skriv ut en

@Html.ValidationMessageFor(m => m.UserName) i anslutning till EditorFor-fälten i Create- vyn för att skriva ut felmeddelande du skrev in i Uppgift 46. Innebär det här något för lösningen vi gjorde i uppg 44?

Som en liten side-note: Nu när vi har ValidationFor, DataAnnotations på vårt User-objekt och

ServerSide-Validation så är det väldigt enkelt att även få med ClientSide-Validation på köpet. Lägg till följande innan @using (Html.BeginForm()) {..} på Create-Sidan: @{

Html.EnableClientSideValidation(true) }. Sedan måste man även slänga med ett par javascripts.

Närmare bestämt jquery.validate.js och jquery.validate.unobtrusive.js. Dessa läggs genom att dra och släppa dem i t.ex. _Layout.cshtml tillsammans med de andra javascript-filerna som laddas där.

Sweet! ServerSide validation, ClientSide validation och felmeddelanden gratis i hela applikationen

(15)

bara genom att skriva t.ex. [Required(ErrorMessage=”Required!”)] över en property! (Detta kommer även hjälpa oss att generera en databas automatiskt.

48. Ett problem med den här lösningen är att även om inte länken för att skapa användare syns för vanliga användare på Index-sidan så kan fortfarande vem som helst skriva in adressen

”/Users/Create” för att komma till Create-sidan. Hur kan vi lösa det? (Spoiler Alert! … En lösning skulle kunna göra att vi kontrollerar något (vadå?) i Controller-koden för Create- metoderna. Det skall dock nämnas att det finns bättre och smidigare sätt att lösa detta när vi väl har en databas påkopplad – t.ex. genom att sätta DataAnnotation

”[Authorize(Roles=”Admin”)]” ovanför de båda Create-ActionMetoderna – och vips så är det klart! Det kan vi inte göra i vår lösning utan några mindre modifieringar dock).

Avancerat: User List sida

49. Vid tid: Gör User List Pageable (Två länkar ”Next” + ”Prev”, Partial View med parameter take, skip, Använd parameter på index för att hantera vilken sida av användar-infon som skall visas. Tex. /Users/Index/3 för sida 3). (Note: Här hade vi velat använda en javascript-lösning för att bara ladda om en del av sidan).

Avancerat: User Update Sida

50. Skapa en ny Action för Update – som skall uppdatera infon för en user (Det är ok om du för tillfället är tvungen att t.ex. spara ned användaren med ett nytt ID då du sparar den – du kan dock lösa det med de kunskaper du har idag – vi kommer lösa detta på ett lite annorlunda sätt senare när vi kopplar på databasen).

51. Ifall användaren är inloggad som den användaren som klickas på, eller är en admin skall länken på User-List sidan istället låta användaren komma till User Update sidan. (Detta löser vi bäst genom en if-sats i rätt metod i UserController).

52. Skapa User Update vyn (den kommer vara väldigt snarlik User Create vyn).

53. Eftersom User Create och User Update är så snarlika varandra är det bättre ifall vi gör om de gemensamma delarna till en Partial View och använder denna Partial View på båda sidorna.

På egen hand:

54. Försök snygga till presentationen på de olika sidorna med HTML/CSS (Vi kommer inte fokusera på detta under kurs-intro, men det kommer bli viktigt under projektet).

Avslutning

Fundera över vad du nu kan göra med MVC 3 – om ni skulle koppla på era HTML/CSS/Javascript kunskaper kommer ni säkert inse att ni kan åstadkomma väldigt mycket redan nu (våra .cshtml-vyer innehåller ju delvis ren HTML + javascript och CSS-filerna ligger i /Content, så där kan ni arbeta med HTML/javascript/CSS som om det vore en vanlig HTML-sida om ni håller er utanför Razor-syntax block och statements).

References

Related documents

Svenska språket är en social markör som säger att jag förstår ”fika”, ”konsensus”..

Artikeln avslutas med att det trots allt redan finns bedömning för elever i årskurs 6 så förändringen från detta till betyg gör inte så stor skillnad ändå (ibid.) Även

Jag anser även att jag har bidragit genom min studie till forskningen hur viktigt det är att ha i varje fall någon på företaget som har en djupgående förståelse

Detta gäller dock inte alla branscher, hotell- och restaurang uppger i högre grad en negativ utveckling i den här enkätomgången än i den förra (80 procent jämfört med 70

För att göra detta möjligt har en kvantitativ enkätundersökning genomförts där vi utformat frågor som ska besvara syftets frågeställningar kring faktorer som

I det program om forskning om funktionshinder och handikapp som FAS tog fram 2001 konstaterades att det fanns få forskare med funktionsnedsättning och att det behövdes kraftiga

Nu vill HRF engagera sig i forskning på bredare front och bland annat utröna intresset för forskartraditionen Disability studies i Sverige.. Disability studies handlar hur

Det faktum att över hälften av Marockos handel sker med EU-länder ger EU och Sverige näst intill obegränsade möjligheter till att använda handelspoliti- ken för att sätta