Eggplication

BobOnline Easter EggVrolijk Pasen! Het is weer zover, we laten de grauwe winterdagen achter ons en vieren de start van de lente. Velen doen dat met het verstoppen van paaseieren. Maar wat is nu vervelender dan, al zoekend, ook de eieren van vorig jaar terug vinden?

Daarom gaan ik vandaag een Easter Egg oplossing maken. Ik wil kunnen opslaan waar ik de eieren heb verstopt, zodat ik deze altijd terug kan vinden. Ook wil ik de status weten van een ei; Untainted, Tainted, Renegade, POW of Terminated (wat respectievelijk onbeschilderd, beschilderd, verstopt, gevonden en opgegeten inhoudt). Daarnaast is het belangrijk, omdat ik niet zo’n zin heb om me helemaal uit te leven op het beschilderen van de eieren, dat ik de keuze heb uit een paar templates hoe de eieren eruit zien. Deze templates wil ik wel kunnen combineren, zodat het nog enig sinds lijkt alsof ik de Rembrandt van de Eierdecorateurs ben.

Deze web applicatie kan dus het volgende:

  • Onthouden waar de paaseieren zijn verstopt,
  • Per ei kunnen aangeven wat de status is, zodat ik weet in wat voor situatie het ei zich bevindt,
  • Per ei nul of meerdere templates toepassen, zodat ik weet hoe het ei eruit ziet,
  • Beveiligde manier van eieren beheren, zodat andere niet zomaar kunnen zien waar ik mijn eieren heb verstopt,
  • Altijd bij de applicatie kunnen komen, zodat ik te allen tijde kan inzien wat de status is van de hele eieren zoektocht.

Living on the egg

Vandaag maak ik er een Code First MVC 4 project van. Dit doe ik omdat het onder andere ook actueel is in de lessen die ik verzorg.

Ik begin met een nieuwe MVC 4 solution in Visual Studio 2012 en ik kies voor een Razor MVC 4 applicatie. Nadat mijn solution is gegenereerd, kan ik aan de slag met mijn Code First modellen te maken. De solution bevat al diverse functionaliteiten. Check ook mijn eerdere blogs over MVC 4 met Razor.

Volgens het MVC model heb ik modellen, controllers en views. Bij Code First begin ik bij mijn modellen. Dit zijn classes die ik aanmaak in de folder Models. De controllers genereer ik later en de daar bij behorende views. Als ik Database First zou beginnen, dan maak ik eerst mijn database, zoals in Qui en genereer ik mijn classes via de edmx file vanuit het Entity Framework. Ook kan ik Model First beginnen waarbij ik een edmx file maak, daar het model in teken en dan de classes en database laat genereren. Het resultaat is het zelfde, alleen het perspectief is anders.

Ik begin met mijn Egg model. Dit is een nieuwe class met de naam Egg. Deze krijgt een Id om hem te identificeren en een auteur, de kunstenaar die hem beschilderd heeft. Het moet natuurlijk wel duidelijk zijn bij wie zich het copyright bevindt. Door middel van Data Annotations geef ik wat meer duidelijkheid over de properties. Deze komen uit de System.ComponentModel.DataAnnotations namespace en spreken voor zich (insert enige database kennis here).

public class Egg
{
    [Key]
    public int Id { get; set; }

    [Required]
    public string Name { get; set; }

    [Required]
    public string Author { get; set; }

    [Required]
    [DisplayName("Hiding place")]
    public string LocationDescription { get; set; }
}

De tweede class die ik wil hebben, is de class van de eieren template.

public class EggTemplate
{
    [Key]
    public int Id { get; set; }

    [Required]
    public string Name { get; set; }

    public string Description { get; set; }
}

Nu wil ik ook de relatie leggen tussen de twee classes. Ik wil dat een ei nul of meerdere templates kunnen hebben en een template moet op nul of meerdere eieren van toepassing kunnen zijn. Dat klinkt dus als een veel-op-veel relatie. Dit doe ik door een foreign key property aan te maken, of beter gezegd een lijst met foreign keys. En dat in beide classes. Later zullen we zien dat het Entity Framework automatisch een koppeltabel maakt om deze gegevens op te slaan. Om het helemaal praktisch te houden, maak ik ook een virtual property met de modellen van de ander. Dus, om het iets duidelijker te zeggen, de Egg class krijgt een List<int> TemplateIds en een List<EggTemplate> Templates mee. De EggTemplate class krijgt dan een List<int> EggIds en virtual List<Egg> Eggs mee. De virtual keyword zorgt ervoor dat de property niet meegenomen wordt bij het genereren van de database. De Id’s wel, dat zijn de foreign keys.

Om de status van de eieren bij te houden, wil ik een derde model maken genaamd EggStatus. Deze koppel ik ook aan mijn Egg. Ik maak daarbij weer een virtual property en ook een Id property. Dit wordt een een op veel relatie, dus ik hoef geen list te gebruiken.

Dit wordt nu mijn nieuwe code:

public class Egg
{
    [Key]
    public int Id { get; set; }

    [Required]
    public string Name { get; set; }

    [Required]
    public string Author { get; set; }

    [Required]
    [DisplayName("Hiding place")]
    public string LocationDescription { get; set; }

    public List<int> TemplateIds { get; set; }
    public virtual List<EggTemplate> Templates { get; set; }

    public int EggStatusId { get; set; }

    [ForeignKey("EggStatusId")
    public EggStatus Status { get; set; }
}

public class EggTemplate
{
    [Key]
    public int Id { get; set; }

    [Required]
    public string Name { get; set; }

    public string Description { get; set; }

    public List<int> EggIds { get; set; }
    public virtual List<Egg> Eggs { get; set; }
}

public class EggStatus
{
    [Key]
    public int Id { get; set; }

    [Required]
    public string Name { get; set; }
}

CD

Mirror Egg

Om even spiegel ei als metafoor te gebruiken voor het gebruiken van de classes in de toepassing, ga ik nu twee controllers genereren voor de oplossing. Dit doe ik door op de rechtermuisknop op de folder Controllers te klikken en Add > Controller te kiezen.

In het controle scherm geef ik mijn controller een naam, ik kies de class en ik maak een nieuwe context aan. Deze context wordt gebruikt als controller voor de database.

NewController

Visual Studio 2012 genereerd nu heel netjes voor mij de controller en de bijbehorende views. Ik heb read/write actions en views gekozen als template, dus maakt hij voor mij deze actions en ook gelijk de views. Ik doe even Ctrl + Shift + B om te builden en te checken of alles goed compiled.

Ik genereer ook gelijk de EggTemplateController op dezelfde manier. Als ik nu op F5 druk of Run kies uit de koppenbalk, dan kan ik mijn web applicatie testen.

Ik heb twee controllers gemaakt. In deze controllers zijn nu diverse actions gemaakt. Deze actions benader ik als volgt: domein/controller/action. Hierin is het domein, als ik deze op mijn lokale ontwikkel omgeving test http://localhost:[port]. De port kan verschillen en deze kan je aanpassen in je properties van je project. De controller is Egg, van EggController en de action is een van de actions in deze controller, bijvoorbeeld Index of Create. Nu is het zo dat als ik Index weglaat hij automatisch toch de Index pakt. Dus ik kan volstaan met http://localhost:27185/Egg. Deze zal de Index action uit de EggController controller pakken welke vervolgens de Index view laadt. Als ik doe http://localhost:27185/Egg/Create dan ga ik direct naar de Create view via de Create action uit de EggController controller.

Nu kan ik nieuwe eieren aanmaken en nieuwe templates. Op de achtergrond heeft het Entity Framework nu de benodigde tabellen aangemaakt.

GeneratedTables

Hier zie je de drie hoofdtabellen Eggs, EggStatus en EggTemplates en ook de koppeltabel EggEggTemplates.

Chocolate Egg Side Note

Op het moment dat je je model aanpast, wil het Entity Framework opnieuw de database genereren. Dit kan waarschijnlijk niet, omdat je al een bestaande database hebt. Dit kan je afvangen in je Global.asmx.cs file.

public class MvcApplication : System.Web.HttpApplication
{
    protected void Application_Start()
    {
        // Aanroepen van het initialiseren van de database
        InitDatabase();

        AreaRegistration.RegisterAllAreas();

        WebApiConfig.Register(GlobalConfiguration.Configuration);
        FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
        RouteConfig.RegisterRoutes(RouteTable.Routes);
        BundleConfig.RegisterBundles(BundleTable.Bundles);
        AuthConfig.RegisterAuth();
    }

    private void InitDatabase()
    {
        // Als je nog in de ontwerpfase zit, kan deze instelling handig zijn
        // deze vernieuwd de database als je je models hebt aangepast.

        // Database.SetInitializer(new DropCreateDatabaseIfModelChanges<Eggplication.Models.EggplicationContext>());

        // Als je in je deploy fase zit, bijvoorbeeld naar AppHarbor, dan kan je deze instelling gebruiken
        // deze maakt de database aan als deze nog niet bestaat.

        Database.SetInitializer(new CreateDatabaseIfNotExists<Eggplication.Models.EggplicationContext>());
    }
}

Hier komt database uit de namespace System.Data.Entity.

Eggcelent

Dit 9Gag-inspired titel kon natuurlijk niet uitblijven in deze eggcentered blog, want het laatste wat ik nog moet doen is mijn views aanpassen zodat ik ook daadwerkelijk mijn een-op-veel en veel-op-veel relaties kan toepassen. Dit is namelijk niet iets wat nu automatisch wordt gegenereerd.

Allereerst ga ik mijn Create van mijn Egg aanpassen. Hier wil ik kunnen aangeven welke status de Egg heeft en welke templates van toepassing zijn. Voor de status neem ik een Dropdown ListBox en voor de template een Multiselect ListBox. Ik moet in mijn controller dadelijk twee dingen aanpassen, namelijk het meegegeven van de data (status en templates) en het ook daadwerkelijk kunnen opslaan van deze onderdelen. Dit alles herhaal ik dan ook in mijn Edit action.

In de view maak ik eerst het status onderdeel aan. Deze koppel ik aan de EggStatusId property, zodat ik kan aangeven welke value hij moet mee geven als ik een item selecteer. Ik voeg de volgende code toe aan mijn view Create.cshtml:

<div class="editor-label">
    @Html.LabelFor(model => model.Status)
</div>
<div class="editor-field">
    @Html.DropDownListFor(model => model.EggStatusId, new SelectList(ViewBag.Status, "Id", "Name"))
    @Html.ValidationMessageFor(model => model.EggStatusId)
</div>

Ik gebruik hier een DropDownListFor en maak daarin een nieuwe SelectList aan. Ook koppel ik deze aan een IEnumeration van items, namelijk de statussen die mogelijk zijn. Deze haal ik uit mijn ViewBag. Dit is een dynamische aangemaakt object. Deze moet ik wel definiëren in mijn controller. Net voor het returnen van de View, maak ik de ViewBag aan. Als ik daar dan toch ben, maar ik ook gelijk de ViewBag property voor de templates.

public ActionResult Create()
{
    ViewBag.Templates = db.EggTemplates.ToList<EggTemplate>();
    ViewBag.SelectedTemplates = new List<int>();
    ViewBag.Status = db.EggStatus.ToList<EggStatus>();
    return View();
}

[HttpPost]
public ActionResult Create(Egg egg)
{
    if (ModelState.IsValid)
    {
        db.Eggs.Add(egg);
        db.SaveChanges();
        return RedirectToAction("Index");
    }

    ViewBag.Templates = db.EggTemplates.ToList<EggTemplate>();
    ViewBag.SelectedTemplates = egg.Templates.Select(t => t.Id).ToList<int>();
    ViewBag.Status = db.EggStatus.ToList<EggStatus>();
    return View(egg);
}

Om te testen, moet ik wel Statussen aanmaken. Anders blijft mijn dropdown leeg. Dit doe ik door naar http://localhost:27185/EggStatus te gaan of gelijk naar http://localhost:27185/EggStatus/Create.

AddStatus

Ook vul ik gelijk mijn templates met wat begin waardes.

AddTemplate

Als ik nu naar mijn Create ga van mijn Egg, dan kan ik ook de status kiezen die de nieuwe Egg heeft. Wat ik hier nu nog mis is een lijst met de templates. Hiervoor maak ik een ListBox aan als volgt:

<div class="editor-label">
    Templates
</div>
<div class="editor-field">
    @Html.ListBox("TemplateIds", new MultiSelectList(ViewBag.Templates, "Id", "Name", ViewBag.SelectedTemplates))
</div>

Let op dat je dit ook in je Edit.cshtml doet. En dan ook gelijk in de Edit Action in je controller.

Nu moeten we nog zorgen dat we de template ook daadwerkelijk opslaan. We hebben hem wel aan de Id’s gekoppeld, maar deze moeten nog wel even omgezet worden naar de objecten. Mijn nieuwe Create en Edit action worden dan:

[HttpPost]
public ActionResult Create(Egg egg, int[] templateIds)
{
    if (ModelState.IsValid)
    {
        if (egg.Templates == null) egg.Templates = new List&lt;EggTemplate&gt;();
        foreach (int templateId in templateIds)
        {
            EggTemplate t = db.EggTemplates.Find(templateId);
            egg.Templates.Add(t);
        }

        db.Eggs.Add(egg);
        db.SaveChanges();
        return RedirectToAction("Index");
    }

    ViewBag.Templates = db.EggTemplates.ToList&lt;EggTemplate&gt;();
    ViewBag.SelectedTemplates = egg.Templates.Select(t =&gt; t.Id).ToList&lt;int&gt;();
    ViewBag.Status = db.EggStatus.ToList&lt;EggStatus&gt;();
    return View(egg);
}

[HttpPost]
public ActionResult Edit(Egg egg, int[] templateIds)
{
    if (ModelState.IsValid)
    {
        // get the original egg
        Egg newEgg = db.Eggs.Find(egg.Id);

        // set modify state
        db.Entry(newEgg).State = EntityState.Modified;

        // copy the new values to the old one
        db.Entry(newEgg).CurrentValues.SetValues(egg);

        // clear all relations, else we get a primary key conflict
        if (newEgg.Templates != null) newEgg.Templates.Clear();
        else newEgg.Templates = new List<EggTemplate>();

        // also for the Id list
        if (newEgg.TemplateIds != null) newEgg.TemplateIds.Clear();
        else newEgg.TemplateIds = new List<int>();

        // add the new templates
        foreach (int templateId in templateIds)
        {
            // find the template
            EggTemplate template = db.EggTemplates.Find(templateId);
            newEgg.Templates.Add(template);
            newEgg.TemplateIds.Add(templateId);
        }

        // save the changes
        db.SaveChanges();
        return RedirectToAction("Index");
    }

    ViewBag.Templates = db.EggTemplates.ToList&lt;EggTemplate&gt;();
    ViewBag.SelectedTemplates = egg.Templates.Select(t =&gt; t.Id).ToList&lt;int&gt;();
    ViewBag.Status = db.EggStatus.ToList&lt;EggStatus&gt;();
    return View(egg);
}

Nu kan ik mijn eieren toevoegen en wijzigen.

AddEgg2

Voor het mooie wil ik ook wat extra informatie in mijn Index view hebben. Dan kan ik duidelijker zien om welke eieren het gaan en wat de status is. De Templates property is nu een collectie van objecten. Die kan ik niet zomaar omzetten naar een string om deze te laten zien in het overzicht. Wat ik wel kan doen is een readonly property maken die virtual is (dus niet wordt opgeslagen in de database) die mij een string terug geeft van de templates. Ik voeg hiervoor de volgende code toe aan mijn Egg model:

public virtual string TemplateDescription {
    get
    {
        string result = "";
        foreach (EggTemplate t in Templates)
        {
            result += t.Name + ", ";
        }
        if (result.EndsWith(", ")) result = result.Substring(0, result.Length - ", ".Length);

        return result;
    }
}

Als je het echt mooi wilt doen, kan je een ViewModel aanmaken. Dat is een tussen model alleen bedoeld voor de view. Kijk eens naar het MVVM model, dat is een afgeleide van het MVC model.

Als laatst wil ik mijn applicatie beveiligen. Ik wil zorgen dat alleen ik bij de eieren administratie kan komen. Ik zet hiervoor boven mijn EggControler, EggStatusController en EggTemplateController classes de [Autorize(Roles=”Admin”)] zodat alleen ik als Admin erbij kan. Let op dat je jezelf wel, na het registreren, ook toevoegd aan de webpages_UsersInRoles koppeltabel en de ‘Admin’rol in de webpages_Roles tabel.

Authentication

Mijn oplossing is gemaakt. Ik kan nu met volle vertrouwen mijn eieren maken, verstoppen en hopelijk weer vinden :).

EggIndex

Wil je de Eggplication in actie zien? Ga dan naar de demo op eggplication.apphb.com.

Geef een reactie

Het e-mailadres wordt niet gepubliceerd. Verplichte velden zijn gemarkeerd met *

Zoals de meeste websites gebruiken we cookies om een meer persoonlijke en snelle service te bieden.

Wij gebruiken cookies zodat onze website meer efficiënt kan functioneren, om de prestaties te verbeteren en, eventueel, om op maat reclame van onze partners aan te bieden. Als u doorgaat gaan we ervan uit dat u akkoord gaat alle cookies te krijgen van onze website.

To accept cookies please Click To Continue