Check alle échte Black Friday-deals Ook zo moe van nepaanbiedingen? Wij laten alleen échte deals zien

[MVC 5] Create action voor object als child van parent

Pagina: 1
Acties:

  • NickThissen
  • Registratie: November 2007
  • Laatst online: 18-11 13:07
Sorry voor de wazige titel, ik weet niet goed hoe ik dit moet noemen (en daarom vind ik ook niks online...).

Ik ben bezig met een website in MVC (ASP.NET), waar ik weinig ervaring mee heb (lees: echt alleen de basis) en loop nu tegen een probleem aan waar ik niet omheen kom.

Mijn ervaring met MVC (en dingen die goed gaan) komt neer op: een Model maken wat overeenkomt met een tabel in mijn database, en daar een Controller voor laten genereren die actions als Create, Edit, etc en bijbehorende Views voor maakt. Dit krijg ik allemaal wel voor elkaar als er geen koppeling tussen Models is.

Nu heb ik echter een model wat wel gekoppeld is aan een ander model, en nu loop ik vast.


Ik heb een model 'Series' wat een race series voorstelt (bijvoorbeeld: Formule 1, Formule 2, etc). Binnen een series kunnen er meerdere 'Seasons' bestaan, wat gewoon seizoenen voorstellen. Het Series model heeft dus een collectie Seasons (en een Season heeft een Series als parent).

C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
    public class Series : BaseModel
    {
        public virtual ICollection<Season> Seasons { get; set; } 

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

        public string Description { get; set; }

        [Required]
        public bool IsActive { get; set; }
    }

    public class Season : BaseModel
    {
        [Required]
        public virtual Series Series { get; set; }

        [Required]
        public int SeasonNumber { get; set; }
    }


In de Details view van een Series laat ik dus een lijstje met Seasons zien die bij die Series horen. Daarnaast is er een knopje om een nieuw Season aan te maken. Dit knopje linkt natuurlijk gewoon naar een Create action in de SeasonController.

Het probleem is dat ik niet weet hoe ik de parent Series koppel aan het nieuwe Season.


Ik heb de volgende code in de SeasonController:
C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
        // GET: /Season/Create
        public ActionResult Create(int? seriesId)
        {
            // Get parent series
            if (seriesId == null)
            {
                return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
            }
            Series series = db.Series.Find(seriesId);
            if (series == null)
            {
                return HttpNotFound();
            }

            // Set the series of the new season
            var season = new Season();
            season.Series = series;

            // Save series in tempdata to retrieve later
            TempData["Series"] = series;

            // Pass season to view
            return View(season);
        }

        // POST: /Season/Create
        [HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult Create([Bind(Include="Id,SeasonNumber,CreatedTime,UpdatedTime,Deleted")] Season season)
        {
            if (ModelState.IsValid)
            {
                // Get series
                var series = TempData["Series"] as Series;
                if (series != null)
                {
                    // Set series parent
                    season.Series = series;

                    db.Seasons.Add(season);
                    db.SaveChanges();

                    return RedirectToAction("Details", "Series", new {id = season.Series.Id});
                }
                else
                {
                    ModelState.AddModelError("NoSeries", "Could not find a series to add the season to.");
                }
            }

            return View(season);
        }


Ik geef dus het ID van de parent Series mee naar de Create GET action en check of dat bij een Series hoort. Die Series sla ik op in TempData (dat had ik online geleerd, maar ik heb zo mijn twijfels of dat de juiste manier is) zodat ik die later in de POST action weer kan ophalen.

In de POST action haal ik de Series dus weer op en koppel ik het nieuwe Season aan de Series.


Voor mijn gevoel zou dit moeten werken, maar dat doet het niet. De ModelState is namelijk niet valid, met de melding "The Series field is required".

Hij verwacht dus dat ik ergens op de pagina de Series invul. Waarschijnlijk omdat de Series property in mijn Season model een Required property is? Maar als ik die Required weg haal dan klopt mijn model niet meer, dan kan er namelijk een Season bestaan zonder Series (het Series_Id veld in de database is dan nullable) en dat mag niet...


Wat doe ik hier nu fout? Waarschijnlijk mis ik een fundamenteel iets, maar alle tutorials die ik vind gaan niet verder dan het maken van een simpel losstaand object, en dit soort koppeling met andere models mis ik overal...


Edit
Een poging om het duidelijker en korter te maken:

Ik heb een Series object in de database, en daar wil ik een Season aan toevoegen. Het aanmaken van een nieuw Season object is simpel genoeg, maar de koppeling met een bestaande Series lukt niet.

Mijn iRacing profiel


  • Haan
  • Registratie: Februari 2004
  • Laatst online: 15:31

Haan

dotnetter

Eerste dingetje: is het zelfstandignaamwoord echt series en niet serie? Nu leest het voor mij steeds alsof het over een lijst van series gaat. Maar dat terzijde ;)

Ik zou aan je Season model een property SeriesId toevoegen, dat is sowieso handiger dan alleen de navigation property (public virtual Series Series) te gebruiken. Het werkt wel, maar is lastiger in gebruik, iets waar jij nu ook tegen aanloopt.

Het gebruik van TempData is niet perse fout, maar gebruikelijker is het om het SeriesId in een Hidden input field te zetten in de Create view, zodat ie in de post gewoon weer netjes mee komt.

Je hoeft dan alleen de SeriesId property te zetten op je nieuwe Season, dat zou genoeg moeten zijn om te laten werken.

Kater? Eerst water, de rest komt later


  • NickThissen
  • Registratie: November 2007
  • Laatst online: 18-11 13:07
Haan schreef op zondag 06 april 2014 @ 12:44:
Eerste dingetje: is het zelfstandignaamwoord echt series en niet serie? Nu leest het voor mij steeds alsof het over een lijst van series gaat. Maar dat terzijde ;)
Erg vervelend inderdaad, maar in het engels is het volgens mij toch echt Series ;(
Haan schreef op zondag 06 april 2014 @ 12:44:
Ik zou aan je Season model een property SeriesId toevoegen, dat is sowieso handiger dan alleen de navigation property (public virtual Series Series) te gebruiken. Het werkt wel, maar is lastiger in gebruik, iets waar jij nu ook tegen aanloopt.
Hmm, ik wist niet dat dat kon. Hoe wordt die property dan aan het ID van de series gekoppeld? Handelt MVC dat vanzelf voor me af?

En als ik de Series property voorvang voor SeriesId, dan kom ik dus niet gemakkelijk meer aan de Series vanuit een Season (stel dat ik in de Details view van een seizoen de naam van bijbehorende Series wil laten zien bijvoorbeeld). Of kan ik gewoon beide properties houden?
Haan schreef op zondag 06 april 2014 @ 12:44:
Het gebruik van TempData is niet perse fout, maar gebruikelijker is het om het SeriesId in een Hidden input field te zetten in de Create view, zodat ie in de post gewoon weer netjes mee komt.

Je hoeft dan alleen de SeriesId property te zetten op je nieuwe Season, dat zou genoeg moeten zijn om te laten werken.
Hm dat klinkt wel logisch. Echter kan een (slimme) gebruiker dmv javascript niet gewoon die hidden input veranderen en zo een seizoen aan elk willekeurige series koppelen??




Edit

Ik heb een property SeriesId toegevoegd aan het Season model. Als ik hierna Add-Migration uitvoer dan lijkt het erop dat MVC inderdaad weet dat dit de foreign key naar Series is, hij wil namelijk de kolom "Series_Id" vervangen voor "SeriesId" ("Series_Id" had hij zelf aangemaakt), in plaats van een nieuwe kolom toevoegen die nergens aan gekoppeld is.

offtopic:
Dit snap ik ten eerste al niet echt, MVC lijkt heel handig met allemaal dingen die hij zelf 'snapt', maar ik snap niet wat hij wel en niet snapt dus het blijft een beetje gokken 8)7


Echter het updaten van de database gaat mis:
MySql.Data.MySqlClient.MySqlException (0x80004005): Fatal error encountered during command execution. ---> MySql.Data.MySqlClient.MySqlException (0x80004005): Parameter '@columnType' must be defined.

[ Voor 19% gewijzigd door NickThissen op 06-04-2014 13:00 ]

Mijn iRacing profiel


  • Haan
  • Registratie: Februari 2004
  • Laatst online: 15:31

Haan

dotnetter

Je haalt twee verschillende onderdelen door elkaar: EntityFramework (ik neem even aan dat je dat gebruikt) en het MVC framework.

De datalaag (models, interactie met database) heeft niets met MVC te maken, vandaar dat je er waarschijnlijk ook niet zoveel over kan vinden als je op die manier zoekt :) Dus ik zou je aanraden om even wat meer in het EntityFramework te duiken, zodat je beter snapt wat er gebeurt. Kijk bijvoorbeeld hier eens.

Kater? Eerst water, de rest komt later


  • NickThissen
  • Registratie: November 2007
  • Laatst online: 18-11 13:07
Je hebt gelijk, ik haal ze door elkaar.

Die link was wel nuttig maar had ik al eens gelezen. Ik kan er echter niet vinden hoe ik mijn probleem oplos. Het enige wat in de buurt komt volgens mij is Updating Related data. Daar hebben ze hetzelfde probleem dat een nieuw object aan een bestaand (parent) object gekoppeld moet worden.

Daar "lossen ze het op" echter door een dropdown te maken waar de gebruiker zelf de parent kan kiezen. Tsja, dat helpt mij niet want dat wil ik helemaal niet... De gebruiker klikt van een Series door naar 'New season' en dat nieuwe seizoen moet dan vanzelf gekoppeld worden aan de series waar de gebruiker vandaan komt.

Het ID in een hidden field stoppen klinkt als een oplossing maar ik meen me te herinneren dat dat geen goed idee was omdat een geavanceerde gebruiker gewoon met wat javascript die waarde kan aanpassen en zo een andere waarde kan doorsturen? Hoe beveilig ik dat dan?

Mijn iRacing profiel


  • krvabo
  • Registratie: Januari 2003
  • Laatst online: 20-11 19:54

krvabo

MATERIALISE!

Je hebt er niet eens javascript voor nodig. Tegenwoordig heeft zowat elke browser een developer console (F12) waarmee je simpel waardes kunt aanpassen.

Normaal vang je zoiets af door te controleren of de waarde daar wel bij kan horen, dat wil zeggen; dat de data niet ongeldig is. Ik zie niet helemaal in hoe het bij jou een probleem zou zijn eigenlijk, want so what als ze het aanpassen? Dan hebben ze er toch alleen henzelf mee? Ze kunnen ook net zo goed naar die andere 'series' gaan en dan daar op 'nieuw seizoen' klikken.

Je moet alleen controleren of de data geldig is (een nummer bijvoorbeeld), of dat nummer bestaat, en of die 'series' actief is (dwz: mensen MOGEN seizoenen toevoegen). Meer kun je, en hoef je, niet te doen.

Ik ben niet bekend met asp.net, maar het heeft vast wel iets als een session scope waarin je waarden van een gebruiker kunt opslaan. Dat zou je nog kunnen overwegen, maar is mogelijk wel foutgevoelig. (Het id van de series opslaan in de sessie-variabele)

[ Voor 14% gewijzigd door krvabo op 06-04-2014 14:34 ]

Pong is probably the best designed shooter in the world.
It's the only one that is made so that if you camp, you die.


  • NickThissen
  • Registratie: November 2007
  • Laatst online: 18-11 13:07
Ok. Mijn probleem daarmee was dat er meerdere gebruikers zijn en gebruiker 1 heeft misschien geen rechten om seizoenen toe te voegen aan een series die gebruiker 2 heeft gemaakt. Maar dat kan ik in de code natuurlijk wel afvangen dan. Duidelijk!


Edit
Het ID opslaan in de Session is volgens mij vergelijkbaar met het gebruiken van die TempData wat ik nu al doe. Het probleem daarbij is dat het model geen SeriesId krijgt door de form terwijl dat wel required is, dus is de ModelState invalid.

[ Voor 34% gewijzigd door NickThissen op 06-04-2014 14:44 ]

Mijn iRacing profiel


  • Haan
  • Registratie: Februari 2004
  • Laatst online: 15:31

Haan

dotnetter

NickThissen schreef op zondag 06 april 2014 @ 14:43:
Ok. Mijn probleem daarmee was dat er meerdere gebruikers zijn en gebruiker 1 heeft misschien geen rechten om seizoenen toe te voegen aan een series die gebruiker 2 heeft gemaakt. Maar dat kan ik in de code natuurlijk wel afvangen dan. Duidelijk!


Edit
Het ID opslaan in de Session is volgens mij vergelijkbaar met het gebruiken van die TempData wat ik nu al doe. Het probleem daarbij is dat het model geen SeriesId krijgt door de form terwijl dat wel required is, dus is de ModelState invalid.
Je kan ook het SeriesId aan je season toevoegen vóór je ModelState.IsValid aanroept, of kan je dan het formulier niet posten vanwege javascript validatie?

Kater? Eerst water, de rest komt later


  • InZane
  • Registratie: Oktober 2000
  • Laatst online: 15:54
Ik snap wel wat Haan bedoelt.

Dit is verwarrend:
C#:
1
Series series = db.Series.Find(seriesId); 

Je doet een Find(). Die geeft altijd maar 1 resultaat terug, dus het volgende zou logischer zijn:

C#:
1
Series serie = db.Series.Find(seriesId); 


Verder moet je hier echt geen TempData gebruiken. Ga eens zoeken naar 'model binding' in combinatie met ASP.NET MVC. Dat gaat je leven echt veel makkelijker maken.
Pagina: 1