0

I'm new to ASP.NET MVC and ran into a newbie question.

I've created a form where some data should only be sent from the view to the control if some conditions are met. Like, for example I got a checkbox with a text saying "today", if its unclicked a text field should appear where the user can enter a earlier date.

My question is: how do I with best practice (I've already solved it with javascript, filling/clearing the field, but that's really ugly) tell it to exclude/include the field depending on some condition.

I tried something like this:

public ActionResult Create([Bind(Include = "EventID,ActivityID", Exclude = "EventDate")] FSEvent fsevent)
{
        if (ModelState.IsValid)
        {
            if (fsevent.EventDate == DateTime.MinValue)
            {
                fsevent.EventDate = DateTime.Now;
            }

            db.FSEvents.Add(fsevent);
            db.SaveChanges();

            return RedirectToAction(returnUrl);
        }

        ViewBag.ActivityID = new SelectList(db.FSEventItems, "ActivityID", "Name", fsevent.ActivityID);
        ViewBag.UserID = new SelectList(db.FSUsers, "UserID", "FirstName", fsevent.UserID);

        return View(fsevent);
    }

But the browser gives me (the user) the error message "The EventDate field is required" with MVC EF buit in javascript validation (so if I disable the javascript it works fine).

2 Answers 2

1


I got a similar problem and solved it using a custom attribute.
Below you can find the ViewModel, the View and the Attribute.

The ViewModel

public class TheViewModel{

    [Display(Name = "YourOtherFieldDisplayName", ResourceType = typeof(YourResourceFile))]
    public string YourOtherField { get; set; }

    [Display(Name = "YourFieldDisplayName", ResourceType = typeof(YourResourceFile))]
    [RequiredIf("TheOtherField", true, ErrorMessageResourceName = "FieldRequired", ErrorMessageResourceType = typeof(YourResourceFile))]
    public string YourField { get; set; }
}

The View

@Html.LabelFor(model => model.YourOtherField)
@Html.CheckBoxFor(model => model.YourOtherField)
@Html.LabelFor(model => model.YourField)
@Html.TextBoxFor(model => model.YourField)
@Html.ValidationMessageFor(model => model.YourField)

The Attribute

namespace YOURNAMESPACE.attributes
{
    public class RequiredIfAttribute : ValidationAttribute, IClientValidatable
    {
        private RequiredAttribute _innerAttribute = new RequiredAttribute();

        public string DependentProperty { get; set; }
        public object TargetValue { get; set; }

        public RequiredIfAttribute(string dependentProperty, object targetValue)
        {
            this.DependentProperty = dependentProperty;
            this.TargetValue = targetValue;
        }

        protected override ValidationResult IsValid(object value, ValidationContext validationContext)
        {
            var containerType = validationContext.ObjectInstance.GetType();
            var field = containerType.GetProperty(this.DependentProperty);

            if (field != null)
            {
                var dependentvalue = field.GetValue(validationContext.ObjectInstance, null);

                // compare the value against the target value
                if ((dependentvalue == null && this.TargetValue == null) ||
                    (dependentvalue != null && dependentvalue.Equals(this.TargetValue)))
                {
                    if (!_innerAttribute.IsValid(value))
                        return new ValidationResult(this.ErrorMessage, new[] { validationContext.MemberName });
                }
            }

            return ValidationResult.Success;
        }

        public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
        {
            var rule = new ModelClientValidationRule()
            {
                ErrorMessage = FormatErrorMessage(metadata.GetDisplayName()),
                ValidationType = "requiredif",
            };

            string depProp = BuildDependentPropertyId(metadata, context as ViewContext);

            string targetValue = (this.TargetValue ?? "").ToString();
            if (this.TargetValue.GetType() == typeof(bool))
                targetValue = targetValue.ToLower();

            rule.ValidationParameters.Add("dependentproperty", depProp);
            rule.ValidationParameters.Add("targetvalue", targetValue);

            yield return rule;
        }

        private string BuildDependentPropertyId(ModelMetadata metadata, ViewContext viewContext)
        {
            // build the ID of the property
            string depProp = viewContext.ViewData.TemplateInfo.GetFullHtmlFieldId(this.DependentProperty);
            var thisField = metadata.PropertyName + "_";
            if (depProp.StartsWith(thisField))
                depProp = depProp.Substring(thisField.Length);
            return depProp;
        }
    }
}

I hope this can help you

Sign up to request clarification or add additional context in comments.

Comments

0

You can also just get the data just from the field where you enter the date at, and only validate that field.

This way the checkbox where you say checked/unchecked is just helping the UI show hide stuff, but it's not part of your validation or payload. You always just get the date back from a hidden or visible field.

It will also simplify your controller code.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.