3

I'm new to MVC and I'm struggling deeply with this behaviour:

When I want to save with the Edit method (POST) I make some validations and when an error occurs I call again the view and I send the same object. However in the view all the inner objects are null.

Here is an example:

public class Product 
{
  public int Id { get; set; }
  public string Name { get; set; }
  public DateTime ExpirationDate { get; set; }
  public int CategoryId { get; set; }
  public virtual Category Category { get; set; }
}

public class Category 
{
  public int CategoryId { get; set; }
  public string Name { get; set; }
}

My controller is this:

[HttpPost]
public ActionResult Edit(Product product, string submitButton)
{
  if(submitButton == "Save")
  {
    if (ModelState.IsValid)
    {
        db.Entry(product).State = EntityState.Modified;
        db.SaveChanges();
        return RedirectToAction("Index");
    }
  }
  else // Validate
  {
    if(product.Expiration <= DateTime.Now)
    {
      ViewBag.Message "Product is expired";
      return View(product); // In the view the object property 'Category' is null. Why?
    }
  }
}

Update. Here's the view.

@model sgt.Models.Product

@{
    ViewBag.Title = "Edit Product";
}

<h2>Edit</h2>

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

    <div class="form-horizontal">
        <h4>Product</h4>
        <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" })
                <input type="submit" value="Validate" class="btn btn-default" />
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(model => model.ExpirationDate, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.ExpirationDate, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.ExpirationDate, "", new { @class = "text-danger" })
                <input type="submit" value="Validate" class="btn btn-default" />
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(model => model.Category, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.DisplayFor(model => model.Category.Name, new { htmlAttributes = new { @class = "form-control" } })
            </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("Return", "Index")
</div>

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

Thanks in advance

Nelson Miranda
  • 5,484
  • 5
  • 33
  • 54
  • You have `if(product.Expiration <= DateTime.Now)`, but I don't see an Expiration property in your Product class. – wooters Apr 04 '15 at 00:34
  • What is the value of `product.Category` when the Edit action is invoked? – jhenderson2099 Apr 04 '15 at 00:43
  • @J.Henderson product.Category in this case is null. – Nelson Miranda Apr 04 '15 at 00:49
  • If `product.Category` is null upon entry of `Edit`, and no further changes are made, why would you expect it to not be null within the View? – jhenderson2099 Apr 04 '15 at 00:51
  • @J.Henderson if product.Category is null then when I send it to the view then the category name is blank. – Nelson Miranda Apr 04 '15 at 01:10
  • You don't generate any controls for any of the properties of `Category` so nothing posts back so its null. If you want the values of Category to be bound then you need to include inputs - e.g. `@Html.HiddenFor(m => m.Category.Name)` –  Apr 06 '15 at 04:39

1 Answers1

1

YOu have a logical Error in your code.

You are not returning the View if(ModelState.IsValid) Fails

[HttpPost]
public ActionResult Edit(Product product, string submitButton)
{
    if(submitButton == "Save")
    {
        if (ModelState.IsValid)
        {
            db.Entry(product).State = EntityState.Modified;
            db.SaveChanges();
            return RedirectToAction("Index");
        }

        // Here RETURN VIEW and MODEL

        return View(product);
    }
    else // Validate
    {
        if(product.Expiration <= DateTime.Now)
        {
        ViewBag.Message "Product is expired";
        return View(product); // In the view the object property 'Category' is null. Why?
        }

        // ALSO HERE YOU ARE NOT RETURNING A VIEW AND MODEL

        return View(product);
    }
}

Just wondering, but I think you want to check if Model is Valid AND product.Expiration <= DateTime.Now

[HttpPost]
public ActionResult Edit(Product product, string submitButton)
{
      if(product.Expiration <= DateTime.Now && submitButton != "Save"){
         ModelState.AddModelError("Expiration", "Product is expired");
       }


       if (ModelState.IsValid)
       {
           db.Entry(product).State = EntityState.Modified;
           db.SaveChanges();
           return RedirectToAction("Index");
        }
        return View(product);
}

EDIT: TRY THIS:

   [HttpPost]
   public ActionResult Edit(Product product, string submitButton)
   {
    if(submitButton == "Save")
    {
            if (ModelState.IsValid)
            {
                db.Entry(product).State = EntityState.Modified;
                db.SaveChanges();
                return RedirectToAction("Index");
            }
            return View(product);
    }
       if(product.Expiration <= DateTime.Now)
       {
           ViewBag.Message "Product is expired";
       }
       return View(product);
   }

EDIT YOUR VIEW:

<div class="form-group">
    @Html.HiddenFor(m => m.Category.CategoryId)
    @Html.HiddenFor(m => m.Category.Name )

    @Html.LabelFor(model => model.Category, htmlAttributes: new { @class = "control-label col-md-2" })
    <div class="col-md-10">
        @Html.DisplayFor(model => model.Category.Name, new { htmlAttributes = new { @class = "form-control" } })
    </div>
</div>      
Dawood Awan
  • 7,051
  • 10
  • 56
  • 119
  • Thanks for the response. I updated the code however my problem is not there; is when submitButton is not "Save" is when the expiration date is validated. The product.Category is null. That's my problem. – Nelson Miranda Apr 04 '15 at 01:07
  • POST the Code of you View – Dawood Awan Apr 04 '15 at 01:12
  • Thanks for your response. ModelState.IsValid is false, that's ok, but when I return View(product) then Category is blank in my view. – Nelson Miranda Apr 04 '15 at 01:21
  • How does your Category Object have value when you dont' Post it back from the view? If you add this line to your view it will work @Html.HiddenFor(model => model.Category.Name) – Dawood Awan Apr 04 '15 at 01:40
  • 1
    @nmiranda Check my Edit. MVC doesn't POST back data that is in DisplayFor http://stackoverflow.com/questions/12314849/html-displayfor-not-posting-values-to-controller-in-asp-net-mvc-3 – Dawood Awan Apr 04 '15 at 01:42
  • @DawoodAwan is correct. You're not posting back any values to be bound to the Category object. – jhenderson2099 Apr 04 '15 at 02:59
  • @DawoodAwan After delivering I discovered that you were right; I had to make hidden the descriptions of the related objects to make them available in the controller. Thanks. – Nelson Miranda Sep 22 '15 at 18:00