Single property not getting bound on HttpPost

2019-08-19 04:46发布

I'm working on the first MVC3 project at our company, and I've hit a block. No one can seem to figure out what's going on.

I have a complex Model that I'm using on the page:

public class SpaceModels : List<SpaceModel> {
    public bool HideValidation { get; set; }
    [Required(ErrorMessage=Utilities.EffectiveDate + Utilities.NotBlank)]
    public DateTime EffectiveDate { get; set; }

    public bool DisplayEffectiveDate { get; set; }
}

In the Controller, I create a SpaceModels object with blank SpaceModels for when Spaces get combined (this would be the destination Space).

// Need a list of the models for the View.
SpaceModels models = new SpaceModels();
models.EffectiveDate = DateTime.Now.Date;
models.DisplayEffectiveDate = true;
models.Add(new SpaceModel { StoreID = storeID, SiteID = siteID, IsActive = true });

        return View("CombineSpaces", models);

Then in the View, I am using that SpaceModels object as the Model, and in the form making a TextBox for the Effective Date:

@model Data.SpaceModels

@using (Html.BeginForm("CombineSpaces", "Space")) {
    <div class="EditLine">
        <span class="EditLabel LongText">
            New Space Open Date
        </span>
        @Html.TextBoxFor(m => m.EffectiveDate, new {
                        size = "20",
                        @class = "datecontrol",
                        // Make this as a nullable DateTime for Display purposes so we don't start the Calendar at 1/1/0000.
                        @Value = Utilities.ToStringOrDefault(Model.EffectiveDate == DateTime.MinValue ? null : (DateTime?)Model.EffectiveDate, "MM/dd/yyyy", string.Empty)
        })
        @Html.ValidationMessageFor(m => m.EffectiveDate)
    </div>

    <hr />        

    Html.RenderPartial("_SpaceEntry", Model);
}

The Partial View that gets rendered iterates through all SpaceModels, and creates a containing the Edit fields for the individual SpaceModel objects. (I'm using the List to use the same Views for when the Spaces get Subdivided as well.)

Then on the HttpPost, the EffectiveDate is still back at it's DateTime.MinValue default:

[HttpPost]
public ActionResult CombineSpaces(SpaceModels model, long siteID, long storeID, DateTime? effectiveDate) {
// processing code
}

I added that DateTime? effectiveDate parameter to prove that the value when it gets changed does in fact come back. I even tried moving the rendering of the TextBox into the _SpaceEntry Partial View, but nothing worked there either.

I did also try using the @Html.EditorFor(m => m.EffectiveDate) in place of the @Html.TextBoxFor(), but that still returned DateTime.MinValue. (My boss doesn't like giving up the control of rendering using the @Html.EditorForModel by the way.)

There has to be something simple that I'm missing. Please let me know if you need anything else.

2条回答
一纸荒年 Trace。
2楼-- · 2019-08-19 05:32

What model binding does is attempt to match the names of things or elements in the view to properties in your model or parameters in your action method. You do not have to pass all of those parameters, all you have to do is add them to your view model, then call TryUpdateModel in your action method. I am not sure what you are trying to do with SpaceModel or List but I do not see the need to inherit from the List. Im sure you have a good reason for doing it. Here is how I would do it.

The view model

public class SpacesViewModel
{
    public DateTime? EffectiveDate { get; set; }
    public bool DisplayEffectiveDate { get; set; }
    public List<SpaceModel> SpaceModels { get; set; }
}

The GET action method

[ActionName("_SpaceEntry")]
public PartialViewResult SpaceEntry()
{
    var spaceModels = new List<SpaceModel>();
    spaceModels.Add(
        new SpaceModel { StoreID = storeID, SiteID = siteID, IsActive = true });

    var spacesVm = new SpacesViewModel
    {
        EffectiveDate = DateTime.Now,
        DisplayEffectiveDate = true,
        SpaceModels = spaceModels
    };

    return PartialView("_SpaceEntry", spacesVm);
}

The POST action method

[HttpPost]
public ActionResult CombineSpaces() 
{
    var spacesVm = new SpacesViewModel();

    // this forces model binding and calls ModelState.IsValid 
    // and returns true if the model is Valid
    if (TryUpdateModel(spacesVm))
    {
        // process your data here
    }
    return RedirectToAction("Index", "Home");
}

And the view

<label>Effective date: </label>
@Html.TextBox("EffectiveDate", Model.EffectiveDate.HasValue ?
    Model.EffectiveDate.Value.ToString("MM/dd/yyyy") : string.empty, 
    new { @class = "datecontrol" })

Sometimes you need to explicitly bind form data using hidden fields such as

@Html.HiddenField("EffectiveDate", Model.EfectiveDate.)

In order to bind the properties of the SpaceModel object you can add individual properties such as SiteID to the view model or add a SpaceModel property for a single SpaceModel. If you want to successfully bind a complex model, add it as a Dictionary populated with key-value pairs rather than a List. You should then add the dictionary to the view model. You can even add a dictionary of dictionaries for hierarchical data.

I hope this helps :)

查看更多
姐就是有狂的资本
3楼-- · 2019-08-19 05:34

Looking at the source code for DefaultModelBinder, specifically BindComplexModel(), if it detects a collection type it will bind the individual elements but will not attempt to bind properties of the list object itself.

查看更多
登录 后发表回答