Asp.net MVC 中的自定义 DateTime 模型绑定器

发布于 2024-08-23 12:29:29 字数 871 浏览 10 评论 0原文

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

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

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

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

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

编辑

由于关注点分离,我的模型类是在一个不(也不应该)引用 System.Web.MVC 程序集的程序集中定义的。此处禁止设置自定义绑定(类似于 Scott Hanselman 的示例)属性。

I would like to write my own model binder for DateTime type. First of all I'd like to write a new attribute that I can attach to my model property like:

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

This is the easy part. But the binder part is a bit more difficult. I would like to add a new model binder for type DateTime. I can either

  • implement IModelBinder interface and write my own BindModel() method
  • inherit from DefaultModelBinder and override BindModel() method

My model has a property as seen above (Birth). So when the model tries to bind request data to this property, my model binder's BindModel(controllerContext, bindingContext) gets invoked. Everything ok, but. How do I get property attributes from controller/bindingContext, to parse my date correctly? How can I get to the PropertyDesciptor of property Birth?

Edit

Because of separation of concerns my model class is defined in an assembly that doesn't (and shouldn't) reference System.Web.MVC assembly. Setting custom binding (similar to Scott Hanselman's example) attributes is a no-go here.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

李白 2024-08-30 12:29:29

您可以使用 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());

阅读更多信息 这个优秀的博客,描述了为什么 Mvc 框架团队向所有用户实施默认文化。

you can change the default model binder to use the user culture using 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);
    }
}

And in the Global.Asax add the following to Application_Start():

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

Read more at this excellent blog that describe why Mvc framework team implemented a default Culture to all users.

℡寂寞咖啡 2024-08-30 12:29:29

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

首先,由于仅在属性上拥有绑定器是不可能的,因此您必须实现完整的 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 的日期

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

I had this very big problem myself and after hours of try and fail I got a working solution like you asked.

First of all since having a binder on just a property is not possibile yuo have to implement a full ModelBinder. Since you don't want the bind all the single property but only the one you care you can inherit from DefaultModelBinder and then bind the single property:

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);
        }
    }
}

In my example I'm parsing date with a fiexed culture, but what you want to do is possible. You should create a CustomAttribute (like DateTimeFormatAttribute) and put it over you property:

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

Now in the BindProperty method, instead of looking for a DateTime property you can look for a property with you DateTimeFormatAttribute, grab the format you specified in the constructor and then parse the date with DateTime.ParseExact

I hope this helps, it took me very long to come with this solution. It was actually easy to have this solution once I knew how to search it :(

季末如歌 2024-08-30 12:29:29

我认为您不应该将特定于区域设置的属性放在模型上。

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

  • 让您的页面将日期从特定于区域设置的格式音译为通用格式,例如 JavaScript 中的 yyyy-mm-dd。 (可以工作,但需要 JavaScript。)
  • 编写一个模型绑定器,在解析日期时考虑当前的 UI 区域性。

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

I don't think you should put locale-specific attributes on a model.

Two other possible solutions to this problem are:

  • Have your pages transliterate dates from the locale-specific format to a generic format such as yyyy-mm-dd in JavaScript. (Works, but requires JavaScript.)
  • Write a model binder which considers the current UI culture when parsing dates.

To answer your actual question, the way to get custom attributes (for MVC 2) is to write an AssociatedMetadataProvider.

夜还是长夜 2024-08-30 12:29:29

对于 ASP.NET Core,您可以使用以下自定义模型绑定器。
下面给出了一个示例模型。

public class MyModel
{        
    public string UserName { get; set; }

    [BindProperty(BinderType = typeof(CustomDateTimeBinder))]
    public DateTime Date1 { get; set; }

    [BindProperty(BinderType = typeof(CustomDateTimeBinder))]
    public DateTime? Date2 { get; set; }
} 

日期时间值的自定义绑定器。它需要格式 dd/MM/yyyy

public class CustomDateTimeBinder : IModelBinder
{
    public Task BindModelAsync(ModelBindingContext bindingContext)
    {
        if (bindingContext == null)
        {
            throw new ArgumentNullException(nameof(bindingContext));
        }

        var modelName = bindingContext.ModelName;

        var valueProviderResult = bindingContext.ValueProvider.GetValue(modelName);

        if (valueProviderResult == ValueProviderResult.None)
        {
            return Task.CompletedTask;
        }

        bindingContext.ModelState.SetModelValue(modelName, valueProviderResult);

        var value = valueProviderResult.FirstValue;

        if (string.IsNullOrEmpty(value))
        {
            return Task.CompletedTask;
        }

        if (!DateTime.TryParseExact(value, "dd/MM/yyyy", CultureInfo.InvariantCulture, DateTimeStyles.None, out var dateTime))
        {
            var fieldName = string.Join(" ", Regex.Split(modelName, @"(?<!^)(?=[A-Z])"));
            bindingContext.ModelState.TryAddModelError(
                modelName, $"{fieldName} is invalid.");

            return Task.CompletedTask;
        }


        bindingContext.Result = ModelBindingResult.Success(dateTime);
        return Task.CompletedTask;
    }
}

For ASP.NET Core, you can use the following custom model binder.
A sample model is given below.

public class MyModel
{        
    public string UserName { get; set; }

    [BindProperty(BinderType = typeof(CustomDateTimeBinder))]
    public DateTime Date1 { get; set; }

    [BindProperty(BinderType = typeof(CustomDateTimeBinder))]
    public DateTime? Date2 { get; set; }
} 

The custom binder for DateTime value. It expects the format dd/MM/yyyy.

public class CustomDateTimeBinder : IModelBinder
{
    public Task BindModelAsync(ModelBindingContext bindingContext)
    {
        if (bindingContext == null)
        {
            throw new ArgumentNullException(nameof(bindingContext));
        }

        var modelName = bindingContext.ModelName;

        var valueProviderResult = bindingContext.ValueProvider.GetValue(modelName);

        if (valueProviderResult == ValueProviderResult.None)
        {
            return Task.CompletedTask;
        }

        bindingContext.ModelState.SetModelValue(modelName, valueProviderResult);

        var value = valueProviderResult.FirstValue;

        if (string.IsNullOrEmpty(value))
        {
            return Task.CompletedTask;
        }

        if (!DateTime.TryParseExact(value, "dd/MM/yyyy", CultureInfo.InvariantCulture, DateTimeStyles.None, out var dateTime))
        {
            var fieldName = string.Join(" ", Regex.Split(modelName, @"(?<!^)(?=[A-Z])"));
            bindingContext.ModelState.TryAddModelError(
                modelName, 
quot;{fieldName} is invalid.");

            return Task.CompletedTask;
        }


        bindingContext.Result = ModelBindingResult.Success(dateTime);
        return Task.CompletedTask;
    }
}
锦欢 2024-08-30 12:29:29

您可以像这样实现自定义 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 时。

You could implement a custom DateTime Binder like so, but you have to take care about the assumed culture and value from the actual client request. May you get an Date like mm/dd/yyyy in en-US and want it to convert in the systems culture en-GB (which it would be like dd/mm/yyyy) or an invariant culture, like we do, then you have to parse it before and using the static facade Convert to change it in its behaviour.

    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;
        }
    }

Anyway, culture dependend DateTime parsing in a stateless Application can by a cruelty...Especially when you work with JSON on javascript clientside and backwards.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文