使用 ViewModel 实体上的 DataAnnotation 进行 Prism IDataErrorInfo 验证

发布于 2024-09-19 23:41:14 字数 512 浏览 10 评论 0原文

我正在使用 Prism MVVM 框架在 WPF 中实现数据验证。我在 ViewModel 中使用绑定到表示层的干净数据实体。

 <TextBox Text="{Binding User.Email, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}" />

我在基本 ViewModel 类中实现了 IDataErrorInfo 的通用实现,该类针对我的实体(在本例中为用户)上的 DataAnnotation 属性运行验证。

问题是,当绑定到实体时,WPF 框架会在实体上查找 IDataErrorInfo,而不是我希望此逻辑存在的 ViewModel。如果我用 ViewModel 中的属性包装实体,那么一切都会正常,但我不希望损害 ViewModel 中实体的使用。

有没有办法告诉 WPF 在 ViewModel 中查找 IDataErrorInfo 而不是正在绑定的子对象?

谢谢, 麦克风

I'm implementing data validation in WPF using the Prism MVVM framework. I'm using clean data Entities in the ViewModel which are being bound to the presentation layer.

 <TextBox Text="{Binding User.Email, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}" />

I've implemented a generic implementation of IDataErrorInfo in a base ViewModel class that runs validation against the DataAnnotation attributes on my Entity (in this case User).

The issue is that when binding to an Entity, the WPF framework looks for IDataErrorInfo on the Entity and not the ViewModel which is where I want this logic to exist. If I wrap my Entity with properties in my ViewModel then everything works, but I don't wish to compromise the use of Entities within the ViewModel.

Is there a way to tell WPF to look for the IDataErrorInfo in the ViewModel and not a child object that's being bound?

Thanks,
Mike

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

謸气贵蔟 2024-09-26 23:41:18

当然,我不知道你的整个场景,但我相信使用 ViewModel 包装你的业务实体(或模型)是 MVVM 模式的重要组成部分,特别是如果你没有可绑定的模型(一个模型)可以直接绑定)。包装可以包括错误管理信息(如本场景中所示)或其他内容(例如自定义模型显示等)。

也就是说,您可以查看 Prism 的 v4.0 MVVM RI,它使用 INotifyDataErrorInfo 进行验证,并且应该提供有趣的见解关于验证方法。

我希望这有帮助。

谢谢,
达米安

Of course, I don't know your entire scenario, but I believe that wrapping your business entities (or model) using the ViewModel is a great part of the MVVM pattern, specially if you don't have a bindable model (a model to which you can bind directly). The wrapping can include error management information as in this scenario or other things such as customizing model display, etc.

That said, you can take a look at Prism's v4.0 MVVM RI, which uses the INotifyDataErrorInfo for validation, and should provide interesting insight on validation approaches.

I hope this helps.

Thanks,
Damian

甜中书 2024-09-26 23:41:17

我选择的选项是在由所有 ViewModel 和实体扩展的基类中显式实现 IDataErrorInfo。这似乎是让 WPF 顺利进行的最佳折衷方案,并且至少使 IDataErrorInfo 的实现对调用者隐藏,这样它们至少看起来是干净的。我公开了一个受保护的 ValidateProperty,如果需要,可以在子类中针对任何自定义行为(例如密码/密码确认场景)覆盖该属性。

public abstract class DataErrorInfo : IDataErrorInfo
{
    string IDataErrorInfo.Error
    {
        get { return null; }
    }

    string IDataErrorInfo.this[string columnName]
    {
        get { return ValidateProperty(columnName); }
    }

    protected virtual string ValidateProperty(string columnName)
    {
         // get cached property accessors
            var propertyGetters = GetPropertyGetterLookups(GetType());

            if (propertyGetters.ContainsKey(columnName))
            {
                // read value of given property
                var value = propertyGetters[columnName](this);

                // run validation
                var results = new List<ValidationResult>();
                var vc = new ValidationContext(this, null, null) { MemberName = columnName };
                Validator.TryValidateProperty(value, vc, results);

                // transpose results
                var errors = Array.ConvertAll(results.ToArray(), o => o.ErrorMessage);
                return string.Join(Environment.NewLine, errors);
            }
            return string.Empty;
    }

    private static readonly Dictionary<string, object> PropertyLookupCache =
        new Dictionary<string, object>();

    private static Dictionary<string, Func<object, object>> GetPropertyGetterLookups(Type objType)
    {
        var key = objType.FullName ?? "";
        if (!PropertyLookupCache.ContainsKey(key))
        {
            var o = objType.GetProperties()
            .Where(p => GetValidations(p).Length != 0)
            .ToDictionary(p => p.Name, CreatePropertyGetter);

            PropertyLookupCache[key] = o;
            return o;
        }
        return (Dictionary<string, Func<object, object>>)PropertyLookupCache[key];
    }

    private static Func<object, object> CreatePropertyGetter(PropertyInfo propertyInfo)
    {
        var instanceParameter = Expression.Parameter(typeof(object), "instance");

        var expression = Expression.Lambda<Func<object, object>>(
            Expression.ConvertChecked(
                Expression.MakeMemberAccess(
                    Expression.ConvertChecked(instanceParameter, propertyInfo.DeclaringType),
                    propertyInfo),
                typeof(object)),
            instanceParameter);

        var compiledExpression = expression.Compile();

        return compiledExpression;
    }

    private static ValidationAttribute[] GetValidations(PropertyInfo property)
    {
        return (ValidationAttribute[])property.GetCustomAttributes(typeof(ValidationAttribute), true);
    }


}

The option I went with was to implement IDataErrorInfo explicitly in a base class which is extended by all ViewModels and Entities. This seems the best compromise to get things ticking over with WPF, and at least keeps the implementation of IDataErrorInfo hidden to callers so they at least appear clean. I expose a protected ValidateProperty which can be overridden if necessary in subclasses for any custom behaviour (such as for Password/PasswordConfirmation scenario).

public abstract class DataErrorInfo : IDataErrorInfo
{
    string IDataErrorInfo.Error
    {
        get { return null; }
    }

    string IDataErrorInfo.this[string columnName]
    {
        get { return ValidateProperty(columnName); }
    }

    protected virtual string ValidateProperty(string columnName)
    {
         // get cached property accessors
            var propertyGetters = GetPropertyGetterLookups(GetType());

            if (propertyGetters.ContainsKey(columnName))
            {
                // read value of given property
                var value = propertyGetters[columnName](this);

                // run validation
                var results = new List<ValidationResult>();
                var vc = new ValidationContext(this, null, null) { MemberName = columnName };
                Validator.TryValidateProperty(value, vc, results);

                // transpose results
                var errors = Array.ConvertAll(results.ToArray(), o => o.ErrorMessage);
                return string.Join(Environment.NewLine, errors);
            }
            return string.Empty;
    }

    private static readonly Dictionary<string, object> PropertyLookupCache =
        new Dictionary<string, object>();

    private static Dictionary<string, Func<object, object>> GetPropertyGetterLookups(Type objType)
    {
        var key = objType.FullName ?? "";
        if (!PropertyLookupCache.ContainsKey(key))
        {
            var o = objType.GetProperties()
            .Where(p => GetValidations(p).Length != 0)
            .ToDictionary(p => p.Name, CreatePropertyGetter);

            PropertyLookupCache[key] = o;
            return o;
        }
        return (Dictionary<string, Func<object, object>>)PropertyLookupCache[key];
    }

    private static Func<object, object> CreatePropertyGetter(PropertyInfo propertyInfo)
    {
        var instanceParameter = Expression.Parameter(typeof(object), "instance");

        var expression = Expression.Lambda<Func<object, object>>(
            Expression.ConvertChecked(
                Expression.MakeMemberAccess(
                    Expression.ConvertChecked(instanceParameter, propertyInfo.DeclaringType),
                    propertyInfo),
                typeof(object)),
            instanceParameter);

        var compiledExpression = expression.Compile();

        return compiledExpression;
    }

    private static ValidationAttribute[] GetValidations(PropertyInfo property)
    {
        return (ValidationAttribute[])property.GetCustomAttributes(typeof(ValidationAttribute), true);
    }


}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文