mvc datetime_拆分DateTime-单元测试ASP.NET MVC自定义模型绑定器

mvc datetime

mvc datetime

I've got this form for users to create an event. One of the fields is a DateTime, like this:

我有此表格供用户创建活动。 字段之一是DateTime,如下所示:

image

And that's kind of lame as it's hard to type in a Date and a Time at the same time. It's also no fun. Most sites would have those separated, and ideally use some kind of Calendar Picker with jQuery or something.

这有点of脚,因为很难同时输入日期时间。 这也没有乐趣。 大多数站点会将它们分隔开,并且理想情况下使用带有jQuery的某种Calendar Picker或其他工具。

image

But when you post a form like this back to a Controller Action, it doesn't exactly line up neatly with a System.DateTime object. There's no clean way to get partials like this and combine them into a single DateTime. The "ViewModel" in doesn't match the Model itself. I could certainly make my method take two DateTimes, along with the other fields, then put them together later. It could get mess though, if I split things up even more, like some travel sites do with Month, Date, Year each in separate boxes, then Hours, Minutes, and Seconds off in their own.

但是,当您将这样的表单发布回Controller Action时,它并不能与System.DateTime对象完全对齐。 没有干净的方法来获取像这样的局部并将它们组合成单个DateTime。 中的“ ViewModel”与模型本身不匹配。 我当然可以使我的方法将两个DateTime以及其他字段一起使用,然后将它们放在一起。 但是,如果我将事情分解得更多,可能会变得一团糟,就像某些旅行网站将月份,日期,年份分别放在单独的框中,然后将小时,分钟和秒分开设置一样。

image

I figured this might be a decent place for a custom "DateAndTimeModelBinder" after my last attempt at a Model Binder was near-universally panned. ;)

我最后一次尝试平移模型活页夹之后,认为对于自定义“ DateAndTimeModelBinder ”来说可能是一个不错的地方。 ;)

Here's my thoughts, and I'm interested in your thoughts as well, Dear Reader.

这是我的想法,亲爱的读者,我也对您的想法感兴趣。

DateAndTimeModelBinder (DateAndTimeModelBinder)

First, usage. You can either put this Custom Model Binder in charge of all your DateTimes by registering it in the Global.asax:

首先,用法。 您可以通过在Global.asax中注册该Custom Model Binder来管理所有DateTime:

ModelBinders.Binders[typeof(DateTime)] = 
new DateAndTimeModelBinder() { Date = "Date", Time = "Time" };

The strings there are the suffixes of the fields in your View that will be holding the Date and the Time. There are other options in there like Hour, Minute, you get the idea.

字符串是视图中字段的后缀,这些后缀将保存日期和时间。 还有其他选项,例如“小时”,“分钟”,您就明白了。

Instead of my View having a date in one field:

而不是我的视图在一个字段中有一个日期:

<label for="EventDate">Event Date:</label>
<%= Html.TextBox("EventDate", Model.Dinner.EventDate) %>

I split it up, and add my chosen suffixes:

我将其拆分,然后添加我选择的后缀:

<label for="EventDate">Event Date:</label>
<%= Html.TextBox("EventDate.Date", Model.Dinner.EventDate.ToShortDateString()) %>
<%= Html.TextBox("EventDate.Time", Model.Dinner.EventDate.ToShortTimeString()) %>

Now, when the Form is POST'ed back, no one is the wiser, and the model is unchanged:

现在,当表单回过帐后,没有人是明智的,并且模型未更改:

[AcceptVerbs(HttpVerbs.Post), Authorize]
public ActionResult Create(Dinner dinnerToCreate) {
//The two fields are now inside dinnerCreate.EventDate
// and model validation runs as before...
}

That's the general idea. You can also just put the attribute on a specific parameter, like this:

那是一般的想法。 您也可以将属性放在特定的参数上,如下所示:

public ActionResult Edit(int id, 
[DateAndTime("year", "mo", "day", "hh","mm","secondsorhwatever")]
DateTime foo) {
...yada yada yada...
}

It's so nice, that I give it the Works On My Machine Seal:

太好了,我把它交给了我的机器印章

Here's the code, so far. It's longish. I'm interested in your opinions on how to make it clearer, cleaner and DRYer (without breaking the tests!)

到目前为止,这是代码。 很长。 我对您的意见如何使它更清晰,更清洁和更干燥(不破坏测试!)感兴趣。

NOTE: If you're reading this via RSS, the code will be syntax highlighted and easier to read if you visit this post on my site directly.

注意:如果您通过RSS阅读本文,则该代码将突出显示语法,如果您直接在我的网站上访问此帖子,则代码更易于阅读

public class DateAndTimeModelBinder : IModelBinder
{
public DateAndTimeModelBinder() { }

public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
if (bindingContext == null)
{
throw new ArgumentNullException("bindingContext");
}

//Maybe we're lucky and they just want a DateTime the regular way.
DateTime? dateTimeAttempt = GetA<DateTime>(bindingContext, "DateTime");
if (dateTimeAttempt != null)
{
return dateTimeAttempt.Value;
}

//If they haven't set Month,Day,Year OR Date, set "date" and get ready for an attempt
if (this.MonthDayYearSet == false && this.DateSet == false)
{
this.Date = "Date";
}

//If they haven't set Hour, Minute, Second OR Time, set "time" and get ready for an attempt
if (this.HourMinuteSecondSet == false && this.TimeSet == false)
{
this.Time = "Time";
}

//Did they want the Date *and* Time?
DateTime? dateAttempt = GetA<DateTime>(bindingContext, this.Date);
DateTime? timeAttempt = GetA<DateTime>(bindingContext, this.Time);

//Maybe they wanted the Time via parts
if (this.HourMinuteSecondSet)
{
timeAttempt = new DateTime(
DateTime.MinValue.Year, DateTime.MinValue.Month, DateTime.MinValue.Day,
GetA<int>(bindingContext, this.Hour).Value,
GetA<int>(bindingContext, this.Minute).Value,
GetA<int>(bindingContext, this.Second).Value);
}

//Maybe they wanted the Date via parts
if (this.MonthDayYearSet)
{
dateAttempt = new DateTime(
GetA<int>(bindingContext, this.Year).Value,
GetA<int>(bindingContext, this.Month).Value,
GetA<int>(bindingContext, this.Day).Value,
DateTime.MinValue.Hour, DateTime.MinValue.Minute, DateTime.MinValue.Second);
}

//If we got both parts, assemble them!
if (dateAttempt != null && timeAttempt != null)
{
return new DateTime(dateAttempt.Value.Year,
dateAttempt.Value.Month,
dateAttempt.Value.Day,
timeAttempt.Value.Hour,
timeAttempt.Value.Minute,
timeAttempt.Value.Second);
}
//Only got one half? Return as much as we have!
return dateAttempt ?? timeAttempt;
}

private Nullable<T> GetA<T>(ModelBindingContext bindingContext, string key) where T : struct
{
if (String.IsNullOrEmpty(key)) return null;
ValueProviderResult valueResult;
//Try it with the prefix...
bindingContext.ValueProvider.TryGetValue(bindingContext.ModelName + "." + key, out valueResult);
//Didn't work? Try without the prefix if needed...
if (valueResult == null && bindingContext.FallbackToEmptyPrefix == true)
{
bindingContext.ValueProvider.TryGetValue(key, out valueResult);
}
if (valueResult == null)
{
return null;
}
return (Nullable<T>)valueResult.ConvertTo(typeof(T));
}
public string Date { get; set; }
public string Time { get; set; }

public string Month { get; set; }
public string Day { get; set; }
public string Year { get; set; }

public string Hour { get; set; }
public string Minute { get; set; }
public string Second { get; set; }

public bool DateSet { get { return !String.IsNullOrEmpty(Date); } }
public bool MonthDayYearSet { get { return !(String.IsNullOrEmpty(Month) && String.IsNullOrEmpty(Day) && String.IsNullOrEmpty(Year)); } }

public bool TimeSet { get { return !String.IsNullOrEmpty(Time); } }
public bool HourMinuteSecondSet { get { return !(String.IsNullOrEmpty(Hour) && String.IsNullOrEmpty(Minute) && String.IsNullOrEmpty(Second)); } }

}

public class DateAndTimeAttribute : CustomModelBinderAttribute
{
private IModelBinder _binder;

// The user cares about a full date structure and full
// time structure, or one or the other.
public DateAndTimeAttribute(string date, string time)
{
_binder = new DateAndTimeModelBinder
{
Date = date,
Time = time
};
}

// The user wants to capture the date and time (or only one)
// as individual portions.
public DateAndTimeAttribute(string year, string month, string day,
string hour, string minute, string second)
{
_binder = new DateAndTimeModelBinder
{
Day = day,
Month = month,
Year = year,
Hour = hour,
Minute = minute,
Second = second
};
}

// The user wants to capture the date and time (or only one)
// as individual portions.
public DateAndTimeAttribute(string date, string time,
string year, string month, string day,
string hour, string minute, string second)
{
_binder = new DateAndTimeModelBinder
{
Day = day,
Month = month,
Year = year,
Hour = hour,
Minute = minute,
Second = second,
Date = date,
Time = time
};
}

public override IModelBinder GetBinder() { return _binder; }
}

测试自定义模型活页夹 (Testing the Custom Model Binder)

It works for Dates or Times, also. If you just want a Time, you'll get a MinDate, and if you just want a Date, you'll get a Date at midnight.

它也适用于日期时间。 如果您只想要时间,则将获得一个MinDate;如果您只想要一个日期,则将在午夜获得一个日期。

Here's just two of the tests. Note I was able to test this custom Model Binder without any mocking (thanks Phil!)

这只是其中的两项测试。 注意,我能够在没有任何模拟的情况下测试此自定义Model Binder(感谢Phil !)

Some custom model binders do require mocking if they go digging around in the HttpContext or other concrete places. In this case, I just needed to poke around in the Form, so it was cleaner to use the existing ValueProvider.

如果某些自定义模型活页夹在HttpContext或其他具体位置进行挖掘,则确实需要模拟。 在这种情况下,我只需要在Form中四处摸索,因此使用现有的ValueProvider更干净。

[TestMethod]
public void Date_Can_Be_Pulled_Via_Provided_Month_Day_Year()
{
var dict = new ValueProviderDictionary(null) {
{ "foo.month", new ValueProviderResult("2","2",null) },
{ "foo.day", new ValueProviderResult("12", "12", null) },
{ "foo.year", new ValueProviderResult("1964", "1964", null) }
};

var bindingContext = new ModelBindingContext() { ModelName = "foo", ValueProvider = dict};

DateAndTimeModelBinder b = new DateAndTimeModelBinder() { Month = "month", Day = "day", Year = "year" };

DateTime result = (DateTime)b.BindModel(null, bindingContext);
Assert.AreEqual(DateTime.Parse("1964-02-12 12:00:00 am"), result);
}

[TestMethod]
public void DateTime_Can_Be_Pulled_Via_Provided_Month_Day_Year_Hour_Minute_Second_Alternate_Names()
{
var dict = new ValueProviderDictionary(null) {
{ "foo.month1", new ValueProviderResult("2","2",null) },
{ "foo.day1", new ValueProviderResult("12", "12", null) },
{ "foo.year1", new ValueProviderResult("1964", "1964", null) },
{ "foo.hour1", new ValueProviderResult("13","13",null) },
{ "foo.minute1", new ValueProviderResult("44", "44", null) },
{ "foo.second1", new ValueProviderResult("01", "01", null) }
};

var bindingContext = new ModelBindingContext() { ModelName = "foo", ValueProvider = dict };

DateAndTimeModelBinder b = new DateAndTimeModelBinder() { Month = "month1", Day = "day1", Year = "year1", Hour = "hour1", Minute = "minute1", Second = "second1" };

DateTime result = (DateTime)b.BindModel(null, bindingContext);
Assert.AreEqual(DateTime.Parse("1964-02-12 13:44:01"), result);
}

Thanks to LeviB for his help. Your thoughts?

感谢LeviB的帮助。 你的想法?

翻译自: https://www.hanselman.com/blog/splitting-datetime-unit-testing-aspnet-mvc-custom-model-binders

mvc datetime

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值