3

Iam trying to validate textbox based on checkbox value. please view my model class and IsValid override method.

public class Product
{
    //Below property value(HaveExperiance)
    [MustBeProductEntered(HaveExperiance)] 
    public string ProductName { get; set; }

    public bool HaveExperiance { get; set; }
}

public class MustBeTrueAttribute : ValidationAttribute
{
    //Here i need the value of HaveExperiance property which 
    //i passed from  [MustBeProductEntered(HaveExperiance)]  in product class above.
    public override bool IsValid(object value)
    {
        return value is bool && (bool)value;
    }
}

You can see above in ProductName property in product class where iam trying to pass the HaveExperiance class property value, If it checked then user must have to fill the ProductName textbox.

So my orignal question is that how can i validate the ProductName textbox based on the HaveExperiance value, thanks in advance.

EDIT:

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Data.Entity;
using System.Linq;
using System.Reflection;
using System.Web;
using System.Web.Mvc;

namespace Mvc.Affiliates.Models
{
    public class MyProducts
    {
        [Key]
        [Required(ErrorMessage = "Please insert product id.")]
        public string ProductId { get; set; }

        [RequiredIf("HaveExperiance")]
        public string ProductName { get; set; }

        public bool HaveExperiance { get; set; }
        public List<MyProducts> prolist { get; set; }
    }

    public class RequiredIfAttribute : ValidationAttribute
    {
        private RequiredAttribute _innerAttribute = new RequiredAttribute();

        public string Property { get; set; }

        public object Value { get; set; }

        public RequiredIfAttribute(string typeProperty)
        {
            Property = typeProperty;
        }

        public RequiredIfAttribute(string typeProperty, object value)
        {
            Property = typeProperty;
            Value = value;
        }

        public override bool IsValid(object value)
        {
            return _innerAttribute.IsValid(value);
        }
    }

    public class RequiredIfValidator : DataAnnotationsModelValidator<RequiredIfAttribute>
    {
        public RequiredIfValidator(ModelMetadata metadata, ControllerContext context, RequiredIfAttribute attribute) : base(metadata, context, attribute) { }

        public override IEnumerable<ModelClientValidationRule> GetClientValidationRules()
        {
            return base.GetClientValidationRules();
        }

        public override IEnumerable<ModelValidationResult> Validate(object container)
        {
            PropertyInfo field = Metadata.ContainerType.GetProperty(Attribute.Property);

            if (field != null)
            {
                var value = field.GetValue(container, null);

                if ((value != null && Attribute.Value == null) || (value != null && value.Equals(Attribute.Value)))
                {
                    if (!Attribute.IsValid(Metadata.Model))
                    {
                        yield return new ModelValidationResult { Message = ErrorMessage };
                    }
                }
            }
        }
    }

Controller

public class HomeController : Controller
    {
        //
        // GET: /Home/

        MvcDbContext _db = new MvcDbContext();
        public ActionResult Index()
        {
          return View();
        }

        [HttpPost]
        public ActionResult Index(MyProducts model)
        {
            string ProductId = model.ProductId;
            string ProductName = model.ProductName;
            //bool remember = model.HaveExperiance;
            return View();
        }
    }

View

@model   Mvc.Affiliates.Models.MyProducts
@{
    ViewBag.Title = "Index";
    Layout = "~/Views/Shared/_Layout.cshtml";
}

<h2>Details</h2>
<br />

<div style="height:200px; width:100%">
  @using  (Html.BeginForm("Index","Home", FormMethod.Post))
    {

   <h2>Details</h2>

        @Html.LabelFor(model => model.ProductId)
        @Html.TextBoxFor(model => model.ProductId)

        @Html.LabelFor(model => model.ProductName)
        @Html.EditorFor(model => model.ProductName)
        @Html.ValidationMessageFor(model => model.ProductName, "*")

        @Html.CheckBoxFor(model => model.HaveExperiance)
        @Html.ValidationMessageFor(model => model.HaveExperiance, "*")
       <input type="submit" value="Submit" />
  }

</div>

So far i have tried the above code, Actually i needed when i click on checkbox then it should start validate my ProductName textbox, if uncheck then not. iam missing a little thing in above code, please help and rectify me.

Rauf Abid
  • 313
  • 2
  • 8
  • 24
  • 2
    Suggest you use a [foolproof](http://foolproof.codeplex.com/) `[RequiredIfTrue]` or similar validation attribute. But if you want to write your own, then [this article](http://www.devtrends.co.uk/blog/the-complete-guide-to-validation-in-asp.net-mvc-3-part-1) is a good starting point –  Jan 27 '16 at 07:27

1 Answers1

5

This is a complete example of how to create a custom validation attribute based on another attribute:

public class RequiredIfAttribute : ValidationAttribute
{
    private RequiredAttribute _innerAttribute = new RequiredAttribute();

    public string Property { get; set; }

    public object Value { get; set; }

    public RequiredIfAttribute(string typeProperty) {
        Property = typeProperty;
    }

    public RequiredIfAttribute(string typeProperty, object value)
    {
        Property = typeProperty;
        Value = value;
    }

    public override bool IsValid(object value)
    {
        return _innerAttribute.IsValid(value);
    }
}

public class RequiredIfValidator : DataAnnotationsModelValidator<RequiredIfAttribute>
{
    public RequiredIfValidator(ModelMetadata metadata, ControllerContext context, RequiredIfAttribute attribute) : base(metadata, context, attribute) { }

    public override IEnumerable<ModelClientValidationRule> GetClientValidationRules()
    {
        return base.GetClientValidationRules();
    }

    public override IEnumerable<ModelValidationResult> Validate(object container)
    {
        PropertyInfo field = Metadata.ContainerType.GetProperty(Attribute.Property);

        if (field != null) {
            var value = field.GetValue(container, null);

            if ((value != null && Attribute.Value == null) || (value != null && value.Equals(Attribute.Value))) {
                if (!Attribute.IsValid(Metadata.Model)) {
                    yield return new ModelValidationResult { Message = ErrorMessage };
                }
            }
        }
    }
}

In Global.asax file in Application_Start add this part:

DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(RequiredIfAttribute), typeof(RequiredIfValidator));

This part is needed to registate the RequiredIfValidator otherwise MVC will use only RequiredIfAttribute ignoring RequiredIfValidator.

If you want to validate ProductName if HaveExperiance have a value:

[RequiredIf("HaveExperiance")]
public string ProductName { get; set; }

If you want to validate ProductName only if HaveExperiance is false:

[RequiredIf("HaveExperiance", false)]
public string ProductName { get; set; }

If you want to validate ProductName only if HaveExperiance is true:

[RequiredIf("HaveExperiance", true)]
public string ProductName { get; set; }

In RequiredIfAttribute class I created a RequiredAttribute object only to validate the value passed to IsValid method.

Property field is used to save the name of property that activate the validation. It is used in class RequiredIfValidator to get the field current value using reflection (field.GetValue(container, null)).

When you call validation in your code (for example when you do if(TryValidateModel(model))) first you call RequiredIfValidator class that then call RequiredIfAttribute (through Attribute.IsValid(Metadata.Model)) class if some conditions are valid ((value != null && Attribute.Value == null) || (value != null && value.Equals(Attribute.Value))).

One last thing. Because RequiredIfAttribute inherits from ValidationAttribute you can also use error messages in the same way of any other validation attribute.

[RequiredIf("HaveExperiance", true, ErrorMessage = "The error message")]
public string ProductName { get; set; }

[RequiredIf("HaveExperiance", true, ErrorMessageResourceName = "ResourceName", ErrorMessageResourceType = typeof(YourResourceType))]
public string ProductName { get; set; }
erikscandola
  • 2,854
  • 2
  • 17
  • 24
  • would be great if you add some explanation as well – Arijit Mukherjee Jan 27 '16 at 08:39
  • @ArijitMukherjee I hope it's better now :-) – erikscandola Jan 27 '16 at 08:53
  • great, yes that would be helpfull to everyone now – Arijit Mukherjee Jan 27 '16 at 09:36
  • Thanks for the reply, your example look cool, will run and respond today, thanks for detail explanation. – Rauf Abid Jan 28 '16 at 05:30
  • @erikscandola I have tried your code. Please see EDIT: code in my orignal question, please help to rectify me what iam missing?, thanks. – Rauf Abid Jan 28 '16 at 11:02
  • @RaufAbid Done. I add the piece of code that you need to add in Global.asax. Sorry! – erikscandola Jan 28 '16 at 11:18
  • @erikscandola thanks for your quick response, it resolved my issue and before marking as an answer i need a little explanation on "RequiredIfValidator" and its inheriting class(DataAnnotationsModelValidator) and thier override methods. Please also explain this method "public override IEnumerable Validate(object container)", I did'nt understand their behaviours completely, secondly is there not any simple example to achive the same thing? as my requirement was so simple. I have also seen RangeValidator attribute which has simple implemention, thanks. – Rauf Abid Jan 28 '16 at 17:36
  • @RaufAbid You're welcome :-) I never asked how it works behinde the scenes but I suppose that this is how it works: when validation is called it find that there is a validator related to attribute, so it calls the `Validate` method of `DataAnnotationsModelValidator` (in this case of `RequiredIfValidator`) instead of `IsValid` method of property attribute. This is dictated by the line of code that we have added in Global.asax file. To do this it probably use [reflection](https://msdn.microsoft.com/en-us/library/ms173183.aspx). – erikscandola Jan 28 '16 at 18:19
  • @RaufAbid I usualy use this block of code but I found [this](http://stackoverflow.com/questions/11959431/how-to-create-a-custom-validation-attribute) for you! It seems that there is another override method for `IsValid` that which can bypass `DataAnnotationsModelValidator` and the registration in Global.asax. – erikscandola Jan 28 '16 at 18:19
  • @erikscandola, bundle of thanks. One last question, how can I display messages in case of we use RequiredIfvalidate. Thanks. – Rauf Abid Jan 28 '16 at 18:51
  • @erikscandola , sorry for my many question asked. I am unable to display messages in case of no value entered in product name field. – Rauf Abid Jan 28 '16 at 19:09
  • @RaufAbid this is a good place where to do questions :-D By the way, if you want to pass an error message you can do in the same way as you set error message for any other validation attribute. This because our custom validator inherited from `ValidationAttribute` that contains `ErrorMessage` property. So: `[RequiredIf("HaveExperiance", true, ErrorMessage = "Here the error message")]`. – erikscandola Jan 28 '16 at 21:48
  • @erikscandola, regarding this thread my all issues resolved, Many thanks and God Bless u :). – Rauf Abid Jan 29 '16 at 03:59
  • Brilliant article, still working perfectly in 2017! Thanks so much @erikscandola, people like you make SO work:) – IfElseTryCatch Jan 19 '17 at 12:29