webForumDet fria alternativet

Updatera en domänentitet via webbgränssnitt?

.NET

22 svar · 1 423 visningar · startad av Lukaspojken

Medlem sedan maj 20011 312 inlägg
Frågan#1

Det är så här att jag har ett webbgränssnitt för uppdatering av en kund (Customer-entitet). Hur ser ett bra uppdateringsflöde ut?

Ett exempel på en variant av uppdateringsflöde
1. Hämta en specifik kund från databasen (CustomerService -> CustomerRepository)
2. Mappa önskvärda properties mot webbkontroller (textboxar m.m.)
3. Användaren gör ändringar på kunden. Tex byter adress på kunden.
4. Användaren klickar på spara.
5. Man hämtar upp den specifika kunden igen från databasen.
6. I webbgränssnittet så mappar man tillbaks webbcontrollerna mot entitetens properties.
7. Därefter sparar man entiteten i databasen.

Finns det andra bättre sätt att göra detta på? Det är främst steg 5 som jag är lite fundersam på om det är en bra lösning? Jag har sett andra lösningar där man tex sparat entiteten i viewstate men det kräver att entiteten är serialiserbar och det är den inte alltid. En annan variant är att den sparas i någon form av session. Hur brukar ni göra?

Medlem sedan maj 20012 812 inlägg
#2

Steg 5 behöver du inte göra om du redan har all information på sidan. Är det så att någon information inte finns, så måste du hämta upp kunden igen, göra förändringarna och sedan spara ner den.

Så iprincip så måste man gå igenom alla dessa steg på något sätt för att vara garanterad att kunder ser ut som du förväntar dig.

så här ser min kod ut i ASP.NET MVC för att göra samma sak som du är uteefter:

        [AcceptVerbs(HttpVerbs.Get)]
        public ActionResult Information(Guid? id)
        {
            Player player = null;
            if (id != null && id.HasValue && id.Value != Guid.Empty)
                player = _playerRepository.GetPlayerByIdentifier(id.Value);

            return View(player);
        }

        [AcceptVerbs(HttpVerbs.Post)]
        [ValidateModelAttribute(typeof(FormPlayer))]
        public ActionResult Information([Bind(Prefix = "")] FormPlayer formPlayer) 
        {
            if (!ModelState.IsValid)
                return View();

            Player model = new PlayerMapper().MapToModel(formPlayer);
            _playerRepository.Save(model);

            return RedirectToAction<Shared.SharedController>(m => m.Success());
        }

Det är den koden som finns i min controller, sedan så finns det ju givetviss en del bakom liggande klasser för att validera inputdata och mappa data mellan min presentationsmodell och min domänmodell... Mappandet från kontrollerna på sidan till min presentationsmodell sköter ASP.NET MVC till mig om man använder samma namn både i presentationsmodellen och id:et på kontrollen. Enkelt och smidigt.

- M

Medlem sedan maj 20011 312 inlägg
#3

Tack för svaret och kodexemplet! Jag har klurat en del på detta och ur ett förvaltningsperspektiv är det nog bäst att oftast göra en ny hämtning av entitet och sedan fylla det. För det kan vara så att när man skapade gränssnittet så använde man alla properties men när domänen utvecklas så är det inte ovanligt att nya properties tillkommer. Skulle detta inträffa så kommer man editeringsgränssnittet fortsätta att fungera som vanligt.

En nyfiken fråga. Vad är formplayer för något? I MapToModel antar jag att du för över värdena från webkontrollerna till entiteten. Använder du reflection i detta fallet eller har du metadatan i tex xml?

Medlem sedan maj 20012 812 inlägg
#4

lukaspojken skrev:

Jag har klurat en del på detta och ur ett förvaltningsperspektiv är det nog bäst att oftast göra en ny hämtning av entitet och sedan fylla det. För det kan vara så att när man skapade gränssnittet så använde man alla properties men när domänen utvecklas så är det inte ovanligt att nya properties tillkommer. Skulle detta inträffa så kommer man editeringsgränssnittet fortsätta att fungera som vanligt.

Helt rätt, ett alternativ är ju att ha det cachat någonstans så du slipper hämta datan från databasen igen. Men det kräver ju att din cache kan känna av om det sker ändringar i databasen, eller att alla program alltid måste gå via denna cache för att hämta/spara data....

lukaspojken skrev:

En nyfiken fråga. Vad är formplayer för något? I MapToModel antar jag att du för över värdena från webkontrollerna till entiteten. Använder du reflection i detta fallet eller har du metadatan i tex xml?

Jag har en baseMapToModel som använder reflection för att mappa namn mot namn, väldigt ofta så stämmer de dock inte, så det bästa man kan göra är att använda en OOMapper. Object till object mapper. Men idagsläget så har jag en mapper för varje presentationsentitet som jag skall mappa. och i detta fallet så heter min Presentationentitet, formPlayer där min domänentitet heter Player...

- M

Medlem sedan dec. 19996 522 inlägg
#5

Jag har en baseMapToModel som använder reflection för att mappa namn mot namn, väldigt ofta så stämmer de dock inte,

Tipsar om AutoMapper av Jimmy Bogard från LosTechies (http://automapper.codeplex.com/Release/ProjectReleases.aspx?ReleaseId=27293)

Medlem sedan maj 20011 312 inlägg
#6

-> Gladh
Om jag förstår dig rätt så gör du ungefär så här när du tex hämtar upp en player och mappar den mot webkontroller:

1. Hämta player-entiteten via PlayerRepository.
2, Mappar (via reflection) player-entiteten med formplayer-presentationsentiteten
3. FormPlayer mappar du sedan manuellt till webkontrollerna

Är det så du ungefär gör? Det jag börjat att experimentera med är att direkt mappa tex player-entiteten med webbkontrollerna via reflection. Jag har alltså skipat presentationsentiteten. Vad är förresten syftet med presentationsentiteten?

Du nämnde också att vid reflection-mappningen så stämde oftast inte namnen med varandra. Skulle du inte kunna sätta attribut på formplayer som anger vilka properties i player den ska mappa mot i de fall propertynamnen skiljer sig åt?

Medlem sedan aug. 20003 575 inlägg
#7

Ett presentationsobjekt för mig är något som är UI specifikt, dvs ett presentationsobjekt innehåller de saker som skall visas i UI:t och inte något annat. presentationsobjektet kan bestå av en eller flera entiteter. Det kan erbjuda funktionalitet som Undo/Redo. Det kan innehålla saker som är UI specifikt som t.ex. en sökväg till vilken bild det är som skall visas (om det är web). osv osv. Presenentationsobjekt kan också implemmentera interface som INotifyPropertyChanged, IDataError osv som är enligt mig tillhör UI och inte domänmodellen.

Medlem sedan maj 20012 812 inlägg
#8

lukaspojken skrev:

-> Gladh
Om jag förstår dig rätt så gör du ungefär så här när du tex hämtar upp en player och mappar den mot webkontroller:

1. Hämta player-entiteten via PlayerRepository.
2, Mappar (via reflection) player-entiteten med formplayer-presentationsentiteten
3. FormPlayer mappar du sedan manuellt till webkontrollerna

I stort sett så blir det så. I detta fall så mappar jag min domänentitet mot kontrollerna på websidan, men när jag hämtar tillbaka datan så görs det som en presentationsentitet, eftersom jag där i har validering som jag inte vill ha i mina domänentiteter.

Mest korrekt hade varit att först mappa domänentitet till en presentationentitet och sedan skicka den till sidan!

lukaspojken skrev:

Du nämnde också att vid reflection-mappningen så stämde oftast inte namnen med varandra. Skulle du inte kunna sätta attribut på formplayer som anger vilka properties i player den ska mappa mot i de fall propertynamnen skiljer sig åt?

Visst kan man det, eller ha det i ett xml-dokument...

- M

Medlem sedan aug. 20003 575 inlägg
#9

Du får ändra citatet Gladh :)

Red: bra :)

Medlem sedan maj 20011 312 inlägg
#10

-> Nickemannen
Det är lite av ett DTO kan man säga och med "utvidgad funktionalitet" som är anpassat för det GUI man jobbar med. Jag har aldrig använt mig av presentationsobjekt men DTO:r har det blivit i vissa fall. Använder du presentationsobjekt alltid eller enbart i vissa fall där du behöver den "utvidgade funktionalitet"?

-> Gladh
Du nämnde att du hade valideringen på presentationsobjekten. En sak som jag funderar på är att du skulle kunna ha använt dig av en specifikation för att sköta Gui-valideringen. Sedan skickar du bara in specikationen in i ditt domänobjekt som validerar propertyvärdena som din specifikation anger. Då skulle du slippa skapa ett presentationsobjekt för det känns rent spontant som att det kan leda till mer förvaltning. Men i de fall du har ännu mer "utvidgad funktionalitet" än enbart validering då känns det som att presentationsobjekten kan tänkas vara bra att använda.

Medlem sedan maj 20012 812 inlägg
#11

lukaspojken skrev:

Du nämnde att du hade valideringen på presentationsobjekten. En sak som jag funderar på är att du skulle kunna ha använt dig av en specifikation för att sköta Gui-valideringen. Sedan skickar du bara in specikationen in i ditt domänobjekt som validerar propertyvärdena som din specifikation anger. Då skulle du slippa skapa ett presentationsobjekt för det känns rent spontant som att det kan leda till mer förvaltning.

Säkert en lösning på problemet, men precis som nickemannen sa så kan ju presentationobjektet innehålla information som inte finns i något domänentitet men som du ändå måste validera, eller så innhåller den information från flera olika domänentitet.

Vilket som blir jobbigast att underhålla i längden har jag ingen anning om. Men jag håller min validering av inputdata i mitt presentationslager där tycker jag den hör hemma och valideringen bör inte ske mot domänentiteterna.

lukaspojken skrev:

Men i de fall du har ännu mer "utvidgad funktionalitet" än enbart validering då känns det som att presentationsobjekten kan tänkas vara bra att använda.

De är bra att använda om man vill separerar sina domänentiteter från sitt presentationslager. Många gånger så kommer presentationentitetn och domänentiteten se likadanna ut och man får väga sin tid att skapa en massa nya objekt mot vinsten att separerar sina lager ifrån varandra. Visa tycker att det är viktigt, andra inte...

- M

Medlem sedan maj 20011 312 inlägg
#12

Gladh skrev:

Men jag håller min validering av inputdata i mitt presentationslager där tycker jag den hör hemma och valideringen bör inte ske mot domänentiteterna.

Detta kan du fortfarande göra genom att låta din specifikation vara knuten till ditt presentationslager sedan blir din domänentitetsvalidering mer dynamisk genom att den klarar av att hantera mängder med olika typer av valideringstyper (dvs olika specifikationer). Vi hamnar utanför frågan men det är en intressant diskussion...

Medlem sedan aug. 20003 575 inlägg
#13

PresentationsObjektet/ViewModel, kan också göra lite fler saker som inte en entitet får, min ViewModel får lyssna på kommandon från UI och utföra sparningar i databas osv. Samt att min ViewModel har som jag sa tidigare INotifyPropertyChanged implemmenterat vilket jag inte skulle klara mig utan.

ViewModel behöver inte heller representera någon entitet utan kan vara vanlig input som utför olika saker.

Om man utför förändringar i Domänmodellen som säkert sker flera gånger behöver inte UI:t ändras utan bara kopplingen mellan ViewModel eller PresentationsObjektet.

Själklart hör validering av businessregler till domänlagret, men kontroller om användaren som man försöker registrera redan finns eller liknande så klarar in domänlagret av det utan behöver hjälp av en domänservice t,ex, enligt mig får den lov att finnas i en ViewModel.

Medlem sedan maj 20011 312 inlägg
#14

Nickemannen skrev:

PresentationsObjektet/ViewModel, kan också göra lite fler saker som inte en entitet får, min ViewModel får lyssna på kommandon från UI och utföra sparningar i databas osv.

I vilka sammanhang använder du denna funktionalitet? (nyfiken)

Nickemannen skrev:

Samt att min ViewModel har som jag sa tidigare INotifyPropertyChanged implemmenterat vilket jag inte skulle klara mig utan.

Varför skulle du inte klara dig utan denna? (nyfiken)

Nickemannen skrev:

ViewModel behöver inte heller representera någon entitet utan kan vara vanlig input som utför olika saker.

Håller helt med dig...

Nickemannen skrev:

Själklart hör validering av businessregler till domänlagret, men kontroller om användaren som man försöker registrera redan finns eller liknande så klarar in domänlagret av det utan behöver hjälp av en domänservice t,ex, enligt mig får den lov att finnas i en ViewModel.

Jag vet inte om jag förstod dig rätt men har du en domänservice som tex checkar om användaren finns eller inte i ditt presentationsobjekt? Hur kommer det sig? Är det inte vanligt att skapa tex en UserService-klass som har en metod som heter CreateUser eller något liknande och där i har man bland annat valideringsprocessen om tex en användare med det användarnamnet redan är registrerat.

Medlem sedan maj 20012 812 inlägg
#15

lukaspojken skrev:

Citat:
Nickemannen skrev:
Samt att min ViewModel har som jag sa tidigare INotifyPropertyChanged implemmenterat vilket jag inte skulle klara mig utan.

Varför skulle du inte klara dig utan denna? (nyfiken)

Nu kidnappar jag nickemannens svar här. Men jag tror att han liksom jag använder sig av DataBinding och i det sammanhanget så är INotifyPropertyChanged interfacet en krav för att få det att fungerar. Och när databindingen fungerar som den skall så är det ett underbart verktyg för att binda data till kontroller på sidan.

Så mycket bättre än att själv sätta datan till kontrollerna och sedan hämta värdet från kontrollerna och sätta till entiteten.

Det kräver dock att man tänker till lite när det gäller möjlighet att ångra förändringar som man har gjort på en sida...

- M

Medlem sedan maj 20011 312 inlägg
#16

-> Gladh
Jag har inte testat på den varianten av databinding-funktionaliteten som ni kör men jag har hört talas om den. Men hur kommer sig att man är tvungen att implementera INotifyPropertyChanged? På vilket sätt använder sig databindingen denna funktionalitet senare? Många frågor :)

Medlem sedan aug. 20003 575 inlägg
#17

Lukaspojken skrev:

-> Gladh
Jag har inte testat på den varianten av databinding-funktionaliteten som ni kör men jag har hört talas om den. Men hur kommer sig att man är tvungen att implementera INotifyPropertyChanged? På vilket sätt använder sig databindingen denna funktionalitet senare? Många frågor :)

Jag tror att jag gått över lite mycket till WPF och Silverlight nu med INotifyPropertyChanged än Web sorry.

Men INotifyPropertyChanged är bara en grej.
Vi kan se presentationsobjektet lite som en översättare också, säg t.ex. att du har en bild i databasen, antingen innehåller ditt domänobjekt antingen en referens till bilden eller en massa byte's. Det du vill att ditt presentationsobjekt skall göra är att ha en länk till bilden. Då skiljer du på UI logik och domänlogik. Eftersom du bör kunna återanvända samma logik i en windowsapp men där ser presentationsobjektet annorlunda ut där kanske ditt presentationsobjekt kräver att du har en Image som datatyp.

Medlem sedan aug. 20003 575 inlägg
#18

Lukaspojken skrev:

Jag vet inte om jag förstod dig rätt men har du en domänservice som tex checkar om användaren finns eller inte i ditt presentationsobjekt? Hur kommer det sig? Är det inte vanligt att skapa tex en UserService-klass som har en metod som heter CreateUser eller något liknande och där i har man bland annat valideringsprocessen om tex en användare med det användarnamnet redan är registrerat.

UserService = en domänservice för mig.
UserService får finnas i ett presentationsobjekt för mig.

t.ex. domänen får inte kunna ha t.ex. User.Save() på sig eftersom den inte är tillåten att känna till infrastrukturen. Däremot får ett presentationsobjekt ha det enligt mig UserPresentationObject.Save() som sparar sitt underliggande user objekt med en UserService den har blivit tilldelad.

Medlem sedan maj 20011 312 inlägg
#19

Nickemannen skrev:

Jag tror att jag gått över lite mycket till WPF och Silverlight nu med INotifyPropertyChanged än Web sorry.

Men INotifyPropertyChanged är bara en grej.
Vi kan se presentationsobjektet lite som en översättare också...

Jag förstår syftet med presentationsobjektet. Det jag inte riktigt förstod var varför man är tvungen att implemetera INotifyPropertyChanged. På vilket sätt är databinding beroende av detta, dvs hur använder man sig av kollektionen med propertyn som blivit ändrade?

Nickemannen skrev:

...
t.ex. domänen får inte kunna ha t.ex. User.Save() på sig eftersom den inte är tillåten att känna till infrastrukturen. Däremot får ett presentationsobjekt ha det enligt mig UserPresentationObject.Save() som sparar sitt underliggande user objekt med en UserService den har blivit tilldelad.

Du skriver enligt dig får ett presentationsobjekt vara mer intelligent men vad säger de som inte håller med dig? Dvs varför bör man inte ha det så? Det som jag gillar med intelligenta objekt är att man samlar ihop funktionalitet som hör till objektet. Det blir lättare att hitta det man söker efter. Men det har sina nackdelar också rent spontant känner jag att risken för dubblicering av kod ökar när man kapslar in "extern" intelligens i sina objekt eftersom det blir svårare att återanvända funktionalitet när den kapslats in. Det finns nog också risk för att objekten blir hårdare knutna till andra objekt genom beroenden vilket också borde påverka testbarheten en aning negativt. En annan sak är väl att intelligenta objekt inte riktigt speglar verklighten vilket påverkar "ubiquitous language". Tänker vi oss tex en klassisk user-entitet så har användaren inte en spara-metod i sig själv utan det är oftast en tjänst som man anropar. Det var några spontana tankar kring intelligenta objekt.

Medlem sedan maj 20012 812 inlägg
#20

lukaspojken skrev:

Det jag inte riktigt förstod var varför man är tvungen att implemetera INotifyPropertyChanged. På vilket sätt är databinding beroende av detta, dvs hur använder man sig av kollektionen med propertyn som blivit ändrade?

Som nickemanen sa tidigare, så har databinding mer med WPF och WinForms att göra än med Web-programmering, eftersom du har en stateless miljö i web, och där fungerar inte databindningen så som det gör i WPF/WinForm.

Om vi håller oss i WPF miljön så skulle din kod för ett WPFfönster se ut så här:

<TextBox Text="{Binding Path=FirstName}"/>

Här har vi nu en Textbox på i ditt WPFfönster och denna är nu bunden mot din property FirstName på din klass "User". (Man binder klassen user till WPF-Fönsters "ItemSource")

Så det som står i User.FirstName finns nu alltså även i Textboxen, och skulle du ändra i Textboxen och när du lämnar textboxen så ändrar du även värdet i din FirstName-property. Helt vanlig databinding precis som om du själv skulle gjort det i kod-behind filen, men betydligt enklare.

Nu gör vi så att vi lägger till lite fler kontroller.

<TextBox Text="{Binding Path=FirstName}"/>
<TextBox Text="{Binding Path=LastName}"/>

<TextBox Text="{Binding Path=DisplayName, Mode=OneWay}" IsReadOnly="True"/>

Här har vi nu alltså både FirstName och LastName samt en textbox som har en referens till property DisplayName så i sin tur bara är en sammanslagning av FirstName och LastName.

Så om vi nu ändrar i FirstName eller LastName så kommer dessa värden skickas till din Userklass och värden sättas där, men värdet i DisplayName kommer inte att ändras!!! Varför inte det då. Jo för hur skall den kontrollen vet att DisplayName värdet har ändrats? Det vet den inte! Men om vi implementerar INotifyPropertyChanged interfacet så kan vi när vi ändrar i FirstName och LastName även meddela att vi vill att DisplayName property har ändrats! Typ så här.

public class User{
   public string FirstName{
       get{return _firstName;}
       set{
             _firstName = value;
             base.OnPropertyChanged("FirstName");
             base.OnPropertyChanged("DisplayName");
       }
      .
      .
      .
}

Här kommer vi nu alltså berätta för alla som lyssnar på detta objekt att både FirstName och DisplayName's värden har ändrats och alla kontroller som visar dessa värden bör hämta nya värden och visa dessa...

Det fina är att det sker transparant till alla fönster. Så om du har en lista med alla användare så kan du se i den samtidigt som du ändrar i ditt detaljfönster att FirstName och DisplayName kommer få nya värden. Jättesmidigt!!!

Speciellt om det är så att du håller en lista med object i en cache, och du har en tråd i bakgrunden som kan manipulera denna lista, typ via WCF/WebServices eller något annat, så när denna tråden lägger till ett objekt i listan så sköter ICollectionChanged interfacet (din lista måste implementera det interfacet för att kunna hantera Add,Remove. osv osv) att visa denna förändring i ditt fönster utan att du behöver skriva kod att du skall refresha fönster varje 5 sek eller något....

- M

259 ms totalt · 4 externa anrop · v20260731065814-full.86ec41c2
122 ms — deklarationer (db)
0 ms — hämta statistik (cache)
135 ms — hämta tråd, inlägg och bilagor (db)
121 ms — ändringar (db)