【问题标题】:How do I update the Order property on a DisplayAttribute programmatically?如何以编程方式更新 DisplayAttribute 上的 Order 属性?
【发布时间】:2017-01-22 17:05:33
【问题描述】:

给定以下代码:

using System.ComponentModel.DataAnnotations;
using System.Linq;
using Xunit;

namespace Company.Tests
{
    public class MyObject
    {
        [Display(Order = 1000)]
        public virtual string StringPropertyB { get; set; }

        [Display(Order = 2000)]
        public virtual string StringPropertyA { get; set; }
    }

    public class MyObjectTest
    {
        [Fact]
        public void X()
        {
            var properties = typeof(MyObject).GetProperties();
            var stringPropertyBPropertyInfo = properties[0];
            var stringPropertyAPropertyInfo = properties[1];

            // Debugger Display = "{[System.ComponentModel.DataAnnotations.DisplayAttribute(Order = 1000)]}"
            var bDisplayAttribute = stringPropertyBPropertyInfo.GetCustomAttributesData().FirstOrDefault();

            // Debugger Display = "{[System.ComponentModel.DataAnnotations.DisplayAttribute(Order = 2000)]}"
            var aDisplayAttribute = stringPropertyAPropertyInfo.GetCustomAttributesData().FirstOrDefault();
        }
    }
}

如何以编程方式更新 Order 属性?

我想在它设置为新值后对其进行更新。 (用例不必指定 Order,而是自动为 Order 分配一个值,以匹配 MyObject 上的属性从上到下出现的顺序。)

【问题讨论】:

  • 如果您需要更改它,我想您可能只想将其存储在一个属性中。
  • 我可能错了,但在我看来,在运行时修改属性似乎会破坏范式。如果我是你,我会以其他方式重新排列你的属性。
  • 但是我已经很接近了... :) 一直在尝试... var property = cad.AttributeType.GetProperty("Order"); property.SetValue(whatToUseHere, 30000); 只是想不通whatToUseHere 应该使用什么
  • 这可能有助于为您指明正确的方向:stackoverflow.com/questions/2160476/… 我也知道您可以在运行时使用反射访问属性: public static object GetAttribute(object parent, Type attributeType) { var property = parent .GetType().GetProperties().Where(prop => Attribute.IsDefined(prop, attributeType)).Single(); var 属性 = property.GetCustomAttribute(attributeType);返回属性; }
  • 另外,你也可以检查对象是否有一个私有的“set”值......如果我今天晚些时候有时间,并且没有其他人弄清楚,我会一起扔一些东西看看我能不能给你POC。

标签: c# reflection


【解决方案1】:

正如 Skeet 先生在答复中所写,Thumper 指出,据我所知,这无法做到。您可以更新 Order 值,但它不会持续存在:

using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Reflection;
using Shouldly;
using Xunit;

namespace Tests
{
    public class ObjectWithDisplayOrder
    {
        [Display(Order = 0)]
        public virtual string StringPropertyB { get; set; }

        [Display(Order = 0)]
        public virtual string StringPropertyA { get; set; }
    }

    public class DisplayOrderTests
    {
        [Fact]
        public void ShouldUpdateDisplayOrderProperty()
        {
            const int updatedOrderValue = 1000;

            var properties = typeof(ObjectWithDisplayOrder).GetProperties();
            foreach (var property in properties)
            {
                var displayAttribute = (DisplayAttribute) property.GetCustomAttributes().First(a => a is DisplayAttribute);
                var props = displayAttribute.GetType().GetProperties(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public).ToList();
                props.Single(p => p.Name == "Order").SetValue(displayAttribute, updatedOrderValue);
                // displayAttribute Order is 1000 here, but it's not persisted...
            }

            foreach (var property in properties)
            {
                var displayAttribute = (DisplayAttribute) property.GetCustomAttributes().First(a => a is DisplayAttribute);
                displayAttribute.GetOrder().ShouldBe(updatedOrderValue); // Fails - Order is still 0
            }
        }
    }
}

【讨论】:

    【解决方案2】:

    更新答案: 因为属性是“静态元数据”,所以它们在编译后无法持久更改。 (Can attributes be added dynamically in C#?)

    因此,完成 OP 对属性值进行持久更改的请求的一种方法是在运行时编译代码。如果这是需要的,那么可能值得认真考虑一种不同的方法来解决您要解决的问题,因为这对您来说意味着更多的工作(但这对我来说非常酷)。

    还可以使用称为 TypeBuilder (How Can I add properties to a class on runtime in C#?) 的东西。我对此并不熟悉,但它看起来很有希望。它看起来也确实使用运行时编译。

    运行时编译:

    这是实现此目的的一种方法,即在运行时编译代码。 (使用 NUnit,而不是 XUnit,就像 OP 所做的那样):

    namespace OpUnitTest {
    
        [TestClass]
        public class OpTest{
    
            //Use some web templating model so we can easily change it later (#=variable#)
            string myClassToCompile = @"
    
    using System.ComponentModel.DataAnnotations;
    
    namespace Test {
        public class ObjectWithDisplayOrder {
            [Display(Order = #=0#)]
            public virtual string StringPropertyB { get; set; }
    
            [Display(Order = #=1#)]
            public virtual string StringPropertyA { get; set; }
        }
    }
    ";
            [TestMethod]
            public void AssignAtributeValuesDynamically() {
    
                const int order = 1000;     
    
                //Escape curly braces.
                myClassToCompile = myClassToCompile.Replace("{", "{{").Replace("}", "}}");
    
                //We could use Regex, or even a for loop, to make this more-elegant and scalable, but this is a Proof of Concept.
                myClassToCompile = myClassToCompile.Replace("#=0#", "{0}").Replace("#=1#", "{1}");
    
                myClassToCompile = string.Format(myClassToCompile, order, order);
    
                CSharpCodeProvider provider = new CSharpCodeProvider();
                CompilerParameters parameters = new CompilerParameters();
                parameters.ReferencedAssemblies.Add("System.ComponentModel.DataAnnotations.dll");
                parameters.GenerateInMemory = true;
                CompilerResults results = provider.CompileAssemblyFromSource(parameters, myClassToCompile);
    
                //You would normally check for compilation errors, here.
    
                Assembly assembly = results.CompiledAssembly;
                Type myCompiledObject = assembly.GetType("Test.ObjectWithDisplayOrder");
                PropertyInfo[] properties = myCompiledObject.GetProperties();
    
                foreach (var property in properties) {
                    var displayAttribute = (DisplayAttribute)property.GetCustomAttributes().First(a => a is DisplayAttribute);
                    Assert.AreEqual(order, displayAttribute.GetOrder());
    
                }
            }
    }
    

    一个很好的 C# 运行时编译入门指南(我有点喜欢):http://www.codeproject.com/Tips/715891/Compiling-Csharp-Code-at-Runtime

    原答案:

    从技术上讲,它表明您确实可以更改属性的值,但正如 OP 指出的那样 - 这不会持续存在。

    using System.ComponentModel.DataAnnotations;
    using System.Linq;
    using Xunit;
    
    namespace Company.Tests
    {
        public class MyObject
        {
            [Display(Order = 1000)]
            public virtual string StringPropertyB { get; set; }
    
            [Display(Order = 2000)]
            public virtual string StringPropertyA { get; set; }
        }
    
        public class MyObjectTest
        {
            [Fact]
            public void X()
            {
                var properties = typeof(MyObject).GetProperties();
                var stringPropertyBPropertyInfo = properties[0];
                var bDisplayAttribute = (DisplayAttribute)stringPropertyBPropertyInfo.GetCustomAttributes().First();
                var props = bDisplayAttribute.GetProperties(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public).ToList();
                props.Single(p => p.Name == "Order").SetValue(bDisplayAttribute, 5);
    
            }
        }
    }
    

    【讨论】:

    • 谢谢,但我无法让价值观保持不变,请参阅我的回答。但“A”代表努力! :)
    • 我决心完成这项工作。我会看看我能用你的答案做什么(你应该把它作为你帖子的更新发布)
    • @vsdev - 检查一下。从技术上讲,它回答了你的问题——但这意味着你还有很多工作要做。可能值得研究一种不同的方式来做到这一点 - 您需要使用“显示”的用例是什么?
    • 谢谢,我使用的框架会动态扫描 DisplayAttribute 并根据该属性的 Order 在 UI 中设置顺序。所以我不能使用运行时编译或类似的东西。手动设置 Order 属性以使排序顺序正确并不是那么麻烦。我只是在寻找一种设置默认值的方法。谢谢,我会接受你的回答。 :)
    • 嗯...如果是动态扫描,应该可以在运行时编译。另外,框架是否在您的控制之下?因为如果是这样,您绝对可以查看 CsvReader 使用的模型 - 它允许在运行时装饰属性。您可以使用映射 + 设置框架来支持它。
    【解决方案3】:

    而不是你有什么做这个

    public class MyObject
    {
        [DefaultValue(1000)]
        public virtual int StringPropertyBOrder { get; set; }
        public virtual string StringPropertyB { get; set; }
    
        [DefaultValue(2000)]
        public virtual int StringPropertyAOrder { get; set; }
        public virtual string StringPropertyA { get; set; }
    }
    

    【讨论】:

    • 但是他如何让StringPropertyBOrder 做与他使用[Order] 相同的事情呢?不管是什么
    • 是的,我需要使用 DisplayAttribute(它是密封的......),因为这是我的底层框架使用的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-09-30
    • 1970-01-01
    • 1970-01-01
    • 2021-05-06
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多