i going to create some validation for custom object in my app. But i have some trouble when try to create CustomValidation rule. My object has field - BirthDay - which not required but if user enter it i need to validate it on simple validation, for example user DataType validation - DataType.DateTime. When i am try to do it i have validation error - BirthDay is required. When i create custom validation and always return true i have same error. Below some lines of code:
[MetadataType(typeof(User.Metadata))]
public class User
{
#region Metadata
private class Metadata
{
[Required(ErrorMessage="Name is required")]
[StringLength(5, ErrorMessage="Max Length is 5")]
public string Name { get; set; }
[CustomValidation(typeof(User), "ValidateBirthDay", ErrorMessage="We have trouble.")]
public DateTime BirthDay { get; set; }
}
#endregion
public static bool ValidateBirthDay(object value)
{
return true;
}
public int? ID { get; set; }
public string Name { get; set; }
public DateTime BirthDay { get; set; }
}
p.s. sorry for my English =)
You need to make your propery nullable, ie
public DateTime? BirthDay { get; set; }
so it can have a null value and not required to be set.
Also the way you use the CustomValidation attribute doesn't seem right. I believe you need to create a class that derives from ValidationAttribute base class and pass its type in CustomValidation attribute's first param.
Related
I would like to "turn off" the Required field validation on a certain property of the parent model (VehicleManufacturer), when saving a child model (VehicleModel), i.e.:
public class VehicleManufacturer
{
public virtual Guid Id { get; set; }
[Required]
[StringLength(50, MinimumLength = 1)]
public virtual string Name { get; set; }
}
public class VehicleModel
{
public virtual Guid Id { get; set; }
[Required]
[StringLength(50, MinimumLength = 1)]
public virtual string Name { get; set; }
public virtual VehicleManufacturer Manufacturer { get; set; }
}
So, when I'm saving a new model, all I care about is it's Name and ManufacturerID, which would be selected from a drop-down list, however, because the ManufacturerName is marked [Required] in its entity, it invalidates my ModelState when saving a new VehicleModel, because ManufacturerName is null :(
I would like to know what is the best approach to this and how to do it.
I can think of a few solutions but none seem to be the right way:
Set a "default" ManufacturerName value before ModelState check, i.e.
"-" just to satisfy the DataAnnotation
Populate both ManufacturerName
and ManufacturerId in my VehicleModelView - not good if your parent
model has a bunch of required field you don't really need to use in a
child model
Turn off [Required] validation for child model (not sure
how?)
what do you think?
The simplest way is to have hidden fields for the required properties you don't want to show.
A possible solution is to add the foreign key column to the VehicleManufacturer (VehicleManufacturerId) to the VehicleModel and use that column in your view.
The IValidatableObject interface is for custom model validation.
For example:
public class VehicleModel : IValidatableObject
{
public virtual Guid Id { get; set; }
[StringLength(50, MinimumLength = 1)]
public virtual string Name { get; set; }
public virtual VehicleManufacturer Manufacturer { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if(string.IsNullOrWhiteSpace(Name))
{
yield return new ValidationResult("Name is required")
}
}
}
Then in your controller call ModelState.IsValid
What I ended up doing is subclassing the parent model with something like ParentWithExtendedValidation : Parent
In ParentWithExtendedValidation all of the various [Required] Fields were tagged.
When I wanted to specifically edit the parent, I used type ParentWithExtendedValidation
-- since it is a subclass of Parent, once it passes model validation, you can cast it back to parent and pass it to your DAL with no issues.
When you are using it in a relationship e.g editing a child that references this, you can use the regular Parent class.
Hope this helps
EG I have a class Person -- with all of my normal virtual properties, and only the ID is required (to make validation that selects on person->ID still work right)
And a Class PersonEV
public class PersonWithExtendedValidation : Person
{
[Required]
public override string FullName { get; set; }
[Required]
public override string FirstName { get; set; }
[Required]
public override string LastName { get; set; }
[Required]
public override string DomainName { get; set; }
[Required]
public override string WorkPhone { get; set; }
[Required]
public override string CellPhone { get; set; }
[Required]
public override string Email { get; set; }
}
Then in your View/Controller work with e.g PersonEV for the model and argument.
Once you check for ModelState.IsValid , cast back to (Person) and pass to whatever repository or etc as normal
Ok. I looked at this and realized my method was wasteful.
How does this look?
I created an Annotation called ExtendedValidationRequired.
It has a static value that turns on conditional checking for extended values or not.
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false)]
public class ExtendedValidationRequiredAttribute : RequiredAttribute
{
// Summary:
// Initializes a new instance of the System.ComponentModel.DataAnnotations.RequiredAttribute
// class.
public ExtendedValidationRequiredAttribute()
{
}
// Summary:
// Checks that the value of the required data field is not empty.
//
// Parameters:
// value:
// The data field value to validate.
//
// Returns:
// true if validation is successful; otherwise, false.
//
// Exceptions:
// System.ComponentModel.DataAnnotations.ValidationException:
// The data field value was null.
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (ExtendedValidation.IsExtendedValidationEnabled(validationContext.ObjectType))
{
return base.IsValid(value,validationContext);
}
else
{
return ValidationResult.Success;
}
}
}
I then mark up my Sometimes required (eg when im directly editing that class) fields with [ExtendedValidationRequired(typeof(MyClassName))]
this works for other types of validators as well.
I actually went ahead and created a set of 'Sometimes On' validators and moved my setting to a separate class:
public static class ExtendedValidation
{
private static Dictionary extendedValidationExemptions = new Dictionary();
/// <summary>
/// Disable extended validation for a specific type
/// </summary>
/// <param name="type"></param>
public static void DisableExtendedValidation(Type type)
{
extendedValidationExemptions[type] = true;
}
/// <summary>
/// Clear any EV exemptions
/// </summary>
public static void Reset()
{
extendedValidationExemptions.Clear();
}
/// <summary>
/// Check if a class should perform extended validation
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
public static bool IsExtendedValidationEnabled(Type type)
{
if (extendedValidationExemptions.ContainsKey(type))
{
return false;
}
else
{
return true;
}
}
}
}
now i just turn ExtendedValidation off when editing children.
E.G: When editing a Child, Can DisableExtendedValidation(typeof(Parent)) and not get in the way.
Edit: Hrm, I realize this doesnt quite work. -- Can I determine what class I am looking at inside of a validationProperty? I guess I could always pass the parent property in but that is a PITA
I have a class, which has 8 properties / 8 columns in DB. In the Edit page, I want to exclude the AddedDate and UserID fields. When a user edits a voucher, he can't overwrite the AddedDate or UserID values in the DB.
public class Voucher
{
public int ID { get; set; }
public string Title { get; set; }
public string SiteName { get; set; }
public string DealURL { get; set; }
public DateTime AddedDate { get; set; }
public DateTime? ExpirationDate { get; set; }
public string VoucherFileURL { get; set; }
public Guid UserID { get; set; }
}
Here is what I have for Edit controller:
// POST: /Voucher/Edit/5
[HttpPost]
public ActionResult Edit([Bind(Exclude = "AddedDate")]Voucher voucher)
{
if (ModelState.IsValid)
{
db.Entry(voucher).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(voucher);
}
On Edit page, when I click on submit, I got the following error:
System.Data.SqlServerCe.SqlCeException: An overflow occurred while converting to datetime.
Seems like the AddedDate didn't get excluded from the voucher object and triggered the error.
Would you please let me know how to fix it? Thanks!
(it is an updated version of asp.net mvc3 UpdateModel exclude properties is not working, I will go with another approach)
Never use your domain entities as action arguments and never pass your domain entities to your views. I would recommend you to use view models. In the view model you will include only the properties that you want to be bound from the view. The view model is a class that's specifically tailored to the requirements of a given view.
public class VoucherViewModel
{
public int ID { get; set; }
public string Title { get; set; }
public string SiteName { get; set; }
public string DealURL { get; set; }
public DateTime? ExpirationDate { get; set; }
public string VoucherFileURL { get; set; }
}
and then:
[HttpPost]
public ActionResult Edit(VoucherViewModel model)
{
// TODO: if the view model is valid map it to a model
// and pass the model to your DAL
// To ease the mapping between your models and view models
// you could use a tool such as AutoMapper: http://automapper.org/
...
}
UPDATE:
In the comments section #Rick.Anderson-at-Microsoft.com points out that while I have answered your question I haven't explained where the problem comes from.
The thing is that DateTime is a value type meaning it will always have a value. The [Bind(Exclude = "AddedDate")] works perfectly fine and it does what it is supposed to do => it doesn't bind the AddedDate property from the request. As a consequence the property will have its default value which for a DateTime field is 1/1/0001 12:00:00 AM and when he attempts to save this in SQL Server it blows because SQL Server doesn't support such format.
I have a UserFormModel which contains a UserModel which has a set of properties with the [Required] attribute set. I have read that MVC 3 out of the box will validate models within models by default. However when I submit an empty form in my view passing back a UserFormModel containing an empty UserModel the ModelState.IsValid is always true.
I have tried sending just the UserModel back to my controller and that validates ok. It just seem to be when I am working with complex models that it does not validate.
I have also tried it with the [Required] attribute on the User property within the UserFormModel (which I believe is not required for default behaviour to work) but still no validation takes place.
Any ideas on this one would be much appreciated.
public class UserFormModel
{
public UserModel User;
public IEnumerable<SelectListItem> Roles { get; set; }
}
public class UserModel : ModelBase
{
[Required]
public string UserName { get; set; }
public string Title { get; set; }
[Required]
public string FirstName { get; set; }
[Required]
public string LastName { get; set; }
}
[HttpPost]
public ActionResult Create(UserFormModel userFormModel)
{
if (ModelState.IsValid)
{
// Do Something
}
}
You should use properties not fields. So instead of:
public UserModel User;
you should have:
public UserModel User { get; set; }
The reason for this is that the default model binder works only with properties.
I believe that validation only goes one model property deep in the model. For example if you have the following model
public class Product
{
public int ProductId { get; set; }
[Required]
public int ProductName { get; set; }
[Required]
public decimal Price { get; set; }
}
public class ProductViewModel
{
[Required]
public Product Product { get; set; }
}
The validation against the product object in the view model will work, the validation against the product class will not with one caveat. If the Product class is a POCO class used in the entity framework code first method, the validation will work against the database. Validation against a view model will only work one deep in my experience.
I want to input html in the database and also display it back as html. I wrote my view model like this:
public class TemplateVM
{
[HiddenInput(DisplayValue = false)]
public int TemplateId { get; set; }
public string Name { get; set; }
public string Content { get; set; }
}
the property Content should be able to accept html. How can I do this? Right now, it throws the error of:
A potentially dangerous Request.Form value was detected from the client (Content="<p>test</p>").
I'm aware of using this on the action, but I dont want it to apply to every property.:
[ValidateInput(false)]
Instead of using ValidateInput attribute on entire model, I suggest you use AllowHtml attribute on Content property:
public class TemplateVM
{
[HiddenInput(DisplayValue = false)]
public int TemplateId { get; set; }
public string Name { get; set; }
[AllowHtml]
public string Content { get; set; }
}
This attribute is only applied for Content property, while other properties are still validated.
Put [ValidateInput(false)] on top of TemplateVM. It will apply to all properties.
I have an object like this
public class ParentEntityInfo
{
public long? ParentId { get; set; }
public string EntityName { get; set; }
public string ParentProperty { get; set; }
}
and view for this object is:
<%=Html.Hidden("parentInfo.ParentId", parentInfo.ParentId)%>
<%=Html.Hidden("parentInfo.ParentProperty", parentInfo.ParentProperty)%>
<%=Html.Hidden("parentInfo.EntityName", parentInfo.EntityName)%>
I have the case where parentInfo is null and I post this form to controller. On the controller action
public ActionResult SomeAction(..., ParentEntityInfo parentInfo)
I receive constructed object parentInfo but all properties are null. In this case I would rather prefer to have whole parentInfo to be null. I there any possibility to tell default model binder do not pass such object? Or probably I can modify something in this code to make it work this way. I think in mvc 2.0 it worked this way.
Use the HiddenFor(...) helper instead.
I think the default model binder will always use Activator.CreateInstance to bind complex action parameters. What you can do is use ModelState.IsValid to assess whether the parameter was bound successfully. I think in your case this will be false by default, but if not you could add the necessary attribute to ensure this behaviour e.g.
public class ParentEntityInfo
{
[Required(ErrorMessage = "Parent required")]
public long? ParentId { get; set; }
public string EntityName { get; set; }
public string ParentProperty { get; set; }
}