2

我正在使用 EF 4.5 和 DbContext。在业务规则层级别,我应该实施检查以避免在某些实体场景中更改实体值属性。示例:如果不是其他状态,StartProjecteDate则应为只读。ProjectIsStarted

我遵循DRY原则,因此,我应该能够从上下文和 UI 中检查只读属性列表。

我的问题:

是否有可以将属性动态设置为只读的 DataAnnotation 验证器?

(如果没有,这个问题是否有不同/更好的解决方案?)

请注意,我正在使用 Web 表单(和 Telerik)架构,欢迎使用干净优雅的模式。

正如Jesse Webb解释的那样,我正在尝试在运行时设置和获取 EditableAttribute ,但我无法从属性中获取数据注释属性,我的代码:

<EditableAttribute(False)>
<MaxLength(400, ErrorMessage:="Màxim 400 caracters")>
Public Property NomInvertebrat As String

在此处输入图像描述

在挖掘文档后于2013 年 11 月 8 日编辑,似乎数据注释是针对类但例如对象本身。也许 iReadonlyableProperties 接口可能是一种方式。

4

3 回答 3

2

我有一个包含扩展方法的类,可以让我读取这样的数据注释:

int maxRefLen = ReflectionAPI.GetProperty<Organisation, String>(x => x.Name)
                             .GetAttribute<StringLengthAttribute>()
                             .GetValueOrDefault(x => x.MaximumLength, 256);

所以如果你使用它,你应该能够得到这样的值EditableAttribute

bool isEditable = ReflectionAPI.GetProperty<Foo, String>(x => x.NomInvertebrat)
                               .GetAttribute<EditableAttribute>()
                               .GetValueOrDefault(x => x.AllowEdit, true);

至于在运行时设置数据注释,我自己没有做过,但我读到这里有一个解决方案:在运行时设置数据注释

我认为获取特定类型的所有数据注释的列表需要阅读实体框架元数据。我再次没有尝试过这个。

如果你把它加在一起,我个人认为它感觉笨重而不是优雅,但你已经要求使用DataAnnotations 的解决方案,更优雅的东西可能意味着进入你的架构。

我倾向于这样做:

public bool StartDateIsReadOnly
{
   //use this property client-side to disable the input
   get{ return Project.IsStarted;}
}

//Implement IValidatable object to do server side validation
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext
{
   bool startdateIsChanged = // I'll leave you to work out this bit
   var results = new List<ValidationResult>();
   if(StartDateIsReadOnly && startdateIsChanged)
   results.Add(new ValidationResult("Start Date cannot be changed after project is started");
}

这是 ReflectionAPI 类:

请注意,该课程包括@JonSkeet 发布并描述为“邪恶”的部分黑客攻击。我个人认为这一点还不错,但您应该阅读以下参考资料:

覆盖值类型和引用类型的通用方法

邪恶代码 - 重载解决方法

public static class ReflectionAPI
{

    public static int GetValueOrDefault<TInput>(this TInput a, Func<TInput, int> func, int defaultValue)
        where TInput : Attribute
    //Have to restrict to struct or you get the error:
    //The type 'R' must be a non-nullable value type in order to use it as parameter 'T' in the generic type or method 'System.Nullable<T>'
    {
        if (a == null)
            return defaultValue;

        return func(a);
    }

    public static Nullable<TResult> GetValueOrDefault<TInput, TResult>(this TInput a, Func<TInput, TResult> func, Nullable<TResult> defaultValue)
        where TInput : Attribute
        where TResult : struct
    //Have to restrict to struct or you get the error:
    //The type 'R' must be a non-nullable value type in order to use it as parameter 'T' in the generic type or method 'System.Nullable<T>'
    {
        if (a == null)
            return defaultValue;

        return func(a);
    }

    //In order to constrain to a class without interfering with the overload that has a generic struct constraint
    //we need to add a parameter to the signature that is a reference type restricted to a class
    public class ClassConstraintHack<T> where T : class { }

    //The hack means we have an unused parameter in the signature
    //http://msmvps.com/blogs/jon_skeet/archive/2010/11/02/evil-code-overload-resolution-workaround.aspx
    public static TResult GetValueOrDefault<TInput, TResult>(this TInput a, Func<TInput, TResult> func, TResult defaultValue, ClassConstraintHack<TResult> ignored = default(ClassConstraintHack<TResult>))
        where TInput : Attribute
        where TResult : class
    {
        if (a == null)
            return defaultValue;

        return func(a);
    }

    //I don't go so far as to use the inheritance trick decribed in the evil code overload resolution blog, 
    //just create some overloads that take nullable types - and I will just keep adding overloads for other nullable type
    public static bool? GetValueOrDefault<TInput>(this TInput a, Func<TInput, bool?> func, bool? defaultValue)
where TInput : Attribute
    {
        if (a == null)
            return defaultValue;

        return func(a);
    }

    public static int? GetValueOrDefault<TInput>(this TInput a, Func<TInput, int?> func, int? defaultValue)
where TInput : Attribute
    {
        if (a == null)
            return defaultValue;

        return func(a);
    }

    public static T GetAttribute<T>(this PropertyInfo p) where T : Attribute
    {
        if (p == null)
            return null;

        return p.GetCustomAttributes(false).OfType<T>().LastOrDefault();
    }

    public static PropertyInfo GetProperty<T, R>(Expression<Func<T, R>> expression)
    {
        if (expression == null)
            return null;

        MemberExpression memberExpression = expression.Body as MemberExpression;
        if (memberExpression == null)
            return null;

        return memberExpression.Member as PropertyInfo;
    }
}
于 2013-11-08T09:56:25.627 回答
1

.NET 允许您通过实现 System.ComponentModel.ICustomTypeDescriptor 来动态更改类的结构。大多数序列化程序都支持此接口。

// Sample Serialization

foreach(PropertyDescriptor p in TypeDescriptor.GetProperties(obj)){
    string name = p.PropertyName;
    object value = p.GetValue(obj);
}

内部 TypeDescriptor 使用反射,但实现允许我们轻松覆盖反射属性。

以下是实现的三个步骤,

// Implement System.ComponentModel.ICustomTypeDescriptor Interface on
// your Entity

public class MyEntity: System.ComponentModel.ICustomTypeDescriptor
{
     ....
     // most methods needs only call to default implementation as shown below

    System.ComponentModel.AttributeCollection      
    System.ComponentModel.ICustomTypeDescriptor.GetAttributes()
    {
        return TypeDescriptor.GetAttributes(this, true);
    }

    string System.ComponentModel.ICustomTypeDescriptor.GetClassName()
    {
        return TypeDescriptor.GetClassName(this, true);
    }

    string System.ComponentModel.ICustomTypeDescriptor.GetComponentName()
    {
        return TypeDescriptor.GetComponentName(this, true);
    }

    System.ComponentModel.TypeConverter System.ComponentModel.ICustomTypeDescriptor.GetConverter()
    {
        return TypeDescriptor.GetConverter(this, true);
    }

    System.ComponentModel.EventDescriptor System.ComponentModel.ICustomTypeDescriptor.GetDefaultEvent()
    {
        return TypeDescriptor.GetDefaultEvent(this, true);
    }

    System.ComponentModel.PropertyDescriptor System.ComponentModel.ICustomTypeDescriptor.GetDefaultProperty()
    {
        return TypeDescriptor.GetDefaultProperty(this, true);
    }

    object System.ComponentModel.ICustomTypeDescriptor.GetEditor(Type editorBaseType)
    {
        return TypeDescriptor.GetEditor(this, editorBaseType, true);
    }

    System.ComponentModel.EventDescriptorCollection System.ComponentModel.ICustomTypeDescriptor.GetEvents(Attribute[] attributes)
    {
        return TypeDescriptor.GetEvents(this, attributes, true);
    }

    System.ComponentModel.EventDescriptorCollection System.ComponentModel.ICustomTypeDescriptor.GetEvents()
    {
        return TypeDescriptor.GetEvents(this, true);
    }

    System.ComponentModel.PropertyDescriptorCollection System.ComponentModel.ICustomTypeDescriptor.GetProperties(Attribute[] attributes)
    {
        return TypeDescriptor.GetProperties(this, attributes, true);
    }

    object System.ComponentModel.ICustomTypeDescriptor.GetPropertyOwner(System.ComponentModel.PropertyDescriptor pd)
    {
        return this;
    }

    // The Only method that needs different implementation is below
    System.ComponentModel.PropertyDescriptorCollection 
    System.ComponentModel.ICustomTypeDescriptor.GetProperties()
    {
       // ... you are supposed to create new instance of 
       // PropertyDescriptorCollection with PropertyDescriptor

       PropertyDescriptorCollection pdc = new PropertyDescriptorCollection();

       foreach(PropertyDescriptor p in TypeDescriptor.GetProperties(this,true)){
            // if readonly..

            AtomPropertyDescriptor ap = new AtomPropertyDescriptor(p, p.Name);
            // or
            AtomPropertyDescriptor ap = new AtomPropertyDescriptor(p, p.Name, 
                true,
                new XmlIgnoreAttribute(),
                new ScriptIgnoreAttribute(),
                new ReadOnlyAttribute());
            pdc.Add(ap);
       }

       return pdc;
    }
}


// And here is the AtomPropertyDescriptorClass

public class AtomPropertyDescriptor : PropertyDescriptor
{
    PropertyDescriptor desc;

    bool? readOnly = null;

    public AtomPropertyDescriptor(PropertyDescriptor pd, string name, 
        bool? readOnly, params Attribute[] attrs) :
        base(name, attrs)
    {
        desc = pd;
        this.readOnly = readOnly;
    }

    public override bool CanResetValue(object component)
    {
        return desc.CanResetValue(component);
    }

    public override Type ComponentType
    {
        get
        {
            return desc.ComponentType;
        }
    }

    public override object GetValue(object component)
    {
        return desc.GetValue(component);
    }

    public override bool IsReadOnly
    {
        get
        {
            if (readOnly.HasValue)
                return readOnly.Value;
            return desc.IsReadOnly;
        }
    }

    public override Type PropertyType
    {
        get { return desc.PropertyType; }
    }

    public override void ResetValue(object component)
    {
        desc.ResetValue(component);
    }

    public override void SetValue(object component, object value)
    {
        desc.SetValue(component, value);
    }

    public override bool ShouldSerializeValue(object component)
    {
        return desc.ShouldSerializeValue(component);
    }
}
于 2013-11-13T19:31:36.570 回答
1

我认为您正在寻找的是这样的自定义注释属性:

<DisableEditAttribute(this.IsProjectStarted)>
Public Property NomInvertebrat As String

public override bool IsValid(bool value)
{
    bool result = true;
    // Add validation logic here.
    if(value)
    {
         //Compare Current Value Against DB Value.
    }
    return result;
}

请参阅 MSDN: http: //msdn.microsoft.com/en-us/library/cc668224 (v=vs.98).aspx

于 2013-11-14T04:06:17.007 回答