解析 ASP.NET MVC 魔法 - 如何從 o => o.PropName 拿到屬性相關資訊?
3 | 2,523 |
早期 ASP.NET MVC View 有種寫法讓我覺得很酷:參考 ASP.NET MVC 3 豬走路範例 (4)
<div class="editor-label">
@Html.LabelFor(model => model.Score)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.Score)
@Html.ValidationMessageFor(model => model.Score)
</div>
LabelFor()、EditorFor()、ValidationMessageFor() 這些方法,可以從由 o => o.Score 得知 Score 的中文名稱是分數,範圍是 0-65536,這是怎麼辦到的?
[Required]
[Display(Name = "分數")]
[Range(0, 65535,
ErrorMessage = "範圍: 0 - 65535")]
public int Score { get; set; }
查詢 LabelFor 介面規格,會發現 o => o.Score 對映的參數型別其實是 ExpressionTree<TModel,TValue>:
public static MvcHtmlString LabelFor<TModel,TValue> (this HtmlHelper<TModel> html, Expression<Func<TModel,TValue>> expression);
關於 Expression Tree 的詳細介紹,推薦這篇:C# 筆記:Expression Trees by Huan-Lin 學習筆記,這篇則聚焦如何像 LabelFor 一樣抓到 [Display] 的 Name?
微軟文件可以找到解析 ExpressionTree 的簡單範例,ExpressionTree 由 Left, Right, Body, Parameters 組合而成,以 num => num < 5
為例,Paramters[0] 跟 Left 是 ParameterExpression,Name = num、小於是 Body,型別為 BinaryExpression,NodeType 為 LessThan,Right 則為 ConstantExpression,Value = 5。表達式的變化很多,也可以弄到極複雜,複雜的就會層很多層(例如:&& || 括號 等),遇上了需細心折解,原理相同。
至於要取得屬性的 Attribute 資訊,做法是將 Body 轉成 MemberExpression,將 MemberExpression.Member 轉成 PropertyInfo,後面 GetCustomAttributes() 取資訊部分就是標準 Relection 操作,不需多做介紹。
public class Player
{
[Required]
[Display(Name = "分數")]
[Range(0, 65535, ErrorMessage = "範圍: 0 - 65535")]
public int Score { get; set; }
}
public class MyHelper<TModel>
{
public void ShowAttributes<TValue>(Expression<Func<TModel, TValue>> expression)
{
if (expression.Body is System.Linq.Expressions.MemberExpression membExpr)
{
//https://stackoverflow.com/a/672212/288936
if (membExpr.Member is PropertyInfo propInfo)
{
var disp = propInfo.GetCustomAttributes(typeof(DisplayAttribute), false);
if (disp.Length > 0)
{
if (disp[0] is DisplayAttribute da)
Console.WriteLine($"Display = {da.Name}");
}
var rang = propInfo.GetCustomAttributes(typeof(RangeAttribute), false);
if (rang.Any()) {
if (rang[0] is RangeAttribute ra)
Console.WriteLine($"Range = {ra.Minimum} to {ra.Maximum}");
}
}
}
else
{
Console.WriteLine("Not a property.");
}
}
}
void Main()
{
var helper = new MyHelper<Player>();
helper.ShowAttributes(p => p.Score);
}
就醬,我們也有能力由 p => p.Score
取得 [Display(Name = "分數")] 跟 [Range(0, 65535, ErrorMessage = "範圍: 0 - 65535")] 的內容囉~
This article demostrating how to get custom attributes through o => o.propName (ExpressionTree)?
Comments
# by 黑粉
黑大, 您好 https://blog.darkthread.net/2011/08/csharp-expression-trees.html C# 筆記:Expression Trees by Huan-Lin 學習筆記 => 這個連結好像返回404看不到了
# by Jeffrey
to 黑粉,已修正,謝提醒。
# by Jurio
expression.Body有時會是UnaryExpression,MemberExpression可以從它的Operand抓到