如何从IEnumerable中获取's模型元数据
本文关键字:模型 元数据 获取 IEnumerable | 更新日期: 2023-09-27 18:05:58
我在MVC中有GridView,它是以以下方式构建的:
@ model IEnumerable
@(Html.GridFor()
.WithName("PageOverviewGrid")
.WithColumns(model =>
{
model.Bind(x => x.Name);
model.Bind(x => x.DateCreated);
model.Bind(x => x.DateUpdated);
})
)
你在上面看到的是,我正在构建我的模型的网格,这是一个IEnumerable,我绑定了3列到它。
我的HtmlHelper的代码如下:/// <summary>
/// Provides a way to extend the <see cref="HtmlHelper" />.
/// </summary>
public static class HtmlHelperExtensions
{
#region Grid
/// <summary>
/// Constructs a grid for a given model by using a fluent API.
/// </summary>
/// <typeparam name="TModel">The type of the model that is bound to the grid.</typeparam>
/// <param name="htmlHelper">The <see cref="HtmlHelper" /> which is used to create the grid.</param>
/// <returns>An <see cref="IGridBuilder{TModel}" /> that is used to construct the grid.</returns>
public static IGridBuilder<TModel> GridFor<TModel>(this HtmlHelper<IEnumerable<TModel>> htmlHelper)
{
return new GridBuilder<TModel>(htmlHelper);
}
#endregion
}
我将在这个方法中返回一个接口,以允许通过流畅的API进行构造。该接口的代码如下:
/// <summary>
/// When implemented on a class, this class acts as an <see cref="IHtmlString" /> that can construct a grid by using a
/// fluent API.
/// </summary>
/// <typeparam name="TModel">The type of the model that is bound to the grid.</typeparam>
public interface IGridBuilder<TModel> : IHtmlString
{
#region Properties
/// <summary>
/// Gets the name of the <see cref="IGridBuilder{TModel}" />.
/// The outer div of the grid will have an id that matches this name.
/// </summary>
string Name { get; }
/// <summary>
/// The <see cref="HtmlHelper" /> that is used to build the grid.
/// </summary>
HtmlHelper HtmlHelper { get; }
#endregion
#region Methods
/// <summary>
/// Sets the name of the <see cref="IGridBuilder{TModel}" />.
/// </summary>
/// <param name="name">The name that the <see cref="IGridBuilder{TModel}" /> should have.</param>
/// <returns>An <see cref="IGridBuilder{TModel}" /> that is used to construct the grid.</returns>
IGridBuilder<TModel> WithName(string name);
/// <summary>
/// Set the columns of the model that should be bound to grid.
/// </summary>
/// <param name="bindAllColumns">The action that will bind all the columns.</param>
/// <returns>An <see cref="IGridBuilder{TModel}" /> that is used to construct the grid.</returns>
IGridBuilder<TModel> WithColumns(Action<IGridBuilder<TModel>> bindAllColumns);
/// <summary>
/// Binds an column to the grid.
/// </summary>
/// <typeparam name="TItem">The type of the column on which to bind the items.</typeparam>
/// <param name="propertySelector">The functional that will bind the control to the grid.</param>
void Bind<TItem>(Expression<Func<TModel, TItem>> propertySelector);
#endregion
}
然后我有,当然,它的实现:
/// <summary>
/// An implementation of the <see cref="IGridBuilder{TModel}" /> that is used to build a grid.
/// </summary>
/// <typeparam name="TModel">The type of the model that is bound to the grid.</typeparam>
public class GridBuilder<TModel> : IGridBuilder<TModel>
{
#region Constructors
/// <summary>
/// Creates a new instance of the <see cref="GridBuilder{TModel}" />.
/// </summary>
/// <param name="htmlHelper">The <see cref="HtmlHelper{TModel}" /> that is used to render this one.</param>
public GridBuilder(HtmlHelper<IEnumerable<TModel>> htmlHelper)
{
HtmlHelper = htmlHelper;
properties = new Dictionary<string, List<string>>();
}
#endregion
#region Properties
/// <summary>
/// A <see cref="List{TKey}" /> that conains the names of the property and the display name that belongs
/// to the property.
/// </summary>
public readonly Dictionary<string, List<string>> properties;
#endregion
#region IGridBuilder Members
/// <summary>
/// Gets the name of the <see cref="IGridBuilder{TModel}" />.
/// The outer div of the grid will have an id that matches this name.
/// </summary>
public string Name { get; private set; }
/// <summary>
/// The <see cref="HtmlHelper" /> that is used to build the grid.
/// </summary>
public HtmlHelper HtmlHelper { get; private set; }
/// <summary>
/// Sets the name of the <see cref="IGridBuilder{TModel}" />.
/// </summary>
/// <param name="name">The name that the <see cref="IGridBuilder{TModel}" /> should have.</param>
/// <returns>An <see cref="IGridBuilder{TModel}" /> that is used to construct the grid.</returns>
public IGridBuilder<TModel> WithName(string name)
{
Name = name;
return this;
}
/// <summary>
/// Binds an column to the grid.
/// </summary>
/// <typeparam name="TItem">The type of the column on which to bind the items.</typeparam>
/// <param name="propertySelector">The functional that will bind the control to the grid.</param>
public void Bind<TItem>(Expression<Func<TModel, TItem>> propertySelector)
{
string name = ExpressionHelper.GetExpressionText(propertySelector);
name = HtmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(name);
ModelMetadata metadata =
ModelMetadataProviders.Current.GetMetadataForProperty(() => Activator.CreateInstance<TModel>(),
typeof(TModel), name);
// Get's the name to display on the column in grid. The Display attribute is used if present, otherwise the name of the property is used.
string displayName = string.IsNullOrEmpty(metadata.DisplayName)
? metadata.PropertyName
: metadata.DisplayName;
var items = (from TModel entity in HtmlHelper.ViewData.Model as IEnumerable select propertySelector.Compile().Invoke(entity).ToString()).ToList();
properties.Add(displayName, items);
}
/// <summary>
/// Set the columns of the model that should be bound to grid.
/// </summary>
/// <param name="bindAllColumns">The action that will bind all the columns.</param>
/// <returns>An <see cref="IGridBuilder{TModel}" /> that is used to construct the grid.</returns>
public IGridBuilder<TModel> WithColumns(Action<IGridBuilder<TModel>> bindAllColumns)
{
bindAllColumns(this);
return this;
}
#endregion
#region IHtmlString Members
/// <summary>
/// Returns an HTML-encoded string.
/// </summary>
/// <returns>Returns an HTML-encoded string.</returns>
public string ToHtmlString()
{
var output = new StringBuilder();
BaseElementBuilder parentBuilder = DivFactory.DivElement().WithCssClass("gridHolder v-scroll").WithId(Name);
BaseElementBuilder headerBuilder = DivFactory.DivElement().WithCssClass("header");
output.Append(parentBuilder.ToString(TagRenderMode.StartTag));
output.Append(headerBuilder.ToString(TagRenderMode.StartTag));
var index = 0;
foreach (
var propertyBuilder in
properties.Select(
property =>
(index == 0)
? DivFactory.DivElement().WithCssClass("inline").WithInnerHtml(property.Key)
: DivFactory.DivElement()
.WithCssClass("inline fixed right")
.WithInnerHtml(property.Key)))
{
output.Append(propertyBuilder);
index += 1;
}
output.Append(headerBuilder.ToString(TagRenderMode.EndTag));
for (int i = 0; i < properties.First().Value.Count(); i++)
{
BaseElementBuilder rowBuilder = DivFactory.DivElement().WithCssClass("row");
output.Append(rowBuilder.ToString(TagRenderMode.StartTag));
BaseElementBuilder iconBuilder = DivFactory.DivElement().WithCssClass("inline icon").WithInnerHtml("<img src='"~/Resources/Icons/Grid/Pages/Page.png'" />");
output.Append(iconBuilder.ToString(TagRenderMode.StartTag));
output.Append(iconBuilder.ToString(TagRenderMode.EndTag));
int loopIndex = 0;
foreach (var propertyBuilder in properties)
{
var value = propertyBuilder.Value[i];
BaseElementBuilder propertyConstructor = (loopIndex == 0)
? DivFactory.DivElement().WithCssClass("inline").WithInnerHtml(value)
: DivFactory.DivElement().WithCssClass("inline fixed right").WithInnerHtml(value);
loopIndex += 1;
output.Append(propertyConstructor.ToString(TagRenderMode.Normal));
}
output.Append(rowBuilder.ToString(TagRenderMode.EndTag));
}
output.Append(parentBuilder.ToString(TagRenderMode.EndTag));
return output.ToString();
}
#endregion
}
在这个实现中,我关心的是一件小事:
- 在函数公共void Bind(Expression> propertySelector)我使用反射来获取模型的元数据。我不太确定这是不是正确的方法。
我建议你把签名改成
public static IGridBuilder<TModel> GridFor<TModel>(this HtmlHelper<TModel> htmlHelper, IEnumerable collection)
作为
@Html.GridFor(ViewBag.MyCollection)
then in the helper
// Get the type in the collection
Type type = GetCollectionType(collection); // See below
// Get the metadata of the type in the collection
ModelMetadata typeMetadata = ModelMetadataProviders.Current
.GetMetadataForType(null, type);
然后循环集合
foreach (var item in collection)
{
ModelMetadata itemMetadata = ModelMetadataProviders
.Current.GetMetadataForType(() => item, type);
// Use itemMetadata.Properties to generate the columns
获取类型可能会很复杂,因为IGrouping, IDictionary和Lookup也实现了IEnumerable(我没有包括检查这些的代码)
private static Type GetCollectionType(IEnumerable collection)
{
Type type = collection.GetType();
if (type.IsGenericType)
{
return type.GetInterfaces().Where(t => t.IsGenericType)
.Where(t => t.GetGenericTypeDefinition() == typeof(IEnumerable<>))
.Single().GetGenericArguments().Last();
}
else if (collection.GetType().IsArray)
{
return type.GetElementType();
}
else
{
// Who knows?
return null;
}
}
Edit 1:使用现有代码的另一种选择是更改Bind方法,如下所示
public void Bind<TItem>(Expression<Func<TModel, TItem>> propertySelector)
{
string name = ExpressionHelper.GetExpressionText(propertySelector);
name = HtmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(name);
IEnumerable collection = HtmlHelper.ViewData.Model as IEnumerable;
foreach (var item in collection)
{
ModelMetadata modelMetadata = ModelMetadataProviders.Current
.GetMetadataForType(() => item, item.GetType())
.Properties.First(m => m.PropertyName == name);
string displayName = modelMetadata.GetDisplayName();
if (!properties.ContainsKey(displayName))
{
properties[displayName] = new List<string>();
}
// Take into account display format and null values
string format = modelMetadata.DisplayFormatString ?? "{0}";
properties[displayName].Add(string.Format(format,
modelMetadata.Model ?? modelMetadata.NullDisplayText));
}
}