繁体   English   中英

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

[英]How do I update the Order property on a DisplayAttribute programmatically?

鉴于以下代码:

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 上的属性从上到下出现的顺序。)

正如 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
            }
        }
    }
}

而不是你所拥有的做这个

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; }
}

更新答案:因为属性是“静态元数据”,编译后不能持久更改。 可以在 C# 中动态添加属性吗?

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

还有可能使用称为TypeBuilder 的东西( 如何在 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);

        }
    }
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM