I have a model class that has a couple of required fields:
public class UserMetadata
{
[Required(ErrorMessage = "Please enter a name.")]
public string Name { get; set; }
[Required(ErrorMessage = "Please enter a password.")]
public string Password { get; set; }
}
On the create view, if I don't give a name and/or password, then the validation summary errors appears. All nice and good. For the edit view, I'm only displaying the 'Name' field - I don't to show the 'Password' field.
When I save my changes on the edit page, the validation summary error appears saying that I must enter a password.
How can I control the validation of the password field, so that for the edit view, it should not bother with it? Or, am I approaching this the wrong way? I still want the 'Name' field validation to work on the edit view.
EDIT:
For my MVC project, I'm using Entity Framework. Thus, I have a 'UserMetadata' class defined so that I can attached things like '[Required]' onto certain fields on the 'User' class (which is in the EDMX file).
I should also explain that I'm using a view model eg 'UserEditViewModel' which has a property 'User' attached to it. So on my post:
[HttpPost]
public ActionResult Edit(UserEditViewModel inputViewModel)
{
if(ModelState.IsValid) { inputViewModel.User blah.... }
}
Think I rushed a bit when typing this question. Any other missing information you think is important, then please give me a shout.
Cheers.
Jas.
I ended up doing this in my action method:
ModelState.Remove("User.Password");
Now my code runs fine, only raising validation errors on the "Name" field, which is what I wanted..
ModelState.Remove("User.Password") did not work for me in MVC 3.
However, the following worked.
Option 1:
ModelState.Remove("Password")
Option 2:
ModelState.Where(m => m.Key == "Password").FirstOrDefault().Value.Errors.Clear()
Assuming you're using your UserMetadata class as a view model, you should be using a different view model per page (view).
e.g.
public class UserMetaDataCreate
{
[Required(ErrorMessage = "Please enter a name.")]
public string Name { get; set; }
[Required(ErrorMessage = "Please enter a password.")]
public string Password { get; set; }
}
and UserMetaDataEdit
public class UserMetaDataEdit
{
[Required(ErrorMessage = "Please enter a name.")]
public string Name { get; set; }
}
Basically, if the edit view doesn't need password, it shouldn't be in the model anyway.
In your controller,
public ActionResult Create()
{
return View(new UserMetaDataCreate());
}
// and subsequent post actions
[HttpPost]
public ActionResult Edit(UserMetaDataEdit vm)
{
if(ModelState.IsValid)
{
// do something
}
else
return View(vm);
}
Of course, you could go about some inheritance as your models become more complex e.g.
public class UserMetaData
{
[Required(ErrorMessage = "Please enter a name.")]
public string Name { get; set; }
}
And subclass your view models
public class UserMetaDataEdit
{
[Required(ErrorMessage = "Please enter a password.")]
public string Password { get; set; }
}
public class UserMetaDataCreate
{
}
But, I'm not sure that makes sense contextually since UserMetaData does semantically include a password.
Related
Working in MVC3, c#
I am wondering about the 'hierarchy' of a model, class, db table and a partial class.
In the db I have a UserAccount table structured as:
USERACCOUNT: UserId, firstname, Lastname, Login, Password, email
In my project I have a model called UserModel. I am using it so i can decorate the properties with dataannotations. it is set up as the metadata type for the useraccount class, like so:
[MetadataType(typeof(UserModel))]
public partial class useraccount
{
}
public class UserModel
{
public int UserId { get; set; }
[Display(Name="First Name")]
[StringLength(20)]
[Required]
public string FirstName { get; set; }
[Display(Name = "Last Name")]
[StringLength(30)]
[Required]
public string LastName { get; set; }
[Display(Name = "Email Address")]
[StringLength(20)]
[Required]
public string email { get; set; }
[Remote("IsUserNameAvailable", "Validation")]
[Display(Name = "Choose a Login Name")]
[StringLength(40)]
[Required]
public string login { get; set; }
[Display(Name = "Choose a Password")]
[StringLength(64)]
[Required]
public string password { get; set; }
[Display(Name = "Enter Password Again")]
[StringLength(64)]
[Required]
public string confirmPassword { get; set; }
}
Notice in the database table there is a 'Password' column, but no 'confirmPassword' however in the UserModel, there is.
My thinking is the UserAccount class, using the UserModel class for metadata, should now contain a definition for 'confirmPassword'.
Here is the problem I am having. On the Register view, i am using the UserModel as the model, so at the top of the page I have:
#model OurAgreements.Models.UserModel
the problem comes in the controller when I try to save. This code:
public ActionResult Register(UserModel model)
{
var repo = new Repository();
if (ModelState.IsValid)
{
using (var db = new ouragreementEntities())
{
db.useraccount.Add(model);
db.SaveChanges();
}
return View();
}
gives the error, cant convert UserModel to useraccount. I can understand this, I am trying to put data into a table that doesn't match all the columns.
So then I figured I would change the model on the view to:
#model OurAgreements.Models.useraccount
because per my thinking, the useraccount class should be using the UserModel, but doing that gives an error, 'useraccount has no definition for 'confirmPassword'
So I am a bit stuck. i know I can switch back to using the UserModel as the model, then inthe controller I can build a new useraccount instance, and fill it with the data from the model, then save the instance to the database.
So I guess my question then, is why does useraccount not contain a definition for confirmPassword?
First things first. Change the name of your classes. Use UserAccountMetadata / UserAccount. This makes things a lot easier to read and understand.
Also, use the compare attribute on the confirm password field so that you can be sure they match. The datatype attribute should be present on both password fields.
[DataType(DataType.Password)]
[Display(Name = "Confirm Password")]
[Compare("Password", ErrorMessage = "Password and confirm password must be same!")]
public string confirmPassword { get; set; }
The model your view uses should be UserAccount.
Before you pass your view model (UserAccount) to the method that saves it in the database, you need to map it to the object that the method takes, which looks like is your domain model object. You can do that manually but that is cumbersome. Use a mapping tool, such as AutoMapper to do that automatically for you. You can tell AutoMapper which fields in the UserAccount map to which fields in you domain model.
You were on the right path before by creating a View Model that contains your DataAnnotations and additional properties that are not in your database. This ViewModel and its related DataAnnotations help you to perform your business validation logic as well as allow you to map one or more tables worth of data to a single class for your view.
So, as to what I think your overall question is, why is there not a confirm password in the database? Why should there be? Having two copies of the same data in the database does not make sense in the realm of storing data.
The confirm password is a business logic / user input check to ensure that they did not 'fat finger' the password and lock themselves out of their account. Once you have confirmed that the password they entered is the one that they intended (password == confirmPassword), you hash the password and place it into the database.
Public class UserMetdata
{
[Required]
[Display(Name="User ID")]
public int UserID { get; set; }
[Display(Name="User Name")]
public string UserName { get; set; }
}
I dont want to UserName to be shown in View. Its similar like creating not required Annotation. One solution is by deleting UserName form Class but i dont want that.
How can it be done using Data Annotation.
You could use ScaffoldColumnAttribute for that property
[ScaffoldColumn(false)]
public string UserName { get; set; }
This will work only when you let framework dynamically generate your views by calling #Html.DisplayForModel() or like, and you DO NOT have defined display template for that model at Views/Shared/DisplayTemplates or Views/ControllerName/DisplayTemplates. Otherwise, you should edit that display template and remove corresponding line from it
I have a Model which contains an Address and Person twice, once for the "main" contact, and once for the "invoice" contact, and a boolean value called InvoiceContactSameAsMain - a clumsy name, but descriptive. The getter of the property checks to see if the Address and Contact objects for "main" and "invoice" are the same, and returns true if they are. The setter checks to see if the value is true, and if so, copies the main Person over the invoice Person , and the main Address over the invoice Address.
In my View, the boolean value is represented by a check box (as you'd expect). Attached to this is a small JS function which, if the check box is checked, hides the invoice fields and "switches off" the client-side validation by setting the data-val HTML attribute to false and forcing a re-parse of the unobtrusive validation attributes in the form. Un-checking the box naturally shows the fields and turns the validation back on.
All of this works fine, until I get to my Controller.
Despite the Model being "valid" and containing the correct fields (thanks to my InvoiceContactSameAsMain setter), ModelState.IsValid remains resolutely false, and I can't seem to find any way to revalidate the model. If I clear the ModelState, any and all errors disappear. I'd very much rather avoid digging through the fields in the ModelState by name, as the Person and Address objects are used throughout the project and may need to change or be extended at some point.
Is there something obvious I've missed here that will allow me to revalidate the ModelState? I've tried TryUpdateModel and TryValidateModel, but they both appear to use the cached ModelState values. I've even tried recursively calling my Action again, passing in the "fixed" model. I'm almost thankful that one didn't work.
Please let me know if any more detail or examples will help.
Edit: Obviously, if this is completely the wrong way to approach the problem, just let me know.
Edit 2: Added code samples as per Ron Sijm's suggestion.
The model is as follows:
public class Details
{
public int? UserID { get; set; }
public Company Company { get; set; }
public Address CompanyAddress { get; set; }
public Person MainPerson { get; set; }
public Address InvoiceAddress { get; set; }
public Person InvoiceContact { get; set; }
[Display(Name = "Promotional code")]
[StringLength(20, ErrorMessage = "Promotional code should not exceed 20 characters")]
public string PromotionalCode { get; set; }
[Display(Name = "Invoice contact same as main")]
public bool InvoiceContactSameasMain
{
get { return InvoiceContact.Equals(MainPerson); }
set
{
if (value)
{
InvoiceContact = MainPerson.Copy();
InvoiceAddress = CompanyAddress.Copy();
}
}
}
[_Common.MustAccept]
[Display(Name = "I agree with the Privacy Policy")]
public bool PrivacyFlag { get; set; }
[Display(Name = "Please subscribe to Sodexo News Letter")]
public bool MarketingOption { get; set; }
[Display(Name = "Contract number")]
public int? ContractNumber { get; set; }
public Details()
{
Company = new Company();
CompanyAddress = new Address();
MainPerson = new Person();
InvoiceAddress = new Address();
InvoiceContact = new Person();
}
}
This is wrapped in a ViewModel as there are a number of SelectLists involved in the page:
public class DetailsViewModel
{
public Details Details { get; set; }
public SelectList MainContactTitles { get; set; }
public SelectList InvoiceContactTitles { get; set; }
public SelectList SICCodes { get; set; }
public SelectList TypesOfBusiness { get; set; }
public SelectList NumbersOfEmployees { get; set; }
public DetailsViewModel()
{
}
}
The Controller's two relevant actions are as follows:
public class DetailsController : _ClientController
{
[Authorize]
public ActionResult Index()
{
DetailsViewModel viewModel = new DetailsViewModel();
if (Client == null)
{
viewModel.Details = DetailsFunctions.GetClient((int)UserId, null);
}
else
{
viewModel.Details = DetailsFunctions.GetClient((int)UserId, Client.ContractNumber);
}
viewModel.MainContactTitles = DetailsFunctions.GetTitles((int)UserId, viewModel.Details.MainPerson.title);
viewModel.InvoiceContactTitles = DetailsFunctions.GetTitles((int)UserId, viewModel.Details.InvoiceContact.title);
viewModel.SICCodes = DetailsFunctions.GetSICCodes(viewModel.Details.Company.sic_code);
viewModel.NumbersOfEmployees = DetailsFunctions.GetNumbersOfEmployees(viewModel.Details.Company.number_of_employees);
viewModel.TypesOfBusiness = DetailsFunctions.GetTypesOfBusiness(viewModel.Details.Company.public_private);
return View(viewModel);
}
[Authorize]
[HttpPost]
public ActionResult Index(DetailsViewModel ViewModel)
{
if (ModelState.IsValid)
{
//go to main page for now
DetailsFunctions.SetClient((int)UserId, ViewModel.Details);
return RedirectToAction("Index", "Home");
}
else
{
ViewModel.MainContactTitles = DetailsFunctions.GetTitles((int)UserId, ViewModel.Details.MainPerson.title);
ViewModel.InvoiceContactTitles = DetailsFunctions.GetTitles((int)UserId, ViewModel.Details.InvoiceContact.title);
ViewModel.SICCodes = DetailsFunctions.GetSICCodes(ViewModel.Details.Company.sic_code);
ViewModel.NumbersOfEmployees = DetailsFunctions.GetNumbersOfEmployees(ViewModel.Details.Company.number_of_employees);
ViewModel.TypesOfBusiness = DetailsFunctions.GetTypesOfBusiness(ViewModel.Details.Company.public_private);
return View(ViewModel);
}
}
}
I can provide the view and JS if needs be, but as the Model binding is all working just fine, I'm not sure how much help that is.
It's a moderately crap hack, but I've ended up just clearing the ModelState errors for the relevant fields in the controller before checking ModelState.IsValid:
if(ViewModel.Details.InvoiceContactSameasMain)
{
//iterate all ModelState values, grabbing the keys we want to clear errors from
foreach (string Key in ModelState.Keys)
{
if (Key.StartsWith("Details.InvoiceContact") || Key.Startwith("Details.InvoiceAddress"))
{
ModelState[Key].Errors.Clear();
}
}
}
The only upside is, if the Person or Address objects change, this code won't need to be altered.
I have been playing around with MVC 3 and looking at populating dropdownlists. I have seen a few examples online that recommend using view models, so here is my first attempt. My code seems to work, but can anybody tell me if this is the correct way to do this?
My model :
public class ContactGP
{
public int TeamID { get; set; }
[Required(ErrorMessage = "Please select a Team Name")]
[DataType(DataType.Text)]
[DisplayName("Team Name")]
public string TeamName { get; set; }
}
My view model :
public class ContactGPViewModel
{
public string SelectedTeamID { get; set; }
public IEnumerable<Team> Teams { get; set; }
}
My controller :
public IEnumerable<Team> PopulateTeamsDropDownList()
{
IEnumerable<Team> lstTeams = _Base.DataRepository.GetTeams();
return lstTeams;
}
public ActionResult ContactGP()
{
var model = new ContactGPViewModel
{
Teams = PopulateTeamsDropDownList()
};
return View(model);
}
And my view :
<p>
#Html.DropDownListFor(
x => x.SelectedTeamID,
new SelectList(Model.Teams, "TeamID", "TeamName")
)
</p>
Your code seems correct. You have defined a view model containing the necessary properties your view will require, filled it up in the controller and passed to this strongly typed view.
I have only a minor remark on the following line inside the PopulateTeamsDropDownList method:
_Base.DataRepository.GetTeams();
I hope you have abstracted this repository with interfaces (or abstract classes) and used DI in order to inject some concrete implementation into your controller. This will weaken the coupling between your controller and the way data is accessed and to simplify unit testing the different layers of your application in isolation.
For example, I'd like to validate a user registration form and check whether user has entered his password in "password" and "confirm password" fields, AND that those two values are the same.
Found this but is reflection really the only way?
You can try this way:
[System.ComponentModel.DataAnnotations.CustomValidation(typeof(Test), "Verify", ErrorMessage = "No match!")]
public class Test
{
[Required]
public string Password { get; set; }
[Required]
public string ConfirmPassword { get; set; }
public static ValidationResult Verify(Test t)
{
if (t.Password == t.ConfirmPassword)
return ValidationResult.Success;
else
return new ValidationResult("");
}
}