2016-11-04 1 views
1

I Possède un ViewModel possédant une interface en tant que propriété. Lorsque je soumets la page, j'ai l'erreur "Impossible de créer une instance d'interface".asp.net Erreur de liaison au modèle MVC "Impossible de créer une instance d'interface"

Le ViewModel est comme ceci:

public class PlanoPagamentoViewModel 
{ 
    //some properties 
    public IPlanoPagamentosParcelas PlanoPagamentosParcelas { get; set; }  
} 

Il sont deux classes qui mettent en œuvre cette interface. Les ViewModels correspondants sont dynamiquement chargés avec une vue partielle, en fonction de l'option sélectionnée.

public class PlanoPagamentoCartaoViewModel : IPlanoPagamentosParcelas 
{ 
    //some properties 
} 

public class PlanoPagamentoCrediarioViewModel : IPlanoPagamentosParcelas 
{ 
    //some properties 
} 

J'ai fait une recherche et je trouve que la nécessité de créer un modèle personnalisé contraignant, et je l'ai fait:

public class PlanoPagamentoParcelasBinder : DefaultModelBinder 
{ 
    protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType) 
    { 
     var type = typeof(PlanoPagamentoCartaoViewModel); 
     var model = Activator.CreateInstance(type); 
     bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, type); 

     return model; 
    } 
} 

Et ajouter cette nouvelle coutume liant dans Global.asax, méthode Application_Start:

ModelBinders.Binders.Add(typeof(IPlanoPagamentosParcelas), new PlanoPagamentoParcelasBinder()); 

Il fonctionne bien pour PlanoPagamentoCartaoViewModel, mais je besoin d'avoir un autre des liaisons personnalisées pour le PlanoPagamentoCrediarioViewModel, mais je ne peux pas simplement ajouter une nouvelle ModelBinders.Binders.Add avec la même clé (IPlanoPagamentosParcelas) car il y a déjà une clé avec ce type.

Alors, existe-t-il une autre approche pour créer une liaison de modèle personnalisé pour ViewModels qui implémente la même interface?

+0

Modifier typeof (IPlanoPagamentosParcelas) à typeof (PlanoPagamentoParcelasBinder) ou typeof (DefaultModelBinder) –

+0

@viveknuna J'ai eu la même erreur. Je devrais ajouter l'IPlanoPagamentosParcelas pour les deux ViewModels, mais ce n'est pas possible car c'est un dictionnaire – Maturano

Répondre

2

Je crois que la solution est la suivante:

protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType) 
{ 
     var typeValueProvider = bindingContext.ValueProvider.GetValue("Type"); 

     var type = (int)typeValueProvider.ConvertTo(typeof(int)); 

     Type instanceType = null; 

     switch (type) 
     { 
      case 1: 
       instanceType = typeof(PlanoPagamentoCartaoViewModel); 
       break; 

      case 2: 
       instanceType = typeof(PlanoPagamentoCrediarioViewModel); 
       break; 
     } 

     if (!typeof(IPlanoPagamentosParcelas).IsAssignableFrom(instanceType)) 
     { 
      throw new InvalidOperationException("Bad Type"); 
     } 
     var model = Activator.CreateInstance(instanceType); 
     bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, instanceType); 
     return model; 
} 

Dans le code ci-dessus var typeValueProvider = bindingContext.ValueProvider.GetValue("Type");Type serait quelque chose à décider quelle classe concrète à instancier. J'ai testé cette solution et cela a fonctionné pour moi. Il peut être amélioré pour être plus extensible (en enlevant le commutateur pour quelque chose d'autre peut-être) mais j'ai juste essayé de le faire fonctionner.

est ici d'où je basé ma question (vous pouvez peut-être obtenir plus d'informations à partir de là) Polymorphic model binding et au-dessous est le code que j'ai créé pour simuler ce scénario (juste en cas de doute):

MODÈLES:

public class NewVehicleViewModel 
{ 
    public string Type { get; set; } 

    public VehicleViewModel Vehicle { get; set; } 
} 

public interface VehicleViewModel 
{ 
    string Name { get; set; } 
    string Color { get; set; } 
} 

public class CarViewModel : VehicleViewModel 
{ 
    public string Color { get; set; } 

    public string Name { get; set; } 

    public string Brand { get; set; } 
} 

public class TankViewModel : VehicleViewModel 
{ 
    public string Color { get; set; } 

    public string Name { get; set; } 

    public string Weapon { get; set; } 
} 

BINDER:

public class VehicleBinder : DefaultModelBinder 
{ 
    protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType) 
    { 
     var typeValueProvider = bindingContext.ValueProvider.GetValue("Type"); 

     var type = (int)typeValueProvider.ConvertTo(typeof(int)); 

     Type instanceType = null; 

     switch (type) 
     { 
      case 1: 
       instanceType = typeof(CarViewModel); 
       break; 

      case 2: 
       instanceType = typeof(TankViewModel); 
       break; 
     } 

     if (!typeof(VehicleViewModel).IsAssignableFrom(instanceType)) 
     { 
      throw new InvalidOperationException("Bad Type"); 
     } 
     var model = Activator.CreateInstance(instanceType); 
     bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, instanceType); 
     return model; 
    } 
} 

CONTRÔLEUR:

public class VehiclesController : Controller 
{ 
    // GET: Vehicles 
    public ActionResult Index() 
    { 
     return View(); 
    } 

    [HttpPost] 
    public ActionResult Create(NewVehicleViewModel vm) 
    { 
     return View(); 
    } 
} 

Global.asax:

protected void Application_Start() 
    { 
     AreaRegistration.RegisterAllAreas(); 
     FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); 
     RouteConfig.RegisterRoutes(RouteTable.Routes); 
     BundleConfig.RegisterBundles(BundleTable.Bundles); 
     ModelBinders.Binders.Add(typeof(VehicleViewModel), new VehicleBinder()); 
    } 

VIEW (Index.cshtml):

@{ 
    ViewBag.Title = "New Vehicle"; 
} 


@using (Html.BeginForm("Create", "Vehicles")) 
{ 
    <label>Type</label> 
    <input name="type" type="text" /> 

    <label >Name</label> 
    <input name="Vehicle.Name" type="text"/> 

    <label>Color</label> 
    <input name="Vehicle.Color" type="text" /> 

    <label>Weapon</label> 
    <input name="Vehicle.Weapon" type="text" /> 

    <label>Brand</label> 
    <input name="Vehicle.Brand" type="text" /> 

    <input type="submit" /> 
} 

Cordialement.

+0

approche très intéressante, mais je reçois NullRecerenceException sur: var typeValueProvider = bindingContext.ValueProvider.GetValue ("Type"); Je suis désolé pour ma question de débutant, mais dois-je changer le "Type" pour quelque chose d'autre? – Maturano

+0

J'ai également essayé de changer "Type" en bindingContext.ModelName, mais il retourne aussi null – Maturano

+0

Type est juste une propriété utilisée donc peut être décidé quelle classe à instancier. Cela pourrait être quelque chose d'autre, mais vous devez décider, en quelque sorte, quelle classe concrète vous allez instancier. Vous auriez pu utiliser la propriété booléenne, peut-être 'IsPlanoPagamentoCartao', la placer comme un champ caché à la vue, et dans le modelbinder utiliser cette propriété' bindingContext.ValueProvider.GetValue ("IsPlanoPagamentoCartao"); 'décider quel instante créer. – dime2lo