简体   繁体   中英

Custom Model Binder for List nested inside a wrapper model

I have the following model:

public class SidebarViewModel
{
    public SidebarViewModel()
    {
        Filters = new List<FilterViewModel>();
    }

    public List<FilterViewModel> Filters { get; set; }
}

And each Filter looks like this:

public abstract class FilterViewModel
{
    [Required]
    public string Value { get; set; }

    [HiddenInput(DisplayValue = false)]
    public bool Visible { get; set; }

    [HiddenInput(DisplayValue = false)]
    public abstract string DisplayName { get; }

    [HiddenInput(DisplayValue = false)]
    public string ModelType { get { return GetType().Name; } }
}

And there are filter subtypes that look like this:

public class TextFilter : FilterViewModel
{
    public override string DisplayName
    {
        get { return "Some Name Here"; }
    }
}

When the post occurs, the entire SidebarViewModel should be posted to the controller:

[HttpPost]
public ActionResult Filter(SidebarViewModel sidebar)
{
    // Stuff here.
}

In order to get the SidebarViewModel to bind, I tried writing a custom ModelBinder for FilterViewModel objects:

public class FilterModelBinder : DefaultModelBinder
{
    protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
    {
        var modelName = bindingContext.ModelName;
        var filterTypeValue = bindingContext.ValueProvider.GetValue(modelName + ".ModelType");
        var filterType = Type.GetType(filterTypeValue.ToString(), true);
        var model = Activator.CreateInstance(filterType);

        bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, filterType);

        return model;
    }
}

And registered that ModelBinder in Global.asax.cs:

ModelBinders.Binders.Add(typeof(FilterViewModel), new FilterModelBinder());

Finally, the view:

@model SidebarViewModel
@using (Ajax.BeginForm("Filter", SessionVars.ControllerName, new AjaxOptions
{
    UpdateTargetId = "tool-wrapper",
    LoadingElementId = "loading-image",
    HttpMethod = "POST"
}))
{
    <fieldset>
        @Html.EditorFor(m => m.Filters)

        <div class="form-actions">
            <button type="submit">Submit Options</button>
        </div>
    </fieldset>
}

And EditorTemplate:

@model FilterViewModel

<div class="control-group">
    @Html.Label(Model.DisplayName, new { @class = "control-label" })

    <div class="controls">
        @Html.TextBoxFor(m => m.Value)

        @Html.HiddenFor(m => m.DisplayName)
        @Html.HiddenFor(m => m.ModelType)
        @Html.HiddenFor(m => m.Visible)
    </div>
</div>

The problem is that when the sidebar is posted, the FilterModelBinder is never called. Do I need another custom model binder for SidebarViewModel? If so, how would I call the FilterModelBinder for each filter in the sidebar's list?

Ah, it ended up being an issue with my RuntimeRoute registration. There was an invalid one in there, and removing it fixed my binding issue.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM