2009-09-09 8 views
6

J'essaie d'obtenir UpdateModel pour remplir un modèle qui est défini uniquement comme une interface lors de la compilation. Par exemple, j'ai:ASP.NET MVC UpdateModel avec l'interface

// 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> 

Notez également que j'ai un liant de modèle personnalisé qui hérite de DefaultModelBinder utilisé qui renseigne IPROFILE avec une instance de StandardProfile dans la méthode surchargée de CreateModel.

Le problème est que FavoriteFood et FavoriteMusic ne sont jamais remplis. Des idées? Idéalement, tout cela serait fait dans le classeur modèle, mais je ne suis pas sûr que ce soit possible sans écrire une implémentation complètement personnalisée.

Merci, Brian

Répondre

2

Je dois vérifier le code ASP.NET MVC (DefaultModelBinder) mais je devine que son réflexion sur le type IPROFILE, et non l'instance, StandardProfile.

Donc, il recherche des membres IProfile qu'il peut essayer de lier, mais c'est une interface vide, donc il se considère comme fait.

Vous pouvez essayer quelque chose comme la mise à jour du BindingContext et changer le ModelType à StandardProfile puis appeler

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

Quoi qu'il en soit, ayant une interface vide est bizarre ~


Edit: je veux juste ajouter ce code ci-dessus est juste pseudo code, vous devez vérifier DefaultModelBinder pour voir exactement ce que vous voulez écrire.


Edit # 2:

Pouvez-vous faire:

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

Pas besoin de faire un modèle de liaison pour AccountView, que l'on fonctionne très bien.


Edit # 3

testé sur, le liant ci-dessus fonctionne, il faut juste ajouter:

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

Votre action ressemble à:

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

Vous pouvez utiliser IOC lors de la définition du modèle de liant (par exemple, le constructeur du type a été injecté).

+0

L'interface vide me permet de réutiliser le même code de base sur chaque site sur lequel je l'utilise, tout en fournissant un type de profil différent en utilisant un IOC. Je pourrais peut-être essayer une classe de base à la place d'une interface à cet effet, mais je ne suis pas sûr de ce que je peux faire d'autre qui me donne la flexibilité que je cherche. Je vais regarder dans le ModelType que vous avez mentionné. –

+0

Une classe de base vide ne vous fera pas beaucoup de bien non plus. Donc, dans votre code pour la méthode CreateModel vous appelez quelque chose comme: IoC.GetInstance () que vous avez branché pour retourner un nouveau profil standard? Intéressant :). Je ne sais toujours pas quelle réutilisation de code vous pouvez obtenir quand tout ce qui utilise IProfile doit le lancer dans la bonne classe en premier, mais yah ... Je pense que spécifier le type dans le contexte de liaison fonctionnera. – anonymous

+0

La réutilisation vient du fait que beaucoup de contrôleurs de mon site sont dans un assemblage commun que je référence. Chaque site que je construis fait référence à cet assemblage commun pour les contrôleurs et les modèles. Je peux ensuite ajouter des contrôleurs, des modèles, des vues, etc. spécifiques pour chaque site. Dans ce cas, je devais pouvoir définir des champs de profil entièrement différents sur une base site par site. D'où le besoin d'une interface juste ici. –

0

pas inspecter le type réel derrière l'interface a été discuté ici: http://forums.asp.net/t/1348233.aspx

Cela dit, je trouve un moyen hackish autour du problème. Comme j'avais déjà un classeur de modèle personnalisé pour ce type, j'ai pu ajouter du code pour effectuer la reliure pour moi.Voici ce que mon classeur modèle ressemble maintenant:

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; 
    } 
} 

En fait, lorsque le liant modèle est « fait » liant la principale AccountViewModel, je modifie alors le contexte de liaison (comme suggéré par Eyston) et appelle bindModel encore une fois. Cela lie ensuite mon profil. Notez que j'ai appelé GetType sur le profileViewModel (qui est fourni par le conteneur IOC dans le constructeur). Notez également que j'inclure un indicateur pour indiquer si le modèle de profil a déjà été lié. Sinon, il y aurait une boucle sans fin de OnModelUpdated étant appelée. Je ne dis pas que c'est joli, mais ça marche assez bien pour mes besoins. J'aimerais toujours entendre parler d'autres suggestions.

+0

voir Édition # 2/# 3. – anonymous

Questions connexes