我有一个静态通用的FormBuilder HTML帮助器方法(HTMLHelper类的扩展方法),它接受视图模型类型的泛型参数,然后,当从数据库传递一个或多个字符串属性名称时,生成一个HTML表单。带有.NET 4.5的ASP.NET MVC 5.1。
我有一个公共方法来生成表单,并使用单独的私有方法生成表单中的“模块”部分,然后在其中呈现每个字段。类型参数从上到下传递给这个链。
在“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);
这适用于字符串编辑器,但如果我尝试可以为空的日期时间,那么我会收到错误 -
类型'System.Nullable`1 [System.DateTime]'的表达式不能 用于返回类型'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);
我收到的错误消息是无法从使用中推断出类型参数。
如果我将返回类型更改为“dynamic”,则表示“无法动态调度扩展方法”。
我不能将“modelPropertyType”指定为泛型方法的返回参数 - 大概是因为它不能保证在编译时是具体类型。
有没有什么方法可以在运行时动态指定表达式的返回类型为属性,所以我可以使用ASP.NET MVC提供的EditorFor帮助方法?
答案 0 :(得分:3)
假设您使用TModel
和TProperty
的通用参数创建辅助函数,您可以在其中构建html以编辑单个模型属性。这个帮助器将接收HtmlHelper<TModel>
和PropertyInfo
的实例,它将创建EditorFor
之类所需的正确lambda表达式。帮助器可能如下所示:
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; }
}
您可以在以下视图中使用它:
@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="'Confirm password' and 'Password' 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!