Pergunta

everyone! I'm confusing with implementing a piece of code to make work .net data annotation in asp.net mvc 3 with model with different required fields in several cases (6). I have a model:

  public class OpportunityModel
{
    public Guid OpportunityId { get; set; }

    [Display(Name = "Value")]
    [RegularExpression(@"^[-+]?\d{1,10}(\.\d{0,4})?$", ErrorMessage = "Must be a number")]
    public decimal? ActualValue { get; set; }  

    [Display(Name = "Name")]
    [Required(ErrorMessage = "Name is required")]
    public string Name { get; set; } 
    public string Product { get; set; } 

    [Display(Name = "Estimated Date")]
    public DateTime? EstimateDate { get; set; }


    public bool? Sales6ixFallDown { get; set; }


    [Display(Name = "Stage")]
    public Stages Sales6ixStage { get; set; }

    public DateTime? Sales6ixDateInBoard { get; set; }

    public DateTime? Sales6ixDateInCurrentStage { get; set; }

    public DateTime? Sales6ixNextAppointmentDate { get; set; }

    [Display(Name = "Description")]
    public string Description { get; set; }

    public string Sales6ixNextAppointmentDescription { get; set; }

    public int NewColumn { get; set; }

    public Guid? CustomerId { get; set; }

    public string CustomerName { get; set; }
}

What I need is the possibility dynamically change required fiefs in it. After some googling that's impossible and came to idea to use model inheritance. I mean: I have a base model like this:

  public class BaseOpportunityModel
{
    public Guid OpportunityId { get; set; }

    public virtual decimal? ActualValue { get; set; }  
    public virtual string Name { get; set; }  

    public string Product { get; set; } 

    public DateTime? EstimateDate { get; set; }

    public bool? Sales6ixFallDown { get; set; }


    [Display(Name = "Stage")]
    public Stages Sales6ixStage { get; set; }

    public DateTime? Sales6ixDateInBoard { get; set; }

    public DateTime? Sales6ixDateInCurrentStage { get; set; }

    public DateTime? Sales6ixNextAppointmentDate { get; set; }

    [Display(Name = "Description")]
    public string Description { get; set; }

    public string Sales6ixNextAppointmentDescription { get; set; }

    public int NewColumn { get; set; }

    public Guid? CustomerId { get; set; }

    public string CustomerName { get; set; }
}

where virtual properties are properties that may be or not a required fields. And then I have several derived model from base like this one:

  public class OpportunityModel0: BaseOpportunityModel
{
    [Display(Name = "Value")]
    [Required(ErrorMessage = "Name is required")]
    [RegularExpression(@"^[-+]?\d{1,10}(\.\d{0,4})?$", ErrorMessage = "Must be a number")]
    public override decimal? ActualValue { get; set; }  

    [Display(Name = "Name")]
    [Required(ErrorMessage = "Name is required")]
 public override string Name { get; set; }  

}

And then I be able to use in View and Controller base model BaseOpportunityModel. But I encountered follow problem:

  • Validation use annotation attributes from BaseOpportunityModel and ignore attributes in derived models.

What do I wrong? Can somebody steer me in the right direction or help me with this issue? Thanks in advance.

Foi útil?

Solução 2

I figured out my problem with different reqiured validation of model with using custom RequiredIfValidator. So now I have just one model and one view. Here is code, may be some one find it useful:

RequiredIfAttribute:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;

namespace Infrastructure.Extensions
{
    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)
        {
            // get a reference to the property this validation depends upon
            var containerType = validationContext.ObjectInstance.GetType();
            var field = containerType.GetProperty(this.DependentProperty);

            if (field != null)
            {
                // get the value of the dependent property
                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)))
                {
                    // match => means we should try validating this field
                    if (!_innerAttribute.IsValid(value))
                        // validation failed - return an error
                        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);

            // find the value on the control we depend on;
            // if it's a bool, format it javascript style 
            // (the default is True or False!)
            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);
            // unfortunately this will have the name of the current field appended to the beginning,
            // because the TemplateInfo's context has had this fieldname appended to it. Instead, we
            // want to get the context as though it was one level higher (i.e. outside the current property,
            // which is the containing object (our Person), and hence the same level as the dependent property.
            var thisField = metadata.PropertyName + "_";
            if (depProp.StartsWith(thisField))
                // strip it off again
                depProp = depProp.Substring(thisField.Length);
            return depProp;
        }
    }
}



RequiredIfValidator

namespace Infrastructure.Extensions
{
    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)
        {
            // get a reference to the property this validation depends upon
            var field = Metadata.ContainerType.GetProperty(Attribute.DependentProperty);

            if (field != null)
            {
                // get the value of the dependent property
                var value = field.GetValue(container, null);

                // compare the value against the target value
                if ((value == null && Attribute.TargetValue == null) ||
                    (value.Equals(Attribute.TargetValue)))
                {
                    // match => means we should try validating this field
                    if (!Attribute.IsValid(Metadata.Model))
                        // validation failed - return an error
                        yield return new ModelValidationResult { Message = ErrorMessage };
                }
            }
        }
    }
}



Client Validation

/// <reference path="jquery-1.4.4-vsdoc.js" />
    /// <reference path="jquery.validate.unobtrusive.js" />

    $.validator.addMethod('requiredif',
        function (value, element, parameters) {
            var id = '#' + parameters['dependentproperty'];

            // get the target value (as a string, 
            // as that's what actual value will be)
            var targetvalue = parameters['targetvalue'];
            targetvalue =
              (targetvalue == null ? '' : targetvalue).toString();

            // get the actual value of the target control
            // note - this probably needs to cater for more 
            // control types, e.g. radios
            var control = $(id);
            var controltype = control.attr('type');
            var actualvalue =
                controltype === 'checkbox' ?
                control.is(":checked").toString() :
            //control.attr('checked').toString() :
                control.val();

            actualvalue = actualvalue.toLocaleLowerCase();

            // if the condition is true, reuse the existing 
            // required field validator functionality
            if (targetvalue === actualvalue)
                return $.validator.methods.required.call(
                  this, value, element, parameters);

            return true;
        }
    );

    $.validator.unobtrusive.adapters.add(
        'requiredif',
        ['dependentproperty', 'targetvalue'], 
        function (options) {
            options.rules['requiredif'] = {
                dependentproperty: options.params['dependentproperty'],
                targetvalue: options.params['targetvalue']
            };
            options.messages['requiredif'] = options.message;
        });

Outras dicas

This tip should work in mvc 3 accroding to THIS. One thing that may be a problem is your post action. You should specify your inherited model as param in the post action.

public ActionResult MyPostAction(OpportunityModel0 model)

If base model is param in action, validation will not work.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top