如何优雅地为Razor Pages数组类型模型属性应用数据注解?
优雅实现方案
方法3:复用方案A的注解,仅修改视图(最接近需求)
这种方式完全保留OutputData的数组结构和方案A的注解,只在视图中做少量调整,直接从注解中读取格式字符串来格式化元素:
- 保留方案A的
OutputData注解:
[DisplayName("Annual Production")] [DisplayFormat(DataFormatString = "{0:F2}")] public double[] Production { get; set; } [DisplayName("Cash Flow")] [DisplayFormat(DataFormatString = "{0:C2}")] public double[] CashFlow { get; set; }
- 修改视图代码,获取属性的格式字符串并应用到数组元素:
@using System.ComponentModel.DataAnnotations; <table class="table"> <thead> <tr> <th>Year</th> <th>@Html.DisplayNameFor(model => model.OutputData.Production)</th> <th>@Html.DisplayNameFor(model => model.OutputData.CashFlow)</th> </tr> </thead> <tbody> @{ // 从属性的元数据中读取格式字符串 var productionFormat = ModelMetadata.FromLambdaExpression(model => model.OutputData.Production, ViewData).DisplayFormatString; var cashFlowFormat = ModelMetadata.FromLambdaExpression(model => model.OutputData.CashFlow, ViewData).DisplayFormatString; } @for (int year = 0; year <= Model.InputData.SystemLifetime; year++) { <tr> <td>@year</td> <td>@string.Format(productionFormat, Model.OutputData.Production[year])</td> <td>@string.Format(cashFlowFormat, Model.OutputData.CashFlow[year])</td> </tr> } </tbody> </table>
优点:不用修改业务逻辑层的代码,完全复用方案A的注解思路,仅视图层做适配,代码量少且符合原有设计。
方法1:通用数值包装类(减少重复代码)
如果不想在视图中写逻辑,可以用通用包装类替代方案B中重复的结构体,避免冗余代码:
- 定义通用包装类和元数据类:
// 通用数值包装基类 public class FormattedNumber<T> where T : struct, IFormattable { public T Value { get; init; } public FormattedNumber(T value) => Value = value; } // 产量数值类型+元数据 [ModelMetadataType(typeof(ProductionMetadata))] public class ProductionNumber : FormattedNumber<double> { public ProductionNumber(double value) : base(value) { } } public class ProductionMetadata { [DisplayFormat(DataFormatString = "{0:F2}")] public double Value { get; init; } } // 现金流数值类型+元数据 [ModelMetadataType(typeof(CashFlowMetadata))] public class CashFlowNumber : FormattedNumber<double> { public CashFlowNumber(double value) : base(value) { } } public class CashFlowMetadata { [DisplayFormat(DataFormatString = "{0:C2}")] public double Value { get; init; } }
- 修改
OutputData类:
public class OutputData { [DisplayName("Annual Production")] public ProductionNumber[] Production { get; set; } [DisplayName("Cash Flow")] public CashFlowNumber[] CashFlow { get; set; } public OutputData(InputData inputData) { ISimulation mySim = SimulationBuilder.BuildSim(inputData); Production = mySim.LifetimeProduction() .Select(item => new ProductionNumber(item)) .ToArray(); CashFlow = mySim.LifetimeCashFlows() .Select(item => new CashFlowNumber(item)) .ToArray(); } }
- 修改视图绑定到
Value属性:
<td> @Html.DisplayFor(model => model.OutputData.Production[year].Value) </td> <td> @Html.DisplayFor(model => model.OutputData.CashFlow[year].Value) </td>
优点:统一管理格式化逻辑,避免方案B中重复定义结构体的繁琐,后续新增数值类型只需添加新的子类和元数据。
方法2:自定义显示模板(视图层解耦)
如果想把格式化逻辑放在视图层,且不修改业务代码,可以创建针对不同数值类型的显示模板:
- 在
Views/Shared/DisplayTemplates目录下创建两个模板文件:
Production.cshtml:
@model double @string.Format("{0:F2}", Model)
CashFlow.cshtml:
@model double @string.Format("{0:C2}", Model)
- 保留方案A的
OutputData注解:
[DisplayName("Annual Production")] public double[] Production { get; set; } [DisplayName("Cash Flow")] public double[] CashFlow { get; set; }
- 修改视图指定模板名称:
<td> @Html.DisplayFor(model => model.OutputData.Production[year], "Production") </td> <td> @Html.DisplayFor(model => model.OutputData.CashFlow[year], "CashFlow") </td>
优点:格式化逻辑完全放在视图层,业务层代码不受影响,模板可复用在其他视图。
内容的提问来源于stack exchange,提问作者Heather C.
相关产品推荐
相关产品推荐

