Seperate view and model for enum display

This commit is contained in:
Ryan Brandenburg 2016-11-22 12:16:53 -08:00
parent 52ee9afc31
commit 8f8bf5af34
11 changed files with 303 additions and 45 deletions

View File

@ -3,7 +3,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
@ -524,23 +523,6 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
string templateName,
object additionalViewData)
{
var modelEnum = modelExplorer.Model as Enum;
if (modelExplorer.Metadata.IsEnum && modelEnum != null)
{
var value = modelEnum.ToString("d");
var enumGrouped = modelExplorer.Metadata.EnumGroupedDisplayNamesAndValues;
Debug.Assert(enumGrouped != null);
foreach (var kvp in enumGrouped)
{
if (kvp.Value == value)
{
// Creates a ModelExplorer with the same Metadata except that the Model is a string instead of an Enum
modelExplorer = modelExplorer.GetExplorerForModel(kvp.Key.Name);
break;
}
}
}
var templateBuilder = new TemplateBuilder(
_viewEngine,
_bufferScope,

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Diagnostics;
using System.Globalization;
using Microsoft.AspNetCore.Html;
using Microsoft.AspNetCore.Mvc.ModelBinding;
@ -81,19 +82,6 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
_model = null;
}
var formattedModelValue = _model;
if (_model == null && _readOnly)
{
formattedModelValue = _metadata.NullDisplayText;
}
var formatString = _readOnly ? _metadata.DisplayFormatString : _metadata.EditFormatString;
if (_model != null && !string.IsNullOrEmpty(formatString))
{
formattedModelValue = string.Format(CultureInfo.CurrentCulture, formatString, _model);
}
// Normally this shouldn't happen, unless someone writes their own custom Object templates which
// don't check to make sure that the object hasn't already been displayed
if (_viewData.TemplateInfo.Visited(_modelExplorer))
@ -108,6 +96,40 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
// though _model may have been reset to null. Otherwise we might lose track of the model type /property.
viewData.ModelExplorer = _modelExplorer.GetExplorerForModel(_model);
var formattedModelValue = _model;
if (_model == null && _readOnly)
{
formattedModelValue = _metadata.NullDisplayText;
}
else if (viewData.ModelMetadata.IsEnum)
{
// Cover the case where the model is an enum and we want the string value of it
var modelEnum = _model as Enum;
if (modelEnum != null)
{
var value = modelEnum.ToString("d");
var enumGrouped = viewData.ModelMetadata.EnumGroupedDisplayNamesAndValues;
Debug.Assert(enumGrouped != null);
foreach (var kvp in enumGrouped)
{
if (kvp.Value == value)
{
// Creates a ModelExplorer with the same Metadata except that the Model is a string instead of an Enum
formattedModelValue = kvp.Key.Name;
break;
}
}
}
}
var formatString = _readOnly ?
viewData.ModelMetadata.DisplayFormatString :
viewData.ModelMetadata.EditFormatString;
if (_model != null && !string.IsNullOrEmpty(formatString))
{
formattedModelValue = string.Format(CultureInfo.CurrentCulture, formatString, formattedModelValue);
}
viewData.TemplateInfo.FormattedModelValue = formattedModelValue;
viewData.TemplateInfo.HtmlFieldPrefix = _viewData.TemplateInfo.GetFullHtmlFieldName(_htmlFieldName);

View File

@ -71,6 +71,16 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
}
}
[Fact]
public async Task EnumValues_SerializeCorrectly()
{
// Arrange & Act
var response = await Client.GetStringAsync("http://localhost/HtmlGeneration_Home/Enum");
// Assert
Assert.Equal($"Vrijdag{Environment.NewLine}Month: January", response, ignoreLineEndingDifferences: true);
}
[Theory]
[MemberData(nameof(WebPagesData))]
public async Task HtmlGenerationWebSite_GeneratesExpectedResults(string action, string antiforgeryPath)

View File

@ -94,6 +94,94 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
viewEngine.Verify();
}
public static TheoryData<FormatModel, string> EnumFormatModels
{
get
{
return new TheoryData<FormatModel, string>
{
{
new FormatModel{ FormatProperty = Status.Created },
"Value: CreatedKey"
},
{
new FormatModel { FormatProperty = Status.Done },
"Value: Done"
}
};
}
}
public static TheoryData<FormatModel, string> EnumUnformattedModels
{
get
{
return new TheoryData<FormatModel, string>
{
{
new FormatModel {NonFormatProperty = Status.Created },
"CreatedKey"
},
{
new FormatModel {NonFormatProperty = Status.Done },
"Done"
}
};
}
}
[Theory]
[MemberData(nameof(EnumUnformattedModels))]
public void Display_UsesTemplateUnFormatted(FormatModel model, string expectedResult)
{
// Arrange
var view = new Mock<IView>();
view.Setup(v => v.RenderAsync(It.IsAny<ViewContext>()))
.Callback((ViewContext v) => v.Writer.WriteAsync(v.ViewData.TemplateInfo.FormattedModelValue.ToString()))
.Returns(Task.FromResult(0));
var viewEngine = new Mock<ICompositeViewEngine>(MockBehavior.Strict);
viewEngine
.Setup(v => v.GetView(/*executingFilePath*/ null, It.IsAny<string>(), /*isMainPage*/ false))
.Returns(ViewEngineResult.NotFound(string.Empty, Enumerable.Empty<string>()));
viewEngine
.Setup(v => v.FindView(It.IsAny<ActionContext>(), "DisplayTemplates/Status", /*isMainPage*/ false))
.Returns(ViewEngineResult.Found("Status", view.Object))
.Verifiable();
var helper = DefaultTemplatesUtilities.GetHtmlHelper(model, viewEngine.Object);
// Act
var displayResult = helper.DisplayFor(x => x.NonFormatProperty);
// Assert
Assert.Equal(expectedResult, HtmlContentUtilities.HtmlContentToString(displayResult));
}
[Theory]
[MemberData(nameof(EnumFormatModels))]
public void Display_UsesTemplateFormatted(FormatModel model, string expectedResult)
{
// Arrange
var view = new Mock<IView>();
view.Setup(v => v.RenderAsync(It.IsAny<ViewContext>()))
.Callback((ViewContext v) => v.Writer.WriteAsync(v.ViewData.TemplateInfo.FormattedModelValue.ToString()))
.Returns(Task.FromResult(0));
var viewEngine = new Mock<ICompositeViewEngine>(MockBehavior.Strict);
viewEngine
.Setup(v => v.GetView(/*executingFilePath*/ null, It.IsAny<string>(), /*isMainPage*/ false))
.Returns(ViewEngineResult.NotFound(string.Empty, Enumerable.Empty<string>()));
viewEngine
.Setup(v => v.FindView(It.IsAny<ActionContext>(), "DisplayTemplates/Status", /*isMainPage*/ false))
.Returns(ViewEngineResult.Found("Status", view.Object))
.Verifiable();
var helper = DefaultTemplatesUtilities.GetHtmlHelper(model, viewEngine.Object);
// Act
var displayResult = helper.DisplayFor(x => x.FormatProperty);
// Assert
Assert.Equal(expectedResult, HtmlContentUtilities.HtmlContentToString(displayResult));
}
[Fact]
public void Display_UsesTemplateNameAndAdditionalViewData()
{
@ -423,6 +511,29 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
Assert.Equal("SomeField", HtmlContentUtilities.HtmlContentToString(displayResult));
}
public class StatusResource
{
public static string FaultedKey { get { return "Faulted from ResourceType"; } }
}
public enum Status : byte
{
[Display(Name = "CreatedKey")]
Created,
[Display(Name = "FaultedKey", ResourceType = typeof(StatusResource))]
Faulted,
Done
}
public class FormatModel
{
[DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "Value: {0}")]
public Status FormatProperty { get; set; }
public Status NonFormatProperty { get; set; }
}
private class SomeModel
{
public string SomeProperty { get; set; }
@ -432,19 +543,5 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
{
public Status Status { get; set; }
}
public class StatusResource
{
public static string FaultedKey { get { return "Faulted from ResourceType"; } }
}
private enum Status : byte
{
[Display(Name = "CreatedKey")]
Created,
[Display(Name = "FaultedKey", ResourceType = typeof(StatusResource))]
Faulted,
Done
}
}
}

View File

@ -0,0 +1,68 @@
// 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.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.TestCommon;
using Microsoft.AspNetCore.Mvc.ViewEngines;
using Moq;
using Xunit;
namespace Microsoft.AspNetCore.Mvc.Rendering
{
public class HtmlHelperEditorExtensionsTest
{
[Theory]
[MemberData(nameof(HtmlHelperDisplayExtensionsTest.EnumUnformattedModels),
MemberType = typeof(HtmlHelperDisplayExtensionsTest))]
public void Display_UsesTemplateUnFormatted(HtmlHelperDisplayExtensionsTest.FormatModel model, string expectedResult)
{
// Arrange
var view = new Mock<IView>();
view.Setup(v => v.RenderAsync(It.IsAny<ViewContext>()))
.Callback((ViewContext v) => v.Writer.WriteAsync(v.ViewData.TemplateInfo.FormattedModelValue.ToString()))
.Returns(Task.FromResult(0));
var viewEngine = new Mock<ICompositeViewEngine>(MockBehavior.Strict);
viewEngine
.Setup(v => v.GetView(/*executingFilePath*/ null, It.IsAny<string>(), /*isMainPage*/ false))
.Returns(ViewEngineResult.NotFound(string.Empty, Enumerable.Empty<string>()));
viewEngine
.Setup(v => v.FindView(It.IsAny<ActionContext>(), "EditorTemplates/Status", /*isMainPage*/ false))
.Returns(ViewEngineResult.Found("Status", view.Object))
.Verifiable();
var helper = DefaultTemplatesUtilities.GetHtmlHelper(model, viewEngine.Object);
// Act
var displayResult = helper.EditorFor(x => x.NonFormatProperty);
// Assert
Assert.Equal(expectedResult, HtmlContentUtilities.HtmlContentToString(displayResult));
}
[Theory]
[MemberData(nameof(HtmlHelperDisplayExtensionsTest.EnumFormatModels), MemberType = typeof(HtmlHelperDisplayExtensionsTest))]
public void Display_UsesTemplateFormatted(HtmlHelperDisplayExtensionsTest.FormatModel model, string expectedResult)
{
// Arrange
var view = new Mock<IView>();
view.Setup(v => v.RenderAsync(It.IsAny<ViewContext>()))
.Callback((ViewContext v) => v.Writer.WriteAsync(v.ViewData.TemplateInfo.FormattedModelValue.ToString()))
.Returns(Task.FromResult(0));
var viewEngine = new Mock<ICompositeViewEngine>(MockBehavior.Strict);
viewEngine
.Setup(v => v.GetView(/*executingFilePath*/ null, It.IsAny<string>(), /*isMainPage*/ false))
.Returns(ViewEngineResult.NotFound(string.Empty, Enumerable.Empty<string>()));
viewEngine
.Setup(v => v.FindView(It.IsAny<ActionContext>(), "EditorTemplates/Status", /*isMainPage*/ false))
.Returns(ViewEngineResult.Found("Status", view.Object))
.Verifiable();
var helper = DefaultTemplatesUtilities.GetHtmlHelper(model, viewEngine.Object);
// Act
var displayResult = helper.EditorFor(x => x.FormatProperty);
// Assert
Assert.Equal(expectedResult, HtmlContentUtilities.HtmlContentToString(displayResult));
}
}
}

View File

@ -54,6 +54,11 @@ namespace HtmlGenerationWebSite.Controllers
_productsListWithSelection = new SelectList(_products, "Number", "ProductName", 2);
}
public IActionResult Enum()
{
return View(new AClass { DayOfWeek = Models.DayOfWeek.Friday, Month = Month.FirstOne });
}
public IActionResult Order()
{
ViewData["Items"] = _productsListWithSelection;

View File

@ -0,0 +1,14 @@
// 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.ComponentModel.DataAnnotations;
namespace HtmlGenerationWebSite.Models
{
public class AClass
{
public DayOfWeek DayOfWeek { get; set; }
[DisplayFormat(DataFormatString = "Month: {0}")]
public Month Month { get; set; }
}
}

View File

@ -0,0 +1,16 @@
// 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.
namespace HtmlGenerationWebSite.Models
{
public enum DayOfWeek
{
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday,
Sunday
}
}

View File

@ -0,0 +1,14 @@
// 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.ComponentModel.DataAnnotations;
namespace HtmlGenerationWebSite.Models
{
public enum Month
{
[Display(Name = "January")]
FirstOne,
LastOne
}
}

View File

@ -0,0 +1,4 @@
@using HtmlGenerationWebSite.Models
@model AClass
@Html.DisplayFor(x => x.DayOfWeek)
@Html.DisplayFor(x => x.Month)

View File

@ -0,0 +1,26 @@
@model HtmlGenerationWebSite.Models.DayOfWeek
@switch (Model)
{
case HtmlGenerationWebSite.Models.DayOfWeek.Monday:
<text>Maandag</text>
break;
case HtmlGenerationWebSite.Models.DayOfWeek.Tuesday:
<text>Dinsdag</text>
break;
case HtmlGenerationWebSite.Models.DayOfWeek.Wednesday:
<text>Woensdag</text>
break;
case HtmlGenerationWebSite.Models.DayOfWeek.Thursday:
<text>Donderdag</text>
break;
case HtmlGenerationWebSite.Models.DayOfWeek.Friday:
<text>Vrijdag</text>
break;
case HtmlGenerationWebSite.Models.DayOfWeek.Saturday:
<text>Zaterdag</text>
break;
case HtmlGenerationWebSite.Models.DayOfWeek.Sunday:
<text>Zondag</text>
break;
}