How to get's the model metadata from an IEnumerable?

Complexity

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:

  1. In the function public void Bind(Expression> propertySelector) I'm using reflection to get the metadata of the model. Not too sure if this is the correct way.
user3559349

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.

edited at
0

Comments

0 comments
Login to comment

Related

From Dev

How to get's the model metadata from an IEnumerable?

From Dev

how to return model with index from IEnumerable<model> in mvc razor view?

From Dev

How to get the table dependencies from nhibernate metadata?

From Dev

How to get the table dependencies from nhibernate metadata?

From Dev

How to get the value from metadata, in Dart?

From Dev

How To Get MetaData From Music URL

From Dev

How to get metadata from Media objects

From Dev

How do you get metadata from Amazon S3 object using AWS SDK PHP?

From Dev

How to get metadata of path from amazon s3 using aws v2?

From Dev

How much should I download from a mp4 to get it's metadata only

From Dev

Laravel eloquent model how to get data from relationship's table

From Dev

Get from IQueryable to IEnumerable

From Dev

How to get model_id from Model?

From Java

How to match objectids from Autodesk Model Derivative API metadata with Forge Viewer model dbids?

From Dev

How do I get metadata from Spotify SDK on Android?

From Dev

How to get client ip from request metadata with grpc-java

From Dev

How do I get string from metadata, not int

From Dev

How to get column names and other metadata from a table or resultset

From Dev

How to get image metadata from ACF gallery images?

From Dev

How to get value from IEnumerable collection using its Key?

From Dev

Get Metadata from navigation property

From Dev

Get metadata from shoutcast stream

From Dev

Get metadata from a video in the terminal

From Dev

Get Metadata from navigation property

From Dev

Get IEnumerable from LINQ select

From Dev

Get IEnumerable<SelectListItem> from dbContext

From Dev

How to get field value from a model from another model in Odoo?

From Dev

Django: how to get a model's keys

From Dev

How to get a model's ID in Ember?

Related Related

  1. 1

    How to get's the model metadata from an IEnumerable?

  2. 2

    how to return model with index from IEnumerable<model> in mvc razor view?

  3. 3

    How to get the table dependencies from nhibernate metadata?

  4. 4

    How to get the table dependencies from nhibernate metadata?

  5. 5

    How to get the value from metadata, in Dart?

  6. 6

    How To Get MetaData From Music URL

  7. 7

    How to get metadata from Media objects

  8. 8

    How do you get metadata from Amazon S3 object using AWS SDK PHP?

  9. 9

    How to get metadata of path from amazon s3 using aws v2?

  10. 10

    How much should I download from a mp4 to get it's metadata only

  11. 11

    Laravel eloquent model how to get data from relationship's table

  12. 12

    Get from IQueryable to IEnumerable

  13. 13

    How to get model_id from Model?

  14. 14

    How to match objectids from Autodesk Model Derivative API metadata with Forge Viewer model dbids?

  15. 15

    How do I get metadata from Spotify SDK on Android?

  16. 16

    How to get client ip from request metadata with grpc-java

  17. 17

    How do I get string from metadata, not int

  18. 18

    How to get column names and other metadata from a table or resultset

  19. 19

    How to get image metadata from ACF gallery images?

  20. 20

    How to get value from IEnumerable collection using its Key?

  21. 21

    Get Metadata from navigation property

  22. 22

    Get metadata from shoutcast stream

  23. 23

    Get metadata from a video in the terminal

  24. 24

    Get Metadata from navigation property

  25. 25

    Get IEnumerable from LINQ select

  26. 26

    Get IEnumerable<SelectListItem> from dbContext

  27. 27

    How to get field value from a model from another model in Odoo?

  28. 28

    Django: how to get a model's keys

  29. 29

    How to get a model's ID in Ember?

HotTag

Archive