Reputation: 635
I am trying to validate a form. For some reason, when I go to submit the form, the form validation says "The ID field is required." However, despite it saying this, I have a HiddenFor input that should take care of it...
Here is my view UserReportForm.cshtml (shortened for readability):
@model BugTracker.ViewModels.UserReportViewModel
@{
ViewBag.Title = "Issue Form";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h1>Report Bug/Request Change</h1>
@Html.ValidationSummary(false, "Please fix the following errors.")
@using (Html.BeginForm("Save", "Report", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
<div class="form-group">
<label>Are you reporting a bug, or requesting a change to a page?</label>
@Html.DropDownListFor(m => m.Report.RequestTypeID, new SelectList(Model.RequestType, "ID", "RequestBC"), "Select Issue", new { @class = "form-control" })
@Html.ValidationMessageFor(m => m.Report.RequestTypeID)
</div>
<div class="form-group">
<label>Name of Issue</label>
@Html.TextBoxFor(m => m.Report.Name, new { @class = "form-control"})
@Html.ValidationMessageFor(m => m.Report.Name)
</div>
<div class="form-group">
<label>Detailed Description of Issue</label>
@Html.TextAreaFor(m => m.Report.Description, new { @class = "form-control", @rows = "10"})
@Html.ValidationMessageFor(m => m.Report.Description)
</div>
@Html.HiddenFor(m => m.Report.ID)
<button type="submit" class="btn btn-primary">Save</button>
}
And here is the view model UserReportViewModel.c:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using BugTracker.Models;
using System.Web.Mvc;
namespace BugTracker.ViewModels
{
public class UserReportViewModel
{
public Report Report { get; set; }
public IEnumerable<RequestType> RequestType { get; set; }
}
}
And the Report.cs model:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel.DataAnnotations;
namespace BugTracker.Models
{
public class Report
{
//For User
public int ID { get; set; }
[Required]
public String Name { get; set; }
[Required]
public String Description { get; set; }
public RequestType RequestType { get; set; }
[Display (Name="Request Type")]
public byte RequestTypeID { get; set; }
}
}
Prior to me adding validation, these forms had no issues with getting an ID in the database (incremented by 1 each time). Why is this complaining about the ID, and what can I do to stop it from saying it is required?
Edit: I didn't think this had much to do with it, but upon further review, I think my controller may be causing the issue:
New Form
public ActionResult UserReportForm()
{
var requestType = _context.RequestType.ToList();
var viewModel = new UserReportViewModel
{
RequestType = requestType,
};
return View(viewModel);
}
Save Form
[HttpPost]
public ActionResult Save(UserReportViewModel viewModel)
{
if (!ModelState.IsValid)
{
var viewM = new UserReportViewModel
{
RequestType = _context.RequestType.ToList(),
};
return View("UserReportForm", viewM);
}
if (viewModel.Report.ID == 0)
{
_context.Reports.Add(viewModel.Report);
}
else
{
var reportInDb = _context.Reports.Single(c => c.ID == viewModel.Report.ID);
reportInDb.Name = viewModel.Report.Name;
reportInDb.Description = viewModel.Report.Description;
reportInDb.RequestTypeID = viewModel.Report.RequestTypeID;
}
_context.SaveChanges();
return RedirectToAction("Report", "Report", new { stat = 1 });
}
Upvotes: 0
Views: 278
Reputation: 635
Thanks for your replies, SAR and Tetsuya Yamamoto - I appreciate your advice! After doing some more research, I was able to resolve my issue. The solution I discovered was in how I used the view model.
To start with, it seems that @Html.ValidationSummary will check each field to determine if the information defined in the model is required or not. In my case, the ID defined in my Report model is required. Given this circumstance, I could have fixed my problem by simply removing this line from my view:
@Html.HiddenFor(m => m.Report.ID)
However, I wanted to use this view for both creating a form AND for editing it. In that case, we would need to have this ID nullable. The solution was, rather than editing the Report model, to edit the view model UserReportViewModel.cs. I opened this file up, and added this line of code:
public int? Id {get; set;}
Then, I went back to my view and changed it to this:
@Html.HiddenFor(m => m.Id)
Almost done. Back in the controller, I need to adjust things just a bit. There are no changes in my New controller, but in the save controller, I changed instances where I used viewModel.Report.ID to viewModel.Id. I also added a single line of code in the "if (!ModelState.Valid) branch". The updated code looks like this:
[HttpPost]
public ActionResult Save(UserReportViewModel viewModel)
{
if (!ModelState.IsValid)
{
var viewM = new UserReportViewModel
{
RequestType = _context.RequestType.ToList(),
};
viewM.Id = viewModel.Report.ID;
return View("UserReportForm", viewM);
}
if (viewModel.Id == 0)
{
_context.Reports.Add(viewModel.Report);
}
else
{
var reportInDb = _context.Reports.Single(c => c.ID == viewModel.Id);
reportInDb.Name = viewModel.Report.Name;
reportInDb.Description = viewModel.Report.Description;
reportInDb.RequestTypeID = viewModel.Report.RequestTypeID;
}
_context.SaveChanges();
return RedirectToAction("Report", "Report", new { stat = 1 });
}
After these changes, I was able to successfully create and edit my forms with validation! In summary, the way to fix this problem is to use a nullable ID in the view model rather than a required ID from a specific model.
Upvotes: 0
Reputation: 24957
If the ID
column is an auto-generated primary key (i.e. identity column), you should mark it with KeyAttribute
and DatabaseGeneratedAttribute
like this example:
public class Report
{
// add 2 attributes below
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
[Required]
public String Name { get; set; }
[Required]
public String Description { get; set; }
public RequestType RequestType { get; set; }
[Display(Name="Request Type")]
public byte RequestTypeID { get; set; }
}
By using those attributes, no need to use RequiredAttribute
on the ID
property.
Regarding your second issue for DropDownListFor
, the problem occurs when trying return View(viewModel)
because UserReportViewModel.RequestType
still not assigned after form submit, which resulting in UserReportViewModel.RequestType
collection contains null value and throwing ArgumentNullException
when building SelectList
.
Just assign List<RequestType>
into existing viewmodel in case of validation errors should enough:
[HttpPost]
public ActionResult Save(UserReportViewModel viewModel)
{
if (!ModelState.IsValid)
{
viewModel.RequestType = _context.RequestType.ToList(); // use this line instead
return View("UserReportForm", viewModel); // change to return existing viewmodel
}
// other stuff - skipped for brevity
return RedirectToAction("Report", "Report", new { stat = 1 });
}
Upvotes: 1
Reputation: 1845
Please Update the Model & Rebuild the Project
public class Report
{
//For User-> here add the [Key]
[Key]
public int ID { get; set; }
[Required]
public String Name { get; set; }
[Required]
public String Description { get; set; }
public RequestType RequestType { get; set; }
[Display (Name="Request Type")]
public byte RequestTypeID { get; set; }
}
Upvotes: 1