11

Let's say I have this view model:

    public class MyModel
    {
        [Range(0, 999, ErrorMessage = "Invalid quantity")]
        public int Quantity { get; set; }
    }

Now, for specific instances of this model the range of valid values will change: some may not be 0, some may not be higher than 5. The min/max values for the valid ranges are coming from the DB and can change anytime.

How do I change the min/max properties of the RangeAttribute on the fly? Or what it the best way to validate my scenario?

5
  • I wont submit this as an answer but; I had this problem last week, after a ton of reading it turns out the attribute properties are required to be constants. There was a solution to using dynamic values using a CustomValidationAttribute class i think. Link: msdn.microsoft.com/en-us/library/… Commented Feb 15, 2012 at 16:13
  • I think I am going to try to combine Shark's and Andrei's answers below and create a custom attribute that uses the additional min/max properties from the model to validate the quantity field... Commented Feb 15, 2012 at 16:51
  • See my edited answer for a working and complete example and implementation. Commented Feb 15, 2012 at 17:20
  • What I ended up doing -- I added the min and max properties to the view model (as Shark suggested), implemented IValidatableObject in the view model and put validation into Validate method. It works and it is clean enough, but then the link posted by Matt has even better solution: validation happens on the client side and I can change the ranges for my model's instances. So I guess Matt is getting the credit for answering both of my questions. Thank you! Commented Feb 16, 2012 at 23:45
  • Can you tell the namespace to be used/or to add any reference for "IClientValidatable", actually, right now Complier is not recognizing it. I am using VS 2010. I added a new project and the project type used is - "ASP.Net MVC2 Web Application Visual C#" Commented Feb 17, 2012 at 8:36

2 Answers 2

10

Something along the lines of this might be more what your after...

ViewModel:

public class ViewModel
{
    public DateTime MinDate {get; set;}
    public DateTime MaxDate {get; set;}

    [DynamicRange("MinDate", "MaxDate", ErrorMessage = "Value must be between {0} and {1}")]
    public DateTime Date{ get; set; }
}

Library class or elsewhere:

public class DynamicRange : ValidationAttribute, IClientValidatable
    {
        private readonly string _minPropertyName;
        private readonly string _maxPropertyName;

    public DynamicRange(string minPropName, string maxPropName)
    {
        _minPropertyName = minPropName;
        _maxPropertyName = maxPropName;
    }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        var minProperty = validationContext.ObjectType.GetProperty(_minPropertyName);
        var maxProperty = validationContext.ObjectType.GetProperty(_maxPropertyName);

        if(minProperty == null)
            return new ValidationResult(string.Format("Unknown property {0}", _minPropertyName));

        if (maxProperty == null)
            return new ValidationResult(string.Format("Unknown property {0}", _maxPropertyName));

        var minValue = (int) minProperty.GetValue(validationContext.ObjectInstance, null);
        var maxValue = (int) maxProperty.GetValue(validationContext.ObjectInstance, null);

        var currentValue = (int) value;

        if (currentValue <= minValue || currentValue >= maxValue)
        {
            return new ValidationResult(string.Format(ErrorMessage, minValue, maxValue));
        }

        return null;
    }

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

        rule.ValidationParameters["minvalueproperty"] = _minPropertyName;
        rule.ValidationParameters["maxvalueproperty"] = _maxPropertyName;
        yield return rule;
    }

From: MVC unobtrusive range validation of dynamic values

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

3 Comments

Can you tell the namespace to be used/or to add any reference for "IClientValidatable", actually, right now complier is not recognizing it.
Can you tell the namespace to be used/or to add any reference for "IClientValidatable", actually, right now Compiler is not recognizing it. I am using VS 2010. I added a new project and the project type used is - "ASP.Net MVC2 Web Application Visual C#"
1

I think your best bet might be to implement a custom model binder for your specific Model (MyModel). What you could have is something like this:

public class MyModel
{
    public int Quantity { get; set; }
} // unchanged Model

public class MyViewModel
{
    public MyModel myModel { get; set; }
    public int QuantityMin { get; set; }
    public int QuantityMax { get; set; }
}

Then you can set these values, and in your custom model binder you can compare your myModel.Quantity property to the QuantityMin and QuantityMax properties.


Example

Model:

public class QuantityModel
{
    public int Quantity { get; set; }
}

ViewMode:

public class QuantityViewModel
{
    public QuantityModel quantityModel { get; set; }
    public int QuantityMin { get; set; }
    public int QuantityMax { get; set; }
}

Custom Model Binder:

public class VarQuantity : IModelBinder
{
    public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        int MinValue = Convert.ToInt32(bindingContext.ValueProvider.GetValue("QuantityMin").AttemptedValue);
        int MaxValue = Convert.ToInt32(bindingContext.ValueProvider.GetValue("QuantityMax").AttemptedValue);
        int QuantityValue = Convert.ToInt32(bindingContext.ValueProvider.GetValue("quantityModel.Quantity").AttemptedValue);

        if (!(QuantityValue >= MinValue && QuantityValue <= MaxValue))
            bindingContext.ModelState.AddModelError("Quantity", "Quantity not between values");

        return bindingContext.Model;
    }
}

Register Custom Model Binder:

ModelBinders.Binders.Add(typeof(QuantityViewModel), new VarQuantity());

Test Controller Action Methods:

    public ActionResult Quantity()
    {
        return View();
    }

    [HttpPost]
    public string Quantity(QuantityViewModel qvm)
    {
        if (ModelState.IsValid)
            return "Valid!";
        else
            return "Invalid!";
    }

Test View Code:

@model MvcTest.Models.QuantityViewModel

<h2>Quantity</h2>

@using (Html.BeginForm())
{
    @Html.Label("Enter Your Quantity: ")
    @Html.TextBoxFor(m => m.quantityModel.Quantity)
    <br />
    @Html.Label("Quantity Minimum: ")
    @Html.TextBoxFor(m => m.QuantityMin)
    <br />
    @Html.Label("Quantity Maximum: ")
    @Html.TextBoxFor(m => m.QuantityMax)
    <br /><br />
    <input type="submit" value="Submit" />
}

2 Comments

This is not the correct answer. OP is interesting to know whether it is possible to do it on the fly ?
@PankajGarg It is possible to do it on the fly. See my edit and my working example. This is surely a way.

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.