Search code examples
c#entity-frameworkmvvmdata-annotationsef-database-first

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

  • I ended up fixing this myself by binding to a property in the viewmodel rather than the model itself and adding the annotations to the view model rather than trying to extend and clutter the model.

    Thinking about it, I probably should have done that first....