如何在ASP.NET及ASP.NET Core中从模型类动态生成HTML表格?
如何在ASP.NET及ASP.NET Core中从模型类动态生成HTML表格?
嘿,很高兴能帮你解决这个从UnitPrice模型生成HTML表格的问题!不管你用的是传统ASP.NET(Web Forms)还是ASP.NET Core,都有灵活的动态实现方案,我结合你的模型给你拆解一下:
核心思路
动态生成的关键是利用反射获取模型的属性信息,同时读取DisplayName、ReadOnly这类数据注解,这样既能自动生成友好的表头,还能识别只读字段做特殊处理,不用硬编码每个列。
1. 传统ASP.NET(Web Forms)的实现方式
方式一:后台生成HTML字符串(灵活可控)
你可以写一个通用方法,传入UnitPrice的集合,直接生成完整的HTML表格:
using System.Text; using System.Reflection; using System.ComponentModel; public string GenerateHtmlTable<T>(IEnumerable<T> data) { var sb = new StringBuilder(); var type = typeof(T); var properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance); // 构建表格框架和表头 sb.Append("<table border='1' cellpadding='4' cellspacing='0'>"); sb.Append("<thead><tr style='background-color: #f0f0f0;'>"); foreach (var prop in properties) { // 优先用DisplayName,没有则用属性名 var headerText = prop.GetCustomAttribute<DisplayNameAttribute>()?.DisplayName ?? prop.Name; // 给只读字段的表头加样式标记 var isReadOnly = prop.GetCustomAttribute<ReadOnlyAttribute>()?.IsReadOnly ?? false; var headerStyle = isReadOnly ? " style='color: #666;'" : ""; sb.Append($"<th{headerStyle}>{headerText}</th>"); } sb.Append("</tr></thead><tbody>"); // 构建数据行 foreach (var item in data) { sb.Append("<tr>"); foreach (var prop in properties) { var cellValue = prop.GetValue(item)?.ToString() ?? "-"; var isReadOnly = prop.GetCustomAttribute<ReadOnlyAttribute>()?.IsReadOnly ?? false; var cellStyle = isReadOnly ? " style='color: #666;'" : ""; sb.Append($"<td{cellStyle}>{cellValue}</td>"); } sb.Append("</tr>"); } sb.Append("</tbody></table>"); return sb.ToString(); }
然后在ASPX页面里直接调用:
<%-- 假设UnitPriceList是你从数据库获取的List<UnitPrice> --%> <%= GenerateHtmlTable(UnitPriceList) %>
方式二:用GridView自动生成(快速便捷)
如果不想自己写HTML,ASP.NET的GridView控件可以直接绑定数据源,自动识别数据注解:
<asp:GridView ID="UnitPriceGridView" runat="server" AutoGenerateColumns="True" DataKeyNames="IdProduct,IdUnit" CssClass="table table-striped" EmptyDataText="暂无数据"> </asp:GridView>
后台绑定数据:
protected void Page_Load(object sender, EventArgs e) { if (!IsPostBack) { // 替换成你的数据源获取逻辑 var unitPrices = GetUnitPricesFromDatabase(); UnitPriceGridView.DataSource = unitPrices; UnitPriceGridView.DataBind(); } }
GridView会自动把DisplayName作为列标题,ReadOnly属性也会让对应单元格不可编辑(如果开启编辑模式的话)。
2. ASP.NET Core的实现方式
ASP.NET Core更推荐用Razor视图或自定义辅助工具来实现,这里给你三种常用方案:
方式一:Razor视图直接反射生成(快速实现)
直接在.cshtml视图里写逻辑,适合简单场景:
@model IEnumerable<UnitPrice> @using System.Reflection @using System.ComponentModel <table class="table table-bordered table-striped"> <thead class="table-light"> <tr> @foreach (var prop in typeof(UnitPrice).GetProperties(BindingFlags.Public | BindingFlags.Instance)) { var headerText = prop.GetCustomAttribute<DisplayNameAttribute>()?.DisplayName ?? prop.Name; var isReadOnly = prop.GetCustomAttribute<ReadOnlyAttribute>()?.IsReadOnly ?? false; <th class="@(isReadOnly ? "text-muted" : "")">@headerText</th> } </tr> </thead> <tbody> @if (!Model.Any()) { <tr> <td colspan="@typeof(UnitPrice).GetProperties().Length" class="text-center text-muted">暂无数据</td> </tr> } else { @foreach (var item in Model) { <tr> @foreach (var prop in typeof(UnitPrice).GetProperties(BindingFlags.Public | BindingFlags.Instance)) { var cellValue = prop.GetValue(item)?.ToString() ?? "-"; var isReadOnly = prop.GetCustomAttribute<ReadOnlyAttribute>()?.IsReadOnly ?? false; <td class="@(isReadOnly ? "text-muted" : "")">@Html.Raw(Html.Encode(cellValue))</td> } </tr> } } </tbody> </table>
方式二:自定义HTML Helper(复用性强)
如果要在多个视图里复用,可以写一个通用的HTML Helper:
- 先创建
Helpers/HtmlTableHelper.cs:
using Microsoft.AspNetCore.Mvc.Rendering; using System.Reflection; using System.ComponentModel; namespace YourProjectNamespace.Helpers { public static class HtmlTableHelper { public static IHtmlContent GenerateDynamicTable<T>(this IHtmlHelper htmlHelper, IEnumerable<T> data) { var type = typeof(T); var properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance); var table = new TagBuilder("table"); table.AddCssClass("table table-bordered"); // 生成表头 var thead = new TagBuilder("thead"); var headerRow = new TagBuilder("tr"); headerRow.AddCssClass("table-light"); foreach (var prop in properties) { var headerText = prop.GetCustomAttribute<DisplayNameAttribute>()?.DisplayName ?? prop.Name; var isReadOnly = prop.GetCustomAttribute<ReadOnlyAttribute>()?.IsReadOnly ?? false; var th = new TagBuilder("th"); if (isReadOnly) th.AddCssClass("text-muted"); th.InnerHtml.Append(headerText); headerRow.InnerHtml.AppendHtml(th); } thead.InnerHtml.AppendHtml(headerRow); table.InnerHtml.AppendHtml(thead); // 生成数据行 var tbody = new TagBuilder("tbody"); if (!data.Any()) { var emptyRow = new TagBuilder("tr"); var emptyCell = new TagBuilder("td"); emptyCell.Attributes["colspan"] = properties.Length.ToString(); emptyCell.AddCssClass("text-center text-muted"); emptyCell.InnerHtml.Append("暂无数据"); emptyRow.InnerHtml.AppendHtml(emptyCell); tbody.InnerHtml.AppendHtml(emptyRow); } else { foreach (var item in data) { var row = new TagBuilder("tr"); foreach (var prop in properties) { var cellValue = prop.GetValue(item)?.ToString() ?? "-"; var isReadOnly = prop.GetCustomAttribute<ReadOnlyAttribute>()?.IsReadOnly ?? false; var td = new TagBuilder("td"); if (isReadOnly) td.AddCssClass("text-muted"); td.InnerHtml.Append(htmlHelper.Encode(cellValue)); row.InnerHtml.AppendHtml(td); } tbody.InnerHtml.AppendHtml(row); } } table.InnerHtml.AppendHtml(tbody); return table; } } }
- 在
_ViewImports.cshtml里注册Helper:
@using YourProjectNamespace.Helpers @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
- 然后在视图里直接调用:
@model IEnumerable<UnitPrice> @Html.GenerateDynamicTable(Model)
方式三:自定义Tag Helper(Core风格)
Tag Helper是Core的特色,可以像HTML标签一样使用,更直观:
- 创建
TagHelpers/DynamicTableTagHelper.cs:
using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Razor.TagHelpers; using System.Reflection; using System.ComponentModel; namespace YourProjectNamespace.TagHelpers { [HtmlTargetElement("dynamic-table", Attributes = "model")] public class DynamicTableTagHelper : TagHelper { [HtmlAttributeName("model")] public IEnumerable<object> Model { get; set; } public override void Process(TagHelperContext context, TagHelperOutput output) { output.TagName = "table"; output.AddClass("table table-bordered", HtmlEncoder.Default); if (Model == null || !Model.Any()) { output.Content.SetHtmlContent("<tr><td colspan='100%' class='text-center text-muted'>暂无数据</td></tr>"); return; } var itemType = Model.First().GetType(); var properties = itemType.GetProperties(BindingFlags.Public | BindingFlags.Instance); // 生成表头 var headerRow = new TagBuilder("tr"); headerRow.AddCssClass("table-light"); foreach (var prop in properties) { var headerText = prop.GetCustomAttribute<DisplayNameAttribute>()?.DisplayName ?? prop.Name; var isReadOnly = prop.GetCustomAttribute<ReadOnlyAttribute>()?.IsReadOnly ?? false; var th = new TagBuilder("th"); if (isReadOnly) th.AddCssClass("text-muted"); th.InnerHtml.Append(headerText); headerRow.InnerHtml.AppendHtml(th); } var thead = new TagBuilder("thead"); thead.InnerHtml.AppendHtml(headerRow); output.Content.AppendHtml(thead); // 生成数据行 var tbody = new TagBuilder("tbody"); foreach (var item in Model) { var row = new TagBuilder("tr"); foreach (var prop in properties) { var cellValue = prop.GetValue(item)?.ToString() ?? "-"; var isReadOnly = prop.GetCustomAttribute<ReadOnlyAttribute>()?.IsReadOnly ?? false; var td = new TagBuilder("td"); if (isReadOnly) td.AddCssClass("text-muted"); td.InnerHtml.Append(HtmlEncoder.Default.Encode(cellValue)); row.InnerHtml.AppendHtml(td); } tbody.InnerHtml.AppendHtml(row); } output.Content.AppendHtml(tbody); } } }
- 在
_ViewImports.cshtml注册Tag Helper:
@using YourProjectNamespace.TagHelpers @addTagHelper *, YourProjectNamespace
- 视图里使用:
@model IEnumerable<UnitPrice> <dynamic-table model="@Model"></dynamic-table>
注意事项
- 性能优化:如果要处理大量数据,建议缓存模型的属性信息(比如用
MemoryCache),避免每次反射消耗资源。 - 类型格式化:对于
DateTime、decimal这类特殊类型,可以在反射时判断类型,做格式化处理(比如价格保留两位小数)。 - XSS防护:输出数据时一定要用
HtmlEncoder编码,避免跨站脚本攻击,上面的例子里已经做了处理。
内容的提问来源于stack exchange,提问作者Propaanbanaan




