c# - 动态表达返回类型Lambda表达式

标签 c# asp.net-mvc lambda

我有一个静态通用 FormBuilder HTML 帮助器方法(HTMLHelper 类上的扩展方法),它采用 View 模型类型的通用参数,然后,当从数据库传递一个或多个字符串属性名称时,生成一个 HTML 表单ASP.NET MVC 5.1 与 .NET 4.5。

我有一个公共(public)方法来生成表单,并使用单独的私有(private)方法来生成表单中的“模块”部分,然后呈现其中的每个字段。类型参数沿着这条链从上到下传递。

在“RenderField”方法中,我使用代码创建了一个类型化的 HtmlHelper-

var typedHelper = helper as HtmlHelper<TModel>;

其中helper是RenderForm方法中扩展的HtmlHelper。

然后我使用代码创建一个表达式-

var modelType = typeof(TModel);

...

var modelProperty = modelType.GetProperty(field.PropertyName);

if (modelProperty == null)
{
    Elmah.ErrorSignal.FromCurrentContext().Raise(new ArgumentException(string.Format("Model {0} does not contain property {1}", modelType.Name, field.PropertyName)));
    return null;
}

var modelPropertyType = modelProperty.PropertyType;

var parameter = Expression.Parameter(modelType, "m");

var property = Expression.Property(parameter, field.PropertyName);

var expression = Expression.Lambda<Func<TModel, object>>(property, parameter);

稍后我可以使用它来创建 EditorFor、DisplayFor 或 ValidationMessageFor,如下所示 -

fieldContainer.InnerHtml += typedHelper.EditorFor(expression);
fieldContainer.InnerHtml += typedHelper.ValidationMessageFor(expression);

这适用于字符串编辑器,但如果我尝试可为空的日期时间,则会收到错误 -

Expression of type 'System.Nullable`1[System.DateTime]' cannot be used for return type 'System.Object'

如果我尝试通过更改以下行将属性转换为对象,就像我在 Jon Skeet 答案中看到的那样 -

var expression = Expression.Lambda<Func<TModel, object>>(Expression.Convert(property, typeof(object)), parameter);

并将编辑器代码更改为 -

var compiledExpression = expression.Compile()(model);

fieldContainer.InnerHtml += typedHelper.EditorFor(compiledExpression);
fieldContainer.InnerHtml += typedHelper.ValidationMessageFor(compiledExpression);

我收到错误消息,无法从使用中推断出类型参数。

如果我将返回类型更改为“动态”,则它会指出“扩展方法无法动态分派(dispatch)”。

我无法指定“modelPropertyType”作为泛型方法的返回参数 - 大概是因为它在编译时不能保证是具体类型。

有没有什么方法可以在运行时动态地将表达式的返回类型指定为属性的返回类型,以便我可以使用 ASP.NET MVC 提供的 EditorFor 辅助方法?

最佳答案

假设您使用 TModel 的通用参数创建一个辅助函数。和TProperty ,您可以在其中构建用于编辑单个模型属性的 html。该助手将收到 HtmlHelper<TModel> 的实例和一个 PropertyInfo它将创建适当的 lambda 表达式,例如 EditorFor要求。助手可能看起来像这样:

private static MvcHtmlString GetPropertyEditor<TModel, TProperty>(HtmlHelper<TModel> htmlHelper, PropertyInfo propertyInfo)
{
    //Get property lambda expression like "m => m.Property"
    var modelType = typeof(TModel);
    var parameter = Expression.Parameter(modelType, "m");
    var property = Expression.Property(parameter, propertyInfo.Name);
    var propertyExpression = Expression.Lambda<Func<TModel, TProperty>>(property, parameter);

    //Get html string with label, editor and validation message
    var editorContainer = new TagBuilder("div");
    editorContainer.AddCssClass("editor-container");
    editorContainer.InnerHtml += htmlHelper.LabelFor(propertyExpression);
    editorContainer.InnerHtml += htmlHelper.EditorFor(propertyExpression);
    editorContainer.InnerHtml += htmlHelper.ValidationMessageFor(propertyExpression);
    return new MvcHtmlString(editorContainer.ToString());
}

该助手将生成一个容器 div <div class="editor-container"></div> ,其内部 html 将包含标签、编辑器和验证消息。

如您所见,帮助器仍然要求您提供属性的泛型类型 TProperty ,当您使用反射循环遍历每个属性时,您将不会拥有它。但是,您也可以使用反射来为每个属性调用此帮助器:

foreach (var propertyInfo in modelType.GetProperties())
{
    var openMethod = typeof(HtmlExtensions).GetMethod("GetPropertyEditor", BindingFlags.Static | BindingFlags.NonPublic);
    var genericMethod = openMethod.MakeGenericMethod(modelType, propertyInfo.PropertyType);
    var editorHtml = genericMethod.Invoke(null, new object[] { htmlHelper, propertyInfo });
    //add editorHtml to the form

}

因此,您可以创建自己的 HtmlHelper 扩展方法,为给定模型生成表单:

public static MvcHtmlString RenderForm<TModel>(this HtmlHelper<TModel> htmlHelper)
{
    var modelType = typeof(TModel);
    var form = new TagBuilder("form");
    foreach (var propertyInfo in modelType.GetProperties())
    {
        //call generic GetPropertyEditor<TModel, TProperty> with the type of this property
        var openMethod = typeof(HtmlExtensions).GetMethod("GetPropertyEditor", BindingFlags.Static | BindingFlags.NonPublic);
        var genericMethod = openMethod.MakeGenericMethod(modelType, propertyInfo.PropertyType);
        var editorHtml = genericMethod.Invoke(null, new object[] { htmlHelper, propertyInfo });
        //add the html to the form
        form.InnerHtml += editorHtml;
    }

    return new MvcHtmlString(form.ToString());
}

给定如下模型:

public class RegisterViewModel
{
    [Required]
    [Display(Name = "User name")]
    public string UserName { get; set; }

    [Required]
    [StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)]
    [DataType(DataType.Password)]
    [Display(Name = "Password")]
    public string Password { get; set; }

    [DataType(DataType.Password)]
    [Display(Name = "Confirm password")]
    [System.ComponentModel.DataAnnotations.Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
    public string ConfirmPassword { get; set; }

    public DateTime? RegisterDate { get; set; }
}

您可以在 View 中使用它,如下所示:

@Html.RenderForm()

它将生成以下 html:

<form>
    <div class="editor-container">
        <label for="UserName">User name</label>
        <input class="text-box single-line" data-val="true" data-val-required="The User name field is required." id="UserName" name="UserName" type="text" value="" /><span class="field-validation-valid" data-valmsg-for="UserName" data-valmsg-replace="true"></span>
    </div>
    <div class="editor-container">
        <label for="Password">Password</label>
        <input class="text-box single-line password" data-val="true" data-val-length="The Password must be at least 6 characters long." data-val-length-max="100" data-val-length-min="6" data-val-required="The Password field is required." id="Password" name="Password" type="password" value="" /><span class="field-validation-valid" data-valmsg-for="Password" data-valmsg-replace="true"></span>
    </div>
    <div class="editor-container">
        <label for="ConfirmPassword">Confirm password</label>
        <input class="text-box single-line password" data-val="true" data-val-equalto="&#39;Confirm password&#39; and &#39;Password&#39; do not match." data-val-equalto-other="*.Password" id="ConfirmPassword" name="ConfirmPassword" type="password" value="" /><span class="field-validation-valid" data-valmsg-for="ConfirmPassword" data-valmsg-replace="true"></span>
    </div>
    <div class="editor-container">
        <label for="RegisterDate">RegisterDate</label>
        <input class="text-box single-line" data-val="true" data-val-date="The field RegisterDate must be a date." id="RegisterDate" name="RegisterDate" type="datetime" value="" /><span class="field-validation-valid" data-valmsg-for="RegisterDate" data-valmsg-replace="true"></span>
    </div>
</form>

您对生成的 html 结构、类名、属性等会有不同的要求,但我希望这可以帮助您完成您正在编写的 FormBuilder!

关于c# - 动态表达返回类型Lambda表达式,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24240598/

相关文章:

c# - 如何限制 C# 中的文本框只接收数字和(点 "."或逗号 ","),在 "."或 ","之后只允许 2 个数字字符

c# - 我如何将 Sevenzipsharp 与此代码一起使用?

asp.net-mvc - 如何在MVC中获取网站的基本URL

c# - 按 lambda 表达式动态排序

java - Java 8 中 '()->{}' 的类型是什么?

c# - 在本地方法和 lambda 中重载

c# - SAP B1 AddOn,检查表是否存在和创建表的最佳位置是什么?

c# - 同时声明和赋值多个字符串变量

asp.net-mvc - 由于请求正文太大,大文件上传到 ASP.NET Core 3.0 Web API 失败

c# - ASP.NET Core 2.1 [FromQuery] 没有获取参数