I have a wizard with 5 steps, each step has a View Model that implements IUserAddUpdateStep Interface.

An Action Method in my Controller looks like this

public ActionResult AddUpdate(IUserAddUpdateStep step)

And a DefaultModelBinder override to let ASP.NET know how to deal with IUserAddUpdateStepand get the concrete type for deserialization for HTTP posts to the above controller action method, AddUpdate:

 public class StepViewModelBinder : DefaultModelBinder
        protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
            ValueProviderResult stepTypeValue = bindingContext.ValueProvider.GetValue("StepType");
            Type stepType = Type.GetType((string)stepTypeValue.ConvertTo(typeof(string)), true);
            object step = Activator.CreateInstance(stepType);
            bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => step, stepType);
            return step;

I post View Models of type "IUserAddStep", like so in my AddUpdate.cshtml view:

@model UserAddUpdate

    IUserAddUpdateStepcurrentStep = Model.Steps[Model.CurrentStepIndex];

@using (Html.BeginForm("AddUpdate", "Employee", FormMethod.Post, new { @role = "form" }))
     @Html.Hidden("StepType", currentStep.GetType())
     @Html.EditorFor(x => currentStep, null, "")

     <button type="submit">Submit</button>

You do not need to worry about Model UserAddUpdate so much for this question, it just contains a list of IUserAddUpdateStep but here is what it looks like

 public class UserAddUpdate
        public int CurrentStepIndex { get; set; }
        public IList<IUserAddUpdateStep> Steps { get; set; }

        public void Initialize()
            Steps = typeof(IUserAddUpdateStep)
                .Where(t => !t.IsAbstract && typeof(IUserAddUpdateStep).IsAssignableFrom(t))
                .Select(t => (IUserAddUpdateStep)Activator.CreateInstance(t))


@Html.EditorFor, for a particular step, will load a .cshtml Editor Template matching a concrete implementation View Model of type IUserAddUpdateStep.

This setup all works great!

Then I wanted a common value to be shared between all View Models that implement IUserAddUpdateStep I added the common property to IUserAddUpdateStep:

public interface IUserAddUpdateStep
    string UserId { get; set; }

And then my View Models inherit from a new base abstract class instead of IUserAddUpdateStep :

public abstract class UserAddUpdateStepBase: IUserAddUpdateStep
    public string UserId { get; set; }

And then specific concrete implementation View Models of type look like this:

public class UserAddUpdateOverview: UserAddUpdateStepBase 

UserAddUpdateOverview is still of type IUserAddUpdateStep but now inherits from the abstract base class UserAddUpdateStepBase.

And finally I added to my AddUpdate.cshtml view, with in my form of course:

 <input type="hidden" id="UserId" value="@(((UserAddUpdateStepBase) currentStep).UserId)"/>

Everything still works great except for the UserId property, I cannot get that to deserialize when the form posts to my controller method public ActionResult AddUpdate(IUserAddUpdateStep step). UserId is null.

I can see the hidden input value using browser F12 Developer Tools Inspector in the DOM, it is a child of my <form />:

enter image description here

I have also tried:

<input type="hidden" id="UserAddUpdateStepBase_UserId" value="@(((UserAddUpdateStepBase) currentStep).UserId)"/>


<input type="hidden" id="UserAddUpdateStepBase.UserId" value="@(((UserAddUpdateStepBase) currentStep).UserId)"/>


 <input type="hidden" id="IUserAddUpdateStep.UserId" value="@(((UserAddUpdateStepBase) currentStep).UserId)"/>


 <input type="hidden" id="IUserAddUpdateStep_UserId" value="@(((UserAddUpdateStepBase) currentStep).UserId)"/>

My solution, for now, will be to remove the abstract base class and just implement the UserId property in each concrete implementation View Model of type IUserAddUpdateStep. But if anyone has an explanation on how I might guide ASP.NET to correctly deserialize my hidden input UserId that maps to my abstract class that would be awesome to know if their is a solution.

Related posts

Recent Viewed