0

I'm facing a problem ith a custom modelbinder.

I have two models (inheriting from a base class) that are displayed by EditorTemplates.

Base-Class:

public abstract class QuestionAnswerInputModel {
    public Guid QuestionId {
        get; set;
    }
}

Modelclass 1:

public class RatingQuestionInputModel : QuestionAnswerInputModel{
    [Required]
    [Range(1,4)]
    public int? Rating { get; set; }
}

Modelclass 2:

public class FreeTextQuestionInputModel: QuestionAnswerInputModel{
    [Required]
    public string FreeText { get; set; }
}

To get it bound I implemented a custom modelbinder:

public class QuestionAnswerModelBinder : DefaultModelBinder {
    public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) {

        QuestionAnswerInputModel model;

        if ((typeof(QuestionAnswerInputModel) != bindingContext.ModelType)) {
            return null;
        }

        ModelBindingContext context = new ModelBindingContext(bindingContext);

        Type typeOfModel;

        string prefix = bindingContext.ModelName;
        if (bindingContext.ValueProvider.ContainsPrefix(prefix + "." + new FreeTextQuestionInputModel().GetPropertyName(m => m.FreeText))) {
            typeOfModel = typeof(FreeTextQuestionInputModel);
        } else if (bindingContext.ValueProvider.ContainsPrefix(prefix + "." + new RatingQuestionInputModel().GetPropertyName(m => m.Rating))) {
            typeOfModel = typeof(RatingQuestionInputModel);
        } else {
            return null;
        }

        context.ModelMetadata = new ModelMetadata(new DataAnnotationsModelMetadataProvider(), bindingContext.ModelMetadata.ContainerType, null, typeOfModel, bindingContext.ModelName);
        return base.BindModel(controllerContext, context);
    }
}

All in all it works great, BUT the values fpr properties of the models (QuestionId and Rating/Freetext) are not set? Can anyone tell me why? What am I doing wrong?

I also tried to call

new DefaultModelBinder().BindModel(controllerContext, context)

but the result is the same. Correctly instantiated objects but properties are not set.


UPDATE:

I now tried to override just the CreateModel-Methode of the DefaultBinder like in this post MVC 3 Model Binding a Sub Type (Abstract Class or Interface).

protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType) {

        if ((typeof(QuestionAnswerInputModel) != bindingContext.ModelType)) {
            return null;
        }

        string prefix = bindingContext.ModelName;
        if (bindingContext.ValueProvider.ContainsPrefix(prefix + "." + new FreeTextQuestionInputModel().GetPropertyName(m => m.FreeText))) {
            return new FreeTextQuestionInputModel();
        } else if (bindingContext.ValueProvider.ContainsPrefix(prefix + "." + new RatingQuestionInputModel().GetPropertyName(m => m.Rating))) {
            return new RatingQuestionInputModel();
        } else {
            return null;
        }
    }

The model is still instantiated correctly. The problem now is, that only the properties of the base class are set.

Community
  • 1
  • 1
Tobias
  • 2,945
  • 5
  • 41
  • 59
  • What type of model your action(s) expect? Is it a base abstract classs ? – macpak Nov 27 '14 at 12:45
  • Yes the ActionMethode wants a model that has a property of type IList! – Tobias Nov 27 '14 at 12:48
  • Have you seen http://stackoverflow.com/questions/9417888/mvc-3-model-binding-a-sub-type-abstract-class-or-interface? – macpak Nov 27 '14 at 12:53
  • Yes I did. But this means I would have to resolve all the properties on my own, what wouldn't be a big problem in that case. But for more complexe models it would be really annoying. I just wonder why the default modelbinder cannot resolve the properties? – Tobias Nov 27 '14 at 13:00
  • But have you tried solution proposed by Manas (2. Now create a modelbinder and override CreateModel) ? – macpak Nov 27 '14 at 13:02
  • Not yet. I'm dooing it right now ;-) See the update of the question. – Tobias Nov 27 '14 at 13:02

1 Answers1

0

After some discussion with @macpak I found the solution. This works great for me:

protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType) {

    if ((typeof(QuestionAnswerInputModel) != bindingContext.ModelType)) {
        return null;
    }

    string prefix = bindingContext.ModelName;

    QuestionAnswerInputModel obj;
    if (bindingContext.ValueProvider.ContainsPrefix(prefix + "." + new FreeTextQuestionInputModel().GetPropertyName(m => m.FreeText))) {
        obj = new FreeTextQuestionInputModel();
    } else if (bindingContext.ValueProvider.ContainsPrefix(prefix + "." + new RatingQuestionInputModel().GetPropertyName(m => m.Rating))) {
        obj = new RatingQuestionInputModel();
    } else {
        return null;
    }

    bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, obj.GetType());
    bindingContext.ModelMetadata.Model = obj;

    return obj;
}

I just have to override the CreateModel-Methode. Thanks to @MacPak!

Tobias
  • 2,945
  • 5
  • 41
  • 59