C# 迭代lambda表达式的属性

C# 迭代lambda表达式的属性,c#,.net,lambda,expression-trees,C#,.net,Lambda,Expression Trees,我试图泛化一个复杂的控件,这个控件在我的网站上经常使用,但有不同的字段。控件中的功能总是相同的,只是底层字段发生了变化 为了实现显示不同字段的方法,我正在尝试创建一个HTMLHelper扩展,它接受一个表达式作为参数,它将包含控件中显示所需的类的属性。例如: 观点: @model Project.Core.Page @Html.MyHelper(p => new { p.Author.Name, p.Author.Location, p.Author.Age }); 这是我遇到问题的扩展

我试图泛化一个复杂的控件,这个控件在我的网站上经常使用,但有不同的字段。控件中的功能总是相同的,只是底层字段发生了变化

为了实现显示不同字段的方法,我正在尝试创建一个HTMLHelper扩展,它接受一个
表达式
作为参数,它将包含控件中显示所需的类的属性。例如:

观点:

@model Project.Core.Page
@Html.MyHelper(p => new { p.Author.Name, p.Author.Location, p.Author.Age });
这是我遇到问题的扩展-我如何迭代lambda中提供的参数,为每个参数提供
TextBoxFor()
,或者甚至手动创建
input
元素,并用lambda参数的
值和
名称填充它

psuedo中的扩展:

public static MvcHtmlString MyHelper<TModel,TProperty>(
    this HtmlHelper<TModel> helper, 
    Expression<Func<TModel,TProperty>> expression) {
    foreach (var parameter in expression.???) {
        // helper.TextBoxFor(???) 
        // TagBuilder("input").Attributes("name", expression.???)
    }
}
publicstaticmvchtmlstring-MyHelper(
这个HtmlHelper助手,
表达式(表达式){
foreach(表达式中的var参数){
//用于(???)的helper.textbox
//标记生成器(“输入”)。属性(“名称”,表达式。?)
}
}
我觉得我已经盯着这个太久了,我也觉得有一个更简单的方法我忽略了实现这一点


非常感谢您的帮助。如果您需要进一步的详细信息,或者我遗漏了一些重要的内容,请告诉我。

您将创建的表达式将相对复杂-它需要获取所有属性,然后调用匿名类型构造函数。“拆卸”可能会让人痛苦。。。虽然如果您仍然想尝试,我建议只留下一个空的方法实现,然后查看调试器,看看表达式是什么样子

如果您愿意接受一种稍微丑陋的调用代码形式,那么实现它会简单得多:

@Html.MyHelper(p => p.Author.Name, p => p.Author.Location, p => p.Author.Age);
您可以使其采用
params表达式
,也可以使用不同数量的参数声明多个重载,例如

// Overload for one property
MyHelper<TModel, TProperty1>(this ..., Expression<Func<TModel, TProperty1>> func1)

// Overload for two properties
MyHelper<TModel, TProperty1, TProperty2>(this ...,
   Expression<Func<TModel, TProperty1>> func1,
   Expression<Func<TModel, TProperty2>> func2)
//一个属性的重载
MyHelper(此…,表达式func1)
//两个属性的重载
我的助手(这个。。。,
表达式func1,
表达式(2)

等等。

如果您假设:

  • 输入表达式的结果是一个投影(返回一个新对象, (匿名或其他)
  • 投影的元素都是
    成员表达式
    ,不包含对模型或其子对象的方法的调用
  • 然后,您可以使用以下方法实现您想要的:

    编辑:

    在意识到我的第一个示例无法处理具有复杂属性的对象之后,我更新了代码以使用helper方法访问属性值。此方法使用递归遍历属性链以返回适当的值

    public static MvcHtmlString MyHelper<TModel,object>(
        this HtmlHelper<TModel> helper, 
        Expression<Func<TModel,object>> expression) {
    
            var newExpression = expression.Body as NewExpression;
            TModel model = helper.ViewData.Model;
    
            foreach (MemberExpression a in newExpression.Arguments) {
    
                var propertyName = a.Member.Name;
                var propertyValue = GetPropertyValue<TModel>(model, a);
    
                // Do whatever you need to with the property name and value;
    
            }
    
        }
    
        private static object GetPropertyValue<T>(T instance, MemberExpression me) {
    
            object target;
    
            if (me.Expression.NodeType == ExpressionType.Parameter) {
                // If the current MemberExpression is at the root object, set that as the target.            
                target = instance;
            }
            else {                
                target = GetPropertyValue<T>(instance, me.Expression as MemberExpression);
            }
    
            // Return the value from current MemberExpression against the current target
            return target.GetType().GetProperty(me.Member.Name).GetValue(target, null);
    
        }
    
    publicstaticmvchtmlstring-MyHelper(
    这个HtmlHelper助手,
    表达式(表达式){
    var newExpression=expression.Body作为newExpression;
    TModel model=helper.ViewData.model;
    foreach(newExpression.Arguments中的MemberExpression a){
    var propertyName=a.Member.Name;
    var propertyValue=GetPropertyValue(模型,a);
    //使用属性名称和值执行任何需要的操作;
    }
    }
    私有静态对象GetPropertyValue(T实例,MemberExpression me){
    目标对象;
    if(me.Expression.NodeType==ExpressionType.Parameter){
    //如果当前MemberExpression位于根对象,则将其设置为目标。
    目标=实例;
    }
    否则{
    target=GetPropertyValue(实例,me.Expression作为MemberExpression);
    }
    //根据当前目标从当前MemberExpression返回值
    返回target.GetType().GetProperty(me.Member.Name).GetValue(target,null);
    }
    

    注意:我没有在我的IDE中直接将其作为MVC扩展方法来实现,因此可能需要对语法稍作修改。

    也许构建器风格的API可以简化:

    @(Html.MyHelper(p)
        .Add(p => p.Author.Age)
        .Add(p => p.Author.LastName, "Last Name")
        .Build())
    
    请注意,这允许您在需要时添加可选参数

    代码应该是这样的

    public static class Test
    {
        public static Helper<TModel> MyHelper<TModel>(this HtmlHelper helper, TModel model)
        {
            return new Helper<TModel>(helper, model);
        }
    }
    
    public class Helper<TModel>
    {
        private readonly HtmlHelper helper;
        private readonly TModel model;
    
        public Helper(HtmlHelper helper, TModel model)
        {
            this.helper = helper;
            this.model = model;
        }
    
        public Helper<TModel> Add<TProperty>(Expression<Func<TModel, TProperty>> expression)
        {
            // TODO
            return this;
        }
    
        public MvcHtmlString Build()
        {
            return new MvcHtmlString("TODO");
        }
    }
    
    公共静态类测试
    {
    公共静态帮助程序MyHelper(此HtmlHelper帮助程序,TModel模型)
    {
    返回新辅助对象(辅助对象、模型);
    }
    }
    公营助理员
    {
    私人只读HtmlHelper助手;
    私有只读TModel模型;
    公共助手(HtmlHelper助手,TModel模型)
    {
    this.helper=helper;
    this.model=模型;
    }
    公共助手添加(表达式)
    {
    //待办事项
    归还这个;
    }
    公共MvcHtmlString生成()
    {
    返回新的MvcHtmlString(“TODO”);
    }
    }
    
    考虑一下:

    创建应用程序代码文件夹

    将razor助手文件Templates.cshtml放入

    如下所示:

     @helper Print(string myvalue,string special="")
     {
       <pre> <input id="id" type="text" value ="@myvalue" data-val="@special"/> </pre>
     }
    
    @model IEnumerable<MvcApplication1.Models.Book>
    @{
        ViewBag.Title = "Books";
     }
    
    <h2>Books</h2>
    
    @foreach(var book in Model)
    {
       @Templates.Print(book.Title,book.ISBN);    
    }
    
    @helper打印(字符串myvalue,字符串special=”“)
    {
    }
    
    这样,您就不必在C#文件中编写HTML。它非常方便

    Authors.cshtml如下所示:

     @helper Print(string myvalue,string special="")
     {
       <pre> <input id="id" type="text" value ="@myvalue" data-val="@special"/> </pre>
     }
    
    @model IEnumerable<MvcApplication1.Models.Book>
    @{
        ViewBag.Title = "Books";
     }
    
    <h2>Books</h2>
    
    @foreach(var book in Model)
    {
       @Templates.Print(book.Title,book.ISBN);    
    }
    
    @model IEnumerable
    @{
    ViewBag.Title=“作者”;
    }
    作者
    @foreach(模型中的var auth)
    {
    @模板打印(授权名称);
    }
    
    books.cshtml如下所示:

     @helper Print(string myvalue,string special="")
     {
       <pre> <input id="id" type="text" value ="@myvalue" data-val="@special"/> </pre>
     }
    
    @model IEnumerable<MvcApplication1.Models.Book>
    @{
        ViewBag.Title = "Books";
     }
    
    <h2>Books</h2>
    
    @foreach(var book in Model)
    {
       @Templates.Print(book.Title,book.ISBN);    
    }
    
    @model IEnumerable
    @{
    ViewBag.Title=“图书”;
    }
    书
    @foreach(模型中的var账簿)
    {
    @模板.打印(book.Title,book.ISBN);
    }
    

    根据每个模型类的需要插入所有特殊属性。如果变得太复杂,请查看dynamic和expando对象。这取决于您的模型/视图模型有多复杂。

    我曾考虑过这种方法,但正如您所说的有点难看,我将把它作为“方案B”保留下来:)谢谢你的回答,Jon。在重新阅读了问题和我的回答之后,我意识到我的第一个示例无法处理对复杂属性值的访问