Validation on Databoud DataGridView per Column
So I have a DataGridView which is used to display a list of custom models. Here is some sample code for a model:
public class TestModel
{
public IEnumerable<string> GetValidationErrors()
{
if (Value1 > 100 || Value1 <= 0)
yield return "Value1 can only be between 1 and 100 (inclusive)";
if (string.IsNullOrEmpty(Value2))
yield return "Value2 can not be empty";
}
public bool IsValid
{
get { return GetValidationErrors().Count() == 0; }
}
public int Value1
{
get; set;
}
public string Value2
{
get; set;
}
}
Now let's say I was Binding a list (or IEnumerable) of these models like so
List<TestModel> list = Helpers.GetListOfTestModels();
dataGridView1.DataSource = list;
(We can assume that the list return is valid, or else it wouldn't be stored).
Now on the RowValidating
event on the DataGridView, I can validate the entire row by accessing th开发者_StackOverflow社区e IsValid
property and set a Rows[].ErrorText
, like so:
private void dataGridView1_RowValidating(object sender, DataGridViewCellCancelEventArgs e)
{
var item = dataGridView1.Rows[e.RowIndex].DataBoundItem as TestModel;
if (item == null)
return;
if(!item.IsValid)
{
dataGridView1.Rows[e.RowIndex].ErrorText = "Failed Validation"; // Or use GetValidationErrors and concat them, but to be simple I've left that out
e.Cancel = true;
}
else
{
dataGridView1.Rows[e.RowIndex].ErrorText = string.Empty;
}
}
Now, what I really want to do (without copying and pasting code all over the place), is validate each Property and set the Rows[].Cells[].ErrorText
Property (this will show errors per cell, instead of the entire row).
How should I go about this?
Maybe something with Custom Attributes, then reflection to get the property name and access the cell that way?
Hopefully this all makes sense!
Solved it!
Have a abstract attribute like so:
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true, Inherited = true)]
public abstract class ValidationAttribute : Attribute
{
public abstract void Validate(object value, PropertyInfo propertyInfo, ref IList<string> errors);
}
and an implementation like so:
public class ValidStringAttribute : ValidationAttribute
{
#region Overrides of ValidationAttribute
public override void Validate(object value, PropertyInfo propertyInfo, ref IList<string> errors)
{
var v = propertyInfo.GetValue(value, null);
if(!string.IsNullOrEmpty(v as string))
{
errors.Add(string.format("`{0}` cannot be null or empty",propertyInfo.Name);
}
}
#endregion
}
Then Modify the Value2
Property like so:
[ValidString]
public string Value2
{
get; set;
}
and in either the RowValidating
or CellValidating
Events do something like this:
if(!item.IsValid)
{
foreach(var propertyInfo in item.GetType().GetProperties())
{
IList<string> list = new List<string>();
foreach (ValidationAttribute attribute in propertyInfo.GetCustomAttributes(typeof(ValidationAttribute),true))
{
attribute.Validate(item,propertyInfo,ref list);
}
if(list.Count > 0)
{
// make sure it's not ignored
var browsable = propertyInfo.GetCustomAttributes(typeof (BrowsableAttribute), true);
if(browsable.Count() == 0)
{
dataGridView1.Rows[e.RowIndex].Cells[propertyInfo.Name].ErrorText = list[0];
}
}
}
e.Cancel = true;
}
and bam errors per property, with databinding!
Could you perhaps make use of the CellValidating
event? This event fires similarly to the RowValidating
except it occurs when you tab out of the cell.
Alternatively, if your row represents a Business Object, then you can expose a Validate
method that will perform validation over the properties and return an Errors
array of properties that have failed validation with a provided error message. It does not automagically link to the UI, but it keeps validation in the object itself rather than the UI.
精彩评论