80

Jeder Artikel, der im Internet bei der Verwendung von ViewModels und der Verwendung von Automapper gefunden wird, gibt die Richtlinien des "Controller -> View" -Richtlinien-Mappings an. Sie nehmen ein Domänenmodell zusammen mit allen Auswahllisten in ein spezielles ViewModel und übergeben es an die Ansicht. Das ist klar und gut.
Die Ansicht hat eine Form, und schließlich sind wir in der POST-Aktion. Hier kommen alle Model Binder zusammen mit [offensichtlich] einem anderen View Model, das [offensichtlich] mit dem ursprünglichen ViewModel zumindest im Teil der Namenskonventionen für die Bindung und Validierung verwandt ist.Wie wird das Modell in einer POST-Aktion dem Domänenmodell zugeordnet?

Wie ordnen Sie es Ihrem Domänenmodell zu?

Lassen Sie es eine Einfügeaktion sein, könnten wir den gleichen Automapper verwenden. Aber was, wenn es sich um eine Update-Aktion handelte? Wir müssen unsere Domain-Entität aus dem Repository abrufen, ihre Eigenschaften gemäß den Werten im ViewModel aktualisieren und im Repository speichern.

ADDENDUM 1 (9. Februar 2010): Manchmal ist die Zuweisung der Eigenschaften des Modells nicht ausreichend. Es sollte eine Aktion gegen das Domänenmodell gemäß den Werten des Ansichtsmodells unternommen werden. Das heißt, einige Methoden sollten im Domänenmodell aufgerufen werden. Wahrscheinlich sollte es eine Art von Application Service Schicht sein, die zwischen Steuerung und Domain um steht Ansicht Modelle zu verarbeiten ...


Wie dieser Code zu organisieren und wo sie zu platzieren, um folgende Ziele zu erreichen ?

  • halten Controller dünn
  • Ehre SoC Praxis
  • folgen Domain-Driven Design-Prinzipien
  • DRY sein
  • fortgesetzt ...

Antwort

6

Tools wie AutoMapper sein kann Wird verwendet, um ein vorhandenes Objekt mit Daten aus dem Quellobjekt zu aktualisieren. Die Controller-Aktion für die Aktualisierung könnte wie folgt aussehen:

[HttpPost] 
public ActionResult Update(MyViewModel viewModel) 
{ 
    MyDataModel dataModel = this.DataRepository.GetMyData(viewModel.Id); 
    Mapper<MyViewModel, MyDataModel>(viewModel, dataModel); 
    this.Repostitory.SaveMyData(dataModel); 
    return View(viewModel); 
} 

von Apart, was im Snippet sichtbar oben:

  • POST Datenmodell + Validierung anzuzeigen in Modelbinder erfolgt (könnte mit benutzerdefinierten Bindungen werden Exended)
  • Fehlerbehandlung (dh fangen Datenzugriff Ausnahme wirft durch Repository) durchgeführt werden kann [Handle]

Filter-Controller Aktion ist ziemlich t hin und Bedenken werden getrennt: Zuordnungsprobleme werden in der AutoMapper-Konfiguration behandelt, die Validierung erfolgt über ModelBinder und der Datenzugriff durch das Repository.

+6

Ich bin nicht sicher AutoMapper nützlich ist hier, da es Abflachung nicht rückgängig machen kann. Domain Model ist schließlich kein einfaches DTO wie das View Model, daher reicht es unter Umständen nicht aus, ihm Eigenschaften zuzuweisen. Wahrscheinlich sollten einige Aktionen für das Domänenmodell gemäß den Inhalten von View Model durchgeführt werden. Allerdings +1 für die Freigabe ziemlich gut Ansatz. –

+0

@Anton ValueInjecter kann flattening umkehren;) – Omu

+0

Mit diesem Ansatz halten Sie den Controller nicht dünn, Sie verletzen SoC und DRY ... wie Omu erwähnt, sollten Sie eine separate Schicht haben, die sich um die Kartierung kümmern. – Rookian

34

Ich verwende eine IBuilder Schnittstelle und implementieren sie die ValueInjecter

public interface IBuilder<TEntity, TViewModel> 
{ 
     TEntity BuildEntity(TViewModel viewModel); 
     TViewModel BuildViewModel(TEntity entity); 
     TViewModel RebuildViewModel(TViewModel viewModel); 
} 

mit ...(Implementierung) RebuildViewModel nur nennt BuildViewModel(BuilEntity(viewModel))

[HttpPost] 
public ActionResult Update(ViewModel model) 
{ 
    if(!ModelState.IsValid) 
    { 
     return View(builder.RebuildViewModel(model); 
    } 

    service.SaveOrUpdate(builder.BuildEntity(model)); 
    return RedirectToAction("Index"); 
} 

btw ich nicht schreibe Ansichtsmodell I Eingang schreiben cuz es viel kürzer ist, aber das einfach nicht wirklich wichtig
hoffen, dass es

-Update hilft: Ich verwende diesen Ansatz jetzt in der ProDinner ASP.net MVC Demo App, es heißt jetzt IMapper, gibt es auch eine PDF zur Verfügung, wo dieser Ansatz im Detail erklärt wird

+0

Ich mag diesen Ansatz. Eine Sache, die mir nicht klar ist, ist die Implementierung von IBuilder, insbesondere im Hinblick auf eine mehrstufige Anwendung. Zum Beispiel hat mein ViewModel 3 SelectLists. Wie ruft die Builder-Implementierung die Auswahllistenwerte aus dem Repository ab? –

+0

@Matt Murrell siehe http://prodinner.codeplex.com Ich mache das dort, und ich nenne es IMapper statt IBuilder – Omu

+5

Ich mag diesen Ansatz, ich habe hier ein Beispiel davon implementiert: https: // gist. github.com/2379583 –

4

Ich möchte sagen, dass Sie den Begriff ViewModel für beide Richtungen der Client-Interaktion wiederverwenden. Wenn Sie genügend ASP.NET MVC-Code in der freien Wildbahn gelesen haben, haben Sie wahrscheinlich den Unterschied zwischen einem ViewModel und einem EditModel gesehen. Ich denke, das ist wichtig.

Ein ViewModel stellt alle Informationen dar, die zum Rendern einer Ansicht erforderlich sind. Dies kann Daten einschließen, die an statischen nicht interaktiven Orten gerendert werden, und auch Daten, die lediglich dazu dienen, eine Prüfung durchzuführen, um zu entscheiden, was genau gerendert werden soll. Eine Controller-GET-Aktion ist allgemein dafür verantwortlich, das ViewModel für seine Ansicht zu packen.

Ein EditModel (oder vielleicht ein ActionModel) stellt die Daten dar, die zum Ausführen der Aktion erforderlich sind, die der Benutzer für diesen POST ausführen wollte. Ein EditModel versucht also wirklich, eine Aktion zu beschreiben. Dies wird wahrscheinlich einige Daten aus dem ViewModel ausschließen und obwohl ich damit verwandt bin, ist es wichtig zu erkennen, dass sie tatsächlich anders sind.

Eine Idee

Das heißt Sie sehr leicht eine AutoMapper Konfiguration für das Gehen von Modell haben könnte -> Ansichtsmodell und eine andere von EditModel zu gehen -> Modell. Dann müssen die verschiedenen Controller-Aktionen nur AutoMapper verwenden. Hell könnte das EditModel eine Funktion haben, um seine Eigenschaften gegen das Modell zu validieren und diese Werte auf das Modell selbst anzuwenden. Es macht nichts anderes und Sie haben ModelBinders in MVC, um die Anfrage trotzdem dem EditModel zuzuordnen.

Eine weitere Idee

Jenseits dieses etwas, das ich diese Art von der Idee eines ActionModel abarbeitet etwa vor kurzem gedacht haben, ist, dass der Kunde, was zurück zu Ihnen Entsendung ist eigentlich die Beschreibung mehrerer Aktionen der Benutzer durchgeführt und nicht nur ein großer Datenklumpen. Dies würde sicherlich erfordern einige Javascript auf der Client-Seite zu verwalten, aber die Idee ist faszinierend, denke ich.

Im Wesentlichen, da der Benutzer Aktionen auf dem Bildschirm ausführt, den Sie ihnen präsentiert haben, würde Javascript eine Liste von Aktionsobjekten erstellen. Ein Beispiel ist möglicherweise, dass sich der Benutzer auf einem Mitarbeiterinformationsbildschirm befindet. Sie aktualisieren den Nachnamen und fügen eine neue Adresse hinzu, weil der Mitarbeiter gerade verheiratet war. Unter dem Deckmantel erzeugt dies eine ChangeEmployeeName und eine AddEmployeeMailingAddress Objekte zu einer Liste. Der Benutzer klickt auf "Speichern", um die Änderungen zu bestätigen, und Sie übermitteln die Liste mit zwei Objekten, von denen jedes nur die Informationen enthält, die für die Ausführung der einzelnen Aktionen erforderlich sind.

Sie würden einen intelligenteren ModelBinder benötigen, dann sollte der standardmäßige, aber gute JSON-Serializer in der Lage sein, die Zuordnung der clientseitigen Aktionsobjekte zu den serverseitigen zu übernehmen. Die serverseitigen (wenn Sie sich in einer 2-Tier-Umgebung befinden) könnten leicht Methoden haben, die die Aktion für das Modell, mit dem sie arbeiten, abgeschlossen haben. Die Controller-Aktion erhält also nur eine ID für die Model-Instanz und eine Liste mit Aktionen, die ausgeführt werden sollen. Oder die Aktionen haben die ID in sich, um sie sehr getrennt zu halten.

Also vielleicht so etwas wie dieses wird auf der Serverseite realisiert:

public interface IUserAction<TModel> 
{ 
    long ModelId { get; set; } 
    IEnumerable<string> Validate(TModel model); 
    void Complete(TModel model); 
} 

[Transaction] //just assuming some sort of 2-tier with transactions handled by filter 
public ActionResult Save(IEnumerable<IUserAction<Employee>> actions) 
{ 
    var errors = new List<string>(); 
    foreach(var action in actions) 
    { 
     // relying on ORM's identity map to prevent multiple database hits 
     var employee = _employeeRepository.Get(action.ModelId); 
     errors.AddRange(action.Validate(employee)); 
    } 

    // handle error cases possibly rendering view with them 

    foreach(var action in editModel.UserActions) 
    { 
     var employee = _employeeRepository.Get(action.ModelId); 
     action.Complete(employee); 
     // against relying on ORMs ability to properly generate SQL and batch changes 
     _employeeRepository.Update(employee); 
    } 

    // render the success view 
} 

Das ist wirklich die Entsendung Aktion zurück macht ziemlich allgemein, da Sie auf Ihrer Modelbinder setzen Sie die richtige IUserAction Instanz und Ihre IUserAction Instanz zu erhalten entweder die richtige Logik selbst ausführen oder (wahrscheinlicher) mit der Information in das Modell aufrufen.

Wenn Sie sich in einer 3-Tier-Umgebung befanden, konnten mit der IUserAction einfach einfache DTOs erstellt werden, die über die Grenze hinweg aufgenommen und in einer ähnlichen Methode auf der App-Ebene ausgeführt wurden. Je nachdem, wie Sie diese Ebene erstellen, kann sie sehr einfach aufgeteilt werden und bleibt trotzdem in einer Transaktion (was Agathas Anfrage/Antwort und die Nutzung der Identitätskarte von DI und NHibernate betrifft).

Ich bin mir sicher, es ist keine perfekte Idee, es würde einige JS auf Client-Seite erfordern, zu verwalten, und ich war noch nicht in der Lage, ein Projekt zu tun, um zu sehen, wie es sich entfaltet, aber der Post versuchte es Denk darüber nach, wie du hin und zurück kommst, also dachte ich, ich würde meine Gedanken geben. Ich hoffe, es hilft und ich würde gerne von anderen Möglichkeiten hören, um die Interaktionen zu verwalten.

+0

Interessant. Was die Unterscheidung zwischen ViewModel und EditModel anbelangt ... sagen Sie, dass Sie für eine Bearbeitungsfunktion ein ViewModel verwenden würden, um das Formular zu erstellen und dann an ein EditModel zu binden, wenn der Benutzer es gepostet hat? Wenn ja, wie würden Sie mit Situationen umgehen, in denen Sie das Formular aufgrund von Validierungsfehlern erneut senden müssten (z. B. wenn das ViewModel Elemente zum Auffüllen eines Dropdowns enthielt) - würden Sie die Dropdown-Elemente einfach in das EditModel einfügen? In welchem ​​Fall würde der Unterschied zwischen den beiden bestehen? – UpTheCreek

+0

Ich vermute, Ihre Sorge ist, dass wenn ich ein EditModel benutze und es einen Fehler gibt, dann muss ich mein ViewModel neu erstellen, was sehr teuer sein könnte. Ich würde sagen, nur das ViewModel neu erstellen und sicherstellen, dass es eine Stelle hat, um Benutzerbenachrichtigungen (wahrscheinlich positive und negative wie Validierungsfehler) zu platzieren. Wenn sich herausstellt, dass es sich um ein Leistungsproblem handelt, können Sie das ViewModel immer zwischenspeichern, bis die nächste Anforderung der Sitzung beendet wurde (wahrscheinlich ist dies der Post des EditModels). –