I have GridView in MVC which is constructed in the following way:
@model IEnumerable
@(Html.GridFor()
.WithName("PageOverviewGrid")
.WithColumns(model =>
{
model.Bind(x => x.Name);
model.Bind(x => x.DateCreated);
model.Bind(x => x.DateUpdated);
})
)
What you see above is that I'm constructing a grid of my model, which is an IEnumerable and I bind 3 columns to it.
The code of my HtmlHelper is the following:
/// <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
}
I'll return an inteface in this method to allow the construction through a fluent API. The code of that interface is the following:
/// <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
}
Then I have, off course, the implementation of it:
/// <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
}
And it's in this implementation that I'm concerned about a little thing:
I suggest you change your signature to
public static IGridBuilder<TModel> GridFor<TModel>(this HtmlHelper<TModel> htmlHelper, IEnumerable collection)
and use it as
@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);
then when looping the collection
foreach (var item in collection)
{
ModelMetadata itemMetadata = ModelMetadataProviders
.Current.GetMetadataForType(() => item, type);
// Use itemMetadata.Properties to generate the columns
Getting the type can be complex because IGrouping, IDictionary and Lookup also implement IEnumerable (I've not included the code for checking these)
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: An alternative using you existing code would be to change the Bind method as follows
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));
}
}
Collected from the Internet
Please contact [email protected] to delete if infringement.
Comments