【问题标题】:DataAnnotations for decimal as a percentage以百分比表示的十进制数据注释
【发布时间】:2020-09-09 20:17:14
【问题描述】:

我在 c# .netcore razor pages 应用程序中对数据对象的字段使用以下 DataAnnotations:

    [Display(Name = "Markup For Profit")]
    [Required]
    [DisplayFormat(DataFormatString = "{0:P0}", ApplyFormatInEditMode = true)]
    public double ProfitMarkup { get; set; }

在 Razor 页面上,我在输入中使用此字段:

<input asp-for="ProfitMarkup" class="form-control" autocomplete="off" />

ProfitMarkup 的十进制值显示正确(即 0.2 显示为 20%),这很好。但是,当我提交表单时,客户端验证会引发错误:

“利润标记字段必须是数字。”

我很确定是因为显示格式添加了“%”符号。

让页面上的 20% 的输入进入对象并将小数点设置为 0.2 的最佳方法是什么?

【问题讨论】:

  • 您可以使用字符串而不是双精度和正则表达式属性来获取数字和 %

标签: c# razor


【解决方案1】:

添加一个新的 Percentage 类型和一个 PercentageConverter TypeConverter 为我解决了这个问题。

我已经声明了一个读取双 ProfitMarkup 属性的新属性,如下所示:

    [Display(Name = "Markup For Profit")]
    [Required]
    [NotMapped]
    public Percentage ProfitMarkupPercentage { get { return new Percentage(ProfitMarkup); } set { ProfitMarkup = value; } }

ProfitMarkup 以双精度形式写入/从数据库写入,ProfitMarkupPercentage 显示在剃刀页面上,如下所示:

<input asp-for="ProfitMarkupPercentage" class="form-control" autocomplete="off" />

百分比对象的代码和它的 TypeConverter 是(这是由该线程中的响应提供的:How to convert percentage string to double?):

[TypeConverter(typeof(PercentageConverter))]
public struct Percentage
{
    public double Value;

    public Percentage(double value)
    {
        Value = value;
    }

    static public implicit operator double(Percentage pct)
    {
        return pct.Value;
    }

    static public implicit operator string(Percentage pct) { return pct.ToString(); }

    public Percentage(string value)
    {
        Value = 0.0;
        var pct = (Percentage)TypeDescriptor.GetConverter(GetType()).ConvertFromString(value);
        Value = pct.Value;
    }

    public override string ToString()
    {
        return ToString(CultureInfo.InvariantCulture);
    }

    public string ToString(CultureInfo Culture)
    {
        return TypeDescriptor.GetConverter(GetType()).ConvertToString(null, Culture, this);
    }
}

public class PercentageConverter : TypeConverter
{
    static TypeConverter conv = TypeDescriptor.GetConverter(typeof(double));

    public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
    {
        return conv.CanConvertFrom(context, sourceType);
    }

    public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
    {
        if (destinationType == typeof(Percentage))
        {
            return true;
        }

        return conv.CanConvertTo(context, destinationType);
    }

    public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
    {
        if (value == null)
        {
            return new Percentage();
        }

        if (value is string)
        {
            string s = value as string;
            s = s.TrimEnd(' ', '\t', '\r', '\n');

            var percentage = s.EndsWith(culture.NumberFormat.PercentSymbol);
            if (percentage)
            {
                s = s.Substring(0, s.Length - culture.NumberFormat.PercentSymbol.Length);
            }

            double result = (double)conv.ConvertFromString(s);
            if (percentage)
            {
                result /= 100;
            }

            return new Percentage(result);
        }

        return new Percentage((double)conv.ConvertFrom(context, culture, value));
    }

    public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
    {
        if (!(value is Percentage))
        {
            throw new ArgumentNullException("value");
        }

        var pct = (Percentage)value;

        if (destinationType == typeof(string))
        {
            return conv.ConvertTo(context, culture, pct.Value * 100, destinationType) + culture.NumberFormat.PercentSymbol;
        }

        return conv.ConvertTo(context, culture, pct.Value, destinationType);
    }
}

默认模型绑定器现在可以正确地填充到剃须刀页面的属性和从属性填充到剃须刀页面。

您还可以使用 Regex 向 ProfitMarkupPercentage 属性添加验证数据注释:

[RegularExpression(@"^(0*100{1,1}\.?((?<=\.)0*)?%?$)|(^0*\d{0,2}\.?((?<=\.)\d*)?%?)$", ErrorMessage = "Invalid percentage")]

【讨论】:

    【解决方案2】:

    默认 ModelBinder 将无法获得正确的发布值,当您在输入框中有百分号时,作为 DataAnnotation 的一部分。您可以创建自定义 ModelBinder。使用 ASP.Net MVC 的示例:

    // The ViewModel
        public class HomeViewModel
        {
            [Display(Name = "Markup For Profit")]
            [Required]
            [DisplayFormat(DataFormatString = "{0:P2}", ApplyFormatInEditMode =true)]
            public double ProfitMarkup { get; set; }
        }
    
    // Two Classes to implement custom ModelBinder
        public class DoublePercentDataBinder : DefaultModelBinder
        {
            public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
            {
                if (bindingContext.ModelType == typeof(HomeViewModel))
                {
                    HttpRequestBase request = controllerContext.HttpContext.Request;
                    string pMarkup = request.Form.Get("ProfitMarkup").TrimEnd('%');
                    return new HomeViewModel
                    {
                        ProfitMarkup = Double.Parse(pMarkup)/100
                    };
                }
                else
                {
                    return base.BindModel(controllerContext, bindingContext);
                }
            }
        }
    
        public class DoublePercentBinder : IModelBinder
        {
            public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
            {
                HttpRequestBase request = controllerContext.HttpContext.Request;
                string pMarkup = request.Form.Get("ProfitMarkup").TrimEnd('%');
                return new HomeViewModel
                {
                    ProfitMarkup = Double.Parse(pMarkup)/100
                };
    
            }
        }
    
    // Attach it in Application_Start in Global.asax
           protected void Application_Start()
            {
                ModelBinders.Binders.Add(typeof(HomeViewModel), new DoublePercentBinder());
                AreaRegistration.RegisterAllAreas();
                RouteConfig.RegisterRoutes(RouteTable.Routes);
            }
    
    // HomeController Get and Post
        public class HomeController : Controller
        {
            public ActionResult Index()
            {
                HomeViewModel vm = new HomeViewModel();
                vm.ProfitMarkup = 1.2;
                return View(vm);
        }
    
            [HttpPost]
            public ActionResult Index([ModelBinder(typeof(DoublePercentBinder))] HomeViewModel hvm)
            {
                return View(hvm);
            }
    
    // The Razor View
    @using (Html.BeginForm("Index", "Home", FormMethod.Post))
    {
        <div>
            @Html.EditorFor(x => x.ProfitMarkup)
            @Html.ValidationMessageFor(x => x.ProfitMarkup)
        </div>
        <br />
        <input type="submit" value="Submit" />
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2022-01-14
      • 1970-01-01
      • 2013-11-17
      • 1970-01-01
      • 1970-01-01
      • 2021-10-10
      • 2022-12-11
      • 1970-01-01
      相关资源
      最近更新 更多