Formulär

När man skapar formulär i ASP.NET med web forms så finns det ett gäng olika kontroller som TextBox, DropDownList, Button m.m. Dessa kan sedan bindas mot olika event, vilket genererar olika javascript och HTML-element som vi inte kan styra över. Dessutom skapas olika id:n, vilka vi inte har kontroll över (förändras dock i ASP.NET 4.0, där vi äntligen kan anpassa dessa).

All denna autogenererade HTML och Javascript-kod leder till vissa stora nackdelar:

  • Stor ViewState, vilken är nästan omöjlig att bli av med. Den innehåller en Base64-kodad sträng (kan dock krypteras) med information om olika element, så att informationen skall bevaras mellan postbacks.
  • Ingen kontroll över HTML-koden som renderas.
  • Ingen kontroll över id-attributet, vilket försvårar vid användning av javascript och css (blir bättre i ASP.NET 4.0).
  • Postbacks som sker via javascript är ej tillgänglighetsanpassade och blockerar många användare. LinkButton är ett exempel på en kontroll som inte fungerar utan javascript.

Det har funnits mängder av önskemål som helt enkelt pekar mot:

  • Ingen ViewState.
  • Full kontroll över HTML-koden som renderas.
  • Full kontroll över id-attributet.
  • Inget postback-javascript.

Med andra ord så vill vi ha ett enkelt och smidigt sätt att skapa hemsidor på, där vi har full kontroll över vad som renderas, men samtidigt kan ta nytta av det enorma klassbibliotek vi får genom .NET Framework.

Lösningen på detta finns i ASP.NET MVC. Vi kan här skapa formulär och samtidigt ha 100% kontroll över vad som renderas hos klienten. De traditionella webbkontrollerna fungerar inte här då vi inte längre har postbacks och ViewState, men istället så har vi helper-klasser som kan underlätta vid skapandet av kontrollerna. Vi behöver dock inte dessa för att skapa formulär, men de underlättar mycket om vi har t.ex. databindning mot en dropdown-lista.

Till att börja med så har jag skapat upp ett nytt ASP.NET MVC-projekt (dock utan testprojekt då artikeln inte tar upp det). Nästa steg är att skapa en modell i form av Customer.cs i Models-mappen:

namespace Forms.Models
{
    public class Customer
    {
        public string Firstname { get; set; }
        public string Lastname { get; set; }
        public int Age { get; set; }
    }
}

Det här är en enkel Customer-klass som vi kommer att använda i formuläret. Klassen kommer att användas i formuläret.

Nästa steg är att skapa upp en Controller som skall ta hand om all interaktion med användaren. Högerklicka på Controllers-mappen, Välj Add –> Controller, ge den namnet CustomerController och välj att lägga till metoder för att skapa, uppdatera och visa detaljer, även om vi inte kommer att använda alla dessa – det enda vi kommer att använda här är just den delen som skapar nya poster.

Med lite modifieringar så har vi kvar det här:

using System.Web.Mvc;
 
namespace Forms.Controllers
{
    public class CustomerController : Controller
    {
        // GET: /Customer/
        public ActionResult Index()
        {
            return View();
        }
 
        // GET: /Customer/Create
        public ActionResult Create()
        {
            return View();
        } 
 
        // POST: /Customer/Create
        [AcceptVerbs(HttpVerbs.Post)]
        public ActionResult Create(FormCollection collection)
        {
            return RedirectToAction("Index");
        }
    }
}

Vi har dels en metod “Index” som är startsidan, men även två Create-metoder. Den utan parametrar är den som används för att helt enkelt bara visa sidan (dvs vid GET), den andra används när man istället har postat mot /Customer/Create. ASP.NET MVC känner automatiskt igen vilken som skall användas vid det aktuella scenariot.

Det vi behöver nu är först och främst ett formulär att använda vid skapande av nya customers. Detta formulär skall ligga under GET-varianten av Create, så genom att högerklicka i metoden och sedan välja “Add View…” så kan vi skapa en vy för denna händelse.

1

Det vi får upp då är en ruta med inställningar för vyn. Vi kan välja om vi vill ha en partiell vy (ascx, fungerar som user controls med web forms), vi kan använda en starkt typad vy (underlättar mycket då vi enkelt kan använda en befintlig modell), vi kan skapa en genererad vy baserad på modellen och vi kan även välja att använda en master page.

I det här fallet så har vi redan en modell som vi vill använda oss utav, så vi väljer att skapa en starkt typad vy (ProjektNamn.Models.Customer, syns den inte så testa att kompilera om projektet) samt att det skall vara av typen “Create”. Dessa olika typer som kan autogenereras bygger på T4-mallar, vilket är standard i Visual Studio. De är dock relativt okända, men väldigt smidiga.

Det bör se ut i stil med det här:

2

När vi nu har skapat filen så kan vi se att vi faktiskt har fått ett genererat formulär med fält baserade på fälten från modellen.

I koden så har vi först och främst en Html.ValidationSummary(), vilket är en av många extension methods i Html-klassen, vilket är en helper med funktioner att använda när vi utvecklar ASP.NET MVC-sidor. Om något har gått fel vid skapandet av sidan så får vi informationen visad här.

Vi har även ett gäng olika Html.TextBox(), vilket renderar vanliga textrutor där vi kan fylla i datan. Dessa har även Html.ValidationMessage(), vilka visar om inputen är felaktig.

Runt dessa textrutor så har vi dock ett using-block med Html.BeginForm, vilket skapar upp ett HTML-formulär runt input-kontrollerna.

Längst ner på sidan så har vi även en Html.ActionLink(). De används för att skapa länkar baserade på den routen som passar bäst in på länken.

Om vi nu surfar in på /Customer/Create så kan vi se detta:

3

Vi har alltså ett formulär där vi kan skriva in vilken typ av data som helst. När vi klickar på Create-knappen så får vi dock ett fel då vi inte har någon Index-sida, vilket är helt korrekt. Anledningen till att den försöker hitta just den sidan är för att vi har den här raden i POST-metoden för Create:

return RedirectToAction("Index");

Allt stämmer alltså än så länge.

För att se om alla värden kommer in korrekt så lägger vi till dessa rader innan RedirectToAction(), samt lägger till en breakpoint på just den raden så att vi enkelt kan se om alla värden kom fram som de skall:

string firstname = collection["Firstname"];
string lastname = collection["Lastname"];
string age = collection["Age"];

Om vi startar debugging, fyller i alla fälten och väljer att skicka så bör vi ha fått upp alla värden korrekt:

4

När vi ser att allt har kommit in korrekt så kommer nästa problem. Vi har olika helpers för validering på sidan, men som det ser ut nu så aktiveras dem aldrig. Det vi behöver göra är att kolla så att alla fälten är ifyllda, och om något inte är det så tvingar vi användaren till att fylla i dessa.

Det som sker nu är att vi först och främst skall ändra i vyn så att vi inte längre tar emot en FormCollection, utan istället rena objekt. Förnamn och efternamn är strängar, så de vill vi ska vara string. Åldern vill vi ha som en int, vilken skall vara över 0 för att valideras.

Då ASP.NET MVC kan mappa om alla fältens värden i formuläret till POCO så kan vi sätta dessa direkt som parametrar. Om man inte anger någon ålder så kommer den dock att vara null, vilket gör att vi kommer att behöva använda en nullable int för åldern.

Sedan kommer vi att lägga till eventuella fel i ViewData.ModelState. ViewData innehåller data för den aktuella vyn, och ModelState innehåller information om statusen på modellen. I det här fallet så innehåller den fel om något fält saknas, vilket vi enkelt kan kontrollera med ViewData.ModelState.IsValid som är false om så är fallet.

Koden som den ser ut nu:

// POST: /Customer/Create
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(string firstname, string lastname, int? age)
{
    if (String.IsNullOrEmpty(firstname))
        ViewData.ModelState.AddModelError("Firstname", "Du måste fylla i ditt förnamn.");
 
    if (String.IsNullOrEmpty(lastname))
        ViewData.ModelState.AddModelError("Lastname", "Du måste fylla i ditt efternamn.");
 
    if (!age.HasValue || age < 0)
        ViewData.ModelState.AddModelError("Age", "Du måste fylla i din ålder.");
 
    if (ViewData.ModelState.IsValid)
        return RedirectToAction("Index");
    else
        return View();
}

Om vi nu kör den här koden och skippar efternamn och ålder så får vi upp detta:

5

Vi kan alltså automatiskt mappa om elementen mot objekt i koden, utan att egentligen behöva cast:a om typerna själva, då detta sker automatiskt av ASP.NET MVC.

Uppdatera modellen

Om vi istället vill skapa upp ett Customer-objekt med dessa värden så skulle vi kunna skapa en ny instans av det och sedan sätta alla properties manuellt baserat på parametrarna. Ett annat sätt att lösa det på är att göra det genom att använda UpdateMode-metoden. Den tar emot namnet på modellen, alla värden från postningen samt eventuellt en whitelist samt blacklist.

Vi kommer att behöva gå tillbaka till en FormCollection, skapa upp en instans och sedan automatiskt få fälten ifyllda.

6

Vi kan här se att alla fälten automatiskt fylldes i enligt vår whitelist. Det här gör det enkelt att generera ett objekt baserat på data från formuläret.

För att göra det ännu lättare så kan vi även se till att ta emot ett Customer-objekt som parameter, vilket gör att vi inte behöver gör något alls för att automatiskt få objektet med alla värden direkt från formuläret. Genom att bara ange en Customer så får vi det här:

7

Vi behöver med andra ord inte göra något alls, utöver att säga att vi vill ha tillbaka ett Customer-objekt från formuläret. Detta kan vi sedan spara ned direkt till databasen om så önskas.

Att direkt hämta ett objekt på detta sätt kan dock leda till vissa problem. Det kan hända att vi behöver exkludera fält, som t.ex. ett ID-fält, då detta inte skall läggas in manuellt. Genom att använda Bind-attributet för parametern så kan vi få in en whitelist, samt blacklist direkt på parametern.

// POST: /Customer/Create
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create([Bind(Include = "Firstname,Age", Exclude = "Lastname")]Customer customer)
{
    return RedirectToAction("Index");
}

Om vi har denna bindning så kan vi se att customer innehåller förnamn och ålder, men null för efternamnet. På så vis kan vi enkelt exkludera data som vi inte vill ha med. Bind-attributet innehåller även en prefix-property där vi kan ange om fälten i formuläret har något prefix. Om vi har “formAge” i formuläret så kan vi sätta Prefix till “form”, vilket gör att endast Age kommer med och behandlas.

1 Comment

  • Har inte hunnit kolla så mkt på MVC än. Du kanske vet om det fortfarande går att använda kontroller (controls)?

    Jag har från vissa håll fått höra att det inte längre är möjligt...

Comments have been disabled for this content.