4

解析 ASP.NET MVC 魔法 - 如何從 o => o.PropName 拿到屬性相關資訊?

 1 year ago
source link: https://blog.darkthread.net/blog/expresstree-analysis/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

如何從 o => o.PropName 拿到屬性相關資訊?-黑暗執行緒

早期 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>
1131-8e05-o.gif

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")] 的內容囉~


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK