[英]Framework for ASP.NET data-binding wrapper classes
顯然, ASP.NET不允許將數據綁定到動態對象 。 莫名其妙,因為我看到這樣的語法非常有用:
public class User
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
...
// No this doesn't exist, I just wish it did!
MyGrid.DataSource = GetAllUsers()
.AsDynamic()
.WithProperty("FullName", user => user.FirstName + " " + user.LastName)
.ToEnumerable(); // returns IEnumerable<dynamic>
MyGrid.DataBind()
...
<asp:BoundField DataField="FirstName" HeaderText="First Name" />
<asp:BoundField DataField="LastName" HeaderText="Last Name" />
<asp:BoundField DataField="FullName" HeaderText="Full Name" />
在此示例中, AsDynamic()
將返回一個類,該類將配置稍后由.ToEnumerable()
返回的動態對象(因為您無法實現IEnumerable<dynamic>
),從而有效地向包裝的數據對象添加屬性。 對FirstName和LastName的請求將由實際對象“服務”,並且對FullName的請求將路由到要動態評估的委托或表達式。
這是一個簡單的示例,因為在大多數情況下,您可以輕松地將FullName屬性添加到User對象,並且可以使用TemplatedField輕松實現。
但是,如果沒有后面幾行數據綁定代碼,添加的屬性太難以在TemplatedField中實現呢? 如果您不控制User類的源代碼怎么辦? 或者,如果由於屬性的計算依賴於程序集而又不能依賴於用戶的程序集而又無法將其添加到User中,該怎么辦? (循環參考題)
因此,擁有這樣一個非常易於應用的數據綁定包裝器將是很棒的,您不必每次都生成一個全新的類。
那我到底在追求什么?
有沒有允許這種事情的框架或技術? 上面的確切語法並不是很重要,只是能夠動態地向類中添加內容並在數據綁定中使用這些代理,而無需一堆手動管道代碼。
我發現使用C#解決(某些)問題的三種方法,以及使用Visual Studio工具擴展其中一些方法的方法。
ASP.NET可以將數據綁定到匿名類型 :
DataGrid.DataSource = GetAllUsers().
.AsQueryable()
.Select(u => new { User = u, FullName = GetFullName(u) });
DataGrid.DataBind()
匿名類型仍然可以輕松訪問原始類型(在此示例中,通過User
屬性)。 這將使數據綁定相對容易(使用<asp:TemplateField>
),並且您已將復雜的邏輯移至對User
對象進行操作的單獨方法。
<%# Eval("User.FirstName") %>
<%# Eval("User.LastName") %>
<%# Eval("FullName") %>
數據綁定語法應放在<asp:TemplateField>
的ItemTemplate
內,但是為了簡潔起見,我省略了該代碼。 當然,最后一個屬性也可以使用<asp:BoundField>
:
<asp:BoundField DataField="FullName" />
請注意,您不必將原始類型的每個屬性都映射為匿名類型,只需將一個屬性映射到原始對象即可。 (唯一的?)缺點是您不能再為這些屬性使用<asp:BoundField>
,而必須使用<asp:TemplateField>
。
為了補充這種方法,即使您無權訪問類的源代碼,也可以使用擴展方法將方法 “附加”到類上:
public static class UserExtensions
{
public static string GetFullName(this User user)
{
return user.FirstName + " " + user.LastName;
}
}
對於數據綁定,我們必須使用<asp:TemplateField>
:
<%# Eval("User.FirstName") %>
<%# Eval("User.LastName") %>
<%# (Container.DataItem as User).GetFullName() %>
自C#2.0起可用的另一種選擇是編寫局部類 ,但前提是原始類也被聲明為局部類並且在您的項目中聲明(屬於同一模塊)。 如果用工具生成User
類,例如,如果您在項目中使用某種自動數據庫映射器工具,則此方法很有用。
public partial class User
{
public string FullName
{
get { return this.FirstName + " " + this.LastName; }
}
}
對於數據綁定,我們現在回到使用”:
<asp:BoundField DataField="FirstName" />
<asp:BoundField DataField="LastName" />
<asp:BoundField DataField="FullName" />
這些都是C#編譯器和.NET運行時的全部可能性,因此它們屬於技術而不是框架。 當然,也可以使用基本繼承,但是它可能不適用於您的情況嗎?
如果您對數據綁定類的外觀有非常特定的需求,但不能使用上述任何方法,則可以隨時在Visual Studio中查看T4模板 。 (它們在ASP.NET Web應用程序項目中有效,但在ASP.NET Web站點項目中無效。)
使用這些模板,您可以在設計時生成代碼,例如,創建淺的部分類UserViewModel
,該類將所有屬性透明地映射到內部User對象。 然后,使用部分類方法,可以使用.cs文件中的另一個部分類聲明為該類型添加額外的屬性和方法,並且只需將數據綁定到UserViewModel
:
DataGrid.DataSource = GetAllUsers().
.AsQueryable()
.Select(u => new UserViewModel(u));
DataGrid.DataBind()
數據綁定再次使用<asp:BoundField>
變得簡單:
<asp:BoundField DataField="FirstName" />
<asp:BoundField DataField="LastName" />
<asp:BoundField DataField="FullName" />
使用T4模板,您可以為所有域類型自動生成這些自定義視圖模型類。 在T4中使用反射時,有一些警告:
您可能想研究Clay庫(請參閱此概述 ):
public interface IUser {
public string FirstName { get; set; }
public string LastName { get; set; }
public string FullName { get; set; }
}
dynamic New = new ClayFactory();
existingUser = //grab your existing user here
IUser clayUser = New.User(){
FirstName: existingUser.FirstName,
LastName: existingUser.LastName,
FullName: existingUser.FirstName + " " + existingUser.LastName;
當然,尤其是在語法上,有多種方法可以使貓咪皮膚。 另外,我還沒有深入研究它(這是您的工作!;),所以我不知道Clay對象是否可以與現有對象結合,或者您是否需要從現有對象中填充新的Clay用戶像我一樣 重要的是,如果您從接口繼承它們,則Clay對象將生活在CLR中,並獲得Intellisense,並且如果我正確地閱讀了這篇文章,它們的行為就像真正的非動態對象一樣。
實現此目的的一種方法是使用asp:TemplateField。
您還可以使用Dynamic Linq來執行此操作,請參閱ScottGu的博客,了解Dynamic Linq的基礎知識 。
然后,您可以使用Dynamic Linq創建語句的動態選擇部分。 這是一些用於創建select語句的代碼,這些語句選擇所有基礎對象的屬性並基於動態表達式創建其他屬性。
public class ExtraProperty
{
public string Name { get; set; }
public string Expression { get; set; }
}
/// <summary>
/// Creates a string on the form "new (property1, property2, ..., expression1 as extraproperty1, ... )
/// </summary>
/// <param name="t"></param>
/// <param name="extraProperties"></param>
/// <returns></returns>
public string CreateSelectClauseWithProperty(Type objecType, ExtraProperty[] extraProperties)
{
string ret = "new(";
bool notFirst = false;
System.Reflection.PropertyInfo[] typeProps = objecType.GetProperties();
// Equivalent of "Select objectType.*"
foreach (System.Reflection.PropertyInfo p in typeProps)
{
if (notFirst)
ret += ",";
else
notFirst = true;
ret += p.Name;
}
// Equivalent of "expression1 as name1, expression2 as name2, ..." - giving the extra columns
foreach (ExtraProperty ep in extraProperties)
{
if (notFirst)
ret += ",";
else
notFirst = true;
ret += ep.Expression + " as " + ep.Name;
}
return ret + ")";
}
樣例使用如下:
MyGrid.AutoGenerateColumns = false;
string selectClause = CreateSelectClauseWithProperty(typeof(User),
new ExtraProperty[] {
new ExtraProperty()
{ Name = "FullName", Expression = "FirstName + \" \" + LastName" }
}
);
IQueryable<User> list = GetAllUsers();
var query = list.Select( selectClause );
MyGrid.DataSource = query;
MyGrid.DataBind();
您需要在標頭中添加以下內容:
using System.Linq.Dynamic;
在閱讀了Jesse Smith關於Clay庫的答案之后 ,我調查了Clay並認為它與我所追求的不完全匹配。 但是,Clay在內部使用Castle Project的DynamicProxy庫 ,該庫中確實包含一些有趣的內容,盡管並不完美,但確實可以滿足我希望的存在。
Castle DynamicProxy可以通過發出代碼,然后攔截對它的調用來創建對象的代理。 關於您的業務對象的唯一要求是,方法和屬性需要標記為virtual
,以便Castle攔截對它們的調用。
然后,您可以將“ mixins”添加到代理對象。 我將以該問題的用戶示例進行演示:
public class User
{
public virtual string FirstName { get; set; }
public virtual string LastName { get; set; }
}
如果我們想將FullName添加到該對象的代理中,則需要做一些工作以使其實現,方法是創建一個聲明屬性的接口,然后創建一個可以從現有用戶提供值的實現對象。 :
public interface IUserProxy
{
string FullName { get; }
}
public class UserProxyImpl : IUserProxy
{
public User User { get; set; }
public string FullName
{
get { return User.FirstName + " " + User.LastName; }
}
}
現在,對於數據綁定,我真的很想在枚舉上使用它,因此擴展方法可以完成創建代理和添加混合的工作。 我們將允許調用代碼使用Func<T, object>
提供混合(基本上只是對象) Func<T, object>
以便我們可以使用lambda表達式進行定義:
public static class ProxyExtensions
{
public static IEnumerable<T> ProxyAddMixins<T>(this IEnumerable<T> collection, params Func<T, object>[] mixinSelectors)
where T : class
{
ProxyGenerator factory = new ProxyGenerator();
foreach (T item in collection)
{
ProxyGenerationOptions o = new ProxyGenerationOptions();
foreach (var func in mixinSelectors)
{
object mixin = func(item);
o.AddMixinInstance(mixin);
}
yield return factory.CreateClassProxyWithTarget<T>(item, o);
}
}
}
然后我們的客戶端代碼(我在Windows控制台應用程序中對其進行了模擬,因為它更易於測試)看起來像這樣。 當到達提供混合包的lambda時,我們返回一個新的UserProxyImpl
,將基礎User
對象傳遞給它。 Castle分析了UserProxyImpl
,注意到它實現了IUserProxy
,並使發出的代理類使用該實現來實現該接口。 所有其他屬性均流經原始對象的虛擬實現,而不會被代理攔截。
class Program
{
static void Main(string[] args)
{
List<User> users = new List<User>();
users.Add(new User { FirstName = "John", LastName = "Doe" });
users.Add(new User { FirstName = "Jane", LastName = "Doe" });
var userProxies = users
.ProxyAddMixins(u => new UserProxyImpl { User = u })
.ToList();
Console.WriteLine("First\tLast\tFull");
foreach (var userProxy in userProxies)
{
Console.WriteLine("{0}\t{1}\t{2}",
DataBinder.Eval(userProxy, "FirstName"),
DataBinder.Eval(userProxy, "LastName"),
DataBinder.Eval(userProxy, "FullName"));
}
Console.ReadLine();
}
}
我確實想要一些您可以僅通過定義一些lambda而無需定義其他接口或實現類來創建代理的方法,但這似乎是唯一的方法。 當然,與其他方法相比,是否真的值得發出這些自定義類型來完成此工作需要考慮。
這是完整代碼的要點 ,因此您無需進行匯編即可嘗試。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.