MVC Validation with Entity Framework - asp.net-mvc

I'm having trouble getting validation to work on my MVC page when using Entity Framework. If someone could point out what I'm doing wrong I would appreciate it. It is definitely seeing the meta data because the labels are working. However, when I hit submit, it just continues on to the next page. Could it have something to do with the fact that I have an instance of a class inside the view model?
Meta Data Class
[MetadataType(typeof(CompanyMetaData))]
public partial class Company
{
}
[MetadataType(typeof(CompanyUserMetaData))]
public partial class CompanyUser
{
}
public class CompanyMetaData
{
[Required(ErrorMessage = "Company Name is required")]
public string Name { get; set; }
[Required(ErrorMessage = "Service Center is required")]
public string ServiceCenterCode { get; set; }
[Required(ErrorMessage = "Account Number is required")]
public string AccountNumber { get; set; }
[Required(ErrorMessage = "Edition is required")]
public string Edition { get; set; }
}
public class CompanyUserMetaData
{
[Required]
[RegularExpression(#"^\w+#[a-zA-Z_]+?\.[a-zA-Z]{2,3}$", ErrorMessage = "Invalid Email Address")]
public string EmailAddress { get; set; }
[Required(ErrorMessage = "Password is required")]
public string Password { get; set; }
[Required(ErrorMessage = "First Name is required")]
public string FirstName { get; set; }
[DisplayName("Last Name")]
[Required(ErrorMessage = "Last Name is required")]
public string LastName { get; set; }
}
View Model
public class CreateCompanyViewModel : ILayoutAwareViewModel
{
public List<AdvisorServiceCenterVW> ServiceCenters { get; set; }
public LayoutViewModel LayoutViewModel { get; set; }
public Company newCompany { get; set; }
public CompanyUser newUser { get; set; }
public List<FuneralHome> newFuneralHomes { get; set; }
}
Markup Sample
<div class="form-group">
<label>#Html.LabelFor(d=>d.newUser.LastName)</label>
<div class="controls">
#Html.TextBoxFor(d => d.newUser.LastName, new { #class = "form-control" })
#Html.ValidationMessageFor(d => d.newUser.LastName)
</div>
</div>
Controller
public ActionResult CreateCompanyLocations(CreateCompanyViewModel incomingModel)
{
var model = (CreateCompanyViewModel)TempData["model"];
LayoutViewModel lvm = _layoutHelper.GetLayoutViewModelData("Configure New Company");
model.LayoutViewModel = lvm;
model.newCompany = incomingModel.newCompany;
model.newUser = incomingModel.newUser;
var fhs = _siteService.GetCustomerLocations(model.newCompany.AccountNumber);
model.newFuneralHomes = new List<FuneralHome>();
foreach (var fh in fhs)
{
model.newFuneralHomes.Add(new FuneralHome()
{
Address = fh.Address,
Name = fh.CustomerName,
City = fh.City,
AccountNumber = fh.AccountNumber,
ServiceCenterCode = fh.ServiceCenterCode,
State = fh.State,
ZipCode = fh.ZipCode,
Phone = fh.Phone,
ContactName = fh.ContactName
});
}
TempData["model"] = model;
return View(model);
}

You need to check ModelState.IsValid in your controller code and branch accordingly. Currently your controller is just processing the model whether it is valid or not. The typical pattern looks something like this:
if(ModelState.IsValid)
{
// Do stuff for when model is valid
}
else
{
// return the view with the invalid model to give the user
// a chance to fix it
return View(model);
}

It wound up having nothing to do with the above answer. I was missing the jquery validation and jquery unobtrusive scripts on my layout page so that is what was causing the validation not to fire. You do NOT need to do anything in the controller for this to work correctly.

Related

Passing textbox data between forms in ASP MVC causes validation at page load

I have a form that sends data to another form. Like page 1 would have your zip code and you hit next. That would direct you to page 2 displaying the zip in a textbox along with other input fields like address, city and state.
The problem is when the data is passed it triggers my mvc validation when the user lands on the page. So Address and City are already highlighted red. What steps can I use to fix this issue?
***The validation was fixed no data is passed, data is hitting the view inputs but no data is sent
page 1, sends data to form SellYourHouse
#model myModel.Models.AsideModel
#using (#Html.BeginForm("quoteModel1", "Home", FormMethod.Post, new { name = "frmSell1", id = "frmSell1" }))
{
#Html.AntiForgeryToken()
#Html.TextBoxFor(m => m.firstName, "", new { #class = "name-sm", #placeholder = "First Name" })<br />
#Html.TextBoxFor(m => m.lastName, "", new { #class = "name-sm", #placeholder = "Last Name" })<br/>
Other inputs not shown for less content........
<input type="submit" value="Submit Offer" class="btn2" /> </div>
}
SellYourHouse, receives data from action quoteModel1
#model myModel.Models.quoteModel
#using (#Html.BeginForm("SellYourHouse", "Home", FormMethod.Post, new { name = "frmSell", id = "frmSell" }))
{
#Html.AntiForgeryToken()
#Html.TextBoxFor(m => m.address, "", new { #class = "name-sm", #placeholder = "Address" })<br /><br />
#Html.TextBoxFor(m => m.city, "", new { #class = "name-sm", #placeholder = "City" })<br />
}
model that send the data
public class AsideModel
{
public string firstName { get; set; }
public string lastName { get; set; }
public string address { get; set; }
public string DdlState { get; set; }
public string city { get; set; }
public int zip { get; set; }
}
model that receives the data
public class quoteModel
{
[Required(ErrorMessage ="Required")]
public string firstName { get; set; }
[Required(ErrorMessage = "Required")]
public string lastName { get; set; }
[Display(Name = "Phone")]
[Required(ErrorMessage = " Required")]
[DataType(DataType.PhoneNumber)]
[RegularExpression(#"^\(?([0-9]{3})\)?[-. ]?([0-9]{3})[-. ]?([0-9]{4})$", ErrorMessage = " Invalid phone number")]
public string phone { get; set; }
[Required(ErrorMessage = " Required")]
[EmailAddress(ErrorMessage = " Valid email required")]
public string email { get; set; }
[Required(ErrorMessage = " Required")]
public string address { get; set; }
[Required(ErrorMessage = " Required")]
public string city { get; set; }
[Required(ErrorMessage = " ")]
public string DdlState { get; set; }
[Required(ErrorMessage = " ")]
[RegularExpression(#"^\d{5}(-\d{4})?$", ErrorMessage = " *")]
public string zip { get; set; }
[Required(ErrorMessage = " ")]
public string bedrooms { get; set; }
public string additional { get; set; }
}
}
controller that sends the data
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult quoteModel1(AsideModel model)
{
return RedirectToAction("SellYourHouse", new {FirstName = model.firstName, LastName = model.lastName, Address = model.address, DDLState = model.DdlState, Zip = model.zip});
}
controller that receives the data
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> SellYourHouse(quoteModel model)
{
if (ModelState.IsValid)
{
//Email code with the rest of the forms
}
}
Split up your model and controller actions.
Split view models
public class QuoteModelStep1
{
[Required(ErrorMessage = " ")]
[RegularExpression(#"^\d{5}(-\d{4})?$", ErrorMessage = " *")]
public string Zip { get; set; }
}
public class QuoteModelStep2
{
[Required(ErrorMessage = " Required")]
public string Address { get; set; }
[Required(ErrorMessage = " Required")]
public string City { get; set; }
public string Zip { get; set; }
}
Split controller actions
public ActionResult Step1()
{
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Step1(QuoteModelStep1 model)
{
if (!ModelState.IsValid)
{
return View(model);
}
return RedirectToAction("Step2", new {zip = model.Zip});
}
public ActionResult Step2(string zip)
{
var model = new QuoteModelStep2 {Zip = zip};
return View(model);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Step2(QuoteModelStep2 model)
{
if (!ModelState.IsValid)
{
return View(model);
}
return RedirectToAction("Index");
}
For the Step2 GET method, the zip will be passed as a query string, but you don't have to do that. you could use TempData or Session to store it so that you don't see it on the url.
I has visual studio generate my Step1 and Step2 views from the Add view wizard passing in the correct view model.
Stepped through the code in the debugger and I'm seeing the zip get passed from Step1 to Step2 and the form data from Step2 getting POST'ed correctly.

database.SaveChanges() throws EntityValidationException

New to MVC. When I try to add a user to the database using Entity Framework Database First I get this exception:
An exception of type 'System.Data.Entity.Validation.DbEntityValidationException' occurred in EntityFramework.dll but was not handled in user code
Additional information: Validation failed for one or more entities. See 'EntityValidationErrors' property for more details.
This is the code:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Index(RegisterViewModel account)
{
if (ModelState.IsValid)
{
using (db)
{
bool duplicate = db.Users.Any(a => a.UserName == account.UserName);
if (duplicate)
{
ModelState.AddModelError("", "Username already exists in database!");
}
else
{
db.Users.Add(new StoreFront.Models.User { UserName = account.UserName, Password = account.Password, EmailAddress = account.EmailAddress, IsAdmin = false, DateCreated = DateTime.Now });
db.SaveChanges();
ModelState.Clear();
ModelState.AddModelError("RegisterSuccess", "Successfully registered!");
}
}
}
return View();
}
I have validation in my RegisterViewModel for all fields, and when I debug, IsValid = true, otherwise it wouldn't run anyway. Any help would be greatly appreciated...I have been struggling with this for a while.
P.S. Yes the password is currently being stored as a string, this is just a test project that won't be used in the real world.
EDIT: Added Models:
User Model from database:
public partial class User
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public User()
{
this.Addresses = new HashSet<Address>();
this.Orders = new HashSet<Order>();
this.ShoppingCarts = new HashSet<ShoppingCart>();
}
public int UserID { get; set; }
public string UserName { get; set; }
public string Password { get; set; }
public string EmailAddress { get; set; }
public Nullable<bool> IsAdmin { get; set; }
public Nullable<System.DateTime> DateCreated { get; set; }
public string CreatedBy { get; set; }
public Nullable<System.DateTime> DateModified { get; set; }
public string ModifiedBy { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<Address> Addresses { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<Order> Orders { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<ShoppingCart> ShoppingCarts { get; set; }
}
Partial Model to add ConfirmPassword:
namespace StoreFront.Models
{
[MetadataType(typeof(RegisterViewModel))]
public partial class User
{
[DisplayName("Confirm Password")]
[DataType(DataType.Password)]
[Compare("Password", ErrorMessage = "Passwords must match")]
public string ConfirmPassword { get; set; }
}
}
RegisterViewModel:
public class RegisterViewModel
{
public int UserID { get; set; }
[DisplayName("Username")]
[Required(ErrorMessage = "Username is required")]
public string UserName { get; set; }
[DisplayName("Password")]
[DataType(DataType.Password)]
[Required(ErrorMessage = "Password is required")]
public string Password { get; set; }
[DisplayName("Confirm Password")]
[DataType(DataType.Password)]
[Compare("Password", ErrorMessage = "Passwords must match")]
public string ConfirmPassword { get; set; }
[DisplayName("Email")]
[Required(ErrorMessage = "Email is required")]
[RegularExpression(#"^([a-zA-Z0-9_\-\.]+)#([a-zA-Z0-9_\-\.]+)\.([a-zA-Z]{2,5})$",
ErrorMessage = "Please enter a valid email")]
public string EmailAddress { get; set; }
public Nullable<bool> IsAdmin { get; set; }
public Nullable<System.DateTime> DateCreated { get; set; }
}
Fix: When I looked up a tutorial about MVC it asked em to create a partial class and a meta class. That was for code first I believe, which basically made it a new field that my database didn't have a spot for, and I am using database first. So I removed the deleted the partial class for User and it stopped making ConfirmPassword an actual field in the database.
Don't know the real works of it, or if what I said makes sense, but I hope this helps someone eventually.
Remove
[RegularExpression(#"^([a-zA-Z0-9_\-\.]+)#([a-zA-Z0-9_\-\.]+)\.([a-zA-Z]{2,5})$",
ErrorMessage = "Please enter a valid email")]
from RegisterViewModel

How to reuse class in models without using validation for all of them

I have generic model for contact
public class Contact
{
public string Title { get; set; }
public string FirstName { get; set; }
[Required(ErrorMessage = "Please enter LastName")]
public string LastName { get; set; }
[Required(ErrorMessage = "Please enter Email")]
public string Email { get; set; }
public string Phone { get; set; }
}
Now I want to use my contact class in two models but apply the validation only on second?
public class Step1Model{
public Contact Contact{get;set;}
}
public class Step2Model{
[Requried]
public Contact Contact{get;set;}
}
How do I make it work?
I see two options here:
1 - Code to an interface which will require you to create a ContactRequired class and a ContactOptional class based upon the ContactInterface. I believe this will allow you to then have a single StepModel where you would set the StepModel.Contact property to either a new ContactRequired() or a new ContactOption(). Then when the validaiton runs for the StepModel, it will be have based upon the type of class you set for the StepModel.Contact property.
public interface ContactInterface
{
string Title { get; set; }
string FirstName { get; set; }
string LastName { get; set; }
string Email { get; set; }
string Phone { get; set; }
}
public class ContactOptional : ContactInterface
{
public string Title { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public string Phone { get; set; }
}
public class ContactRequired : ContactInterface
{
public string Title { get; set; }
public string FirstName { get; set; }
[Required(ErrorMessage = "Please enter LastName")]
public string LastName { get; set; }
[Required(ErrorMessage = "Please enter Email")]
public string Email { get; set; }
public string Phone { get; set; }
}
public class StepModel
{
public ContactInterface Contact { get; set; }
}
Usage:
StepModel smTest = new StepModel();
ContactRequired crContact = new ContactRequired();
ContactOptional coContact = new ContactOptional();
List<ValidationResult> lErrors = new List<ValidationResult>();
smTest.Contact = coContact;
//Validate Option
if (Validator.TryValidateObject(smTest, new ValidationContext(smTest, serviceProvider: null, items: null), lErrors, true))
{
//Code should reach this as the model should be valid;
}
smTest.Contact = crContact;
//Validate Required
if (Validator.TryValidateObject(smTest, new ValidationContext(smTest, serviceProvider: null, items: null), lErrors, true))
{
//Code should not reach this as the model should be invalid;
}
2 - Create a custom required attribute which will look at another property of the Contact model (such as bool UseValidation) to determine if the required validation should even take place or if it should simply return true as the default. I am not initially providing code for this option as you would need a custom attribute for every type of validation attribute in your class. Also, I think option 1 is the better one unless you have a specific reason against it.
I have decided not to have many view models.
Here is my implementation
https://gist.github.com/cpoDesign/bc9c5980a89cfe7b0caf

how to get ALL errors from viewdata.modelstate

I am trying to do a bit of a custom error handler. We have 4 tabs (using JQuery tabs), they are all build from one large model. Say for simplicity the model looks like:
myModel.HomeInfo
myModel.PhoneNumbers
myModel.Addresses
myModel.PersonalDetails
Each part is an object that have various bits of information. They all have attributes on them and validate messages.
At the top of the page (above the tabs) I want to display some top level errors, by that I mean the errors for attributes on the "myModel" object. This works when I do the:
foreach (ModelState state in viewData.ModelState.Values)
When I do:
#Html.ValidationSummary(false)
on my view I get all errors from each of the four objects and all their children, (more than 10). But when I go through the errors my self, (code above), I only get 2 errors, (the errors for "myModel" only, not its child properties).
I tried to use ILSPY to see what the validation summary is doing and replicate it. Believe I had the code pretty much line for line, but it still only got the two errors.
I do not know what magic is going on when I use the #Html.ValidationSummary().
What I want to know is how I can get all the errors for the whole object my self to be able to display some of the errors on each tab.
for clarification here is my basic model:
public class MemberProfileModel
{
[CompanyTabValid]
public CompanyInformationModel CompanyInformation { get; set; }
[ContactTabValid]
public ContactInformationModel ContactInformation { get; set; }
[InvoiceTabValid]
public InvoiceInformationModel InvoiceInformation { get; set; }
[TabProductIdentificationMarkValid]
public ProductIdentificationMarkModel ProductIdentificationMark { get; set; }
}
public class CompanyTabValid : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext context)
{
var model = value as CompanyInformationModel;
if(model == null) throw new ArgumentNullException("value");
var failed = new ValidationResult("Company information incomplete.");
return model.IsValid ? ValidationResult.Success : failed;
}
}
public class ContactInformationModel : BaseModel
{
public ContactInformationModel()
{
PrimarySiteAddress = new AddressInformation();
PrimarySiteContact = new ContactInformation();
RegisteredOfficeAddress = new AddressInformation();
RegisteredOfficeContact = new ContactInformation();
}
public override void Validate()
{
IsValid = PrimarySiteAddress.IsValid &&
PrimarySiteContact.IsValid &&
RegisteredOfficeAddress.IsValid &&
RegisteredOfficeContact.IsValid;
}
public AddressInformation PrimarySiteAddress { get; set; }
public ContactInformation PrimarySiteContact { get; set; }
public AddressInformation RegisteredOfficeAddress { get; set; }
public ContactInformation RegisteredOfficeContact { get; set; }
}
public class AddressInformation : BaseModel
{
public int Id { get; set; }
public Guid MemberId { get; set; }
/// <summary>
/// This property is only here to make EF happy, do not use
/// </summary>
public int LocationTypeValue { get; set; }
public LocationType LocationType { get { return (LocationType) LocationTypeValue; } set { LocationTypeValue = (int) value; } }
[Required(AllowEmptyStrings = false, ErrorMessage = "Address Line 1 required.")]
[Display(Name = "Address Line 1 *")]
public string AddressLine1 { get; set; }
[Display(Name = "Address Line 2")]
public string AddressLine2 { get; set; }
[Display(Name = "Address Line 3")]
public string AddressLine3 { get; set; }
[Required(AllowEmptyStrings = false, ErrorMessage = "Town required.")]
[Display(Name = "Town *")]
public string Town { get; set; }
[Required(AllowEmptyStrings = false, ErrorMessage = "County required.")]
[Display(Name = "County *")]
public string County { get; set; }
[Display(Name = "Country *")]
public string Country { get; set; }
[RequiredOneOfTwo("InterationalPostCode", ErrorMessage="PostCode or international PostCode are required.")]
[Display(Name = "Post Code *")]
public string PostCode { get; set; }
[RequiredOneOfTwo("PostCode", ErrorMessage = "International PostCode or PostCode are required.")]
[Display(Name = "International Post Code *")]
public string InterationalPostCode { get; set; }
public override void Validate()
{
if (string.IsNullOrEmpty(AddressLine1))
{
this.IsValid = false;
return;
}
else if (string.IsNullOrEmpty(Town))
{
this.IsValid = false;
return;
}
else if (string.IsNullOrEmpty(County))
{
this.IsValid = false;
return;
}
else if (string.IsNullOrEmpty(Country))
{
this.IsValid = false;
return;
}
else if (string.IsNullOrEmpty(PostCode) && string.IsNullOrEmpty(InterationalPostCode))
{
this.IsValid = false;
return;
}
this.IsValid = true;
return;
}
}
I have shown an example of a validation attribute (some of ours are custom, some are normal), the top level MemberProfileModel = myModel in this example, and ContactInformationModel is one of its children which in turn has its own objects such as AddressInformation.
Thanks
I found out why this wasn't working for me. As usual it was me being silly. Because the model has multiple layers / levels to it, I.e. model.someobject.someotherobject.someproperty, when I called tryValidateModel it would validate the top level but not the inner layers.
The solution to this was to ensure they are all called:
TryValidateModel(mp);
TryValidateModel(mp.ContactInformation.PrimarySiteAddress);
TryValidateModel(mp.ContactInformation.RegisteredOfficeAddress);
So my solution is to either create a method to call try validate on each object level or create a refelctive method to do it for me.
In the post event of your page, in the controller just add this:
[HttpPost]
public ActionResult Create(TestViewModel testViewModel)
{
// If not Valid
if (!ModelState.IsValid)
{
return this.View(testViewModel)
}
...
}
And don't forget to add your required and/or other validation to your viewmodel access methods:
[Required(ErrorMessageResourceType = typeof(Resources.MyProject), ErrorMessageResourceName = "validation_Test")]
public virtual string HomeInfo { get; set; }
And in your view:
<div class="editor-row">
<div class="editor-label">
#Html.LabelFor(model => model.HomeInfo)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.HomeInfo)
#Html.ValidationMessageFor(model => model.HomeInfo)
</div>
</div>
I got this from here:
http://www.unknownerror.org/opensource/aspnet/Mvc/q/stackoverflow/1352948/how-to-get-all-errors-from-asp-net-mvc-modelstate
public static List<string> GetErrorListFromModelState
(ModelStateDictionary modelState)
{
var query = from state in modelState.Values
from error in state.Errors
select error.ErrorMessage;
var errorList = query.ToList();
return errorList;
}

MVC Remote Validation accessing field from class above

In the below code I cannot pass the username to the remote validation function:
public string UserName { get; set; }
public class Numbers
{
[Display(Name = "Additonal Numbers")]
[Remote("NumberExists", "Account", AdditionalFields = "UserName", ErrorMessage = "Serial is already taken.")]
public string additionalNumbers { get; set; }
}
public List<Numbers> NumberList { get; set; }
This is a simple example but I would like to pass additional fields from the same model within a list but I cant seem to access anything outside the scope of the public class.
Do I need to pass the rest of the model into the list in some way to achieve this or am I doing something wrong here?
The AdditionalFields parameter in the remote validation attribute need to be in the same class as the object being validated.
..edit..
public class Numbers
{
public string UserName { get; set; }
[Display(Name = "Additonal Numbers")]
[Remote("NumberExists", "Account", AdditionalFields = "UserName", ErrorMessage = "Serial is already taken.")]
public string additionalNumbers { get; set; }
}
..edit after comments..
It looks like what you want to do is validate that all the numbers are unique for a Username. Try this:
public string UserName { get; set; }
[Display(Name = "Additonal Numbers")]
[Remote("NumberExists", "Account", AdditionalFields = "UserName", ErrorMessage = "Serial is already taken.")]
public List<String> NumberList { get; set; }
In your NumberExists Action take a List of Strings rather than only 1 string. This will let you validate your whole array all at once.
Public ActionResult NumberExists(List<String> NumberList, String UserName){
//Validate list is unique for username
}
UserName Property should be in the same class of the additionalNumbers property:
public class NumbersViewModel
{
public string UserName { get; set; }
[Display(Name = "Additonal Numbers")]
[Remote("NumberExists", "Account", AdditionalFields = "UserName", ErrorMessage = "Serial is already taken.")]
public string additionalNumbers { get; set; }
public List<Numbers> NumberList { get; set; }
}

Resources