From c9a67c1205b5c220f1d356246e79c353b7ca1275 Mon Sep 17 00:00:00 2001 From: Neil Brommer Date: Mon, 10 Jul 2023 11:15:57 -0700 Subject: [PATCH] Add table generator post --- src/posts/TableAutoGenerator.md | 138 ++++++++++++++++++++++++++++++++ 1 file changed, 138 insertions(+) create mode 100644 src/posts/TableAutoGenerator.md diff --git a/src/posts/TableAutoGenerator.md b/src/posts/TableAutoGenerator.md new file mode 100644 index 0000000..d3546d3 --- /dev/null +++ b/src/posts/TableAutoGenerator.md @@ -0,0 +1,138 @@ +--- +title: ASP.NET Core Table Generator +description: A tag helper that takes a list and generates a table +tags: [ Programming, C#, ASP.NET Core ] +--- + +This tag helper takes any `IEnumerable` and will use reflection to generate a table for it. This will pick up on attributes in the model for customizing the output. + +## How To Use + +The model in the `IEnumerable`: + +```csharp +public class Thing +{ + // Don't make a column for this property + [Display(AutoGenerateField = false)] + public int ThingId { get; set; } + + // Use "Thing Name" for the column header instead of "ThingName" + [Display(Name = "Thing Name")] + public string ThingName { get; set; } + + // Use string.Format with the DataFormatString + [DisplayFormat(DataFormatString = "{0:C}")] + public decimal Price { get; set; } +} +``` + +Then use the tag helper in the view: + +```cshtml +
+``` + +## Code + +The tag helper: + +```csharp +using Microsoft.AspNetCore.Mvc.Rendering; +using Microsoft.AspNetCore.Mvc.ViewFeatures; +using Microsoft.AspNetCore.Razor.TagHelpers; +using System.ComponentModel.DataAnnotations; +using System.Reflection; + +/// +/// When the is a list, fill the table with the list data with +/// columns for each property. +/// +/// Properties can be hidden from the table by adding [Diaplay(AutoGenerateField = false)] +/// +/// +/// The column heading for each property is the property name by default. This can be overriden by +/// adding [Display(Name = "Property Name")] +/// +/// +[HtmlTargetElement("table", Attributes = "asp-for")] +public class TableTagHelper : TagHelper +{ + [HtmlAttributeName("asp-for")] + public ModelExpression For { get; set; } + + public override void Process(TagHelperContext context, TagHelperOutput output) + { + var modelType = this.For.ModelExplorer.ModelType.GenericTypeArguments[0]; + var list = this.For.Model as IEnumerable; + + if (list is null) + output.TagName = ""; + + // Don't display properties that have AutoGenerateField set to false + IList properties = modelType.GetProperties() + .Where(p => !p.GetCustomAttributes(false) + .Any(a => a.GetAutoGenerateField() == false)) + .ToList(); + + // Header row + List tableHeadCells = properties + .Select(p => + { + string displayName = p.GetCustomAttributes(false) + .Where(a => !string.IsNullOrEmpty(a.GetName())) + .FirstOrDefault() + ?.GetName() + ?? p.Name; + + TagBuilder th = new("th"); + th.InnerHtml.Append(displayName); + + return th; + }) + .ToList(); + + TagBuilder theadTr = new("tr"); + tableHeadCells.ForEach(th => theadTr.InnerHtml.AppendHtml(th)); + + TagBuilder thead = new("thead"); + thead.InnerHtml.AppendHtml(theadTr); + output.Content.AppendHtml(thead); + + + // Content rows + List tableRows = list + .Select(item => + { + List cells = properties + .Select(p => + { + TagBuilder td = new("td"); + + var content = p.GetValue(item); + string dataFormatString = p.GetCustomAttributes(false) + .Where(a => !string.IsNullOrWhiteSpace(a.DataFormatString)) + .Select(a => a.DataFormatString) + .FirstOrDefault(); + + if (dataFormatString is not null) + td.InnerHtml.Append(string.Format(dataFormatString, content)); + else if (content is not null) + td.InnerHtml.Append(content.ToString()); + + return td; + }) + .ToList(); + + TagBuilder tr = new("tr"); + cells.ForEach(td => tr.InnerHtml.AppendHtml(td)); + return tr; + }) + .ToList(); + + TagBuilder tbody = new("tbody"); + tableRows.ForEach(tr => tbody.InnerHtml.AppendHtml(tr)); + output.Content.AppendHtml(tbody); + } +} +```