I have an ASP.NET MVC application with AJAX updating. CRUD operations for every entity are working correctly, except for creating a "Receipt". The problem arises when the controller hits ModelState.IsValid which translates to false when in fact it should be true. I debugged everything step by step so many times, and it is always false when it should be true.
I am using Entity Framework for entity manipulation and this is the code that was generated from the SQL Server database:
public partial class Receipt
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public Receipt()
{
this.Receipts1 = new HashSet<Receipt>();
this.Seminars = new HashSet<Seminar>();
}
public int Id { get; set; }
public System.DateTime IssueDate { get; set; }
public Nullable<System.DateTime> DeliveryDate { get; set; }
public Nullable<System.DateTime> PaymentDue { get; set; }
public Nullable<short> CompanyId { get; set; }
public Nullable<int> Number { get; set; }
public Nullable<int> ClosedReceiptId { get; set; }
public Nullable<decimal> ReturnedAmount { get; set; }
public Nullable<short> ReturnTypeId { get; set; }
public virtual Company Company { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<Receipt> Receipts1 { get; set; }
public virtual Receipt Receipt1 { get; set; }
public virtual ReturnType ReturnType { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<Seminar> Seminars { get; set; }
}
On top of which I have ReceiptMetadata for annotations and [Required] fields:
[MetadataType(typeof(ReceiptMetadata))]
public partial class Receipt
{
}
public class ReceiptMetadata
{
[Required]
public int Number { get; set; }
[Required]
[DisplayName("Issue Date")]
[DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:G}")]
public DateTime IssueDate { get; set; }
[Required]
[DisplayName("Payment Due")]
[DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:dd.MM.yyyy}")]
public DateTime? PaymentDue { get; set; }
[DisplayName("Return amount")]
[DisplayFormat(DataFormatString = "{0:c}")]
public Nullable<decimal> ReturnedAmount { get; set; }
}
And my Create function in controller is as follows:
[HttpPost]
[ValidateAntiForgeryToken]
public JsonResult Create([Bind(Include = "Id,IssueDate,DeliveryDate,PaymentDue,CompanyId,Number")]Receipt receipt, int? seminarId)
{
if (seminarId == null)
{
Response.StatusCode = (int)HttpStatusCode.BadRequest;
return Json(new { Message = "ID is required" });
}
Seminar seminar = db.Seminars.Find(seminarId);
if (seminar == null)
{
Response.StatusCode = (int)HttpStatusCode.BadRequest;
return Json(new { Message = "Seminar doesn't exist" });
}
int receiptNumber = db.Receipts.Where(r => r.CompanyId == receipt.CompanyId && r.ClosedReceiptId == null && r.IssueDate.Year == DateTime.Now.Year).Count() + 1;
receipt.Number = receiptNumber;
receipt.IssueDate = TimeZoneInfo.ConvertTimeBySystemTimeZoneId(DateTime.Now, TimeZoneInfo.Local.Id, "Central European Standard Time");
if (ModelState.IsValid)
{
try
{
db.Receipts.Add(receipt);
db.SaveChanges();
seminar.ReceiptId = receipt.Id;
db.Entry(seminar).State = EntityState.Modified;
db.SaveChanges();
GeneratePDF(receipt.Id, receipt.ReceiptNumber, receipt.CompanyId.ToString());
return Json(new { receipt.Id, receipt.PDFLink, Action = "Create", Message = "Receipt successfully added! -> " }, JsonRequestBehavior.AllowGet);
}
catch (Exception ex)
{
Response.StatusCode = (int)HttpStatusCode.BadRequest;
return Json(new { Message = ex.Message });
}
}
Response.StatusCode = (int)HttpStatusCode.BadRequest;
IEnumerable<ModelError> allErrors = ModelState.Values.SelectMany(v => v.Errors);
List<string> errorMessages = new List<string>();
foreach (ModelError error in allErrors)
{
errorMessages.Add(error.ErrorMessage);
}
return Json(new { Message = errorMessages });
}
The error I get is
The Number field is required.
when clearly the field Number is filled in with:
receipt.Number = receiptNumber;
a few lines before the IsValid.
Can someone please help?
EDIT: this is a screenshot from debugging, the modelstate keys are completely wrong:
The ModelState is already evaluated (and invalid) when entering your action, setting the "Number" property won't evaluate the model again. After setting the property, you may have to manually remove it from the model errors by writing this:
ModelState.Remove("Number");
I had the same problem for a long time and finally I found it. In my case, it was the Id field :)
Just place a breakpoint and check your ModelState in runtime and go to this section :
ModelState -> Root -> Children
and you will see all valid and invalid Keys
Related
Hello guys I am making a college website project in MVC it has admission form so whenever a admission form is filled by user and gets submitted it goes for approval and the user receives his admission code to check the status of his/her account approval so I am having a problem here.
The Code:
Model:
public partial class admission_tbl
{
public int adm_id { get; set; } //- by this user checks his status//
[Required]
[StringLength(50)]
public string std_name { get; set; }
[Required]
[StringLength(50)]
public string std_father { get; set; }
[Required]
[StringLength(50)]
public string std_mother { get; set; }
[AgeRangeValidation(ErrorMessage = "Age must be between 18 - 28", MinAge = 18, MaxAge = 28)]
[Required]
[DataType(DataType.Date), DisplayFormat(DataFormatString = "{0:dd/MM/yyyy}", ApplyFormatInEditMode = true)]
public System.DateTime DOB { get; set; }
[Required]
public string std_gender { get; set; }
[Required]
public string R_address { get; set; }
[Required]
public string P_address { get; set; }
[Required]
public string adm_for { get; set; }
[Required]
public string university { get; set; }
[Required]
public string E_no { get; set; }
[Required]
public string Center { get; set; }
[Required]
public string City { get; set; }
[Required]
public string Field { get; set; }
[Required]
public string Marks_secured { get; set; }
[Required]
public string out_of { get; set; }
[Required]
public string Class_obtained { get; set; }
public string Sports_details { get; set; }
public string adm_status { get; set; } //--This is the method which i am using to control account
approval or non approval status.
}}
Controller:
public ActionResult Status_check()
{
return View();
}
[HttpPost]
public ActionResult Status_check(admission_tbl svm)
{
admission_tbl u = db.admission_tbl.Where(a => a.adm_id == svm.adm_id).SingleOrDefault();
if (u.adm_status == "Approved")
{
ViewBag.Message1 = "Your account is Approved";
}
else if (u.adm_status == "Unapproved")
{
ViewBag.Message = "Your account is not Approved ";
}
else
{
ViewBag.Message = "Your account is in pending please wait";
}
return View();
}
So, here in status check the user inputs his admission code to check whether account is approved or not everything works fine but the problem is when I try to input a random number input it throws null exception error instead I want to throw a message error for example "Invalid Admission Code no such exists" i couldn't catch it what am i missing?
For the scenario that svm.adm_id is not existed in the records for admission_tbl
admission_tbl u = db.admission_tbl.SingleOrDefault(a => a.adm_id == svm.adm_id);
This .SingleOrDefault() will return NULL. Thus when you try to access adm_status from u which is null, you will get NullReferenceException.
To solve this issue, you need to add validation to check the u object cannot be null before proceeding to access its inner properties.
[HttpPost]
public ActionResult Status_check(admission_tbl svm)
{
admission_tbl u = db.admission_tbl.Where(a => a.adm_id == svm.adm_id).SingleOrDefault();
// Validate u cannot be null, if null do error handling
if (u == null)
{
ViewBag.Message = "Invalid Admission Code no such exists";
return View();
}
if (u.adm_status == "Approved")
{
ViewBag.Message1 = "Your account is Approved";
}
else if (u.adm_status == "Unapproved")
{
ViewBag.Message = "Your account is not Approved ";
}
else
{
ViewBag.Message = "Your account is in pending please wait";
}
return View();
}
Note:
This statement
db.admission_tbl.Where(a => a.adm_id == svm.adm_id).SingleOrDefault();
can be replaced to:
db.admission_tbl.SingleOrDefault(a => a.adm_id == svm.adm_id);
Both will return same result and return null if the record is not found.
Second Edit version:
You can bind your error message in ViewBag, or add any handling for the scenario and immediately exit
ViewBag.Message = "Invalid Admission Code no such exists";
return View();
Upon my post ActionResult Edit, I am receiving an error. System.Web.Mvc.WebViewPage<TModel>.Model.get returned null
My controller:
[HttpPost]
public ActionResult Edit(editRequestViewModel _editRequestViewModel, int id)
{
try
{
if (ModelState.IsValid)
{
using (var db = new HelpDeskContext())
{
db.Entry(_editRequestViewModel.userRequest).State = System.Data.Entity.EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Digest",new { id = _editRequestViewModel.userRequest.ID });
}
}
else
return View();
}
catch (Exception ex)
{
return View("Error", new HandleErrorInfo(ex, "Change", "Browse"));
}
}
My View includes this for the models field to bind:
#Html.DropDownListFor(model => model.userRequest.forApplication, Model.Applications, "Select Application", new { #class = "form-control" })
My Model has the field as nullable int?:
public int? forApplication { get; set; }
It seems to update the other fields in the model with this field just fine on POST. When the request is first created and saved to the DB, it saves fine in that field when its null. It seems to me that nullable should be OK as a value when its posting (Edit ActionResult)?
EDIT: This is my GET Method that populates the View Model which is passed in to the POST.
public ActionResult Edit(int id)
{
try
{
if (ModelState.IsValid)
{
using (var db = new HelpDeskContext())
{
var query = (from m in db.Requests
where m.ID == id
select new editRequestViewModel()
{
Applications = (from r in db.Applications
select new SelectListItem(){
Text = r.Name,
Value = r.ID.ToString()
}).ToList(),
closeReasons = (from r in db.CloseReasons
select new SelectListItem()
{
Text = r.Name,
Value = r.ID.ToString()
}).ToList(),
userRequest = m
}).FirstOrDefault();
return View(query);
}
}
else
return View();
}
catch (Exception ex)
{
return View("Error", new HandleErrorInfo(ex, "Change", "Browse"));
}
}
And my View has #model HelpDeskSolution.ViewModels.editRequestViewModel
EDIT 2: ViewModel and Model
namespace HelpDeskSolution.Models
{
public class Request
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity), Key()]
public int ID { get; set; }
[Required]
[StringLength(99, MinimumLength = 3)]
public string Title { get; set; }
[StringLength(1000, MinimumLength = 1)]
[Required]
public string Description { get; set; }
[Required]
[Display(Name = "Submit Date")]
public DateTime SubmitDate { get; set; }
public DateTime? CloseDate { get; set; }
[Required]
[StringLength(30)]
public string Author { get; set; }
[Required]
[StringLength(30)]
public string AuthDept { get; set; }
[StringLength(30)]
[Display(Prompt = "Text at top of Epicor Screen...(Optional)")]
public string Module { get; set; }
public int Urgency { get; set; }
[StringLength(30)]
public string Type { get; set; }
public int Status { get; set; }
[StringLength(30)]
[Display(Name = "Request For")]
public string RequestFor { get; set; }
[Required]
public bool Closed { get; set; }
[StringLength(30)]
[Display(Name = "Assign To")]
public string AssignedTo { get; set; }
[Display(Name = "Application")]
public int? forApplication { get; set; }
public int? closeReason { get; set; }
public string ClosedBy { get; set; }
[Display(Name = "ID")]
public int? duplicateOf { get; set; }
}
}
Model:
namespace HelpDeskSolution.ViewModels
{
public class editRequestViewModel
{
public Request userRequest { get; set; }
public List<SelectListItem> Applications { get; set; }
public List<SelectListItem> closeReasons { get; set; }
}
}
Ended up solving this with the direction of #StephenMuecke. The reason I was getting the exception is because upon the return View() in the else portion of the post action, it was attempting to return the view without Applications list, as Stephen said. However that led me to realize that there was first a problem with the Model State, hence why it was even going to the else in the first place. I had another field that was being passed null when it wasnt a nullable type.
I simply changed the type to int? and added a migration and the Action result is A'Okay now.
I am using autocomplete from JQueryUIHelpers in my Asp.Net MVC project with EF6.
Model structure:
public class Employee
{
[Key]
public int Id { get; set; }
[Required]
public string FirstName { get; set; }
[Required]
public string SecondName { get; set; }
[NotMapped]
public string FullName => FirstName + " " + SecondName;
public bool IsDriver { get; set; } = false;
public virtual ICollection<Delivery> Deliveries { get; set; }
}
public class Delivery
{
[Key]
public int Id { get; set; }
[Required]
public Employee Driver { get; set; }
public virtual ICollection<EggsMag> Eggs { get; set; }
}
EmployeeController:
public ActionResult Drivers(string term)
{
var drivers = _rep.GetAll(e => e.IsDriver && (e.FirstName.StartsWith(term) || e.SecondName.StartsWith(term)));
return Json((from d in drivers select new { label = d.FullName, value = d.Id }).ToList(), JsonRequestBehavior.AllowGet);
}
DeliveriesController:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit([Bind(Include = "ID,DateOfDelivery,Driver")] Delivery delivery)
{
if (ModelState.IsValid)
{
_rep.Save(delivery);
return RedirectToAction("Index");
}
return View(delivery);
}
View:
#Html.JQueryUI().AutocompleteFor(m => m.Driver.Id, Url.Action("Drivers", "Employees"), "DriverId", null)
Problem Description:
Autocomplete is working correctly but when in Edit view I send POST request I receive all the data, but ModelState.IsValid is false.
The error shows that fields of FirstName and SecondName are empty which is true because I sent just Id of existing Driver, not whole object.
Is there a way to fix it?
Maybe some way to change validation to not check inner model(Driver) fields except Driver.Id existence.
I have the following method that saves an EmailTemplate. Based on the ID of a dropdown it populates the EmailAccount as the foreign entity property.
public ActionResult Edit([Bind(Include = "EmailAccountId, EmailTemplate")] EmailTemplateViewModel emailTemplateViewModel)
{
if (ModelState.IsValid)
{
if (emailTemplateViewModel.EmailAccountId > 0)
{
emailTemplateViewModel.EmailTemplate.EmailAccount = db.EmailAccounts.Find(emailTemplateViewModel.EmailAccountId);
}
db.Entry(emailTemplateViewModel.EmailTemplate).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(emailTemplateViewModel);
}
Everything in the EmailTemplate saves fine apart from EmailAccount. In debugger I can see that the property is populated before db.SaveChanges() is called.
I am setting the entity state to EntityState.Modified but it's not picking up the foreign property.
I tried adding:
db.Entry(emailTemplateViewModel.EmailTemplate.EmailAccount).State = EntityState.Modified;
But this didn't work. How do I tell EntityFramework that it needs to save the nested entity?
Edit:
As requested
public class EmailTemplateViewModel
{
public List<EmailAccount> EmailAccounts { get; set; }
public EmailTemplate EmailTemplate { get; set; }
[Display(Name = "Email Account")]
public int EmailAccountId { get; set; }
public IEnumerable<SelectListItem> EmailAccountsList
{
get
{
var allEmails = EmailAccounts.Select(e => new SelectListItem { Value = e.ID.ToString(), Text = e.Email });
return DefaultEmailAccountList.Concat(allEmails);
}
}
public IEnumerable<SelectListItem> DefaultEmailAccountList
{
get
{
return Enumerable.Repeat(new SelectListItem
{
Value = "-1",
Text = "Select Email Account"
}, count: 1);
}
}
}
public class EmailTemplate
{
public int ID { get; set; }
[StringLength(50)]
[Index(IsUnique = true)]
public string Identifier { get; set; }
public int Interval { get; set; }
public string TitleTemplate { get; set; }
[DataType(DataType.MultilineText)]
public string BodyTemplate { get; set; }
public virtual EmailAccount EmailAccount { get; set; }
}
I was modifying before I attached so the change wasn't tracked.
This works
if (ModelState.IsValid)
{
db.Entry(emailTemplateViewModel.EmailTemplate).State = EntityState.Modified;
if (emailTemplateViewModel.EmailAccountId > 0)
{
emailTemplateViewModel.EmailTemplate.EmailAccount = db.EmailAccounts.Find(emailTemplateViewModel.EmailAccountId);
}
db.SaveChanges();
return RedirectToAction("Index");
}
return View(emailTemplateViewModel);
Is there any way to somehow combine the data from two models and THEN map them both to the same viewModel in the context of an edit action?
I have never had to update several tables at once in an edit action in ASP.NET MVC with Entity Framework 6.1.3. This is the layout:
I have a DB table called "Address" which has fields for StreetNumber, StreetName, City, State, ZipCode. It has a one-to-one relationship with another table called Bars. As in, a bar can only have one address and one address can only have one bar.
Because I am storing this data in two separate tables, I am having a very difficult time trying to successfully implement an Edit action which takes data from one form (BarForm) and should update both the Bar and Address database tables. See my code:
BarController
public ActionResult Edit(int id)
{
var bar = _context.Bars.SingleOrDefault(m => m.Id == id);
var address = _context.Addresses.SingleOrDefault(a => a.BarId == id);
//Make sure that the id actually exists:
if (bar == null)
{
return HttpNotFound();
}
var viewModel = Mapper.Map<Bar, BarFormViewModel>(bar, new BarFormViewModel());
if (address == null)
{
address = new Address();
}
Mapper.Map<Address, BarFormViewModel>(address, viewModel);
viewModel.IsNew = false;
return View("BarForm", viewModel);
}
[ValidateAntiForgeryToken]
public ActionResult Save(BarFormViewModel bar)
{
if (!ModelState.IsValid)
{
var viewModel = Mapper.Map<BarFormViewModel, BarFormViewModel>(bar, new BarFormViewModel());
viewModel.IsNew = false;
return View("BarForm", viewModel);
}
if (bar.Id == 0)
{
var newbar = Mapper.Map<BarFormViewModel, Bar>(bar);
newbar.LastUpdated = DateTime.UtcNow;
_context.Bars.Add(newbar);
var addressToAdd = Mapper.Map<BarFormViewModel, Address>(bar);
_context.Addresses.Add(addressToAdd);
}
else
{
var barInDb = _context.Bars.Single(b => b.Id == bar.Id);
var addressInDb = _context.Addresses.Single(a => a.BarId == bar.Id);
Mapper.Map<BarFormViewModel, Bar>(bar, barInDb);
Mapper.Map<BarFormViewModel, Address>(bar, addressInDb);
}
_context.SaveChanges();
return RedirectToAction("Index", "Bar");
}
Domain Models:
public class Bar
{
public int Id { get; set; }
public string Name { get; set; }
[Required]
public string GooglePlaceId { get; set; }
public string SundayDiscounts { get; set; }
public string MondayDiscounts { get; set; }
public string TuesdayDiscounts { get; set; }
public string WednesdayDiscounts { get; set; }
public string ThursdayDiscounts { get; set; }
public string FridayDiscounts { get; set; }
public string SaturdayDiscounts { get; set; }
[Display(Name = "Last Updated")]
public DateTime LastUpdated { get; set; }
}
public class Address
{
public int Id { get; set; }
public int? Number { get; set; }
public string StreetName { get; set; }
public string City { get; set; }
public string State { get; set; }
[Required]
public int ZipCode { get; set; }
public Bar Bar { get; set; }
public int BarId { get; set; }
}
View Model which includes both Address and Bar properties:
{
public class BarFormViewModel
{
public int? Id { get; set; }
public string Name { get; set; }
[Required]
[Display(Name = "Google Place ID")]
public string GooglePlaceId { get; set; }
[Display(Name = "Sunday Happy Hour Info:")]
public string SundayDiscounts { get; set; }
[Display(Name = "Monday Happy Hour Info:")]
public string MondayDiscounts { get; set; }
[Display(Name = "Tuesday Happy Hour Info:")]
public string TuesdayDiscounts { get; set; }
[Display(Name = "Wednesday Happy Hour Info:")]
public string WednesdayDiscounts { get; set; }
[Display(Name = "Thursday Happy Hour Info:")]
public string ThursdayDiscounts { get; set; }
[Display(Name = "Friday Happy Hour Info:")]
public string FridayDiscounts { get; set; }
[Display(Name = "Saturday Happy Hour Info:")]
public string SaturdayDiscounts { get; set; }
[Display(Name = "Last Updated")]
public DateTime? LastUpdated { get; set; }
//Address Model Info
public Address Address { get; set; }
public int? AddressId { get; set; }
[RegularExpression("([1-9][0-9]*)", ErrorMessage = "Must be a number")]
public int? Number { get; set; }
public string StreetName { get; set; }
public string City { get; set; }
public string State { get; set; }
[Required]
public int? ZipCode { get; set; }
public bool IsNew { get; set; }
}
The problem here is that I am getting an empty AddressId with this setup, which is causing an exception when the Save action gets run. This is because the BarForm view is getting passed a ViewModel which has been mapped from a Bar object and the Bar domain model actually has no Address information in it, since it is not the Address model/table.
Is there any way to somehow combine the data from both the Address and Bar models and THEN map them both to the same viewModel?
I keep getting a Sequence Contains no Elements error for this line in the Save action:
var addressInDb = _context.Addresses.Single(a => a.Id == bar.AddressId);
I also tried:
var addressInDb = _context.Addresses.Single(a => a.BarId == bar.Id);
Neither work. I understand what the error is saying and have also checked the actual HTML for my hidden Addressid field and it is blank... See code in my BarForm View:
#Html.HiddenFor(m => m.Id)
#Html.HiddenFor(m => m.AddressId)
#Html.AntiForgeryToken()
Remove the new BarFormViewModel() as the second parameter in your mapping calls as it is not necessary.
In your post action, inside your if statement that checks if the ModelState is valid and if bar.Id == 0, bar is already a view model, so no need to mapping.
And when you create your AutoMapper mapping, you must create a custom property mapping because the Address.Id property will not map automatically to the AddressId property as the name is not the same.
AutoMapper.Mapper.CreateMap<Address, BarFormViewModel>()
.ForMember(dest => dest.AddressId, o => o.MapFrom(source => source.Id));
And then do the same for the inverse mapping.