2009-09-09 14 views
6

Sto cercando di ottenere UpdateModel per popolare un modello impostato come solo un'interfaccia in fase di compilazione. Ad esempio, ho:ASP.NET MVC UpdateModel con interfaccia

// View Model 
public class AccountViewModel { 
    public string Email { get; set; } 
    public IProfile Profile { get; set; } 
} 

// Interface 
public interface IProfile { 
    // Empty 
} 

// Actual profile instance used 
public class StandardProfile : IProfile { 
    public string FavoriteFood { get; set; } 
    public string FavoriteMusic { get; set; } 
} 

// Controller action 
public ActionResult AddAccount(AccountViewModel viewModel) { 
    // viewModel is populated already 
    UpdateModel(viewModel.Profile, "Profile"); // This isn't working. 
} 

// Form 
<form ... > 
    <input name='Email' /> 
    <input name='Profile.FavoriteFood' /> 
    <input name='Profile.FavoriteMusic' /> 
    <button type='submit'></button> 
</form> 

noti inoltre che ho un legante modello personalizzato che eredita da DefaultModelBinder utilizzato che popola iProfile con un'istanza StandardProfile nel metodo CreateModel overriding.

Il problema è che FavoriteFood e FavoriteMusic non vengono mai popolati. Qualche idea? Idealmente, questo dovrebbe essere fatto nel modello di legatura, ma non sono sicuro che sia possibile senza scrivere un'implementazione completamente personalizzata.

Grazie, Brian

risposta

2

avrei dovuto controllare il codice ASP.NET MVC (DefaultModelBinder) ma credo che la sua riflessione sul tipo di IProfile, e non l'istanza, StandardProfile.

Quindi cerca tutti i membri di IProfile che può provare a collegarsi, ma è un'interfaccia vuota, quindi si considera eseguita.

Si potrebbe provare qualcosa di simile l'aggiornamento del BindingContext e cambiando il ModelType per StandardProfile e quindi chiamando

bindingContext.ModelType = typeof(StandardProfile); 
IProfile profile = base.BindModel(controllerContext, bindingContext); 

In ogni modo, avendo un'interfaccia vuota è strano ~


Edit: voglio solo aggiungere quel codice sopra è solo uno pseudo codice, dovresti controllare DefaultModelBinder per vedere esattamente cosa vuoi scrivere.


Edit # 2:

Potete fare:

public class ProfileModelBinder : DefaultModelBinder 
{ 
    public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) { 
    { 
     bindingContext.ModelType = typeof(StandardProfile); 
     return base.BindModel(controllerContext, bindingContext); 
    } 
} 

Non c'è bisogno di fare un modello legante per AccountView, quello funziona bene.


Modifica # 3

Testato fuori, quanto sopra legante funziona, basta aggiungere:

ModelBinders.Binders[typeof(IProfile)] = new ProfileModelBinder(); 

La vostra azione assomiglia:

public ActionResult AddAccount(AccountViewModel viewModel) { 
    // viewModel is fully populated, including profile, don't call UpdateModel 
} 

È possibile utilizzare IOC quando si imposta il raccoglitore del modello (ad esempio è stato iniettato il tipo constructor).

+0

L'interfaccia vuota consente di riutilizzare lo stesso codice di codice principale su ciascun sito su cui lo utilizzo, fornendo un diverso tipo di profilo utilizzando un IOC. Potrei essere in grado di provare una classe base invece di un'interfaccia per questo scopo, ma non sono sicuro di cos'altro posso fare che mi dà la flessibilità che sto cercando. Guarderò il ModelType che hai menzionato. –

+0

Una classe base vuota non ti farà molto bene neanche. Quindi nel tuo codice per il metodo CreateModel stai chiamando qualcosa come: IoC.GetInstance () che hai collegato per restituire un nuovo profilo standard? Interessante :). Non sono ancora chiaro il grado di riutilizzo del codice che è possibile ottenere quando tutto ciò che utilizza IProfile deve prima eseguire il cast della classe giusta, ma credo che la specifica del tipo nel contesto di binding funzionerà. – anonymous

+0

Il riutilizzo deriva dal fatto che molti dei controller del mio sito si trovano in un assembly comune a cui faccio riferimento. Ogni sito che costruisco fa riferimento a questo assembly comune per controller e modelli. Posso quindi aggiungere ulteriori controller, modelli, viste, ecc. Specifici per ogni sito. In questo caso, dovevo essere in grado di definire campi del profilo completamente diversi su base sito per sito. Da qui la necessità di una semplice interfaccia qui. –

0

Non ispezionare il tipo reale dietro l'interfaccia è stata discussa qui: http://forums.asp.net/t/1348233.aspx

Detto questo, ho trovato un modo per aggirare il problema hacker. Dato che ho già un legante modello personalizzato per questo tipo, sono stato in grado di aggiungere del codice per eseguire il binding per me.Ecco ciò che il mio modello legante appare come ora:

public class AccountViewModelModelBinder : DefaultModelBinder 
{ 
    private readonly IProfileViewModel profileViewModel; 
    private bool profileBound = false; 

    public AccountViewModelModelBinder(IProfileViewModel profileViewModel) 
    { 
     this.profileViewModel = profileViewModel; 
    } 

    protected override void OnModelUpdated(ControllerContext controllerContext, ModelBindingContext bindingContext) 
    { 
     // Bind the profile 
     if (profileBound) 
      return; 

     profileBound = true; 

     bindingContext.ModelType = profileViewModel.GetType(); 
     bindingContext.Model = profileViewModel; 
     bindingContext.ModelName = "Profile"; 

     BindModel(controllerContext, bindingContext); 
    } 

    protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, System.Type modelType) 
    { 
     var model = new AccountViewModel(); 
     model.Profile = profileViewModel; 

     return model; 
    } 
} 

In sostanza, quando il modello legante è "fatto" vincolante l'AccountViewModel principale, ho quindi modificare il contesto vincolante (come suggerito da Eyston) e chiamo BindModel ancora una volta. Questo poi lega il mio profilo. Si noti che ho chiamato GetType sul profileViewModel (fornito dal contenitore IOC nel costruttore). Notare anche che includo un flag per indicare se il modello del profilo è già stato associato. In caso contrario, verrà chiamato un ciclo infinito di OnModelUpdated.

Non sto dicendo che questo è bello, ma funziona abbastanza bene per le mie esigenze. Mi piacerebbe ancora sentire altri suggerimenti.

+0

vedi Modifica # 2/# 3. – anonymous