Kom igång med ASP.NET MVC
Microsoft släppte ganska nyligen ett tillägg till ASP.NET 3.5 SP 1, vid namn ASP.NET MVC. Det här kan ses som ett alternativt sätt att skriva ASP.NET-applikationer på, vid sidan av ASP.NET Web Forms, som tidigare var det enda sättet att skriva ASP.NET-applikationer på. MVC-mönstret är inget nytt, utan har funnits sedan 1979, alltså i 30 års tid.
Det som gör att Microsoft har fått bra respons vad gäller ASP.NET MVC är att det är mycket lättare att testa, det har ingen ViewState (alla ASP.NET-utvecklare vet hur kolossal den kan bli, det är dock förbättrat i ASP.NET 4.0, men mer om det i någon annan post), inga postbacks, ingen code behind m.m. ASP.NET MVC är egentligen ASP.NET Web forms utan allt lull-lull runtomkring. Då det inte finns någon viewstate så försvinner även möjligheten att använda webbkontroller, vilket kan låta lite skrämmande. Det kan dock ses som en fördel, då det faktiskt tvingar oss att skriva koden som den skall se ut, vi kan alltså inte vara för lata och därmed öka möjligheten att rendera dålig HTML. Om man ser på till exempel GridView, calendar, LinkButton osv så kan man se att de slänger ur sig riktigt dålig HTML. Något system där man tydligt kan se vad webbkontrollsberoende kan leda till är SharePoint (HTML:en där är fantastiskt dålig).
Men förutom just på klientsidan så har vi även möjlighet att påverka allt som sker på servern. MVC är en akronym för Model-View-Controller, vilket förklarar hur applikationen skall byggas upp.
En förklaring på de olika stegen
Model
Modellen tar hand om datan och returnerar den som objekt, vilka sedan kan användas av de andra lagren. Det kan ses som en person som åker till IKEA och köper en möbel. Den är inte ihopskruvad när personen köper den, men allting finns strukturerat då IKEA redan har pakterat allt och gjort det möjligt att hämta ut det man vill.
View
I vyn så presenteras modellen på ett sätt som gör det möjligt för användaren att arbeta med det som kommer fårn modellen. Här har personen skruvat ihop möbeln och ställt fram den. Man kan nu se slutresultatet.
Controller
Controllern innehåller all funktionalitet som krävs för att vi skall kunna använda och jobba mot vyn. Om vår möbel är en bokhylla med olika skåp så använder vi controllern för att fylla bokhyllan med böcker, samt hämta en lista på böckerna och presentera dem för användaren. Vi kan här även välja att fylla bokhyllan med enbart de böcker som är skrivna av H.C. Andersen.
Skapa en ASP.NET MVC-applikation
Vad förväntas av dig?
Innan du läser vidare så tar jag förgivet att du sitter med antingen Visual Studio (2008 SP 1 eller senare) eller Visual Web Developer 2008 SP 1 (eller senare), eller motsvarande. Jag antar även att du har åtminstonde grundläggande kunskap inom webbutveckling, och gärna även kunskap om programmering på serversidan (helst ASP.NET).
Om du inte uppfyller dessa förväntningar så kan du få det svårt att förstå vad det handlar om, och bör börja med att komma igång med grunderna.
Skapa mallsidan av ASP.NET MVC
När man har installerat ASP.NET MVC så följer det med en standardmall i Visual Studio (även Visual Web Developer, men jag kommer att referera till Visual Studio i fortsättningen). Denna standardmall kan användas för att man enkelt skall kunna komma igång med en ASP.NET MVC-sida och kunna gå vidare med det riktiga arbetet.
ASP.NET MVC fungerar enbart med Web Application Project, och inte Web Site, vilket är en av anledningarna till varför man måste ha Service Pack 1 till Visual Web Developer om man använder det.
Det vi gör är att vi börjar med att skapa ett nytt projekt, väljer Web och sedan ASP.NET MVC Web Application, väljer ett namn på projektet och klickar på OK.
Det som sker när vi klickar å OK är att vi får en fråga om vi vill skapa ett unit testing-projekt tillsammans med ASP.NET MVC-projektet. Som standard kan man välja Visual Studio Unit Test, men om man har MBUnit, xUnit eller annat så kan man även använda dessa, då det finns en möjlighet för tredjepartsprodukter att integrera sig enkelt i processen.
Jag kommer att välja nej på denna fråga då posten handlar om ASP.NET MVC i sig, men om man skall arbeta med ett projekt som skall användas skarpt så bör man absolut testa det för att vara helt säker (nåja, åtminstonde nästan helt säker) på att allt fungerar som det ska.
När vi har klickat på OK även här så har vi fått ett nytt projekt skapat med alla filer, mappar referenser och så vidare som behövs för att vi skall kunna använda ASP.NET MVC.
Projektet som är skapat bör se ut i stil med det här:
Vi kan se att det bl.a. finns tre olika mappar för just MVC, det vill säga Model, View och Controller. I View så har vi sedan olika mappar för olika routes (kommer till det snart). Utöver det så har vi vanliga filer som web.config, default.aspx (dock enbart en placeholder, används inte - egentligen), global.asax osv. Vi kan även se att vi får med jQuery 1.3.2 med tillhörande intellisense i projektet.
Bland referenserna har vi dels System.Web.Mvc, men även System.Web.Routing som kom med i .NET 3.5 SP 1, det vill säga innan ASP.NET MVC. Routing är det som ASP.NET MVC använder sig för att kunna få till URL:erna så att de bli vänligare för besökaren. Det används även bl.a. i ASP.NET Dynamic Data som även det kom med ASP.NET 3.5 SP 1. Just vad gäller Routing så är det många missförstånd, det finns många som tror att det är en del utav ASP.NET MVC, men så är inte fallet. Det går utmärkt att använda det tillsammans med ASP.NET Web Forms för att ge möjligheten att få snygga URL:er även till sidor baserade på Web Forms.
De delar som är viktigast nu till en början är mapparna Models, View och Controller, samt Global.asax.
Vi kan se att Models-mappen är tom. Vi kommer inte att använda denna direkt, utan den kommer i ett senare skede. I Controllers-mappen så kan vi se att vi har två olika controllers – AccountController och HomeController. Alla controllers måste ha “Controller” som suffix! Om vi sedan kikar i Views-mappen så kan vi se att vi har dessa filer:
Account och Home används av sina aktuella controllers. Shared kan användas av alla controllers. Det gäller alltså att redan här få en bra struktur på hur innehållet skall ligga. För att göra det lättare för oss så plockar vi bort Account-mappen här då vi inte kommer att behöva den i exemplet. Den har Views som kan användas för att skapa användare och låta dem logga in på sidan. Vi tar även bort AccountController från Controllers-mappen. När de är borta så kan vi även plocka bort LogOnUserControl.aspx i Shared-mappen. Då vi redan nu vet att vi inte kommer att behöva dessa så finns det ingen anledning till att låta dem ligga där.
Det vi har kvar nu är:
- /Home/Index.aspx – Startsidan
- /Home/About.aspx – Info om sidan
- /Shared/Error.aspx – Sida som visas om något går fel.
- /Shared/Site.master – Precis som med Web Forms så kan vi använda master pages för att styra hur sidan skall vara uppbyggd.
Då vi har plockat bort bitar ur projektet så måste vi även redigera lite i standardsidorna som följde med. Rad 18-20 i Site.Master måste tas bort.
Om vi nu trycker på F5 i Visual Studio och väljer att kompilera och köra sidan så får vi fram det här:
För den uppmärksamme så kan ni se att jag var har klickat i compatibility view-knappen då sidan inte är helt perfekt med IE 8. ;-)
Om vi sedan klickar på About-länken i menyn så kan vi se att vi faktiskt kommer till /Home/About. Är man van vid vanlig Web Forms-utveckling så kan det här se helknas ut. Kollar vi i katalogstrukturen så ligger ju ändå About-sidan som /Views/Home/About.aspx.
Anledningen till detta är att ASP.NET MVC använder ASP.NET Routing. Om vi kikar i Global.asax.cs så kan vi se bl.a. det här:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = "" } // Parameter defaults
);
}
Vad som sker här är att vi vid Application_Start registrerar olika routes som skall användas. Just i det här fallet så har vi bara en route. Vi kan se att den har ett namn (en route måste ha ett unikt namn), en placeholder för URL:en som visar hur den skall vara uppbyggd, och sedan standardvärden som används om inget värde skickas in.
Vi kan här se att när vi går till /Home/About så går vi mot controllern “Home” och händelsen (vyn) “About”.
Det som är så fantastiskt med ASP.NET Routing, jämfört med traditionell URL Rewriting är att vi inte bara kan säga att en viss url skall göra att besökaren kommer till en viss sida, utan vi kan hämta den bäst lämpade URL:en för en viss sida.
Säg att vi bara vill använda en viss controller, och då vill slippa “/Home/” i URL:en, utan vi vill att alla sidor skall gå mot /About direkt. Då räcker det med att vi skapar en Route som pekar mot denna:
routes.MapRoute(
"ShortUrl", // Route name
"{action}", // URL with parameters
new { controller = "Home", action = "Index", id = "" } // Parameter defaults
);
Genom att lägga in den här i Global.asax och kompilera om så räcker det med att vi nu anger Action, dvs vi kan surfa direkt till “/About” istället för “/Home/About”. Dessutom så skrivs våra länkar om automatiskt (om vi har använt ActionLink), vilket vi kan se här:
Magic!
För att vi automatiskt skall få länkarna pekade rätt så måste vi som sagt använda Action Link. Vi kan se ett bra exempel på det om vi ser hur länken i menyn är skapad:
<%= Html.ActionLink("About", "About", "Home")%>
Här skickar vi med texten på länken, action samt controller och får automatiskt en länk skapad med rätt URL. Kan det bli mycket smidigare?
För att allt skall fungera smidigt med andra controllers så tar vi dock bort den nya routern, men den kan anpassas hur mycket som helst utan att länkarna slutar fungera.
Liva upp sidan med en gästbok
För att få lite liv i sidan så vill vi nu även ha en gästbok, med en sida för att lista inlägg, och en sida för att skapa nya inlägg.
Det vi behöver för att kunna lagra inläggen från besökarna är en databas, en modell, vyer för presentation och skapande av inlägg, samt en controller som ser till att vi hamnar rätt.
Databasen jag använder här är en enkel SQL Server Express-databas i form av en mdf-fil i App_Data och modellen kommer att bygga på Linq to Entities.
Så det första vi gör nu är att högerklicka på App_Data, och välja att skapa en ny SQL Server Database. Vi ska göra den här så enkel som möjligt och skapar bara upp en tabell med tre fält för id, namn och meddelande och kallar denna tabellen för Entries.
Nästa steg är att i Models-katalogen skapa upp en ny ADO.NET Entity Data Model vid namn Guestbook. I guiden som kommer upp så väljer vi vår mdf-fil, samt väljer att hämta Entries-tabellen.
Det som sker när vi klickar på Finish är att vi får några nya referenser till ADO.NET Entity Framework-dll:er, samt att en datamodell skapas upp åt oss.
Då modellen visar ett enskilt inlägg så byter vi namn på tabellen till “Entry”, vilket visar på att det är just ett inlägg.
Då vi har en modell med tillhörande databas så kan vi gå vidare med att skapa en Controller som skall användas. Om vi högerklickar på Controllers, och väljer “Add” så kan vi se att Controller dyker upp automatiskt som ett alternativ.
Vi väljer att skapa en controller vid namn “GuestbookController”, samt bockar i rutan som frågar om vi vill ha metoder för att lägga till, ändra, samt ta bort inlägg.
Det vi får nu är ett gäng metoder för de olika händelserna. Om vi ser Index-metoden så kan vi se att den returnerar “View()” utan parametrar eller annat. Det är egentligen index-sidan, och det är där vi kommer att lista alla inlägg. Det finns vissa metoder vi kommer att plocka bort här då vi inte behöver dem.
Så här bör vår ändrade controller se ut:
using System.Web.Mvc;
namespace StartMvc.Controllers
{
public class GuestbookController : Controller
{
//
// GET: /Guestbook/
public ActionResult Index()
{
return View();
}
//
// GET: /Guestbook/Create
public ActionResult Create()
{
return View();
}
//
// POST: /Guestbook/Create
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(FormCollection collection)
{
try
{
// TODO: Add insert logic here
return RedirectToAction("Index");
}
catch
{
return View();
}
}
}
}
Det vi har är Index() för startsidan, Create() för formuläret, samt Create(FormCollection collection) för själva sparandet av inlägg. Vi kan se att den sistnämnda har ett attribut vid namn AcceptVerbs() och som tar emot HttpVerbs.Post som parameter. Det innebär att den här metoden enbart kommer att köras om den anropas via en postning. Är det istället GET så körs den andra Create-metoden.
För att det öht skall hända något nu så skall vi skapa upp två views i en katalog vid namn Guestbook, Index och Create.
Om vi i controllern högerklickar i Index-metoden så får vi upp det här:
Vi kan alltså skapa upp en vy för det aktuella ActionResult som vi står på, bara genom att högerklicka i metoden.
När vi väljer att skapa en ny vy så väljer vi även att ha den starkt typad, och pekar den mot Entry-objektet vi har i vår modell. Vi specifierar även att det här är en listning.
Det som sker nu är att vi får en ny sida av typen System.Web.Mvc.ViewPage<IEnumerable<StartMvc.Models.Entry>>, vilket tyder på att det är en listning av Entries. Vi har även fått en tabell automatiskt genererad med bl.a. detta:
<% foreach (var item in Model) { %>
<tr>
<td>
<%= Html.ActionLink("Edit", "Edit", new { id=item.id }) %> |
<%= Html.ActionLink("Details", "Details", new { id=item.id })%>
</td>
<td>
<%= Html.Encode(item.id) %>
</td>
<td>
<%= Html.Encode(item.name) %>
</td>
<td>
<%= Html.Encode(item.message) %>
</td>
</tr>
<% } %>
Det här hämtar ut den aktuella modellen (i det här fallet Entry), och låter oss hämta informationen från den. Den har dock ej ännu fått någon data bunden till sig, utan det får vi lägga till i controllern:
public ActionResult Index()
{
StartMvcEntities ent = new StartMvcEntities();
IEnumerable<Entry> entries = ent.EntriesSet.OrderByDescending(e => e.id);
return View(entries);
}
Det som sker här är att vi hämtar ut alla inlägg från vår modell och fyller vår vy med dessa. Då vi fortfarande inte har några inlägg så får vi börja med att lägga till vår sida som vi använder för att skapa inlägg.
Vi gör på samma sätt som tidigare och högerklickar i Create-metoden och väljer att skapa en ny vy. Vi väljer samma modell som innan, men den här gången väljer vi Create istället för List i den andra dropdown-listan:
Det som händer nu är att vi får en ny sida skapad åt oss med ett färdigt formulär för att skapa inlägg, baserat på fälten i modellen. Vi måste dock ta bort id härifrån, då det inte kan skapas upp av användaren.
För att enkelt kunna hitta till gästboken så väljer vi att lägga till en ActionLink i menyn:
<li><%= Html.ActionLink("Guestbook", "Index", "Guestbook")%></li>
Vi bör nu få med gästboken i menyn. Klickar vi på den så får vi upp detta:
Börjar likna något! Det vi gör härnäst är att klicka på “Create New”, för att få upp formuläret. Vi ser här att vi har ett formulär med de fält som vi vill att besökaren fyller i. Om vi fyller i båda fälten och klickar på knappen så märker vi att vi skickas tillbaka till gästboken utan att något har sparats. Det beror på att vi inte har något i controllern som säger att det som står i rutorna skall sparas ned i databasen.
Om vi sätter en breakpoint i den Create-metoden som bara används för postningar, och postar formuläret så kan vi se att våra två fält har skickats med i den FormCollection som tas emot som parameter:
För att nu lagra den här datan så använder vi oss utav den här koden:
StartMvcEntities ent = new StartMvcEntities();
Entry entry = new Entry();
TryUpdateModel(entry, new string[] { "name", "message" },collection.ToValueProvider());
ent.AddToEntriesSet(entry);
ent.SaveChanges();
return RedirectToAction("Index");
Det vi gör är att vi använder TryUpdateModel för att fylla entry med värdena från formuläret. Entity Framework har en begränsning här som säger att vi måste vitlista de fält som kan matas in, vilket sker i den andra parametern. I den tredje parametern så skickar vi in värdena.
Sen sparar vi ned texten till databasen och skickar besökaren till gästboken.
Här har vi resultatet:
Då vi inte har vyer för Edit och Details så tar vi bort dessa. Vi tar även bort id då det inte är relevant, och får då detta:
Så här enkelt var det att komma igång med ASP.NET MVC och skapa upp en sida med en gästbok. Jag kommer även att ta upp andra delar av MVC-ramverket, men tills vidare så är det bara att experimentera och kika på bloggar.