我正在制作自己的 Html.EditorFor 方法,我想在其中检查给定属性的 DataTypeAttribute。获取 ViewData.Model 的类型很容易。但是,如果该属性来自不同的类,就像在示例用法行中一样,除了将其容器类的类型作为参数传递之外,我找不到其他方法。
如何在省略containerType参数的情况下获取属性的DataTypeAttribute?
这是我当前的代码:
public static IHtmlContent MyEditorFor<TModel, TResult>(this IHtmlHelper<TModel> html,
Expression<Func<TModel, TResult>> expression,
object moreData,
Type? containerType = null)
{
var ep = html.ViewContext.HttpContext.RequestServices.GetService(typeof(ModelExpressionProvider)) as ModelExpressionProvider;
var bindName = html.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(ep.GetExpressionText(expression));
if (containerType == null) containerType = html.ViewData.Model.GetType();
var dataType = GetDataType(containerType, bindName);
//etc...
}
private static DataType GetDataType(Type containerType, string bindName)
{
var result = DataType.Text;
var propName = bindName.Split('.')[^1];
var prop = containerType.GetProperty(propName);
var att = prop.GetCustomAttributes(typeof(DataTypeAttribute), true);
var attProperty = typeof(DataTypeAttribute).GetProperty(nameof(DataTypeAttribute.DataType));
result = (DataType)(attProperty.GetValue(att[0]) ?? DataType.Text);
return result;
}
这是一个示例用法:
@Html.EditorForEx(x => x.LinkedTable[i].MyDate, new { placeholder = "Test Date" }, Model.LinkedTable[i].GetType())
您应该像下面这样更改您的
HtmlHelperExtensions
。
using System;
using System.Linq.Expressions;
using System.Reflection;
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Html;
using Microsoft.AspNetCore.Mvc.Rendering;
namespace _79271231.Helpers
{
public static class HtmlHelperExtensions
{
public static IHtmlContent MyEditorFor<TModel, TResult>(
this IHtmlHelper<TModel> html,
Expression<Func<TModel, TResult>> expression,
object moreData)
{
var expressionText = GetExpressionText(expression);
var fullName = html.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(expressionText);
var (containerType, propertyInfo) = GetContainerTypeAndProperty(expression);
var dataType = GetDataType(containerType, propertyInfo);
switch (dataType)
{
case DataType.Date:
return html.TextBoxFor(expression, new { type = "date", @class = "form-control", placeholder = GetPlaceholder(moreData) });
case DataType.MultilineText:
return html.TextAreaFor(expression, new { @class = "form-control", placeholder = GetPlaceholder(moreData) });
default:
return html.TextBoxFor(expression, new { @class = "form-control", placeholder = GetPlaceholder(moreData) });
}
}
private static string GetExpressionText<TModel, TResult>(Expression<Func<TModel, TResult>> expression)
{
var memberNames = new System.Collections.Generic.List<string>();
Expression expr = expression.Body;
while (expr is MemberExpression memberExpr)
{
memberNames.Insert(0, memberExpr.Member.Name);
expr = memberExpr.Expression;
}
if (expr is MethodCallExpression methodCallExpr && methodCallExpr.Arguments.Count > 0)
{
var methodName = methodCallExpr.Method.Name;
if (methodName == "get_Item" && methodCallExpr.Arguments[0] is ConstantExpression constExpr)
{
memberNames.Insert(1, $"[{constExpr.Value}]");
}
else
{
memberNames.Insert(1, "[*]");
}
}
return string.Join(".", memberNames);
}
private static (Type containerType, PropertyInfo propertyInfo) GetContainerTypeAndProperty<TModel, TResult>(
Expression<Func<TModel, TResult>> expression)
{
MemberExpression memberExpr = null;
if (expression.Body is MemberExpression)
{
memberExpr = (MemberExpression)expression.Body;
}
else if (expression.Body is UnaryExpression unaryExpr && unaryExpr.Operand is MemberExpression)
{
memberExpr = (MemberExpression)unaryExpr.Operand;
}
if (memberExpr == null)
throw new ArgumentException("Expression is not a member access", nameof(expression));
var propertyInfos = new System.Collections.Generic.List<PropertyInfo>();
var type = typeof(TModel);
Expression expr = memberExpr;
while (expr is MemberExpression m)
{
if (m.Member is PropertyInfo pi)
{
propertyInfos.Insert(0, pi);
type = pi.PropertyType;
expr = m.Expression;
}
else
{
throw new ArgumentException("Member is not a property", nameof(expression));
}
}
if (propertyInfos.Count == 0)
throw new ArgumentException("No properties found in expression", nameof(expression));
PropertyInfo propertyInfoFinal = propertyInfos[^1];
Type containerTypeFinal = typeof(TModel);
if (propertyInfos.Count > 1)
{
PropertyInfo containerProperty = propertyInfos[^2];
containerTypeFinal = containerProperty.PropertyType;
}
return (containerTypeFinal, propertyInfoFinal);
}
private static DataType GetDataType(Type containerType, PropertyInfo propertyInfo)
{
var att = propertyInfo.GetCustomAttribute<DataTypeAttribute>();
return att?.DataType ?? DataType.Text;
}
private static string GetPlaceholder(object moreData)
{
if (moreData == null) return string.Empty;
var placeholderProperty = moreData.GetType().GetProperty("placeholder");
return placeholderProperty?.GetValue(moreData)?.ToString() ?? string.Empty;
}
}
}
像下面一样使用它,它对我有用。
@model _79271231.Models.SampleModel
@using _79271231.Helpers
@{
ViewData["Title"] = "Custom EditorFor Sample";
}
<h1>@ViewData["Title"]</h1>
<form asp-action="Index" method="post">
<table class="table">
<thead>
<tr>
<th>Date</th>
<th>Description</th>
</tr>
</thead>
<tbody>
@for (int i = 0; i < Model.LinkedTable.Length; i++)
{
<tr>
<td>
@Html.MyEditorFor(x => x.LinkedTable[i].MyDate, new { placeholder = "Test Date" })
</td>
<td>
@Html.MyEditorFor(x => x.LinkedTable[i].Description, new { placeholder = "Test Description" })
</td>
</tr>
}
</tbody>
</table>
<button type="submit" class="btn btn-primary">Submit</button>
</form>