1

I have a form with a submit button that should pass through the item to the actionlistener. I thought it might be similar to the question in @Html.HiddenFor does not work on Lists in ASP.NET MVC but none of the answers seem to work. You can even see my for-loop taken from one of the answers in there.

[ EDIT: I have gotten rid of the mass of hidden loops and replaced with @Html.EditorFor so that you can see, even if not hidden, the flags list does not get to the actionlistener. This is a problem because when someone edits the flags, there is no way to update the db as I cannot get the ID of the flag updated. ]

The ModelState in the controller is never valid, regardless whether I keep the "[Bind(Include =" there or not. That's just there because of the tutorial for ASP.NET MVC Tutorial: Web application development with Azure Cosmos DB.

ItemController.cs:

    [HttpPost]
    [ActionName("ProductEdit")]
    [ValidateAntiForgeryToken]
    public async Task<ActionResult> EditProductAsync( [Bind(Include = "Id, Name, Flags")] Item model)
    {
        Item product = await DocDBRepo<Item>.GetItem(model.Id);
        model.Organisations = product.Organisations;

        if (ModelState.IsValid) //Checks item validation via "required" set on properties
        {
            await DocDBRepo<Item>.UpdateItemAsync(model.Id, model);
            return RedirectToAction("Index");
        }

        return View(model);
    }

    [HttpGet]
    [ActionName("ProductEdit")]
    public async Task<ActionResult> EditProductAsync(string id)
    {
        if (id == null)
        {
            return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
        }

        Item item = await DocDBRepo<Item>.GetItem(id);
        if (item == null)
        {
            return HttpNotFound();
        }

        return View(item);
    }

ProductEdit.cs:

@model RRPortal.Models.Item

@{
    ViewBag.Title = "ProductEdit";
    Layout = "~/Views/Shared/_Layout.cshtml";
}

<h2>ProductEdit</h2>


@using (Html.BeginForm())
{
@Html.AntiForgeryToken()

<div class="form-horizontal">
    <hr />
    @Html.ValidationSummary(true, "", new { @class = "text-danger" })
    @Html.HiddenFor(model => model.Id)

    <div class="form-group">
        @Html.LabelFor(model => model.Name, htmlAttributes: new { @class = "control-label col-md-2" })
        <div class="col-md-10">
            @Html.EditorFor(model => model.Name, new { htmlAttributes = new { @class = "form-control" } })
            @Html.ValidationMessageFor(model => model.Name, "", new { @class = "text-danger" })
        </div>
    </div>

    <div class="form-group">
        @Html.LabelFor(model => model.Flags, htmlAttributes: new { @class = "control-label col-md-2 " })
    </div>

    @*Flags list*@
    @for (int i = 0; i < Model.Flags.Count; i++)   //foreach (var flag in Model.Flags)
    {

        <div class="form-group">
            //@Html.HiddenFor(modelItem => Model.Flags[i].Id)
            @Html.Label(Model.Flags[i].Name, htmlAttributes: new { @class = "control-label col-md-3" })
            @Html.LabelFor(modelItem => Model.Flags[i].Enabled, htmlAttributes: new { @class = "control-label col-md-1" })
            <div class="col-md-8">
                @Html.EditorFor(modelItem => Model.Flags[i].Enabled, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(modelItem => Model.Flags[i].Enabled, "", new { @class = "text-danger" })
            </div>
        </div>
    }

    <div class="form-group">
        <div class="col-md-offset-2 col-md-10">
            <input type="submit" value="Save" class="btn btn-default" />
        </div>
    </div>
</div>
}

<div>
    @Html.ActionLink("Back to List", "Index")
</div>

@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
}

Item.cs:

public class Item
{
    [JsonProperty(PropertyName = "id")]
    public string Id { get; set; }

    [Required]
    [JsonProperty(PropertyName = "name")]
    public string Name { get; set; }

    [JsonProperty(PropertyName = "flags")]
    public List<Flag> Flags { get; set; }

    [JsonProperty(PropertyName = "organisations")]
    public List<Organisation> Organisations { get; set; }
}

public class Flag
{
    [JsonProperty(PropertyName = "id")]
    public int Id { get; set; }

    [Required]
    [JsonProperty(PropertyName = "name")]
    public string Name { get; set; }

    [Required]
    [JsonProperty(PropertyName = "enabled")]
    public bool Enabled { get; set; }
}

public class Organisation
{
    [JsonProperty(PropertyName = "id")]
    public int Id { get; set; }

    [JsonProperty(PropertyName = "name")]
    public string Name { get; set; }

    [JsonProperty(PropertyName = "users")]
    [Display(Name ="Users")]
    public List<User> UserStore { get; set; }
}

public class User
{
    [JsonProperty(PropertyName = "id")]
    public int Id { get; set; }

    [Required]
    [JsonProperty(PropertyName = "fname")]
    public string FName { get; set; }

    [Required]
    [JsonProperty(PropertyName = "lname")]
    public string LName { get; set; }

    [Required]
    [Display(Name = "Admin?")]
    [JsonProperty(PropertyName = "isadmin")]
    public bool IsAdmin { get; set; }
}

The Item's Id and Name comes through and is not null when I debug the controller, but the Flags List is always empty. The ModelState shows the following exception: {"The parameter conversion from type 'System.String' to type 'RRPortal.Models.Flag' failed because no type converter can convert between these types."}

flags obj still empty

I have also been asked where the ModelState is showing the exception so below is a screenshot: Exception on modelstate

I will gladly update the question if anyone has any questions. I have been tweaking the view for 2 days now and still can't get the item to contain anything. The rendered HTML appears to contain the organisation and inner objects perfectly fine.

Any help is appreciated!

Corné
  • 125
  • 1
  • 2
  • 14
  • My view also has a section for the Flags object for editing but I thought we'd keep it simple since I'll be able to figure out what to fix when someone helps me with Organisations. – Corné Jan 04 '18 at 03:02
  • You need to find out why `ModelState` isn't valid. Try to enumerate `ModelState.Values` and check if any of the elements contains `Errors`. If so, then check what the error message is. See [this answer](https://stackoverflow.com/a/1791664/1905949) as reference. – ekad Jan 04 '18 at 03:20
  • The ModelState is not valid because Organisations and Flags contain 0 objects. I'll add a pic of debugging result. This is what the whole problem is. Organisations should at least be filled in with the hidden stuff. – Corné Jan 04 '18 at 03:37
  • Organisations and Flags with 0 objects might be the reason, but there might also be something else that makes `ModelState.IsValid` false. Please check the content of `ModelState.Values` based on [this answer](https://stackoverflow.com/a/1791664/1905949) as I said above. The `Errors` element will tell you exactly what's wrong. – ekad Jan 04 '18 at 03:49
  • {"The parameter conversion from type 'System.String' to type 'RRPortal.Models.Organisation' failed because no type converter can convert between these types."} – Corné Jan 04 '18 at 03:54
  • Your problem is somewhat similar to [this question](https://stackoverflow.com/q/7983023/1905949), but I still can't replicate the problem. Can you show a screenshot of where you see `The parameter conversion` error exactly? – ekad Jan 04 '18 at 04:45
  • 1. The fact the collection count is 0 has nothing to do with `ModelState` being invalid. Check the values in `ModelState` to see what is actually invalid. –  Jan 04 '18 at 05:14
  • 1
    2. The error in your previous comment suggests you actually have an input for the collection property (e.g. `@Html.HiddenFor(m => m.Organisations)` –  Jan 04 '18 at 05:16
  • 1
    3. Why in the world are generating all those hidden inputs - if you need them in the POST method then just get them again. –  Jan 04 '18 at 05:18
  • @ekad I have added a screenshot to show that the error shows on the list. Same error as organisation but I've removed that hidden list as it was probably pointless but now you can see that it also happens on the Flags list which does get edited. – Corné Jan 04 '18 at 20:21
  • @StephenMuecke (1) ModelState is invalid because of the list object, I think. (2) Ok, that's what I need I think. (3) I have gotten rid of it and shown that the same problem exists for the other list Flags which gets edited. – Corné Jan 04 '18 at 20:21
  • It appears to be a problem with the [JsonProperty(PropertyName = ""] tags on the model. When removed and model mocked, everything works fine, but I need those to work with Azure CosmosDB – Corné Jan 04 '18 at 21:41
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/162553/discussion-between-stephen-muecke-and-corne). –  Jan 04 '18 at 22:42

1 Answers1

0

My guess is that in your HttpGet view you have something along the lines of:

[HttpGet]
public ActionResult EditProductAsync()
{
    var model = new ProductViewModel()
    {
        Flags = _uow.Products.GetFlags(),
        Organisations = _uow.Products.GetOrganisations()
    };

    return View(model);
}

Because these objects are not also returned as part of your form, they are returning to the server as empty which is throwing an error for you, thus invalidating the model. Before you check if the model is valid, you should first do something like this:

[HttpPost]
[ActionName("ProductEdit")]
[ValidateAntiForgeryToken]
public async Task<ActionResult> EditProductAsync( [Bind(Include = "Id, Name, Flags, Organisations")] Item model)
{
    model.Organisations = _uow.Products.GetOrganisations();
    model.Flags = _uow.Products.GetFlags();

    if (ModelState.IsValid)
    {
        await DocDBRepo<Item>.UpdateItemAsync(model.Id, model);

        return RedirectToAction("Index");
    }

    return View(model);
}

By populating those fields, any model errors you have are strictly your client's errors on submitting the form.

Horkrine
  • 1,365
  • 12
  • 24
  • I have edited the question to show that i can get organisation like you suggest but flags get updated via 'EditorFor' and need to be passed through to the post. – Corné Jan 04 '18 at 20:12