ModelState.isValid error - asp.net-mvc

I have created an Edit Action method but it is not going inside ModelState.isValid. How can I check the error?
public PartialViewResult UpdateAccountDetails(string accountNumber)
{
CreditReportService crService = new CreditReportService();
AccountInfo account = new AccountInfo();
account.Account = service.GetAccountDetails(accountNumber);
account.AccountStatuses = service.GetAccountStatuses();
account.AccountTypes = service.GetAccountTypes();
account.CreditTerms = service.GetCreditTerms();
return PartialView("_UpdateAccountDetails", account);
}
[HttpPost]
public ActionResult UpdateAccountDetails(Account account)
{
if (ModelState.IsValid)
{
service.SaveAccount(account);
TempData["message"] = "Account has been updated successfully!";
AccountInfo accountInfo = new AccountInfo();
accountInfo.AccountStatuses = service.GetAccountStatuses();
accountInfo.AccountTypes = service.GetAccountTypes();
accountInfo.CreditTerms = service.GetCreditTerms();
return PartialView("_UpdateAccountDetails", accountInfo);
}
else
{
return PartialView("_UpdateAccountDetails", account);
}
}

By accessing the ModelState.Errors collection. The collection contains a collection of ModelError items, which contain the error message and exception that was thrown to cause the model error.
Edit:
I guess I should have looked myself. It seems that the controller's ModelState is actually a (dictionary) collection of ModelState's. To get all the errors, you should be able to get all instances of the ModelError classes via:
var errors = ModelState.Select(x => x.Value.Errors).ToList();

var errors = var errors = ModelState.Where(m=>m.Value.Errors.Any()).Select(m => m.Value.Errors).ToList();
To only get a list of the errored fields only, rather than all fields and error list (exclude Errors length == 0).

Related

Concurrency exceptions when I tried to modifies two tables in MVC

I am having a concurrency Exception when I tried to update MessageTemplate and TemplateLookup table. MessageTemplate table update fine but the error occur when the code tried to update TemplateLookup table. Any help will be appreciated
Error
"Store update, insert, or delete statement affected an unexpected number of rows (0). Entities may have been modified or deleted since entities were loaded. See http://go.microsoft.com/fwlink/?LinkId=472540 for information on understanding and handling optimistic concurrency exceptions."
Controller
public async Task<ActionResult> Edit (MessageTemplate messagetemplate,int Id, bool Enable, int id)
{
TemplateLookup template = new TemplateLookup();
var appdata = DateTime.Now;
try
{
if (ModelState.IsValid)
{
var currentUser = await _db.vwUsers.FirstOrDefaultAsync(i => i.DomainUserName == User.Identity.Name);
if (currentUser != null)
messagetemplate.LastUpdatedByUser = currentUser.FullName;
messagetemplate.LastUpdatedByUserID = User.Identity.Name;
messagetemplate.LastUpdatedOn = appdata;
//messagetemplate.Id = Id;
_db.Entry(messagetemplate).State = EntityState.Modified;
//await _db.SaveChangesAsync();
if (Enable == false)
{
var templateidlist = _db.TemplateLookups.Where(v => v.TemplateId == Id).ToList();
messagetemplate.Enable = Enable;
foreach (var templateid in templateidlist)
{
template.TemplateActive = Enable;
template.LastUpdatedOn = appdata;
template.LastUpdatedByUser = currentUser.FullName;
template.TemplateId = Id;
// Error occur here
_db.Entry(template).State = EntityState.Modified;
}
}
_db.Entry(messagetemplate).State = EntityState.Modified;
await _db.SaveChangesAsync();
return RedirectToAction("Index");
}
return View(messagetemplate);
}
}
Well as it says - it looks like the row is being modified in between your query being started and actually saved...
I noticed you change the Id:
template.TemplateId = Id;
which you also search on;
var templateidlist = _db.TemplateLookups.Where(v => v.TemplateId == Id).ToList();
So if this action is run twice with the same template Ids it may change them before the second one has a chance to run.
Ususally in this concurrent modification scenario you'd catch the exception and re-display the form with the new values - but it depends on your usecase we can't say without more information about what your application does...
You can read about various options to handle it here: https://learn.microsoft.com/en-us/ef/ef6/saving/concurrency

Not able to update the data using entity framework

I have following code. in that i am trying to update my data. but i am getting error message:
An exception of type 'System.InvalidOperationException' occurred in EntityFramework.dll but was not handled in user code
Additional information: An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key.
Here is my code:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit([Bind(Include="CompanyId,Address,EstbalishYear,Email,IsActive")] CompanyMaster companymaster)
{
if (companymaster.CompanyId == 0)
{
return View(companymaster);
}
CompanyMaster company = db.CompanyMasters.SingleOrDefault(x => x.CompanyId == companymaster.CompanyId);
companymaster.Name = company.Name;
companymaster.InsertedBy = company.InsertedBy;
companymaster.InsertedTime = company.InsertedTime;
companymaster.UpdatedBy = 1;
companymaster.UpdatedTime = DateTime.Now;
ModelState.Remove("Name");
if (ModelState.IsValid)
{
db.Entry(companymaster).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(companymaster);
}
Please explain me how can I fix this error message?
This is because you are working with two object instances of a company master, which in reality is a single entity, with the same ID.
One (companyMaster) comes as an argument to the Edit method, via binding.
The other one (company) you are selecting from the database through db.CompanyMasters by ID
What you can do is
Select company by ID, as you do now
Set company properties from companyMaster object (vice-versa, not like you do now)
Save the company object
Please find the sample code below.
Please also note that the best practice is not to use your persistence entity model in UI layer, but rather define a DTO with a minimum set of required fields, and then map it to your entity either manually or using AutoMapper.
[HttpPost] [ValidateAntiForgeryToken]
public ActionResult Edit([Bind(Include="CompanyId,Address,EstbalishYear,Email,IsActive")] CompanyMaster companymaster)
{
if (companymaster.CompanyId == 0)
{
return View(companymaster);
}
CompanyMaster company = db.CompanyMasters.SingleOrDefault(x => x.CompanyId == companymaster.CompanyId);
company.Address = companymaster.Address;
company.EstbalishYear= companymaster.EstbalishYear;
company.Email = companymaster.Email;
company.IsActive= companymaster.IsActive;
company.UpdatedBy = 1;
company.UpdatedTime = DateTime.Now;
ModelState.Remove("Name");
if (ModelState.IsValid)
{
db.SaveChanges();
return RedirectToAction("Index");
}
return View(companymaster);
}

View/Model data isn't refreshing/changing after post/postback, even though I'm using the PRG pattern

Update I have saved my problem a long time ago. The problem was that I was trying to call the view model on the wrong view method! I was calling the base view method (Document), instead of one of it's derived method (like NewDocument, PDFDocument, etc.) Thus it was only giving me the Documents data, which didn't change. I was looking and using the wrong view method all the time... Stephen, when you asked me
"Why do you create derived classes in a method but then return only the base class"
I couldn't answer the question at the time because I didn't even know myself, until I remember that originally, the method wasn't returning the base class. I only changed it so that it can work with the base view method, which was wrong in the first place!
That's what I get for only getting 3-4 hours of sleep in 3 days. Everything works right now. Thanks.
I'm having a hard time trying to figure out why the data in my view isn't changing after I do a post. Originally I was doing it via return View() and it worked, but since it was a partial view, the page didn't look great, so I was reading up and saw that it was better to do it by Post-Redirect-Get pattern (PRG) and to use an id value to retrieve the values instead of sending the entire model via Tempdata. I even used ModelState.Clear() and that didn't even work. When I debugged the code, the model only has the values from when I first called it.
Here's part of my Get controller:
NewDocument Get Controller
[DocumentAuthenticationFilter]
public ActionResult NewDocument(int? id = null)
{
// This doesn't work. The view keeps on showing the data from View(Services.CreateNewDocument()).
if (id != null)
{
return View(Services.GetdocumentViewModelData(DocEnum.Section.NEW_DOC_INDEX, (int)id));
}
// This works fine
return View(Services.CreateNewDocument());
}
And here's the post that calls the redirect:
NewDocument Post controller
[HttpPost]
[ValidateAntiForgeryToken]
[MultipleButton(Name = "action", Argument = "AddDocuments")]
//[OutputCache(Duration = 30, VaryByParam = "*")]
public ActionResult AddDocumentViewModel(FormCollection frm, DocumentViewModel dvm)
{
try
{
if (ModelState.IsValid)
{
int? DocID = Services.AddingNewDocument(dvm);
// See, I even tried to clear it.
ModelState.Clear();
return base.RedirectToAction("NewDocument", new { id = DocID });
}
else
{
// Display errors in the modal
}
return base.RedirectToAction("NewDocument");
}
And here's the old way I did it:
NewDocument Post controller
[HttpPost]
[ValidateAntiForgeryToken]
[MultipleButton(Name = "action", Argument = "AddDocuments")]
//[OutputCache(Duration = 30, VaryByParam = "*")]
public ActionResult AddDocumentViewModel(FormCollection frm, DocumentViewModel dvm)
{
try
{
if (ModelState.IsValid)
{
Services.AddingNewDocument(ref dvm);
dvm.NewRecordMode = DocEnum.Action.UPDATE;
// It worked, but only the partial view showed, and not the entire view.
return PartialView("_NewDocument", dvm);
}
else
{
// Display errors in the model
}
return base.RedirectToAction("NewDocument");
}
Could it be because I'm using a custom model binding?
My Custom Model Binding
public class BaseClassModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var modelType = bindingContext.ModelType;
var modelTypeValue = controllerContext.Controller.ValueProvider.GetValue("ViewModel");
if (modelTypeValue == null)
throw new Exception("View does not contain the needed derived model type name");
var modelTypeName = modelTypeValue.AttemptedValue;
var type = modelType.Assembly.GetTypes().SingleOrDefault(x => x.IsSubclassOf(modelType) && x.Name == modelTypeName);
if (type == null)
{
throw new Exception(String.Format("Derived model type {0} not found", modelTypeName));
}
var instance = bindingContext.Model ?? base.CreateModel(controllerContext, bindingContext, type);
bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => instance, type);
return base.BindModel(controllerContext, bindingContext);
}
}
EDIT: And here's the GetDocumentViewModelData code:
GetDocumentFromViewModelData
public static DocumentViewModel GetDocumentViewModelData(DocEnum.Section docType, int id)
{
switch (docType)
{
case DocEnum.Section.NEW_DOCUMENT_INDEX:
// NewDocumentTypeViewModel is a child to DocumentTypeViewModel
DocumentTypeViewModel nd = NewDocumentService.GetViewModelByID(id);
return nd;
case DocEnum.Section.PDF_DOCUMENT:
DocumentTypeViewModel pdfvm = PDFDocumentService.GetViewModelByID(id);
return pdfvm;
case DocEnum.Section.XLS_DOCUMENT:
DocumentTypeViewModel xlsvm = XLSDocumentService.GetViewModelByID(id);
return xlsvm;
}
return null;
}
Edit: Also adding the GetViewModelByID function
GetViewModelByID
public static DocumentTypeViewModel GetViewModelByID(int id)
{
docEntities db = new docEntities();
NewDocumentTypeViewModel vm = new NewDocumentTypeViewModel();
// Calls a stored procedure called Select_Documents_ByID(id) to get the note entry
// that was submitted.
List<Select_Documents_ByID_Result> prevNotes = db.Select_Documents_ByID(id).ToList();
StringBuilder sNotes = new StringBuilder();
foreach (var note in prevNotes)
{
sNotes.AppendFormat("{0} - {1}: {2}\n\n", note.CreatedDate.ToString("yyyy-MM-dd HH:mm"), note.username, note.Entry);
}
vm.PreviousNotes = sNotes.ToString();
return vm;
}
Edit: I did a direct creation of the view model inside the Get controller, and it's the same result. when i debugged the view itself, the values from the new view model don't show up. Instead, the values from the initial view model, View(Services.CreateNewDocument()), shows.
[DocumentAuthenticationFilter]
public ActionResult NewDocument(int? id = null)
{
// Right here I created the view model to test thing, but I'm getting the same results. Nothing has changed.
if (id != null)
{
var d = new NewDocumentTypeViewModel(1, "Help!");
// This property is from the base class, DocumentTypeViewModel
d.DocumentTitle = "Testing!";
return View(d);
// Inside the view itself, none of the values in the view model, including the one
// belonging to the base class. It still shows the initial values.
}
// This works fine
// Or maybe not...
return View(Services.CreateNewDocument());
}
Edit: I wanted to see if it was also doing the same thing for the initial call to the view return View(Services.CreateNewDocument()), and decided to change the value for documentTitle in the base class from New Document to a randomly-generated number, after the object has been created.
Here's the code for DocumentTypeViewModel's default constructor:
public DocumentTypeViewModel()
{
DocumentTitle = "New Document";
NewRecordMode = DocEnum.Action.ADD;
DocumentID = 0;
}
And here's the Services.CreateNewDocument() code where I change the DocumentTitle after the View Model has been created.
public DocumentTypeViewModel CreateNewDocument()
{
DocumentTypeViewModel dtvm = new DocumentTypeViewModel();
Random r = new Random();
dtvm.DocumentTitle = r.Next(5, Int32.MaxValue).ToString();
return dtvm;
}
Now in the View, when I call DocumentTitle:
<div class="label-text-group">
#Html.LabelFor(model => model.DocumentTitle)
#Html.EditorFor(model => model.DocumentTitle)
</div>
You would expect to see a randomly-generated number every time the View gets called. Nope, what you would see is "New Document". Weird.
It's seems that Services.GetDocumentViewModelData() is not exactly working correctly. It only carries the values created by the base class' constructor when a view is created, not any values that have been added or changed within GetDocumentViewModelData() itself. Why is that? What's going on? Please help anybody!
I have solved it. Look at the Update section on top. Thanks Stephen.

Returning to a URL when in an MVC action

I Have the following code but it gives an error message:
var url = (string) Session["CurrentUrl"];
if (url != null)
{
var ip = new Uri(url);
var ipNoPort = string.Format("{0}://{1}/{2}", ip.Scheme, ip.Host, ip.PathAndQuery);
return RedirectResult(ipNoPort);
}
return View();
The error points to RedirectResult saying Error 1 'System.Web.Mvc.RedirectResult' is a 'type' but is used like a 'variable'
return new RedirectResult(ipNoPort);
I think you're confusing it with
return RedirectToRoute("Home");
Which is a member of the controller.

Validate model on initial request

I'm returning a model to my view on the initial load of a page, the model is populated from the DB, I want to validate the model so that when the user receives the page a validation summary show the errors if any.
I have tried using TryValidateModel(model) but this does not work, it does not update the ModelState, the reasion I assume is that it will only validate against what is populated from the ModelBinder
Is there anyway around this? I just want to validate the model first without the user having to post it back...
[Authorize, HttpGet, ActionName("StepOne")]
public ActionResult StepOneGET(StepOneModel model)
{
var individual = _onsideService.Get(User.Identity.Name);
model.PersonalInformation = new PersonalInformationModel
{
FirstName = individual.FirstName,
LastName = individual.LastName,
DoB = individual.DateOfBirth.ToString("dd/MM/yyyy"),
Email = individual.DefaultEmail.EmailAddress,
Phone = individual.DefaultPhone.Number,
AddressLine1 = location.Address1,
AddressLine2 = location.Address2,
City = location.City,
PostCode = location.PostalCode,
Country = location.Country
};
// NOTE: Does not update ModelState
TryValidateModel(model);
// Need to return potential errors to user on page load
return View(model);
}
Here is a snippet provided in another question here at SO. I don't take any credit for it, but it should do exactly what you want.
public static IList<KeyValuePair<string, string>> GetErrors(object obj)
{
// get the name of the buddy class for obj
MetadataTypeAttribute metadataAttrib = obj.GetType().GetCustomAttributes(typeof(MetadataTypeAttribute), true).FirstOrDefault() as MetadataTypeAttribute;
// if metadataAttrib is null, then obj doesn't have a buddy class, and in such a case, we'll work with the model class
Type buddyClassOrModelClass = metadataAttrib != null ? metadataAttrib.MetadataClassType : obj.GetType();
var buddyClassProperties = TypeDescriptor.GetProperties(buddyClassOrModelClass).Cast<PropertyDescriptor>();
var modelClassProperties = TypeDescriptor.GetProperties(obj.GetType()).Cast<PropertyDescriptor>();
var errors = from buddyProp in buddyClassProperties
join modelProp in modelClassProperties on buddyProp.Name equals modelProp.Name // as this is an inner join, it will return only the properties that are in both the buddy and model classes
from attribute in buddyProp.Attributes.OfType<ValidationAttribute>() // get only the attributes of type ValidationAttribute
where !attribute.IsValid(modelProp.GetValue(obj))
select new KeyValuePair<string, string>(buddyProp.Name, attribute.FormatErrorMessage(string.Empty));
return errors.ToList();
}

Resources