ASP.NET数据绑定包装类的框架

时间:2011-12-13 22:28:08

标签: c# asp.net data-binding dynamic proxy-classes

显然是ASP.NET doesn't allow data-binding to dynamic objects。主要是糟糕的,因为我可以看到这样的语法非常有用:

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()返回的动态对象(因为you can't implement IEnumerable<dynamic>)有效地向包装的数据对象添加属性。对FirstName和LastName的请求将由真实对象“提供”,并且对FullName的请求将被路由到要动态评估的委托或表达式。

这是一个简单的例子,因为在大多数情况下,您可以轻松地将FullName属性添加到User对象,并且可以使用TemplatedField轻松地将其关闭。

但是如果添加的属性在没有几行数据绑定代码隐藏的TemplatedField中实现太难了怎么办?如果你没有控制User类的源代码怎么办?或者如果你不能将属性添加到User,因为它的计算依赖于一个程序集,而该程序集本身依赖于用户的程序集? (循环参考问题)

由于这个原因,拥有一个非常容易应用的数据绑定包装器会非常棒,这样你就不必每次都生成一个全新的类。

那么我真正追求的是什么?

有没有允许这种事情的框架或技术?上面的确切语法并不重要,只是能够动态地向类添加内容并在数据绑定中使用这些代理,而无需一堆手动管道代码。

4 个答案:

答案 0 :(得分:3)

我找到了三种使用C#解决(部分)问题的方法,以及使用Visual Studio工具扩展其中一些方法的方法。

匿名类型

ASP.NET可以将数据绑定到anonymous types

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") %>

数据绑定语法应该放在ItemTemplate的{​​{1}}内,但为了简洁,我省略了该代码。当然,最后一个属性也可以使用<asp:TemplateField>

显示
<asp:BoundField>

请注意,您不必在匿名类型中映射原始类型的每个属性,您只需将一个属性映射到原始对象即可。 (唯一?)缺点是您不能再将<asp:BoundField DataField="FullName" /> 用于这些属性,但必须使用<asp:BoundField>

扩展方法

为了补充这种方法,即使您无法访问类的来源,也可以使用extension methods将“附加”方法添加到类中:

<asp:TemplateField>

对于数据绑定,我们必须使用public static class UserExtensions { public static string GetFullName(this User user) { return user.FirstName + " " + user.LastName; } }

<asp:TemplateField>

部分课程

自C#2.0以来可用的另一个选项是编写partial class,但前提是原始类也被声明为partial并在项目中声明(同一模块的一部分)。如果使用工具生成<%# Eval("User.FirstName") %> <%# Eval("User.LastName") %> <%# (Container.DataItem as User).GetFullName() %> 类,则此方法很有用,例如,如果在项目中使用某种自动数据库映射器工具。

User

对于数据绑定,我们现在回到使用'':

public partial class User
{
    public string FullName
    {
        get { return this.FirstName + " " + this.LastName; }
    }
}

这些都是C#编译器和.NET运行时的可能性,因此它们属于技术类而不是框架类。当然,也可以使用基本继承,但它可能不适用于您的情况?

T4文本模板

如果您对数据绑定类的外观有非常具体的需求但不能使用上述任何方法,那么您始终可以在Visual Studio中查看T4 templates。 (它们在ASP.NET Web应用程序项目中工作,但在ASP.NET Web站点项目中不工作。)

使用这些模板,您可以在设计时生成代码,例如创建一个浅的部分类<asp:BoundField DataField="FirstName" /> <asp:BoundField DataField="LastName" /> <asp:BoundField DataField="FullName" /> ,它将所有属性透明地映射到内部User对象。然后,使用部分类方法,您可以使用.cs文件中的另一个部分类声明向此类型添加额外的属性和方法,并简单地将数据绑定到UserViewModel

UserViewModel

使用DataGrid.DataSource = GetAllUsers(). .AsQueryable() .Select(u => new UserViewModel(u)); DataGrid.DataBind()

再次使数据绑定变得简单
<asp:BoundField>

使用T4模板,您可以自动为所有域类型生成这些自定义视图模型类。在T4中使用反射时,需要注意:

答案 1 :(得分:2)

您可能需要查看Clay库(请参阅this great overview):

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,就像真正的非动态对象一样,如果我正在读这篇文章。

答案 2 :(得分:1)

实现此目的的一种方法是使用asp:TemplateField。

您也可以使用Dynamic Linq执行此操作,请参阅ScottGu's blog about the basics of 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;

答案 3 :(得分:1)

After reading Jesse Smith's answer regarding the Clay library,我看着克莱,并认为它不适合我所追求的。然而,内部Clay使用Castle Project's DynamicProxy library,并且确实有一些有趣的东西,虽然不完美,但肯定会接近我希望存在的东西。

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; }
    }
}

现在对于数据绑定,我真的想在可枚举上使用它,因此扩展方法可以完成创建代理和添加mixin的工作。我们将允许调用代码使用Func<T, object>提供mixins(基本上只是对象),以便我们可以使用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以提供mixins时,我们返回一个新的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();
    }
}

我真的想要一些你可以通过定义一些lambdas而不定义一个额外的接口或实现类来创建代理的东西,但这似乎是唯一的方法。当然,你必须考虑是否真的值得发布这些自定义类型来完成这项工作与其他方法。

Here is a gist of the he full code,所以你不必组装它就试一试。