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

Commit

Permalink
Add Switch.Microsoft.AspNetCore.Mvc.UseDateTimeTypeForDateTimeOffset …
Browse files Browse the repository at this point in the history
…quirks mode

- patch recipients can use switch to undo the #6648 fix
  • Loading branch information
dougbu committed Sep 18, 2017
1 parent 6041c6b commit fd9cb08
Show file tree
Hide file tree
Showing 5 changed files with 208 additions and 62 deletions.
17 changes: 16 additions & 1 deletion src/Microsoft.AspNetCore.Mvc.TagHelpers/InputTagHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
[HtmlTargetElement("input", Attributes = ForAttributeName, TagStructure = TagStructure.WithoutEndTag)]
public class InputTagHelper : TagHelper
{
internal const string UseDateTimeLocalTypeForDateTimeOffsetSwitch = "Switch.Microsoft.AspNetCore.Mvc.UseDateTimeLocalTypeForDateTimeOffset";
private const string ForAttributeName = "asp-for";
private const string FormatAttributeName = "asp-format";

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

static InputTagHelper()
{
if (AppContext.TryGetSwitch(UseDateTimeLocalTypeForDateTimeOffsetSwitch, out var enabled) && enabled)
{
_defaultInputTypes.Remove(nameof(DateTimeOffset));
}
}

/// <summary>
/// Creates a new <see cref="InputTagHelper"/>.
/// </summary>
Expand Down Expand Up @@ -393,8 +402,14 @@ private string GetFormat(ModelExplorer modelExplorer, string inputTypeHint, stri
{
// Rfc3339 mode _may_ override EditFormatString in a limited number of cases. Happens only when
// EditFormatString has a default format i.e. came from a [DataType] attribute.
//
// First condition may occur when Switch.Microsoft.AspNetCore.Mvc.UseDateTimeLocalTypeForDateTimeOffset
// is true in extremely rare cases: The <input/> element for a DateTimeOffset expression must have
// type ="text". Checking the switch again to remove that case.
if (string.Equals("text", inputType) &&
string.Equals(nameof(DateTimeOffset), inputTypeHint, StringComparison.OrdinalIgnoreCase))
string.Equals(nameof(DateTimeOffset), inputTypeHint, StringComparison.OrdinalIgnoreCase) &&
!(AppContext.TryGetSwitch(UseDateTimeLocalTypeForDateTimeOffsetSwitch, out var enabled) &&
enabled))
{
// Auto-select a format that round-trips Offset and sub-Second values in a DateTimeOffset. Not
// done if user chose the "text" type in .cshtml file or with data annotations i.e. when
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
{
public class TemplateRenderer
{
public const string IEnumerableOfIFormFileName = "IEnumerable`" + nameof(IFormFile);
internal const string UseDateTimeLocalTypeForDateTimeOffsetSwitch = "Switch.Microsoft.AspNetCore.Mvc.UseDateTimeLocalTypeForDateTimeOffset";
private const string DisplayTemplateViewPath = "DisplayTemplates";
private const string EditorTemplateViewPath = "EditorTemplates";
public const string IEnumerableOfIFormFileName = "IEnumerable`" + nameof(IFormFile);

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

static TemplateRenderer()
{
if (AppContext.TryGetSwitch(UseDateTimeLocalTypeForDateTimeOffsetSwitch, out var enabled) && enabled)
{
_defaultEditorActions.Remove(nameof(DateTimeOffset));
}
}

public TemplateRenderer(
IViewEngine viewEngine,
IViewBufferScope bufferScope,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Testing;
Expand All @@ -10,6 +11,8 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
{
public class HtmlHelperOptionsTest : IClassFixture<MvcTestFixture<RazorWebSite.Startup>>
{
private const string UseDateTimeLocalTypeForDateTimeOffsetSwitch = "Switch.Microsoft.AspNetCore.Mvc.UseDateTimeLocalTypeForDateTimeOffset";

public HtmlHelperOptionsTest(MvcTestFixture<RazorWebSite.Startup> fixture)
{
Client = fixture.Client;
Expand All @@ -21,22 +24,25 @@ public HtmlHelperOptionsTest(MvcTestFixture<RazorWebSite.Startup> fixture)
public async Task AppWideDefaultsInViewAndPartialView()
{
// Arrange
AppContext.TryGetSwitch(UseDateTimeLocalTypeForDateTimeOffsetSwitch, out var enabled);
var expectedType = enabled ? "datetime-local" : "text";
var expectedOffset = enabled ? string.Empty : "&#x2B;00:00";
var expected =
@"<div class=""validation-summary-errors""><validationSummaryElement>MySummary</validationSummaryElement>
$@"<div class=""validation-summary-errors""><validationSummaryElement>MySummary</validationSummaryElement>
<ul><li>A model error occurred.</li>
</ul></div>
<validationMessageElement class=""field-validation-error"">An error occurred.</validationMessageElement>
<input id=""Prefix!Property1"" name=""Prefix.Property1"" type=""text"" value="""" />
<div class=""editor-label""><label for=""MyDate"">MyDate</label></div>
<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>
<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>
<div class=""validation-summary-errors""><validationSummaryElement>MySummary</validationSummaryElement>
<ul><li>A model error occurred.</li>
</ul></div>
<validationMessageElement class=""field-validation-error"">An error occurred.</validationMessageElement>
<input id=""Prefix!Property1"" name=""Prefix.Property1"" type=""text"" value="""" />
<div class=""editor-label""><label for=""MyDate"">MyDate</label></div>
<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>
<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>
False";

Expand All @@ -52,14 +58,16 @@ public async Task AppWideDefaultsInViewAndPartialView()
public async Task OverrideAppWideDefaultsInViewAndPartialView()
{
// Arrange
AppContext.TryGetSwitch(UseDateTimeLocalTypeForDateTimeOffsetSwitch, out var enabled);
var expectedType = enabled ? "datetime-local" : "text";
var expected =
@"<div class=""validation-summary-errors""><ValidationSummaryInView>MySummary</ValidationSummaryInView>
$@"<div class=""validation-summary-errors""><ValidationSummaryInView>MySummary</ValidationSummaryInView>
<ul><li>A model error occurred.</li>
</ul></div>
<ValidationInView class=""field-validation-error"" data-valmsg-for=""Error"" data-valmsg-replace=""true"">An error occurred.</ValidationInView>
<input id=""Prefix!Property1"" name=""Prefix.Property1"" type=""text"" value="""" />
<div class=""editor-label""><label for=""MyDate"">MyDate</label></div>
<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>
<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>
True
<div class=""validation-summary-errors""><ValidationSummaryInPartialView>MySummary</ValidationSummaryInPartialView>
Expand All @@ -68,7 +76,7 @@ public async Task OverrideAppWideDefaultsInViewAndPartialView()
<ValidationInPartialView class=""field-validation-error"" data-valmsg-for=""Error"" data-valmsg-replace=""true"">An error occurred.</ValidationInPartialView>
<input id=""Prefix!Property1"" name=""Prefix.Property1"" type=""text"" value="""" />
<div class=""editor-label""><label for=""MyDate"">MyDate</label></div>
<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>
<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>
True";

Expand Down
69 changes: 52 additions & 17 deletions test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/InputTagHelperTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
{
public class InputTagHelperTest
{
public static TheoryData MultiAttributeCheckBoxData
public static TheoryData<TagHelperAttributeList, string> MultiAttributeCheckBoxData
{
get
{
Expand Down Expand Up @@ -386,10 +386,22 @@ public async Task ProcessAsync_GeneratesExpectedOutput(
Assert.Equal(expectedTagName, output.TagName);
}

public static TheoryData<string, string> Process_GeneratesFormattedOutputData
{
get
{
AppContext.TryGetSwitch(InputTagHelper.UseDateTimeLocalTypeForDateTimeOffsetSwitch, out var enabled);
return new TheoryData<string, string>
{
{ "datetime", "datetime" },
{ null, enabled ? "datetime-local" : "text" },
{ "hidden", "hidden" },
};
}
}

[Theory]
[InlineData("datetime", "datetime")]
[InlineData(null, "text")]
[InlineData("hidden", "hidden")]
[MemberData(nameof(Process_GeneratesFormattedOutputData))]
public void Process_GeneratesFormattedOutput(string specifiedType, string expectedType)
{
// Arrange
Expand Down Expand Up @@ -1206,20 +1218,43 @@ public async Task ProcessAsync_CallsGenerateTextBox_InputTypeDateTime_RendersAsD
Assert.Equal(expectedTagName, output.TagName);
}

public static TheoryData<string, Html5DateRenderingMode, string, string> ProcessAsync_CallsGenerateTextBox_AddsExpectedAttributesForRfc3339Data
{
get
{
AppContext.TryGetSwitch(InputTagHelper.UseDateTimeLocalTypeForDateTimeOffsetSwitch, out var enabled);
var expectedK = enabled ? string.Empty : "K";
return new TheoryData<string, Html5DateRenderingMode, string, string>
{
{ "Date", Html5DateRenderingMode.CurrentCulture, "{0:d}", "date" }, // Format from [DataType].
{ "Date", Html5DateRenderingMode.Rfc3339, "{0:yyyy-MM-dd}", "date" },
{ "DateTime", Html5DateRenderingMode.CurrentCulture, null, "datetime-local" },
{ "DateTime", Html5DateRenderingMode.Rfc3339, "{0:yyyy-MM-ddTHH:mm:ss.fff}", "datetime-local" },
{ "DateTimeOffset", Html5DateRenderingMode.CurrentCulture, null, enabled ? "datetime-local" : "text" },
{
"DateTimeOffset",
Html5DateRenderingMode.Rfc3339,
$"{{0:yyyy-MM-ddTHH:mm:ss.fff{expectedK}}}",
enabled ? "datetime-local" : "text"
},
{ "DateTimeLocal", Html5DateRenderingMode.CurrentCulture, null, "datetime-local" },
{ "DateTimeLocal", Html5DateRenderingMode.Rfc3339, "{0:yyyy-MM-ddTHH:mm:ss.fff}", "datetime-local" },
{ "Time", Html5DateRenderingMode.CurrentCulture, "{0:t}", "time" }, // Format from [DataType].
{ "Time", Html5DateRenderingMode.Rfc3339, "{0:HH:mm:ss.fff}", "time" },
{ "NullableDate", Html5DateRenderingMode.Rfc3339, "{0:yyyy-MM-dd}", "date" },
{ "NullableDateTime", Html5DateRenderingMode.Rfc3339, "{0:yyyy-MM-ddTHH:mm:ss.fff}", "datetime-local" },
{
"NullableDateTimeOffset",
Html5DateRenderingMode.Rfc3339,
$"{{0:yyyy-MM-ddTHH:mm:ss.fff{expectedK}}}",
enabled ? "datetime-local" : "text"
},
};
}
}

[Theory]
[InlineData("Date", Html5DateRenderingMode.CurrentCulture, "{0:d}", "date")] // Format from [DataType].
[InlineData("Date", Html5DateRenderingMode.Rfc3339, "{0:yyyy-MM-dd}", "date")]
[InlineData("DateTime", Html5DateRenderingMode.CurrentCulture, null, "datetime-local")]
[InlineData("DateTime", Html5DateRenderingMode.Rfc3339, "{0:yyyy-MM-ddTHH:mm:ss.fff}", "datetime-local")]
[InlineData("DateTimeOffset", Html5DateRenderingMode.CurrentCulture, null, "text")]
[InlineData("DateTimeOffset", Html5DateRenderingMode.Rfc3339, "{0:yyyy-MM-ddTHH:mm:ss.fffK}", "text")]
[InlineData("DateTimeLocal", Html5DateRenderingMode.CurrentCulture, null, "datetime-local")]
[InlineData("DateTimeLocal", Html5DateRenderingMode.Rfc3339, "{0:yyyy-MM-ddTHH:mm:ss.fff}", "datetime-local")]
[InlineData("Time", Html5DateRenderingMode.CurrentCulture, "{0:t}", "time")] // Format from [DataType].
[InlineData("Time", Html5DateRenderingMode.Rfc3339, "{0:HH:mm:ss.fff}", "time")]
[InlineData("NullableDate", Html5DateRenderingMode.Rfc3339, "{0:yyyy-MM-dd}", "date")]
[InlineData("NullableDateTime", Html5DateRenderingMode.Rfc3339, "{0:yyyy-MM-ddTHH:mm:ss.fff}", "datetime-local")]
[InlineData("NullableDateTimeOffset", Html5DateRenderingMode.Rfc3339, "{0:yyyy-MM-ddTHH:mm:ss.fffK}", "text")]
[MemberData(nameof(ProcessAsync_CallsGenerateTextBox_AddsExpectedAttributesForRfc3339Data))]
public async Task ProcessAsync_CallsGenerateTextBox_AddsExpectedAttributesForRfc3339(
string propertyName,
Html5DateRenderingMode dateRenderingMode,
Expand Down
Loading

0 comments on commit fd9cb08

Please sign in to comment.