早期 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 Jeffrey

to 黑粉,已修正,謝提醒。

# by Jurio

expression.Body有時會是UnaryExpression,MemberExpression可以從它的Operand抓到

Post a comment