2010-10-25 9 views
11

Wenn ich habe folgende stark typisierte Ansicht:ASP.NET MVC 2 - Bindung an Abstract Modell

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<XXX.DomainModel.Core.Locations.Location>" %> 

Wo Lage eine abstrakte Klasse ist.

Und ich die folgenden Controller verfügen, die ein stark typisierte Modell über eine POST akzeptiert:

[HttpPost] 
public ActionResult Index(Location model) 

Ich erhalte eine Fehlerlaufzeit unter Angabe „Create kann nicht abstrakte Klasse

Welche Natürlich macht es Sinn - aber ich bin nicht sicher, was die beste Lösung ist hier

Ich habe viele konkrete Typen (um 8), und dies ist eine Ansicht, wo Sie c a nur bearbeiten Eigenschaften der abstrakten Klasse.

Was ich habe versucht zu tun ist Überlastungen für die verschiedenen Betontypen erstellen, und führen Sie meine Logik in einer gemeinsamen Methode.

[HttpPost] 
public ActionResult Index(City model) 
{ 
    UpdateLocationModel(model); 
    return View(model); 
} 

[HttpPost] 
public ActionResult Index(State model) 
{ 
    UpdateLocationModel(model); 
    return View(model); 
} 

etc etc

Und dann:

[NonAction] 
private void UpdateLocationModel (Location model) 
{ 
    // ..snip - update model 
} 

Aber das funktioniert nicht, entweder, MVC die Aktionsmethoden nicht eindeutig sind (auch sinnvoll) beklagt.

Was machen wir? Können wir uns einfach nicht an ein abstraktes Modell binden?

+1

Gute Frage. Interessiert, um die Antworten zu sehen! –

+0

Ich bin neugierig, wenn Sie jemals einen besseren Weg gefunden haben, damit umzugehen? –

+0

@Mystere Man - nein. Ich musste es nicht noch einmal machen. Wenn ich es täte, würde ich tun, was die angenommene Antwort vorschlägt. – RPM1984

Antwort

7

Wie über ein eigenes Modell Bindemittel für diese abstrakte Klasse zu schreiben:

public class CustomBinder : DefaultModelBinder 
{ 
    protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType) 
    { 
     // TODO: based on some request parameter choose the proper child type 
     // to instantiate here 
     return new Child(); 
    } 
} 

Diese machen nur Sinn, wenn Sie ein Formular, wo Eingabeelemente eingefügt auf bestimmte Benutzeraktion dynamisch basiert. In diesem Fall müssen Sie einige zusätzliche Parameter übergeben, um anzugeben, welche konkrete Klasse Sie benötigen. Sonst bleibe ich bei konkreten Betrachtungsmodellen als Aktionsparameter.

+0

Nun, dies ist eine sehr einfache Ansicht, bearbeiten Sie Felder für Eigenschaften auf dem abstrakten Modell. Daher wollte ich diesen HTML-Code nicht über mehrere stark typisierte Ansichten hinweg "duplizieren" müssen. Ich werde mir die benutzerdefinierten Modellbinder ansehen - da ich den untergeordneten Typ kenne, wenn ich die Ansicht rendere. Ich werde es morgen im Büro ausprobieren - Prost. – RPM1984

+0

Okay, ich habe Modellbinder gelesen, und ich stimme zu - es ergibt keinen Sinn in meinem Szenario. Ich bleibe bei den konkreten View-Modellen. Das würde aber funktionieren, also werde ich deine Antwort akzeptieren. Vielen Dank. – RPM1984

1

Nur um es raus zu werfen - ich bin sehr interessiert daran, was andere beantworten könnten, aber das ist, was ich getan habe, wenn ich eine ähnliche Situation hatte;

Grundsätzlich habe ich die Modellklasse nicht als Parameter in der Aktionsmethode verwendet, sondern FormCollection übergeben und ein paar bekannte Diskriminatoren getestet, um herauszufinden, welcher Typ erstellt/bearbeitet wird, dann TryUpdateModel von dort.

Es schien, als könnte es einen besseren Weg geben, aber ich war nie dazu gekommen, darüber nachzudenken.

+0

Ja, das war mein nächster Schritt! :) Aber awww, ich wollte die stark typisierte Güte. :) – RPM1984

+0

Es ist lecker und nahrhaft; Teil dieses kompletten Frühstücks! –

3

Sie können auch einen generischen ModelBinder erstellen, der für alle Ihre abstrakten Modelle funktioniert. Meine Lösung erfordert, dass Sie Ihrer Ansicht ein verstecktes Feld mit der Bezeichnung 'ModelTypeName' hinzufügen, wobei der Wert auf den Namen des gewünschten konkreten Typs festgelegt wird. Es sollte jedoch möglich sein, dieses Ding klüger zu machen und einen konkreten Typ auszuwählen, indem man Typeigenschaften mit Feldern in der Ansicht vergleicht.

In Ihrem Global.asax.cs Datei in Application_Start():

ModelBinders.Binders.DefaultBinder = new CustomModelBinder(); 

CustomModelBinder:

public class CustomModelBinder2 : DefaultModelBinder 
{ 
    public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) 
    { 
     var modelType = bindingContext.ModelType; 
     if (modelType.IsAbstract) 
     { 
      var modelTypeValue = controllerContext.Controller.ValueProvider.GetValue("ModelTypeName"); 
      if (modelTypeValue == null) 
       throw new Exception("View does not contain ModelTypeName"); 

      var modelTypeName = modelTypeValue.AttemptedValue; 

      var type = modelType.Assembly.GetTypes().SingleOrDefault(x => x.IsSubclassOf(modelType) && x.Name == modelTypeName); 

      if (type != null) 
      { 
       var instance= bindingContext.Model ?? base.CreateModel(controllerContext, bindingContext, type); 
       bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => instance, type); 
      } 
     } 
     return base.BindModel(controllerContext, bindingContext); 
    } 
} 
+0

Dies ist eine hilfreiche Antwort, aber für jeden, der es anwendet, vermute ich, dass hier ein Sicherheitsrisiko besteht, wenn der Client einen unerwarteten, aber legitimen Namen mit einem schädlichen Konstruktor zurücksendet. Recht? –

+0

siehe auch http://StackOverflow.com/Questions/7222533/polymorphic-model-binding/9813472#9813472 –

+0

@ uosɐs: nur für den Fall, dass MVC versucht, einer Unterklasse zuzuordnen (siehe '.IsSubclassOf (...) ') einer erwarteten abstrakten Klasse, die in der Controlleraktionssignatur oder einer der untergeordneten Eigenschaften der Controlleraktionssignaturobjekte verwendet wird. Und dann muss es einen "schädlichen" Konstruktor haben. Ich denke daran, diesen Code zu verwenden. Können Sie ein Beispiel für ein solches Sicherheitsrisiko geben? –