2010-08-25 22 views
6

In meinem ASP.NET MVC-app, habe ich eine Schnittstelle, die für verschiedene Ansichtsmodelle als Vorlage dient:Passing eine Schnittstelle zu einer ASP.NET MVC-Controller Aktion Methode

public interface IMyViewModel 
{ 
    Client Client1 { get; set; } 
    Client Client2 { get; set; } 

    Validator Validate(); 
} 

, meine Ansicht Modelle So ist wie folgt definiert:

public interface MyViewModel1 : IMyViewModel 
{ 
    Client Client1 { get; set; } 
    Client Client2 { get; set; } 

    // Properties specific to MyViewModel1 here 

    public Validator Validate() 
    { 
     // Do ViewModel-specific validation here 
    } 
} 

public interface MyViewModel2 : IMyViewModel 
{ 
    Client Client1 { get; set; } 
    Client Client2 { get; set; } 

    // Properties specific to MyViewModel2 here 

    public Validator Validate() 
    { 
     // Do ViewModel-specific validation here 
    } 
} 

Dann habe ich noch eine separate Controller-Aktion die Validierung für jede andere Art zu tun, Modell Bindung:

[HttpPost] 
public ActionResult MyViewModel1Validator(MyViewModel1 model) 
{ 
    var validator = model.Validate(); 

    var output = from Error e in validator.Errors 
       select new { Field = e.FieldName, Message = e.Message }; 

    return Json(output); 
} 

[HttpPost] 
public ActionResult MyViewModel2Validator(MyViewModel2 model) 
{ 
    var validator = model.Validate(); 

    var output = from Error e in validator.Errors 
       select new { Field = e.FieldName, Message = e.Message }; 

    return Json(output); 
} 

Das funktioniert gut - aber wenn ich 30 verschiedene Ansichtsmodelltypen hätte, dann müßten 30 separate Controlleraktionen, alle mit identischen Code neben der Methodensignatur, was wie eine schlechte Praxis erscheint.

Meine Frage ist, wie kann ich diese Validierungsaktionen konsolidieren, so dass ich jede Art von Ansichtsmodell übergeben und die Validate() -Methode aufrufen kann, ohne sich darum zu kümmern, um welchen Typ es sich handelt?

Zuerst habe ich versucht, die Schnittstelle selbst als Aktionsparameter verwenden:

public ActionResult MyViewModelValidator(IMyViewModel model)... 

Aber das hat nicht funktioniert: Ich habe eine Cannot create an instance of an interface Ausnahme erhalten. Ich dachte, eine Instanz des Modells würde in die Controller-Aktion übernommen werden, aber das ist offensichtlich nicht der Fall.

Ich bin sicher, ich vermisse etwas Einfaches. Oder vielleicht habe ich das alles falsch angegangen. Kann mir jemand helfen?

Antwort

10

Der Grund, warum Sie die Schnittstelle nicht verwenden können, ist wegen der Serialisierung. Wenn eine Anforderung kommt es nur String-Schlüssel/Wert-Paare enthält, die das Objekt darstellen:

"Client1.Name" = "John" 
"Client2.Name" = "Susan" 

Wenn die Aktionsmethode die MVC-Laufzeit aufgerufen wird, versucht, Werte zu erstellen, um die Methode der Parameter zum Auffüllen (über einen Prozess namens Modellbindungs). Es verwendet den Typ des Parameters, um daraus abzuleiten, wie er erstellt wird. Wie Sie bemerkt haben, kann der Parameter keine Schnittstelle oder ein anderer abstrakter Typ sein, da die Laufzeitumgebung keine Instanz davon erstellen kann. Es braucht einen konkreten Typ.

Wenn Sie wiederholten Code entfernen möchten Sie einen Helfer schreiben konnte:

[HttpPost]   
public ActionResult MyViewModel1Validator(MyViewModel1 model)   
{   
    return ValidateHelper(model);   
}   

[HttpPost]   
public ActionResult MyViewModel2Validator(MyViewModel2 model)   
{   
    return ValidateHelper(model);   
} 

private ActionResult ValidateHelper(IMyViewModel model) { 
    var validator = model.Validate();   

    var output = from Error e in validator.Errors   
       select new { Field = e.FieldName, Message = e.Message };   

    return Json(output); 
} 

Allerdings müssen Sie noch eine andere Aktion Methode für jeden Modelltyp. Vielleicht gibt es andere Möglichkeiten, wie Sie Ihren Code umgestalten könnten. Es scheint, dass der einzige Unterschied in Ihren Modellklassen das Validierungsverhalten ist. Sie könnten eine andere Möglichkeit finden, den Validierungstyp in Ihrer Modellklasse zu codieren.

+0

Ich ging für diesen Ansatz am Ende rein aus zeitlichen Gründen, aber Sie haben auch erklärt, warum das Objekt nicht bereits eine Instanz ist, wenn es in den Controller übergeben wird, was nützlich ist zu wissen. –

1

Sie könnten eine Basisklasse anstelle der Schnittstelle verwenden.

+0

Dies führt zu einer ähnlichen Fehlermeldung. Es kann auch keine Instanz eines abstrakten Typs erstellt werden. –

+1

Sie könnten eine nicht abstrakte Basisklasse verwenden. –

1

Ich denke, ich würde eine abstrakte Basisklasse erstellen, die IMyViewModel implementiert. Ich würde Validate eine abstrakte Methode machen und in meiner konkreten Ansicht Modelle überschreiben, die von MyAbstractViewModel geerbt wurden. Innerhalb Ihres Controllers können Sie, wenn Sie möchten, mit der IMyViewModel-Schnittstelle arbeiten. Für die Bindung und Serialisierung ist jedoch eine konkrete Klasse zum Binden erforderlich. Mein $ .02.

+1

Ich sollte einen zusätzlichen Kommentar hinzufügen - ich würde nicht wirklich die abstrakte Klasse in den Controller übergeben, sondern die konkrete Klasse. Möglicherweise haben Sie einen Validierungsdienst oder eine andere Methode, die mit der abstrakten Klasse oder Schnittstelle umgehen kann, aber aus Gründen der Übersichtlichkeit sollten Sie wirklich erwarten, was Sie in Ihrem Controller bekommen. – Hal

4

Sie könnten dies überprüfen: http://msdn.microsoft.com/en-us/magazine/hh781022.aspx.

Dies wird verursacht, weil DefaultModelBinder nicht wissen kann, welcher konkrete Typ von IMyViewModel erstellen sollte. Für Lösung, die Sie erstellen, erstellen Sie benutzerdefinierte Modellbinder und zeigen an, wie Sie eine Instanz der Schnittstelle erstellen und binden.

+0

+1 IMHO das ist der Weg zu gehen, trumpft alle anderen Antworten (von denen einige offensichtlich nicht in der realen Welt getestet wurden). –

Verwandte Themen