5

Can I do something like this?

[HttpPost]
public ActionResult Index(WizardViewModel wizard, IStepViewModel step)
{

Where I have the following in my global.asax.cs application_start

    ModelBinders.Binders.Add(typeof(IStepViewModel), new StepViewModelBinder());
    ModelBinders.Binders.Add(typeof(WizardViewModel), new WizardViewModelBinder());

Update

So, I tried to see what is wrong. Here is my new code. It seems that the problem is with this WizardViewModel and it's binder. What "tells" the application to expect and incoming Wizard model?

[HttpPost]
public ActionResult Index(WizardViewModel wizard)
{

Where I have the following in my global.asax.cs application_start

    ModelBinders.Binders.Add(typeof(WizardViewModel), new WizardViewModelBinder());

Complete Binder Code

namespace Tangible.Binders
{
    public class StepViewModelBinder : DefaultModelBinder
    {
        protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
        {
            var stepTypeValue = bindingContext.ValueProvider.GetValue("StepType");
            var stepType = Type.GetType((string)stepTypeValue.ConvertTo(typeof(string)), true);
            var step = Activator.CreateInstance(stepType);

            bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => step, stepType); 
            return step; 
        }
    }

    public class WizardViewModelBinder : DefaultModelBinder
    {
        protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
        {
                var wizardValue = bindingContext.ValueProvider.GetValue("wizard");
                if (wizardValue != null)
                {
                    var wizardType = Type.GetType((string)wizardValue.ConvertTo(typeof(string)), true);
                    var wizard = Activator.CreateInstance(wizardType);

                    bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => wizard, wizardType);
                    return wizard;
                }
                else
                {
                    var wizard = new Tangible.Models.WizardViewModel();
                    wizard.Initialize();
                    return wizard;
                }
        }
    }
}
Coder
  • 1,917
  • 3
  • 17
  • 33
Doug Chamberlain
  • 11,192
  • 9
  • 51
  • 91
  • Yes, but I could very easily have many other issues that are causing this not to work. So before I go any further I figured I should check if it was possible. – Doug Chamberlain Jul 26 '11 at 20:19
  • Doug, as I recall from your prior question at http://stackoverflow.com/questions/6834814/modelmetadata-custom-class-attributes-and-an-indescribable-question, WizardViewModel included `IList` as an attribute. Is this still the case? If so, then your WizardViewModelBinder should probably handle the binding for the child IStepViewModel class as well. As others have suggested, please post your model binder code. – counsellorben Aug 08 '11 at 17:09
  • Updated my question. Like before, there are two binders, one "executes" one will not. – Doug Chamberlain Aug 08 '11 at 17:26
  • Doug, can you also please show simplified models, simple GET and POST controller actions and a view? When I try your binders, the problem I have experienced is with the StepViewModelBinder, not the WizardViewModelBinder. – counsellorben Aug 08 '11 at 22:22

4 Answers4

3

The answer is simple - Yes! That is what you SHOULD do when you've got custom logic for binding values to your parameters. You can even do that with the use of ModelBinderAttribute, set on each of parameters individually.

    [HttpPost]
    public ActionResult Index([ModelBinder(typeof(WizardViewModelBinder))]WizardViewModel wizard, 
[ModelBinder(typeof(StepViewModelBinder))]IStepViewModel step)
    { }

And as I see, the mistake is in your model binder code. I don't have time to check it up, but as far as i remember, CreateModel is used by model binder to create instance of the model, and then that returned instance is model-binded. So, override BindModel instead of CreateModel and write your model binding logic in BindModel. That definitely works.

public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) 
{
//your model binding logic here
}
archil
  • 39,013
  • 7
  • 65
  • 82
0

I have done something similar in the past where I passed a string and then split the value.

Internet Engineer
  • 2,514
  • 8
  • 41
  • 54
  • My understanding is the whole purpose of ModelBinders is to allow complex objects to be passed into controller actions as the parameters, instead of simple data types. Is this incorrect? – Doug Chamberlain Jul 26 '11 at 20:21
  • ModelBinders were introduced in 3.5. Before that my solution was a effective but primitive option. – Internet Engineer Jul 26 '11 at 20:29
0

I would say the answer is just: Yes! In your comment you are concerned about "many other issues" which might cause trouble. Without knowing what issues you have in mind, it's hard to help you. But what you have done is exactly what model binders are designed for. And there is no reason, why you should have only one object per action.

Achim
  • 15,415
  • 15
  • 80
  • 144
  • I meant that it's possible my code elsewhere is causing unexpected results. I'd prefer to see a real world example. Rather than an enthusiastic yes. It's a situation where I didn't want to fight code I barely understand without input from some experts. – Doug Chamberlain Aug 03 '11 at 20:01
  • I really don't see your problem: You are using model binders exactly the way they are designed for. In my opinion using attributes as proposed by archil is usually a bad choice, but it depends on your exact use case. If you create strange type hierarchies, you might get problems too. But that's all independent on model binders and a (more or less) completely different topic. So my answer is still: Yes, you are using model binders in the correct way! – Achim Aug 03 '11 at 20:09
  • Not sure if I need to retitle my question, but I really need an example of multiple binders on an action. Which is what Archil provided. – Doug Chamberlain Aug 03 '11 at 20:12
  • Your original example is using muliple binders too!? – Achim Aug 03 '11 at 20:17
  • When I execute the code I would expect to be able to set a break point on each Custom model Binders and during a call to my Action see the Custom Binders get called, But it skips right past the second ModelBinder. Which indicated to me there is more to do than just making the parameter list accept Models instead of simple data types. – Doug Chamberlain Aug 03 '11 at 20:23
  • In your example both Bind methods of your custom binders should be called and you should be able to set breakpoints into those methods. But your binders are created only once of course. Where exactly have you set your break points? – Achim Aug 03 '11 at 20:33
  • I've set my breakpoint here `protected override object CreateModel` in each CustomModelBinder – Doug Chamberlain Aug 04 '11 at 14:43
0

I was really disenchanted with the hoops that ASP.NET MVC Model Binding required me to jump through to get some basic deserialization.

Since model binding was no where near as transparent as I had hoped for complex Model / ViewModels I simply built a custom ActionFilter to resolve the types [and ONLY the types] I want to deserialize in the action method and use ServiceStack.Text for all my serialization / deserialization needs.

Take a look here:

https://gist.github.com/3b18a58922fdd8d5a963

Anuj
  • 3,134
  • 13
  • 17