MVC 4 I have the option to put two email addresses at list one is compulsory
and a dropdownlist preferred email work/Alternative
on submit I want to validate the selected value of the dropdownlistfor and by the selected value to make the Work email address or Alternative email address field required
How do I do that ? is it possible to do that with a custom validation?
Model:
public string WorkEmail { get; set; }
public string AlternativeEmail { get; set; }
public List<SelectListItem> PreferredEmails { get; set; }
Here is solution
public class WorkModel
{
[CorrectMail]
public string WorkEmail { get; set; }
[CorrectMail]
public string AlternativeEmail { get; set; }
public string PreferredEmail { get; set; }
}
public class CorrectMail : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var model = validationContext.ObjectInstance as WorkModel;
if (model == null) return new ValidationResult("Model is empty");
if (model.PreferredEmail == "WorkEmail" && string.IsNullOrEmpty(model.WorkEmail))
{
return new ValidationResult("You have selected work email but did not fill it");
}
if (model.PreferredEmail == "AlternativeEmail" && string.IsNullOrEmpty(model.AlternativeEmail))
{
return new ValidationResult("You have selected alternative email but did not fill it");
}
return ValidationResult.Success;
}
}
Yes, it is possible to do a custom validation routine. My preferred way of doing it is by Validating in the Service Layer
Related
I want create model that will validate required field in model that depend on other field condition.
public class FixedDeposit
{
public int DebitAmount { get; set; }
public string PAN { get; set; }
}
Now if the DebitAmount is greater than 50,000 then PAN field is must be required.
You can implement IValidatableObject
public class FixedDeposit : IValidatableObject
{
public int DebitAmount { get; set; }
public string PAN { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (DebitAmount > 50000 && string.IsNullOrEmpty(PAN))
{
yield return new ValidationResult("PAN required for debits > 50,000.", new [] { "PAN" } );
}
}
}
http://weblogs.asp.net/scottgu/class-level-model-validation-with-ef-code-first-and-asp-net-mvc-3
You can also use MVC Foolproof validation package. This package provides you with many conditional validations in the form of annotations.
Complete list is here:
http://foolproof.codeplex.com/
You can add this library to your VS project as a package:
And, for your FixedPayment class, it should look something like this:
using Foolproof;
public class FixedDeposit
{
public int DebitAmount { get; set; }
[RequiredIf("DebitAmount", Operator.GreaterThan, 50000)]
public string PAN { get; set; }
}
Alternate code
using Foolproof;
public class FixedDeposit
{
public int DebitAmount { get; set; }
private bool _panRequired { get { return DebitAmount > 50000; } }
[RequiredIf("_panRequired", true, ErrorMessage="PAN is required if Debit Amount is greater than 50000")]
public string PAN { get; set; }
}
There are two options which you can use.
The first is the very easy to use and quite concise ExpressiveAnnotations JS library developed by Jaroslaw Waliszko. Follow this link to https://github.com/jwaliszko/ExpressiveAnnotations for more information. This library allows you to perform different conditional validations.
Similarly to Foolproof it is added to your Visual Studio environment through adding the NuGet package. Once added, within your model add the using statement using ExpressiveAnnotations.Attributes; Then simply use the RequiredIf declaration to do what you need. For example:
public class FixedDeposit
{
public int DebitAmount { get; set; }
[RequiredIf("DebitAmount >= 50000")]
public string PAN { get; set; }
}
The second option is to use ModelState.AddModelError(). This is done within your controller. Simply create a new method:
private void ValidateRequiredFields(modelname)
{
if(modelname.DebitAmount >= 50000)
{
if(modelname.PAN == null)
{
ModelState.AddModelError("PAN", "Place whatever error message you want here");
}
}
}
Next you place a reference to your validation method in whichever view method you want this to be called. The line to reference is ValidateRequiredFields(ModelName);
public class RequiredIfAttribute : RequiredAttribute
{
private String PropertyName { get; set; }
private Object DesiredValue { get; set; }
public RequiredIfAttribute(String propertyName, Object desiredvalue)
{
PropertyName = propertyName;
DesiredValue = desiredvalue;
}
protected override ValidationResult IsValid(object value, ValidationContext context)
{
Object instance = context.ObjectInstance;
Type type = instance.GetType();
Object proprtyvalue = type.GetProperty(PropertyName).GetValue(instance, null);
if (proprtyvalue.ToString() == DesiredValue.ToString())
{
ValidationResult result = base.IsValid(value, context);
return result;
}
return ValidationResult.Success;
}
}
Usage
[RequiredIf("DebitAmount",50000, ErrorMessage = "PAN field is required")]
public string PAN
{get;set;
}
I have the following ViewModel:
public class MyViewModel:IValidatableObject
{
public int Id { get; set; }
public string Name { get; set; }
public DateTime? Birthday { get; set; }
public int Status { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (string.IsNullOrWhiteSpace(Name))
yield return new ValidationResult("Please fill the name", new string[] { "Name" });
if (Birthday.HasValue == false)
yield return new ValidationResult("Please fill the birthday", new string[] { "Birthday" });
if(Status <= 0)
yield return new ValidationResult("Please fill the status", new string[] { "Status" });
}
}
Controller:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "Id,Name,Birthday,Status")] MyViewModel myViewModel)
{
if (ModelState.IsValid)
{
db.MyViewModels.Add(myViewModel);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(myViewModel);
}
I would like to display all the validation messages at the same time, however it shows first status and then the other two properties.
This is due to the order in which validation happens. First the ModelBinder does it's job, and if that passes, since you've created a self validating viewmodel by implementing IValidatableObject, the Validate method is called. In the first screenshot the modelbinding process is failing so Validate is never called. In the second screenshot, modelbinding succeeds, but Validate() fails.
You can solve this by using DataAnnotations instead of implementing IValidatableObject like so:
public class MyViewModel:IValidatableObject
{
public int Id { get; set; }
[Required]
public string Name { get; set; }
[Required]
public DateTime Birthday { get; set; }
[Required, Range(0, Int32.MaxValue)]
public int Status { get; set; }
}
I have the following ASP.NET MVC filter attribute:
public void OnActionExecuting(ActionExecutingContext context) {
ControllerBase controller = context.Controller;
}
And on the view I have a form with
#Html.TextBox("Captha");
And my model is:
public class SignUpModel {
public String Email { get; set; }
public String Password { get; set; }
public String Captcha { get; set; }
}
How can I, in my filter attribute, do the following:
Get value inserted in the text box;
Add an error to model state if there is no value or a specific condition is false?
Do I need the captcha property in my model?
Thank You,
Miguel
You don't need an ActionFilter to do this. Use CompareAttribute in your model to validate the Captcha property. Add another property to your model, and call it SessionValue, then use the CompareAttribute to compare the value entered for the Captcha property with the SessionValue property:
public class SignUpModel {
public string Email { get; set; }
public string Password { get; set; }
[Compare("SessionValue")]
public string Captcha { get; set; }
public string SessionValue { get; set; }
}
Then, in your Controller action set the value of the SessionValue property to the value stored in the Session:
var model = new SignUpModel();
model.SessionValue = Session["MyValue"];
return View(model);
And, in your View, you'll have:
#Html.HiddenFor(model => model.SessionValue)
#Html.TextBoxFor(model => model.Captcha)
#Html.ValidationMessageFor(model => model.Captcha)
UPDATE:
If you don't want to have SessionValue as a hidden input in your View, you can create a custom validation attribute like this:
using System.ComponentModel.DataAnnotations;
using System.Web;
public class MyCustomValidationAttribute : ValidationAttribute
{
public override bool IsValid(object value)
{
if (value == null)
return true;
string compareValue = HttpContext.Current.Session["MyValue"];
return (string)value.Equals(compareValue);
}
}
And, use it in your Model like this:
public class SignUpModel {
public string Email { get; set; }
public string Password { get; set; }
[Required]
[MyCustomValidation]
public string Captcha { get; set; }
}
I've got a ViewModel for adding a user with properties: Email, Password, ConfirmPassword with Required attribute on all properties. When editing a user I want the Password and ConfirmPassword properties not to be required.
Is there a way to disable validation for certain properties in different controller actions, or is it just best to create a seperate EditViewModel?
I like to break it down and make a base model with all the common data and inhierit for each view:
class UserBaseModel
{
int ID { get; set; }
[Required]
string Name { get; set; }
[Required]
string Email { get; set; }
// etc...
}
class UserNewModel : UserBaseModel
{
[Required]
string Password { get; set; }
[Required]
string ConfirmPassword { get; set; }
}
class UserEditModel : UserBaseModel
{
string Password { get; set; }
string ConfirmPassword { get; set; }
}
Interested to know if there is a better way as well although this way seems very clean an flexible.
You could write a custom attribute that can test a condition and either allow an empty field or not allow it.
The below is a simple demo i put together for the guys here. You'll need to modify to suit your purposes/
using System.ComponentModel.DataAnnotations;
namespace CustomAttributes
{
[System.AttributeUsage(System.AttributeTargets.Property)]
public class MinimumLength : ValidationAttribute
{
public int Length { get; set; }
public MinimumLength()
{
}
public override bool IsValid(object obj)
{
string value = (string)obj;
if (string.IsNullOrEmpty(value)) return false;
if (value.Length < this.Length)
return false;
else
return true;
}
}
}
Model;
using CustomAttributes;
namespace Models
{
public class Application
{
[MinimumLength(Length=20)]
public string name { get; set; }
}
}
Controller
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Index(Application b)
{
ViewData["Message"] = "Welcome to ASP.NET MVC!";
if (ModelState.IsValid)
{
return RedirectToAction("MyOtherAction");
}
return View(b);
}
enter code here
I've got a form with a dropdownlist in my MVC app. Now that I'm trying to add validation to the mix it seems that a dropdownlist fails validation no matter what it's value is.
Without the validation it will allow the controller to work and redirect as planned. With the validation it does seem to allow the database changes to occur but ModelState.IsValid is false.
I'm stuck. Is this a known issue?
View:
<label for="parent">Child of:</label>
<%= Html.DropDownList("parent", (SelectList)ViewData["pageList"])%>
<%= Html.ValidationMessage("parent") %>
Controller action:
[AcceptVerbs(HttpVerbs.Post)]
[ValidateInput(false)]
[ValidateAntiForgeryToken()]
public ActionResult Create(Page page)
{
try
{
pageRepository.Insert(page);
}
catch (RuleException ex)
{
ex.CopyToModelState(ModelState);
}
if (!ModelState.IsValid)
{
var pageSelectList = pageRepository.GetTop().ToList();
pageSelectList.Add(new Page
{
menuTitle = "None"
});
ViewData["pageList"] = new SelectList(pageSelectList.OrderBy(x => x.listOrder), "ID", "menuTitle");
return View();
}
return RedirectToAction("List");
}
The error returned is: The value 'x' is invalid.
Where 'x' is the numeric value of the current selection. The failure occurs no matter what the chosen value is.
public class Page
{
private EntityRef<Page> _parent = default(EntityRef<Page>);
private EntitySet<Page> _children = new EntitySet<Page>();
public int ID { get; set; }
public string pageTitle { get; set; }
public string menuTitle { get; set; }
public string content { get; set; }
public int listOrder { get; set; }
public bool visible { get; set; }
public int parent { get; set; }
public DateTime? created { get; set; }
public DateTime? edited { get; set; }
public string createdBy { get; set; }
public string lastEditBy { get; set; }
public string linkInfo { get; set; }
public bool IsSelected { get; set; }
public Page Parent
{
// return the current entity
get { return this._parent.Entity; }
set { this._parent.Entity = value; }
}
public EntitySet<Page> Children
{
get { return this._children; }
set { this._children.Assign(value); }
}
public static Page Error404()
{
return (new Page
{
content = "<p>Page not found</p>",
pageTitle = "404. Page not found"
});
}
}
Here's what I tried for a workaround:
public ActionResult Create([Bind(Exclude="parent")] Page page)
{
page.parent = Convert.ToInt32(Request.Form["parent"]);
...
I just excluded the dropdownlist from the ModelBinding and reloaded it back in via the Request.Form. Is it good practice?
What's throwing the RuleException? I'm assuming you're using some sort of validation engine to determine whether the "parent" property is valid or not. I'd step through to see why this exception is being thrown. Maybe the value isn't passing into your controller action correctly or maybe your validation rules are different than what you think they are.
I ended up testing against ModelState["parent"].Value.AttemptedValue instead of the entity property which was nulling out at the attempt to put a string into an int?.