2016-06-28 4 views
2

Übersichtwiederverwendbare MVC-Code: Werttypen in Linq Umgang mit Ausdrücken

ich ein MVC Satz schreibe, die einfache Suche liefern soll/hinzufügen/bearbeiten/löschen Funktionalität für ein Objekt Entity Framework. Es macht auch einige ausgefallene Dinge wie die Unterstützung von Dropdowns, die aus anderen Tabellen mit Attributen gefüllt werden.

Die Hauptaufgabe im Spiel ist ein vererbbarer generische Steuergerät (AdminController<T> wo T das EF-Objekt ist), um ein Array von AdminField<T>, von denen jedes Element ein Feld darstellt/Objekt in T enthält. AdminController<T> kann ein Objekt AdminViewModel<T> generieren. Unten ist ein Visio-Diagramm der Beziehungen zwischen diesen Objekten.

enter image description here

Das Problem

ist mein Problem in den Ansichten. Um Werte anzuzeigen, verwende ich die Html.EditorFor/HiddenFor/TextboxFor Helfer. Eine hyper-vereinfachte Ansicht durchläuft einfach das Feld und zeigt ihre Werte an, so dass sie bearbeitet werden können, wenn sie bearbeitbare Felder sind. Das sieht aus wie:

@model AdminViewModel<ExampleEFObject> 
@using (Html.BeginForm()) 
{ 
    foreach (var f in Model.Fields) 
    { 
     var expression = Model.ExpressionFromField(f); 

     <label class="control-label col-md-3">@f.DisplayName</label> 

     @if (!f.Editable) 
     { 
      @Html.TextBoxFor(expression, new { @class = "form-control", @disabled = "disabled" }) 
     } 
     else 
     { 
      @Html.EditorFor(expression, new { htmlAttributes = new { @class = "form-control" } }) 
     } 
    } 
    <input type="submit" value="Save" class="btn btn-primary" /> 
} 

Nun, das ist, was ich will es aussehen. Es funktioniert gut mit Feldern/Eigenschaften, die Referenztypen sind. Mit Werttypen, jedoch bekomme ich einen System.InvalidOperationException Fehler auf der EditorFor Zeile: „Vorlagen nur mit Feldzugriff verwendet werden können, Zugriff auf Eigenschaften, eindimensionaler Array-Index oder Einzelparameter benutzerdefinierte Indexer Ausdrücke.“

Dies liegt daran, dass die Methode ExpressionFromField, die einen Ausdruck zurückgibt, der auf eine Instanz von T im ViewModel zugreift, den Rückgabetyp Expression<Func<AdminViewModel<T>, object>> hat. Da ein Objekttyp zurückgegeben wird, wird beim Generieren des Ausdrucks Boxing erzwungen, sodass anstelle von (z. B. t => t.Count) meine Methode tatsächlich t => Convert(t.Count) zurückgibt.

Aktuelle Behelfslösung

Meine aktuelle Problemumgehung ist ein zweites Verfahren als Expression<Func<AdminViewModel<T>, int>> IntExpressionFromField(AdminField<T> field) definiert haben. Da es einen int zurückgibt, wird das Boxing in dem Ausdruck nicht erzwungen. Allerdings macht es meine Ansichten extrem hässlich:

@model AdminViewModel<ExampleEFObject> 
@using (Html.BeginForm()) 
{ 
    foreach (var f in Model.Fields) 
    { 
     var expression = Model.ExpressionFromField(f); 
     var intExpression = Model.IntExpressionFromField(f); 

     <label class="control-label col-md-3">@f.DisplayName</label> 

     @if (!f.Editable) 
     { 
      if (intExpression == null) 
      { 
       @Html.TextBoxFor(expression, new { @class = "form-control", @disabled = "disabled" }) 
      } 
      else 
      { 
       @Html.TextBoxFor(intExpression, new { @class = "form-control", @disabled = "disabled" }) 
      } 
     } 
     else 
     { 

      if (intExpression == null) 
      { 
       @Html.EditorFor(expression, new { htmlAttributes = new { @class = "form-control" } }) 
      } 
      else 
      { 
       @Html.EditorFor(intExpression, new { htmlAttributes = new { @class = "form-control" } }) 
      } 
     } 
    } 
    <input type="submit" value="Save" class="btn btn-primary" /> 
} 

Schlimmer noch, diese unterstützt nur 1 Werttyp (int). Ich würde auch gern Dezimal, Lang und was auch immer sonst noch unterstützen - am besten, ohne für jede einzelne Methode eigene Methoden erstellen zu müssen, sowie die gesamte Logik, um sich gegen andere Typen abzusichern.

Hilfe!

Ich würde gerne eine elegante, einfache Möglichkeit finden, dieses Problem zu lösen. Wenn keiner existiert, denke ich, dass mein nächster Schritt wäre, die Html Hilfsmethoden zu stoppen; Dies würde jedoch erfordern, dass die Konsistenz mit dem Rest der Anwendung gebrochen wird, und ich weiß nicht einmal, dass dies das Problem lösen würde.

Jede Hilfe wäre sehr willkommen!

+0

Es ist insgesamt eine schlechte Übung, Ihr EF-Objekt an Ansicht übergeben. Sie müssten wahrscheinlich ein abstraktes FormViewModel erstellen, das Implementierungen einiger abstrakter FieldViewModels (mit Editorvorlagen, wie DatePickerViewModel, TextBoxViewModel usw.) enthält, und in Ihrem Controller müssen Sie dieses ViewModel umwandeln, um Operationen für Ihre Entity Framework-Entität zu aktualisieren in DTO, wenn Sie eine Service-Schicht haben. –

+0

Es lohnt sich möglicherweise nicht, da es Ihnen nicht erlaubt, komplexe Layouts oder ausgefallene Benutzerinteraktion zu machen, also zu ViewModels und manuellem Code zu wechseln, um Entitäten zu aktualisieren, wenn Sie nicht so viele Entitäten haben –

+0

@raderick: Danke für reagieren. Ich habe mein EF-Objekt nicht an die View übergeben, sondern nur ein generisches ViewModel. Aber wenn das eine schlechte Praxis ist, würde ich gerne verstehen, warum ich in Zukunft einen besseren Ansatz wählen kann. Der Grund, warum ich das tue, ist, weil es Dutzende von Objekttypen gibt, die nur einige grundlegende Wartungsfunktionen erfordern. Ich möchte vermeiden, Dutzende von identischen MVC-Sets zu haben. Der von Ihnen beschriebene Ansatz scheint unglaublich schwer und einschränkend zu sein, wie Sie schon sagten, aber ich werde es als Fallback-Option berücksichtigen. – Daniel

Antwort

0

Konnte dieses Problem mit meinem bestehenden Ansatz nicht beheben, habe ich den Ansatz, den ich am Ende erwähnt: HtmlHelper Methoden aufgegeben. Das hat super funktioniert und ich wünschte ich hätte es schon lange gemacht.

MVC Viewmodel Bindung erreicht wird, um die Eigenschaft „Namen“ verwendet wird, so manuell zu meinem Modell zu binden, ich brauchte die uneingeschränkten Eigenschaftsnamen (dh bekommen für das Feld als cust => cust.Records.Address.State ausgedrückt, brauchte ich die Zeichenfolge SelectedItem.Records.Address.State (wo SelectedItem das ist Viewmodel-Objekt, das den angezeigten Wert enthält). Mein AdminField<T> Objekt hatte bereits eine MemberExpression Eigenschaft, so habe ich nur die ToString() Methode, entfernt, um die Parameter und voran das Ansichtsmodell Objektnamen. hässlich, ich weiß, aber zuverlässig und einfach.

Vorher brauchte ich den Ausdruck, um das HtmlHelper Objekt zu füttern, aber jetzt brauchte ich nur den Wert, also ersetzte ich meine ExpressionFromField() Methode mit einer einfacheren GetValue() Methode, die den string Wert zurückgibt. Es ist wichtig, dass es eine string zurückgibt; Diese Konvertierung ermöglicht es mir, alle Werttypen zu lesen und zu schreiben (weil die ViewModel-Bindung die Konvertierung von bearbeiteten Werten zurück zum ursprünglichen Eigenschaftstyp verarbeitet).

Meine neue Ansichten wie folgt aussehen:

@model AdminViewModel<ExampleEFObject> 
@using (Html.BeginForm()) 
{ 
    foreach (var f in Model.Fields) 
    { 
     var propertyName = f.PropertyName(f); //unqualified, can contain multiple parts 
     var value = Model.GetValue(f);  //uses the field expression to retrieve the value 
               //of a "T SelectedItem" object in the ViewModel 
     <label class="control-label col-md-3">@f.DisplayName</label> 

     @if (!f.Editable) 
     { 
      <input class="form-control" disabled="disabled" name="@propertyName" type="text" value="@value" /> 
     } 
     else 
     { 
      <input class="form-control" name="@propertyName" type="text" value="@value" /> 
     } 
    } 
    <input type="submit" value="Save" class="btn btn-primary" /> 
} 

Viel sauberer!

Eine Sache, die ich bei diesem Ansatz verliere, ist die Validierung. Von hier aus kann ich JavaScript-Validierung hinzufügen oder Vorlagen erstellen, um die Datenvalidierung zu duplizieren, die HtmlHelper automatisch hinzugefügt wird. Wie auch immer, keine große Sache.

Ich hoffe, das hilft jemandem.

Hinweis: @Ivan Stoeve empfohlen Html.Editor/Html.Textbox usw. verwenden, die eine Bindung über die Eigenschaftsnamen akzeptieren und nicht als Ausdruck. Dies lässt mich immer noch ohne die stark typisierten Ausdrücke in der Ansicht, aber es gibt mir die Validierung zurück, so dass ich denke, es lohnt sich, es anzupassen. Es wird auch einige hässliche String-Manipulationen beseitigen, die ich beim Hinzufügen von Dropdown-Optionen machen musste. :)

+2

Können Sie nicht einfach die 'HtmlHelper'-Methoden verwenden, die' string' anstelle von 'Expression' akzeptieren (die ohne' 'Suffix, z. B.' TextBox' anstelle von 'TextBoxFor' usw.)? –

+0

Danke für den Vorschlag, ich könnte das verwenden.Letztendlich war das Lernen für mich das bindende Modell und zog den Namen des Eigentums in die Ansicht. Die ganze Idee, den 'HtmlHelper' zu verwenden, bestand darin, den stark typisierten Ausdruck zu verwenden, während ein wiederverwendbares Layout beibehalten wurde. Immer noch, das wird mir zumindest kostenlose Validierung geben. Ich nehme das. – Daniel