[英]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.