【问题标题】:Custom DateTime model binder in Asp.net MVCAsp.net MVC 中的自定义 DateTime 模型绑定器
【发布时间】:2010-03-01 14:52:43
【问题描述】:

我想为DateTime 类型编写自己的模型绑定器。首先,我想编写一个新属性,可以附加到我的模型属性,例如:

[DateTimeFormat("d.M.yyyy")]
public DateTime Birth { get; set,}

这是最简单的部分。但是活页夹部分要困难一些。我想为DateTime 类型添加一个新的模型绑定器。我也可以

  • 实现IModelBinder接口并编写我自己的BindModel()方法
  • DefaultModelBinder 继承并覆盖BindModel() 方法

我的模型有一个如上所示的属性 (Birth)。因此,当模型尝试将请求数据绑定到此属性时,我的模型绑定器的 BindModel(controllerContext, bindingContext) 被调用。一切都好,但是。 如何从控制器/bindingContext 中获取属性属性以正确解析我的日期?我怎样才能到达Birth属性的PropertyDesciptor

编辑

由于关注点分离,我的模型类是在不(也不应该)引用 System.Web.MVC 程序集的程序集中定义的。在这里设置自定义绑定(类似于Scott Hanselman's example)属性是不行的。

【问题讨论】:

  • 这有帮助吗? hanselman.com/blog/…
  • 不是真的,因为他不使用任何自定义属性。我可以使用 BindAttribute 但这不是一个通用的解决方案。你很容易忘记在你的行动中写下来。
  • 你有解决这个问题的有效方法吗?我有同样的问题,我想知道您选择了哪种解决方案
  • @Davide Vosti:我最终将客户端上的日期时间值重新格式化为隐藏字段。当用户从日期选择字段中模糊时,它会被填充。它有效。这是一种解决方法,它没有附带大量附加代码,并且适用于我的场景。
  • 谢谢!与此同时,我能够找到一个好的解决方案。还是谢谢你的建议

标签: asp.net-mvc binding modelbinders custom-model-binder


【解决方案1】:

您可以使用 IModelBinder 更改默认模型绑定器以使用用户文化

public class DateTimeBinder : IModelBinder
{
    public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
        bindingContext.ModelState.SetModelValue(bindingContext.ModelName, value);

        return value.ConvertTo(typeof(DateTime), CultureInfo.CurrentCulture);
    }
}

public class NullableDateTimeBinder : IModelBinder
{
    public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
        bindingContext.ModelState.SetModelValue(bindingContext.ModelName, value);

        return value == null
            ? null 
            : value.ConvertTo(typeof(DateTime), CultureInfo.CurrentCulture);
    }
}

并在 Global.Asax 中将以下内容添加到 Application_Start():

ModelBinders.Binders.Add(typeof(DateTime), new DateTimeBinder());
ModelBinders.Binders.Add(typeof(DateTime?), new NullableDateTimeBinder());

this excellent blog 上阅读更多内容,了解 Mvc 框架团队为何为所有用户实施默认文化。

【讨论】:

  • 谢谢,这种方式效果最好。我看到其他回复也可能正常工作,但是在这里如果我们在下面添加它而不是 value.convertTo,则可以在不影响文化的情况下控制日期时间格式,因为我们也为前端设置了格式(例如 jQueryUI datepicker )。 ->>> var dateFormat = bindingContext.ModelMetadata.EditFormatString; return DateTime.ParseExact(((string[]) value.RawValue)[0], dateFormat, CultureInfo.InvariantCulture);
  • 向您致敬,先生。
  • 添加活页夹似乎会破坏Url.Action。当我将DateTime 传递给它时,它不再使用正确的文化。它似乎改用 en-US。
  • @gdoron 我的错。 Url.Action 似乎总是使用InvariantCulture 来生成通过DateTime 路由值的查询字符串,而不管绑定器。
  • 我认为NullableDateTimeBinder 的实现也适用于not null DateTime。我们只能离开NullableDateTimeBinder 班级。
【解决方案2】:

我自己也遇到了这个非常大的问题,经过数小时的尝试和失败后,我得到了一个可行的解决方案,就像你问的那样。

首先,由于不可能仅在属性上使用绑定器,因此您必须实现完整的 ModelBinder。由于您不希望绑定所有单个属性,而只想绑定您关心的那个,因此您可以从 DefaultModelBinder 继承,然后绑定单个属性:

public class DateFiexedCultureModelBinder : DefaultModelBinder
{
    protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, System.ComponentModel.PropertyDescriptor propertyDescriptor)
    {
        if (propertyDescriptor.PropertyType == typeof(DateTime?))
        {
            try
            {
                var model = bindingContext.Model;
                PropertyInfo property = model.GetType().GetProperty(propertyDescriptor.Name);

                var value = bindingContext.ValueProvider.GetValue(propertyDescriptor.Name);

                if (value != null)
                {
                    System.Globalization.CultureInfo cultureinfo = new System.Globalization.CultureInfo("it-CH");
                    var date = DateTime.Parse(value.AttemptedValue, cultureinfo);
                    property.SetValue(model, date, null);
                }
            }
            catch
            {
                //If something wrong, validation should take care
            }
        }
        else
        {
            base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
        }
    }
}

在我的示例中,我正在使用固定文化解析日期,但您想要做的是可能的。您应该创建一个 CustomAttribute(如 DateTimeFormatAttribute)并将其放在您的属性上:

[DateTimeFormat("d.M.yyyy")]
public DateTime Birth { get; set,}

现在在 BindProperty 方法中,您可以使用 DateTimeFormatAttribute 查找属性,而不是查找 DateTime 属性,获取您在构造函数中指定的格式,然后使用 DateTime.ParseExact 解析日期

我希望这会有所帮助,我花了很长时间才提出这个解决方案。一旦我知道如何搜索它,实际上很容易获得这个解决方案:(

【讨论】:

    【解决方案3】:

    我认为您不应该将特定于语言环境的属性放在模型上。

    这个问题的另外两个可能的解决方案是:

    • 让您的页面将日期从特定于语言环境的格式音译为通用格式,例如 JavaScript 中的 yyyy-mm-dd。 (有效,但需要 JavaScript。)
    • 编写一个模型绑定器,在解析日期时考虑当前的 UI 文化。

    要回答您的实际问题,获取自定义属性(对于 MVC 2)的方法是 write an AssociatedMetadataProvider

    【讨论】:

    • 它与特定于区域设置的格式没有真正的关系。问题是 DateTime 必须在字符串中同时包含日期和时间,以便默认活页夹正确解析它。使用哪种本地化无关紧要。我只想从客户端提供日期并将其正确解析为模型绑定上的 DateTime(时间设置为 00:00:00)实例。
    • 如果可能的话,我想避免编写自定义元数据提供程序。但我想这可能正是我需要的。我可以将自己的属性附加到 ModelMetadata 信息中。
    • DateTime.Parse 需要一个字符串是不正确的。试试var dt = DateTime.Parse("2010-03-01"); 我保证它有效!但是,特定的 DateTime 格式 可能会。
    • DateTime.parse 可能没问题, DefaultModelBinder 显然不是。无论如何,我的日期格式与语言环境定义的相同。我尝试加载一个带有日期时间的视图模型并显示一个使用它的强类型视图并显示包括时间在内的日期。当我在我的 DateTime 属性中包含时间时,Nad 一切正常。否则我给出了验证错误(使用 DataAnnotations)
    • 我不太确定“DefaultModelBinder 显然不是”。它工作得很好,至少在这里。我注意到你在斯洛文尼亚,所以有可能(虽然不是明显!)特定配置的机器不会解析 yyyy-mm-dd,尽管 应该 在任何文化中工作。但回到手头,关联的元数据提供程序只有 20 行左右的代码,它将为您的活页夹提供您想要的信息。
    【解决方案4】:

    您可以像这样实现自定义 DateTime Binder,但您必须注意实际客户请求中假定的文化和价值。愿你在 en-US 中得到一个像 mm/dd/yyyy 这样的日期,并希望它在系统文化 en-GB(它会像 dd/mm/yyyy)或不变的文化中转换,就像我们一样,那么你必须先解析它并使用静态外观 Convert 来改变它的行为。

        public class DateTimeModelBinder : IModelBinder
        {
            public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
            {
                var valueResult = bindingContext.ValueProvider
                                  .GetValue(bindingContext.ModelName);
                var modelState = new ModelState {Value = valueResult};
    
                var resDateTime = new DateTime();
    
                if (valueResult == null) return null;
    
                if ((bindingContext.ModelType == typeof(DateTime)|| 
                    bindingContext.ModelType == typeof(DateTime?)))
                {
                    if (bindingContext.ModelName != "Version")
                    {
                        try
                        {
                            resDateTime =
                                Convert.ToDateTime(
                                    DateTime.Parse(valueResult.AttemptedValue, valueResult.Culture,
                                        DateTimeStyles.AdjustToUniversal).ToUniversalTime(), CultureInfo.InvariantCulture);
                        }
                        catch (Exception e)
                        {
                            modelState.Errors.Add(EnterpriseLibraryHelper.HandleDataLayerException(e));
                        }
                    }
                    else
                    {
                        resDateTime =
                            Convert.ToDateTime(
                                DateTime.Parse(valueResult.AttemptedValue, valueResult.Culture), CultureInfo.InvariantCulture);
                    }
                }
                bindingContext.ModelState.Add(bindingContext.ModelName, modelState);
                return resDateTime;
            }
        }
    

    无论如何,无状态应用程序中依赖于文化的 DateTime 解析可能会很残酷……尤其是当您在 javascript 客户端和向后使用 JSON 时。

    【讨论】:

      猜你喜欢
      • 2018-05-07
      • 2017-02-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-04-27
      • 1970-01-01
      相关资源
      最近更新 更多