'How to display the error message for DataAnnotations
I've spent the last hour or so googling for an answer to this, but nearly every result is either in ASP.NET or talks about Code First approaches which are useless to me.
I've basically got database-first entity framework POCO objects which I'm providing validation for using IDataErrorInfo.
Now this works fine, except I have a indexer that's 200 lines long with about 40 if-statements in it. (I'm serious.)
What I'm doing right now is extending the classes like so:
public partial class MyPocoObject : IDataErrorInfo
{
public string Error
{
get { throw new NotImplementedException("IDataErrorInfo.Error"); }
}
public string this[string columnName]
{
get
{
string result = null;
// There's about 40 if statements here...
}
}
}
Clearly, this is wrong, so I'm trying to use DataAnnotations instead.
This is what I've understood so far.
I've created Metadata classes like so:
[MetadataType(typeof(MyObjectMetaData))]
public partial class MyObject { }
public class MyObjectMetaData
{
[Required(AllowEmptyStrings = false, ErrorMessage = "Forename is a required field.")]
public string Forename;
}
Then I have the controls declared as such:
<TextBox Text="{Binding SelectedObject.Forename, NotifyOnValidationError=True, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}"/>
Then I have a trigger elsewhere:
<Style TargetType="TextBox" BasedOn="{StaticResource Global}">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip" Value="{Binding RelativeSource={x:Static RelativeSource.Self},Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
When I did this with IDataErrorInfo, when the validation failed, I'd get a red border and a tooltip with the error message. Using data annotations I get nothing.
How should I be implementing this? Because 40 if-statements in a single method is insane.
Update: I've tried the code suggested in the answers which doesn't work, although I'm thinking I've done this wrong.
public partial class Member : IDataErrorInfo
{
// Error: The type 'Project.Client.Models.Member' already contains a definition for 'Forename'
[Required(AllowEmptyStrings = false, ErrorMessage = "Forename is a required field.")]
public string Forename;
private readonly Dictionary<string, object> _values = new Dictionary<string, object>();
public string Error
{
get { throw new NotImplementedException("IDataErrorInfo.Error"); }
}
public string this[string columnName]
{
get { return OnValidate(columnName); }
}
protected virtual string OnValidate(string propertyName)
{
if (string.IsNullOrEmpty(propertyName))
{
throw new ArgumentException("Invalid property name", propertyName);
}
string error = string.Empty;
// Error: Project.Client.Models.Member.GetValue<T>(string)' cannot be inferred from the usage. Try specifying the type arguments explicitly
var value = GetValue(propertyName);
var results = new List<System.ComponentModel.DataAnnotations.ValidationResult>(1);
var result = Validator.TryValidateProperty(
value,
new ValidationContext(this, null, null)
{
MemberName = propertyName
},
results);
if (!result)
{
var validationResult = results.First();
error = validationResult.ErrorMessage;
}
return error;
}
protected T GetValue<T>(string propertyName)
{
if (string.IsNullOrEmpty(propertyName))
{
throw new ArgumentException("Invalid property name", propertyName);
}
object value;
if (!_values.TryGetValue(propertyName, out value))
{
value = default(T);
_values.Add(propertyName, value);
}
return (T)value;
}
}
Update 2: The code linked in the article on MVVM seems to work, but no error messages are still being displayed even though OnValidate is firing.
Although something else is wrong now... even though the content of the textbox is changing, the value returned from GetValue is always the same, and on an empty object not loaded from my database, validation doesn't fire at all.
Solution 1:[1]
It seems you have to integrate DataAnnotations validation by hand. Maybe System.ComponentModel.DataAnnotations.Validator doesn't use MetadataType by default but registering it in TypeDescriptor like in my previous answer should work.
What I mean is that you have to implement your validate method and use System.ComponentModel.DataAnnotations.Validator.
Take a loot at PropertyChangedNotification implementation of the source code I linked:
/// <summary>
/// Validates current instance properties using Data Annotations.
/// </summary>
/// <param name="propertyName">This instance property to validate.</param>
/// <returns>Relevant error string on validation failure or <see cref="System.String.Empty"/> on validation success.</returns>
protected virtual string OnValidate(string propertyName)
{
if (string.IsNullOrEmpty(propertyName))
{
throw new ArgumentException("Invalid property name", propertyName);
}
string error = string.Empty;
var value = GetValue(propertyName);
var results = new List<System.ComponentModel.DataAnnotations.ValidationResult>(1);
var result = Validator.TryValidateProperty(
value,
new ValidationContext(this, null, null)
{
MemberName = propertyName
},
results);
if (!result)
{
var validationResult = results.First();
error = validationResult.ErrorMessage;
}
return error;
}
Solution 2:[2]
Some .NET technologies retrieves and prcess MetadataTypes while others not.
Try to register the buddy class before use:
var myObjectMetadaProviderProvider = new
AssociatedMetadataTypeTypeDescriptionProvider(
typeof(MyObject),
typeof(MyObjectMetaData));
TypeDescriptor.AddProvider(myObjectMetadaProviderProvider , typeof(MyObject));
This allow to retrieve attributes declared in buddy classes in a transparent way without having to know about MetadataTypes in the main class.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | |
| Solution 2 | MickyD |
