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