Skip to content
This repository was archived by the owner on Dec 14, 2018. It is now read-only.

Commit fd9cb08

Browse files
committed
Add Switch.Microsoft.AspNetCore.Mvc.UseDateTimeTypeForDateTimeOffset quirks mode
- patch recipients can use switch to undo the #6648 fix
1 parent 6041c6b commit fd9cb08

File tree

5 files changed

+208
-62
lines changed

5 files changed

+208
-62
lines changed

src/Microsoft.AspNetCore.Mvc.TagHelpers/InputTagHelper.cs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
1818
[HtmlTargetElement("input", Attributes = ForAttributeName, TagStructure = TagStructure.WithoutEndTag)]
1919
public class InputTagHelper : TagHelper
2020
{
21+
internal const string UseDateTimeLocalTypeForDateTimeOffsetSwitch = "Switch.Microsoft.AspNetCore.Mvc.UseDateTimeLocalTypeForDateTimeOffset";
2122
private const string ForAttributeName = "asp-for";
2223
private const string FormatAttributeName = "asp-format";
2324

@@ -63,6 +64,14 @@ public class InputTagHelper : TagHelper
6364
{ "time", "{0:HH:mm:ss.fff}" },
6465
};
6566

67+
static InputTagHelper()
68+
{
69+
if (AppContext.TryGetSwitch(UseDateTimeLocalTypeForDateTimeOffsetSwitch, out var enabled) && enabled)
70+
{
71+
_defaultInputTypes.Remove(nameof(DateTimeOffset));
72+
}
73+
}
74+
6675
/// <summary>
6776
/// Creates a new <see cref="InputTagHelper"/>.
6877
/// </summary>
@@ -393,8 +402,14 @@ private string GetFormat(ModelExplorer modelExplorer, string inputTypeHint, stri
393402
{
394403
// Rfc3339 mode _may_ override EditFormatString in a limited number of cases. Happens only when
395404
// EditFormatString has a default format i.e. came from a [DataType] attribute.
405+
//
406+
// First condition may occur when Switch.Microsoft.AspNetCore.Mvc.UseDateTimeLocalTypeForDateTimeOffset
407+
// is true in extremely rare cases: The <input/> element for a DateTimeOffset expression must have
408+
// type ="text". Checking the switch again to remove that case.
396409
if (string.Equals("text", inputType) &&
397-
string.Equals(nameof(DateTimeOffset), inputTypeHint, StringComparison.OrdinalIgnoreCase))
410+
string.Equals(nameof(DateTimeOffset), inputTypeHint, StringComparison.OrdinalIgnoreCase) &&
411+
!(AppContext.TryGetSwitch(UseDateTimeLocalTypeForDateTimeOffsetSwitch, out var enabled) &&
412+
enabled))
398413
{
399414
// Auto-select a format that round-trips Offset and sub-Second values in a DateTimeOffset. Not
400415
// done if user chose the "text" type in .cshtml file or with data annotations i.e. when

src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/TemplateRenderer.cs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,10 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
1717
{
1818
public class TemplateRenderer
1919
{
20+
public const string IEnumerableOfIFormFileName = "IEnumerable`" + nameof(IFormFile);
21+
internal const string UseDateTimeLocalTypeForDateTimeOffsetSwitch = "Switch.Microsoft.AspNetCore.Mvc.UseDateTimeLocalTypeForDateTimeOffset";
2022
private const string DisplayTemplateViewPath = "DisplayTemplates";
2123
private const string EditorTemplateViewPath = "EditorTemplates";
22-
public const string IEnumerableOfIFormFileName = "IEnumerable`" + nameof(IFormFile);
2324

2425
private static readonly Dictionary<string, Func<IHtmlHelper, IHtmlContent>> _defaultDisplayActions =
2526
new Dictionary<string, Func<IHtmlHelper, IHtmlContent>>(StringComparer.OrdinalIgnoreCase)
@@ -75,6 +76,14 @@ public class TemplateRenderer
7576
private readonly string _templateName;
7677
private readonly bool _readOnly;
7778

79+
static TemplateRenderer()
80+
{
81+
if (AppContext.TryGetSwitch(UseDateTimeLocalTypeForDateTimeOffsetSwitch, out var enabled) && enabled)
82+
{
83+
_defaultEditorActions.Remove(nameof(DateTimeOffset));
84+
}
85+
}
86+
7887
public TemplateRenderer(
7988
IViewEngine viewEngine,
8089
IViewBufferScope bufferScope,

test/Microsoft.AspNetCore.Mvc.FunctionalTests/HtmlHelperOptionsTest.cs

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

4+
using System;
45
using System.Net.Http;
56
using System.Threading.Tasks;
67
using Microsoft.AspNetCore.Testing;
@@ -10,6 +11,8 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
1011
{
1112
public class HtmlHelperOptionsTest : IClassFixture<MvcTestFixture<RazorWebSite.Startup>>
1213
{
14+
private const string UseDateTimeLocalTypeForDateTimeOffsetSwitch = "Switch.Microsoft.AspNetCore.Mvc.UseDateTimeLocalTypeForDateTimeOffset";
15+
1316
public HtmlHelperOptionsTest(MvcTestFixture<RazorWebSite.Startup> fixture)
1417
{
1518
Client = fixture.Client;
@@ -21,22 +24,25 @@ public HtmlHelperOptionsTest(MvcTestFixture<RazorWebSite.Startup> fixture)
2124
public async Task AppWideDefaultsInViewAndPartialView()
2225
{
2326
// Arrange
27+
AppContext.TryGetSwitch(UseDateTimeLocalTypeForDateTimeOffsetSwitch, out var enabled);
28+
var expectedType = enabled ? "datetime-local" : "text";
29+
var expectedOffset = enabled ? string.Empty : "&#x2B;00:00";
2430
var expected =
25-
@"<div class=""validation-summary-errors""><validationSummaryElement>MySummary</validationSummaryElement>
31+
$@"<div class=""validation-summary-errors""><validationSummaryElement>MySummary</validationSummaryElement>
2632
<ul><li>A model error occurred.</li>
2733
</ul></div>
2834
<validationMessageElement class=""field-validation-error"">An error occurred.</validationMessageElement>
2935
<input id=""Prefix!Property1"" name=""Prefix.Property1"" type=""text"" value="""" />
3036
<div class=""editor-label""><label for=""MyDate"">MyDate</label></div>
31-
<div class=""editor-field""><input class=""text-box single-line"" id=""MyDate"" name=""MyDate"" type=""text"" value=""2000-01-02T03:04:05.060&#x2B;00:00"" /> </div>
37+
<div class=""editor-field""><input class=""text-box single-line"" id=""MyDate"" name=""MyDate"" type=""{expectedType}"" value=""2000-01-02T03:04:05.060{expectedOffset}"" /> </div>
3238
3339
<div class=""validation-summary-errors""><validationSummaryElement>MySummary</validationSummaryElement>
3440
<ul><li>A model error occurred.</li>
3541
</ul></div>
3642
<validationMessageElement class=""field-validation-error"">An error occurred.</validationMessageElement>
3743
<input id=""Prefix!Property1"" name=""Prefix.Property1"" type=""text"" value="""" />
3844
<div class=""editor-label""><label for=""MyDate"">MyDate</label></div>
39-
<div class=""editor-field""><input class=""text-box single-line"" id=""MyDate"" name=""MyDate"" type=""text"" value=""2000-01-02T03:04:05.060&#x2B;00:00"" /> </div>
45+
<div class=""editor-field""><input class=""text-box single-line"" id=""MyDate"" name=""MyDate"" type=""{expectedType}"" value=""2000-01-02T03:04:05.060{expectedOffset}"" /> </div>
4046
4147
False";
4248

@@ -52,14 +58,16 @@ public async Task AppWideDefaultsInViewAndPartialView()
5258
public async Task OverrideAppWideDefaultsInViewAndPartialView()
5359
{
5460
// Arrange
61+
AppContext.TryGetSwitch(UseDateTimeLocalTypeForDateTimeOffsetSwitch, out var enabled);
62+
var expectedType = enabled ? "datetime-local" : "text";
5563
var expected =
56-
@"<div class=""validation-summary-errors""><ValidationSummaryInView>MySummary</ValidationSummaryInView>
64+
$@"<div class=""validation-summary-errors""><ValidationSummaryInView>MySummary</ValidationSummaryInView>
5765
<ul><li>A model error occurred.</li>
5866
</ul></div>
5967
<ValidationInView class=""field-validation-error"" data-valmsg-for=""Error"" data-valmsg-replace=""true"">An error occurred.</ValidationInView>
6068
<input id=""Prefix!Property1"" name=""Prefix.Property1"" type=""text"" value="""" />
6169
<div class=""editor-label""><label for=""MyDate"">MyDate</label></div>
62-
<div class=""editor-field""><input class=""text-box single-line"" data-val=""true"" data-val-required=""The MyDate field is required."" id=""MyDate"" name=""MyDate"" type=""text"" value=""02/01/2000 03:04:05 &#x2B;00:00"" /> <ValidationInView class=""field-validation-valid"" data-valmsg-for=""MyDate"" data-valmsg-replace=""true""></ValidationInView></div>
70+
<div class=""editor-field""><input class=""text-box single-line"" data-val=""true"" data-val-required=""The MyDate field is required."" id=""MyDate"" name=""MyDate"" type=""{expectedType}"" value=""02/01/2000 03:04:05 &#x2B;00:00"" /> <ValidationInView class=""field-validation-valid"" data-valmsg-for=""MyDate"" data-valmsg-replace=""true""></ValidationInView></div>
6371
6472
True
6573
<div class=""validation-summary-errors""><ValidationSummaryInPartialView>MySummary</ValidationSummaryInPartialView>
@@ -68,7 +76,7 @@ public async Task OverrideAppWideDefaultsInViewAndPartialView()
6876
<ValidationInPartialView class=""field-validation-error"" data-valmsg-for=""Error"" data-valmsg-replace=""true"">An error occurred.</ValidationInPartialView>
6977
<input id=""Prefix!Property1"" name=""Prefix.Property1"" type=""text"" value="""" />
7078
<div class=""editor-label""><label for=""MyDate"">MyDate</label></div>
71-
<div class=""editor-field""><input class=""text-box single-line"" id=""MyDate"" name=""MyDate"" type=""text"" value=""02/01/2000 03:04:05 &#x2B;00:00"" /> <ValidationInPartialView class=""field-validation-valid"" data-valmsg-for=""MyDate"" data-valmsg-replace=""true""></ValidationInPartialView></div>
79+
<div class=""editor-field""><input class=""text-box single-line"" id=""MyDate"" name=""MyDate"" type=""{expectedType}"" value=""02/01/2000 03:04:05 &#x2B;00:00"" /> <ValidationInPartialView class=""field-validation-valid"" data-valmsg-for=""MyDate"" data-valmsg-replace=""true""></ValidationInPartialView></div>
7280
7381
True";
7482

test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/InputTagHelperTest.cs

Lines changed: 52 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
2121
{
2222
public class InputTagHelperTest
2323
{
24-
public static TheoryData MultiAttributeCheckBoxData
24+
public static TheoryData<TagHelperAttributeList, string> MultiAttributeCheckBoxData
2525
{
2626
get
2727
{
@@ -386,10 +386,22 @@ public async Task ProcessAsync_GeneratesExpectedOutput(
386386
Assert.Equal(expectedTagName, output.TagName);
387387
}
388388

389+
public static TheoryData<string, string> Process_GeneratesFormattedOutputData
390+
{
391+
get
392+
{
393+
AppContext.TryGetSwitch(InputTagHelper.UseDateTimeLocalTypeForDateTimeOffsetSwitch, out var enabled);
394+
return new TheoryData<string, string>
395+
{
396+
{ "datetime", "datetime" },
397+
{ null, enabled ? "datetime-local" : "text" },
398+
{ "hidden", "hidden" },
399+
};
400+
}
401+
}
402+
389403
[Theory]
390-
[InlineData("datetime", "datetime")]
391-
[InlineData(null, "text")]
392-
[InlineData("hidden", "hidden")]
404+
[MemberData(nameof(Process_GeneratesFormattedOutputData))]
393405
public void Process_GeneratesFormattedOutput(string specifiedType, string expectedType)
394406
{
395407
// Arrange
@@ -1206,20 +1218,43 @@ public async Task ProcessAsync_CallsGenerateTextBox_InputTypeDateTime_RendersAsD
12061218
Assert.Equal(expectedTagName, output.TagName);
12071219
}
12081220

1221+
public static TheoryData<string, Html5DateRenderingMode, string, string> ProcessAsync_CallsGenerateTextBox_AddsExpectedAttributesForRfc3339Data
1222+
{
1223+
get
1224+
{
1225+
AppContext.TryGetSwitch(InputTagHelper.UseDateTimeLocalTypeForDateTimeOffsetSwitch, out var enabled);
1226+
var expectedK = enabled ? string.Empty : "K";
1227+
return new TheoryData<string, Html5DateRenderingMode, string, string>
1228+
{
1229+
{ "Date", Html5DateRenderingMode.CurrentCulture, "{0:d}", "date" }, // Format from [DataType].
1230+
{ "Date", Html5DateRenderingMode.Rfc3339, "{0:yyyy-MM-dd}", "date" },
1231+
{ "DateTime", Html5DateRenderingMode.CurrentCulture, null, "datetime-local" },
1232+
{ "DateTime", Html5DateRenderingMode.Rfc3339, "{0:yyyy-MM-ddTHH:mm:ss.fff}", "datetime-local" },
1233+
{ "DateTimeOffset", Html5DateRenderingMode.CurrentCulture, null, enabled ? "datetime-local" : "text" },
1234+
{
1235+
"DateTimeOffset",
1236+
Html5DateRenderingMode.Rfc3339,
1237+
$"{{0:yyyy-MM-ddTHH:mm:ss.fff{expectedK}}}",
1238+
enabled ? "datetime-local" : "text"
1239+
},
1240+
{ "DateTimeLocal", Html5DateRenderingMode.CurrentCulture, null, "datetime-local" },
1241+
{ "DateTimeLocal", Html5DateRenderingMode.Rfc3339, "{0:yyyy-MM-ddTHH:mm:ss.fff}", "datetime-local" },
1242+
{ "Time", Html5DateRenderingMode.CurrentCulture, "{0:t}", "time" }, // Format from [DataType].
1243+
{ "Time", Html5DateRenderingMode.Rfc3339, "{0:HH:mm:ss.fff}", "time" },
1244+
{ "NullableDate", Html5DateRenderingMode.Rfc3339, "{0:yyyy-MM-dd}", "date" },
1245+
{ "NullableDateTime", Html5DateRenderingMode.Rfc3339, "{0:yyyy-MM-ddTHH:mm:ss.fff}", "datetime-local" },
1246+
{
1247+
"NullableDateTimeOffset",
1248+
Html5DateRenderingMode.Rfc3339,
1249+
$"{{0:yyyy-MM-ddTHH:mm:ss.fff{expectedK}}}",
1250+
enabled ? "datetime-local" : "text"
1251+
},
1252+
};
1253+
}
1254+
}
1255+
12091256
[Theory]
1210-
[InlineData("Date", Html5DateRenderingMode.CurrentCulture, "{0:d}", "date")] // Format from [DataType].
1211-
[InlineData("Date", Html5DateRenderingMode.Rfc3339, "{0:yyyy-MM-dd}", "date")]
1212-
[InlineData("DateTime", Html5DateRenderingMode.CurrentCulture, null, "datetime-local")]
1213-
[InlineData("DateTime", Html5DateRenderingMode.Rfc3339, "{0:yyyy-MM-ddTHH:mm:ss.fff}", "datetime-local")]
1214-
[InlineData("DateTimeOffset", Html5DateRenderingMode.CurrentCulture, null, "text")]
1215-
[InlineData("DateTimeOffset", Html5DateRenderingMode.Rfc3339, "{0:yyyy-MM-ddTHH:mm:ss.fffK}", "text")]
1216-
[InlineData("DateTimeLocal", Html5DateRenderingMode.CurrentCulture, null, "datetime-local")]
1217-
[InlineData("DateTimeLocal", Html5DateRenderingMode.Rfc3339, "{0:yyyy-MM-ddTHH:mm:ss.fff}", "datetime-local")]
1218-
[InlineData("Time", Html5DateRenderingMode.CurrentCulture, "{0:t}", "time")] // Format from [DataType].
1219-
[InlineData("Time", Html5DateRenderingMode.Rfc3339, "{0:HH:mm:ss.fff}", "time")]
1220-
[InlineData("NullableDate", Html5DateRenderingMode.Rfc3339, "{0:yyyy-MM-dd}", "date")]
1221-
[InlineData("NullableDateTime", Html5DateRenderingMode.Rfc3339, "{0:yyyy-MM-ddTHH:mm:ss.fff}", "datetime-local")]
1222-
[InlineData("NullableDateTimeOffset", Html5DateRenderingMode.Rfc3339, "{0:yyyy-MM-ddTHH:mm:ss.fffK}", "text")]
1257+
[MemberData(nameof(ProcessAsync_CallsGenerateTextBox_AddsExpectedAttributesForRfc3339Data))]
12231258
public async Task ProcessAsync_CallsGenerateTextBox_AddsExpectedAttributesForRfc3339(
12241259
string propertyName,
12251260
Html5DateRenderingMode dateRenderingMode,

0 commit comments

Comments
 (0)