is there a way to use conditional validation on MVC3 model?
for example:
public class User
{
[Required]
public string Password { get; set; }
[Required, Compare("Password")]
public string ComparePassword { get; set; }
}
Where i would like Password and confirmpassword fields to be required ONLY when adding a new user. However when editing I would like these to be empty (text boxes in the view). Only when user does type in a new Password and ConfirmPassword will the password be changed in the DB.
Thanks
Typically you would have separate AddUserViewModel and EditUserViewModel classes with the appropriate fields and validators. Then in your controller action if the model is valid, you convert to your view models to your User entity and pass it on to your business logic / service to save. You can use Automapper for this.
Related
I'm using entity framework and MVC (both CORE if that matters) to make a site. Using the model and tying it directly to the view is fine all the CRUD actions work, everything is lovely.
I wanted to use a couple of pages to access the model so the site looked better, so split the controls out onto separate views and added a corresponding viewmodel for each, so my project looks like this
-Model
--CustomerModel
-ViewModel
--CustomerNameVM
--CustomerAddressVM
-View
--CustomerNameView
--CustomerAddressView
The CustomerModel has a number of properties
Forename
Surname
Address
Postcode
with Forename and Surname in the CustomerNameVM and Address and Postcode in CustomerAddressVM. Surname is defined as [Required] in the model but not in CustomerNameVM which I believe is the correct way to do it.
I'm struggling to get the model loaded into the viewmodel and then trying to save it when I'm editing the address details in CustomerAddressView because it errors when I try and save as the viewmodel doesn't contain Surname (from the model), so it's null and therefore the [Required] criteria isn't being met.
I've tried a few methods of trying to get past this like :-
Jeffrey Palermo's Onion Architecture
Repositories
domain models
amongst others which all end up with the same problem, I can't save the Address as the Surname is null.
How do I ignore validation criteria for the properties of the model that aren't being referenced in the viewmodel?
Or how do I load and reference only those properties of the model that are present in viewmodel?
Edit
For those who keep asking for code, which codeset? I've tried 30 of them now, none of which do the job. Which one of these do you want? I'm trying to get a general idea of how this is supposed to work as none of the methods, documentation and associated examples function.
Here's a starter for 10, it's unlike the other 29 codesets but it's code and probably the shortest.
The controller
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Step2Address(int? id, [Bind("CustomerID,txtAddress,txtPostcode")] VMAddress VMAddress) {
if (ModelState.IsValid) {
//the saving code
}
return View(VMAddress);
}
the model
public class clsCustomer {
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int CustomerID { get; set; }
public string Forename { get; set; }
[Required]
public string Surname { get; set; }
public string Address { get; set; }
public string Postcode { get; set; }
the viewmodel
public class VMAddress {
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int CustomerID { get; set; }
public string Address { get; set; }
public string Postcode { get; set; }
}
the view
#model theProject.Models.VMStep2Contact
<form asp-action="Step2Address">
<input type="hidden" asp-for="ComplaintID" />
<input asp-for="txtAddress"/>
<input asp-for="txtPostcode"/>
<input type="submit" value="Save" />
the context
public class ContextCustomer : DbContext {
public ContextCustomer(DbContextOptions<ContextCustomer> options) : base(options) {
}
public DbSet<clsCustomer> Customer{ get; set; }
}
Clicking "Save" on the webpage calls the controller straight away, which hits the first line if (ModelState.IsValid) and as the Surname isn't set and is [Required] the ModelState is not valid so no save is attempted.
I don't actually understand what the problem is, and without code, it's impossible to say what you might be doing wrong. Generally speaking, you shouldn't have any issues since you're using view models.
A view model is, of course, not saved directly to the database, so it has to be mapped over to an actual entity class that you will be saving. In the case of an edit, you should retrieve all relevant entities from the database, map any posted values onto the appropriate properties on those entities, and then save those entities back to the database. If you're doing this, presumably, the customer model should already contain the Surname and other required properties, and you'd only be modifying/setting the address properties.
If you're doing a create, then, simply you can't just take address information. You need the name as well, so for this, you'd need to pass a view model that contains at least all required fields, such that you have all the information you need to actually save the entity.
If you're trying to do a multi-step form, where you collect all the information over multiple posts, then you simply must persist the posted data somewhere other than the database until you have enough of it to actually save an entity to the database. This is usually accomplished via Session/TempData.
I’ve been implementing IValidatableObject on Model entities, and using Validate(ValidationContext) to perform validation, often complex.
Can I use ValidationContext to distinguish different validation scenerios?
e.g. take for example a User model where I have 3 validation scenerios:
Sign up – I want to test an email is unique, and a small selection of required fields have been entered
Change details – Different email uniqueness check, bit more required details after sign up, not changing password here so it doesn't need checked
Change password – Only password field to validate
Is this a proper use for it, and if so how do I ensure the correct ValidationContext properties are set after a post and before Validate() is called? Or should I be taking a totally different approach?
The IValidatableObject is used to perform multiple validations against a single model. In your case you have a User model and you want to do three validations and you can do that perfectly by implementing the IValidatableObject in the User model.
The ValidationContext is not bringing much benefit (other than providing access to the context) since we can access all the properties directly in the Validate method.
An example of performing multiple validations related to the single model by IValidatableObject. (So what is the use of ValidationContext here?)
public class Party : IValidatableObject
{
[Required(ErrorMessage = "Start date is required")]
[FutureDateValidator(ErrorMessage = "Start date should be a future date")]
public DateTime StartDate { get; set; }
[Required(ErrorMessage = "Duration is required")]
public int DurationInHours { get; set; }
[Required(ErrorMessage = "No. of joinees is required")]
[Range(2, 10, ErrorMessage = "No. of joinees should be minimum 2 and not more than 10")]
public int NoOfJoinees { get; set; }
public bool Drinks { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (StartDate.TimeOfDay > new TimeSpan(22 - DurationInHours, 0, 0))
{
yield return new ValidationResult("The party should not exceed after 10.00 PM");
}
if (NoOfJoinees < 5 && Drinks)
{
yield return new ValidationResult("Drinks are only allowed if no. of joinees is 5 or more.");
}
}
}
For my two cents worth I would say that your model is either in a valid state (applying all validation criteria) or it isn't. If, under certain circumstances, you don't want to apply a validation then I think you should really be using a separate model (ViewModel, actually).
In your example, I would create a RegisterViewModel for sign up and a separate EditUserViewModel for changing details. Each of these would then have their own validation and they would have a single responsibility.
Creating a fat model that you reuse in many different views is, imho, a bit of a code smell. I have a number of reasons for thinking this. Firstly, let's say that you have a single model that is used for all interaction with user data. It looks like this:
public class UserModel
{
public int UserId { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public bool IsAdministrator { get; set; }
}
Later you decide to track the browser that was used during registration with the site. Where do you add that? It really has nothing to do with the user, so it shouldn't go on the UserModel model. If you had a separate RegisterViewModel you could modify it as needed when your registration process changes without concern as to how it would affect the other places it is used.
A more serious problem arises if, for example, you were using the above model with MVC's DefaultModelBinder. As described here, the user could create their own request and grant themselves administrator privileges even if you don't have the IsAdministrator field on the form (by exploiting a mass-assignment vulnerability). Again, if a separate ViewModel was used without the IsAdministrator property it would reduce the surface area for security holes.
The above is just an example, but I'm sure you get the point.
In building an app, we created a generic object model to store some values, the viewmodel looks a bit like this at the moment:
public class FooViewModel {
public int ID { get; set; }
public byte FooType { get; set; }
[Required]
[Display(Name = "Bar Name")]
public string Name { get; set; }
[Required]
public string Email { get; set; }
//etc, etc
}
The problem is: depending on the FooType, we want to have the Display Name to be different and the Email is not required for type 1 and 2, but is required for type 3 and 4.
We tried seperating out the properties that differ per type in to classes that inherit from this one, but the validation does a fallback on what is specified in the base type, so that didn't work.
Currently, the only option seems to be to create a viewmodel for each FooType (and also seperate controllers and view), which leads to a lot of code duplication.
What are other ways to keep this DRY?
To benefit a validation context (e.g. validating objects in different contexts), I strongly recommend using FluentValidation library.
You could implement a custom RequiredIf validation attribute, or you could implement IValidatableObject.
I am trying to implement a foreign key connection between the built-in User model and my models in ASP.NET MVC 3.
How to assign ownership or some other roles to various entries represented with my models. Example of how my models look like:
public class Entry
{
public int Id { get; set; }
public string Value { get; set; }
public User Owner { get; set; }
public User SomeoneElse { get; set; }
}
Where to find the model for users, what do I need to import? Or is there a better approach to accomplish this?
Do you use Entity Framework ?? If so...
Simple solution
You could simply keep the Guid from the Built-In User model. You won't have a "real relationship" but it will do the trick for what you want to do. You can always get the UserId with Membership.GetUser().ProviderUserKey
Other more complex
Completely rewrite and override the MembershipProvider and login module. That way you can use your own User object and add other properties to it aswell.
Not Sure about this one
Not sure if this one will work with the auto generated tables from the MembershipProvider but you can add the Foreign Key Property this way:
[ForeignKey("User")]
public Guid UserId { get; set; }
Is there an attribute I can decorate a single property on my model to tell the engine not to include the property in the validation routine?
[DoNotValidate] or [ValidateIgnore]
----------------------More info.
Ok, I need to give you more information. In my situation, I have a temporary decimal value on my model that is not persisted, that gets formatted into currency. $540,000.
In this one case I do not want to strip the formatting out before I call TryUpdateModel. When you use TryupdateModel, it mvc will try and convert that string text box value back into a decimal and Model.IsValid will return false. I know how to get around this situation, using javascript, but it would be easier if I could tell mvc not to validate that field.
Any model properties not decorated with validation attributes should be ignored.
public class MyModel
{
[Required]
public string SomeProperty { get; set; }
public string IgnoredProperty { get; set; }
}
Should validate that SomeProperty is required, but nothing will happen with IgnoredProperty.
The best tutorial IMHO on Model validation is http://weblogs.asp.net/scottgu/archive/2010/01/15/asp-net-mvc-2-model-validation.aspx
(even though it says for MVC 2, it's applicable).
Change the type of your decimal to nullable decimal to prevent required validation:
public class MyModel
{
public decimal MyValidatingDecimal { get; set; }
public decimal? MyNonValidatingDecimal { get; set; }
}
MyValidatingDecimal will be required (since it is a value-type), while MyNonValidatingDecimal will not be required.
Properties will only be validated if you explicitly apply validation attributes to them.