diff --git a/samples/MvcSample.Web/Models/TestEnum.cs b/samples/MvcSample.Web/Models/TestEnum.cs new file mode 100644 index 0000000000..d638350c71 --- /dev/null +++ b/samples/MvcSample.Web/Models/TestEnum.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Threading.Tasks; + +namespace MvcSample.Web.Models +{ + public enum TestEnum + { + Zero = 0, + [Display(GroupName = "Primes")] + One = 1, + [Display(GroupName = "Evens", Name = "Dos")] + Two = 2, + [Display(GroupName = "Primes")] + Three = 3, + [Display(Name = "4th")] + Four = 4 + } +} diff --git a/samples/MvcSample.Web/Models/User.cs b/samples/MvcSample.Web/Models/User.cs index 00b239a0a9..048221c744 100644 --- a/samples/MvcSample.Web/Models/User.cs +++ b/samples/MvcSample.Web/Models/User.cs @@ -29,6 +29,7 @@ namespace MvcSample.Web.Models public string About { get; set; } public string Log { get; set; } public IEnumerable OwnedAddresses { get; private set; } + public TestEnum EnumInformation { get; set; } // This does not bind correctly. Only gets highest value. public List ParentsAges { get; private set; } diff --git a/samples/MvcSample.Web/Views/Home/Create.cshtml b/samples/MvcSample.Web/Views/Home/Create.cshtml index 7190f1aaf7..b39329393c 100644 --- a/samples/MvcSample.Web/Views/Home/Create.cshtml +++ b/samples/MvcSample.Web/Views/Home/Create.cshtml @@ -105,12 +105,22 @@ @Html.ListBoxFor(model => model.ParentsAges, (IEnumerable)ViewBag.Ages, htmlAttributes: new { @class = "form-control" }) + + + + + + @Html.DropDownList("EnumInformation", Html.GetEnumSelectList()) + + + + } diff --git a/src/Microsoft.AspNet.Mvc.Abstractions/ModelBinding/EnumGroupAndName.cs b/src/Microsoft.AspNet.Mvc.Abstractions/ModelBinding/EnumGroupAndName.cs new file mode 100644 index 0000000000..11f402ef47 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Abstractions/ModelBinding/EnumGroupAndName.cs @@ -0,0 +1,44 @@ +// 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; + +namespace Microsoft.AspNet.Mvc.ModelBinding +{ + /// + /// An abstraction used when grouping enum values for . + /// + public struct EnumGroupAndName + { + /// + /// Initializes a new instance of the EnumGroupAndName structure. + /// + /// The group name. + /// The name. + public EnumGroupAndName(string group, string name) + { + if (group == null) + { + throw new ArgumentNullException(nameof(group)); + } + + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + Group = group; + Name = name; + } + + /// + /// Gets the Group name. + /// + public string Group { get; } + + /// + /// Gets the name. + /// + public string Name { get; } + } +} diff --git a/src/Microsoft.AspNet.Mvc.Abstractions/ModelBinding/ModelMetadata.cs b/src/Microsoft.AspNet.Mvc.Abstractions/ModelBinding/ModelMetadata.cs index 53304d93ca..c8f9aaf83f 100644 --- a/src/Microsoft.AspNet.Mvc.Abstractions/ModelBinding/ModelMetadata.cs +++ b/src/Microsoft.AspNet.Mvc.Abstractions/ModelBinding/ModelMetadata.cs @@ -139,14 +139,14 @@ namespace Microsoft.AspNet.Mvc.ModelBinding public abstract ModelMetadata ElementMetadata { get; } /// - /// Gets the ordered display names and values of all values in + /// Gets the ordered and grouped display names and values of all values in /// . /// /// - /// An of mappings between field names - /// and values. null if is false. + /// An of mappings between + /// field groups, names and values. null if is false. /// - public abstract IEnumerable> EnumDisplayNamesAndValues { get; } + public abstract IEnumerable> EnumGroupedDisplayNamesAndValues { get; } /// /// Gets the names and values of all values in . diff --git a/src/Microsoft.AspNet.Mvc.Core/ModelBinding/Metadata/DefaultModelMetadata.cs b/src/Microsoft.AspNet.Mvc.Core/ModelBinding/Metadata/DefaultModelMetadata.cs index 50b2f72015..62a80040a9 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ModelBinding/Metadata/DefaultModelMetadata.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ModelBinding/Metadata/DefaultModelMetadata.cs @@ -272,11 +272,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata } /// - public override IEnumerable> EnumDisplayNamesAndValues + public override IEnumerable> EnumGroupedDisplayNamesAndValues { get { - return DisplayMetadata.EnumDisplayNamesAndValues; + return DisplayMetadata.EnumGroupedDisplayNamesAndValues; } } diff --git a/src/Microsoft.AspNet.Mvc.Core/ModelBinding/Metadata/DisplayMetadata.cs b/src/Microsoft.AspNet.Mvc.Core/ModelBinding/Metadata/DisplayMetadata.cs index 707bb0d07c..e72ff6113e 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ModelBinding/Metadata/DisplayMetadata.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ModelBinding/Metadata/DisplayMetadata.cs @@ -57,11 +57,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata public string EditFormatString { get; set; } /// - /// Gets the ordered display names and values of all values in + /// Gets the ordered and grouped display names and values of all values in /// . See - /// . + /// . /// - public IEnumerable> EnumDisplayNamesAndValues { get; set; } + public IEnumerable> EnumGroupedDisplayNamesAndValues { get; set; } /// /// Gets the names and values of all values in diff --git a/src/Microsoft.AspNet.Mvc.DataAnnotations/DataAnnotationsMetadataProvider.cs b/src/Microsoft.AspNet.Mvc.DataAnnotations/DataAnnotationsMetadataProvider.cs index 7e35aaed31..8af110ea1e 100644 --- a/src/Microsoft.AspNet.Mvc.DataAnnotations/DataAnnotationsMetadataProvider.cs +++ b/src/Microsoft.AspNet.Mvc.DataAnnotations/DataAnnotationsMetadataProvider.cs @@ -120,19 +120,20 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata // Order EnumDisplayNamesAndValues to match Enum.GetNames(). That method orders by absolute value, // then its behavior is undefined (but hopefully stable). Add to EnumNamesAndValues in same order but // Dictionary does not guarantee order will be preserved. - var displayNamesAndValues = new List>(); + var groupedDisplayNamesAndValues = new List>(); var namesAndValues = new Dictionary(); foreach (var name in Enum.GetNames(underlyingType)) { var field = underlyingType.GetField(name); var displayName = GetDisplayName(field); + var groupName = GetDisplayGroup(field); var value = ((Enum)field.GetValue(obj: null)).ToString("d"); - displayNamesAndValues.Add(new KeyValuePair(displayName, value)); + groupedDisplayNamesAndValues.Add(new KeyValuePair(new EnumGroupAndName(groupName, displayName), value)); namesAndValues.Add(name, value); } - displayMetadata.EnumDisplayNamesAndValues = displayNamesAndValues; + displayMetadata.EnumGroupedDisplayNamesAndValues = groupedDisplayNamesAndValues; displayMetadata.EnumNamesAndValues = namesAndValues; } @@ -258,5 +259,22 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata return field.Name; } + + // Return non-empty group specified in a [Display] attribute for a field, if any; string.Empty otherwise. + private static string GetDisplayGroup(FieldInfo field) + { + var display = field.GetCustomAttribute(inherit: false); + if (display != null) + { + // Note [Display(Group = "")] is allowed. + var group = display.GetGroupName(); + if (group != null) + { + return group; + } + } + + return string.Empty; + } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewFeatures/HtmlHelper.cs b/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewFeatures/HtmlHelper.cs index 4035cb93bc..def84041c3 100644 --- a/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewFeatures/HtmlHelper.cs +++ b/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewFeatures/HtmlHelper.cs @@ -1168,14 +1168,25 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures } var selectList = new List(); - foreach (var keyValuePair in metadata.EnumDisplayNamesAndValues) + var groupList = new Dictionary(); + foreach (var keyValuePair in metadata.EnumGroupedDisplayNamesAndValues) { var selectListItem = new SelectListItem { - Text = keyValuePair.Key, + Text = keyValuePair.Key.Name, Value = keyValuePair.Value, }; + if (!string.IsNullOrEmpty(keyValuePair.Key.Group)) + { + if (!groupList.ContainsKey(keyValuePair.Key.Group)) + { + groupList[keyValuePair.Key.Group] = new SelectListGroup() { Name = keyValuePair.Key.Group }; + } + + selectListItem.Group = groupList[keyValuePair.Key.Group]; + } + selectList.Add(selectListItem); } diff --git a/test/Microsoft.AspNet.Mvc.Abstractions.Test/ModelBinding/ModelMetadataTest.cs b/test/Microsoft.AspNet.Mvc.Abstractions.Test/ModelBinding/ModelMetadataTest.cs index ec7ae4a14a..8e6b1d8ed2 100644 --- a/test/Microsoft.AspNet.Mvc.Abstractions.Test/ModelBinding/ModelMetadataTest.cs +++ b/test/Microsoft.AspNet.Mvc.Abstractions.Test/ModelBinding/ModelMetadataTest.cs @@ -369,7 +369,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding } } - public override IEnumerable> EnumDisplayNamesAndValues + public override IEnumerable> EnumGroupedDisplayNamesAndValues { get { diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/Metadata/DefaultModelMetadataTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/Metadata/DefaultModelMetadataTest.cs index 9f74f3ad99..720fbdc2d2 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/Metadata/DefaultModelMetadataTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/Metadata/DefaultModelMetadataTest.cs @@ -57,7 +57,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata Assert.Null(metadata.DisplayName); Assert.Null(metadata.EditFormatString); Assert.Null(metadata.ElementMetadata); - Assert.Null(metadata.EnumDisplayNamesAndValues); + Assert.Null(metadata.EnumGroupedDisplayNamesAndValues); Assert.Null(metadata.EnumNamesAndValues); Assert.Null(metadata.NullDisplayText); Assert.Null(metadata.TemplateHint); diff --git a/test/Microsoft.AspNet.Mvc.DataAnnotations.Test/DataAnnotationsMetadataProviderTest.cs b/test/Microsoft.AspNet.Mvc.DataAnnotations.Test/DataAnnotationsMetadataProviderTest.cs index 721d8a44b2..a556fcb1cd 100644 --- a/test/Microsoft.AspNet.Mvc.DataAnnotations.Test/DataAnnotationsMetadataProviderTest.cs +++ b/test/Microsoft.AspNet.Mvc.DataAnnotations.Test/DataAnnotationsMetadataProviderTest.cs @@ -410,112 +410,112 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata } // Type -> expected EnumDisplayNamesAndValues - public static TheoryData>> EnumDisplayNamesData + public static TheoryData>> EnumDisplayNamesData { get { - return new TheoryData>> + return new TheoryData>> { { typeof(ClassWithFields), null }, { typeof(StructWithFields), null }, - { typeof(EmptyEnum), new List>() }, - { typeof(EmptyEnum?), new List>() }, + { typeof(EmptyEnum), new List>() }, + { typeof(EmptyEnum?), new List>() }, { typeof(EnumWithDisplayNames), - new List> + new List> { - new KeyValuePair(string.Empty, "0"), - new KeyValuePair(nameof(EnumWithDisplayNames.One), "1"), - new KeyValuePair("dos", "2"), - new KeyValuePair("tres", "3"), - new KeyValuePair("name from resources", "-2"), - new KeyValuePair("menos uno", "-1"), + new KeyValuePair(new EnumGroupAndName("Zero", string.Empty), "0"), + new KeyValuePair(new EnumGroupAndName(string.Empty, nameof(EnumWithDisplayNames.One)), "1"), + new KeyValuePair(new EnumGroupAndName(string.Empty, "dos"), "2"), + new KeyValuePair(new EnumGroupAndName(string.Empty, "tres"), "3"), + new KeyValuePair(new EnumGroupAndName(string.Empty, "name from resources"), "-2"), + new KeyValuePair(new EnumGroupAndName("Negatives", "menos uno"), "-1"), } }, { typeof(EnumWithDisplayNames?), - new List> + new List> { - new KeyValuePair(string.Empty, "0"), - new KeyValuePair(nameof(EnumWithDisplayNames.One), "1"), - new KeyValuePair("dos", "2"), - new KeyValuePair("tres", "3"), - new KeyValuePair("name from resources", "-2"), - new KeyValuePair("menos uno", "-1"), + new KeyValuePair(new EnumGroupAndName("Zero", string.Empty), "0"), + new KeyValuePair(new EnumGroupAndName(string.Empty, nameof(EnumWithDisplayNames.One)), "1"), + new KeyValuePair(new EnumGroupAndName(string.Empty, "dos"), "2"), + new KeyValuePair(new EnumGroupAndName(string.Empty, "tres"), "3"), + new KeyValuePair(new EnumGroupAndName(string.Empty, "name from resources"), "-2"), + new KeyValuePair(new EnumGroupAndName("Negatives", "menos uno"), "-1"), } }, { // Note order duplicates appear cannot be inferred easily e.g. does not match the source. // Zero is before None but Two is before Duece in the class below. typeof(EnumWithDuplicates), - new List> + new List> { - new KeyValuePair(nameof(EnumWithDuplicates.Zero), "0"), - new KeyValuePair(nameof(EnumWithDuplicates.None), "0"), - new KeyValuePair(nameof(EnumWithDuplicates.One), "1"), - new KeyValuePair(nameof(EnumWithDuplicates.Duece), "2"), - new KeyValuePair(nameof(EnumWithDuplicates.Two), "2"), - new KeyValuePair(nameof(EnumWithDuplicates.MoreThanTwo), "3"), - new KeyValuePair(nameof(EnumWithDuplicates.Three), "3"), + new KeyValuePair(new EnumGroupAndName(string.Empty, nameof(EnumWithDuplicates.Zero)), "0"), + new KeyValuePair(new EnumGroupAndName(string.Empty, nameof(EnumWithDuplicates.None)), "0"), + new KeyValuePair(new EnumGroupAndName(string.Empty, nameof(EnumWithDuplicates.One)), "1"), + new KeyValuePair(new EnumGroupAndName(string.Empty, nameof(EnumWithDuplicates.Duece)), "2"), + new KeyValuePair(new EnumGroupAndName(string.Empty, nameof(EnumWithDuplicates.Two)), "2"), + new KeyValuePair(new EnumGroupAndName(string.Empty, nameof(EnumWithDuplicates.MoreThanTwo)), "3"), + new KeyValuePair(new EnumGroupAndName(string.Empty, nameof(EnumWithDuplicates.Three)), "3"), } }, { typeof(EnumWithDuplicates?), - new List> + new List> { - new KeyValuePair(nameof(EnumWithDuplicates.Zero), "0"), - new KeyValuePair(nameof(EnumWithDuplicates.None), "0"), - new KeyValuePair(nameof(EnumWithDuplicates.One), "1"), - new KeyValuePair(nameof(EnumWithDuplicates.Duece), "2"), - new KeyValuePair(nameof(EnumWithDuplicates.Two), "2"), - new KeyValuePair(nameof(EnumWithDuplicates.MoreThanTwo), "3"), - new KeyValuePair(nameof(EnumWithDuplicates.Three), "3"), + new KeyValuePair(new EnumGroupAndName(string.Empty, nameof(EnumWithDuplicates.Zero)), "0"), + new KeyValuePair(new EnumGroupAndName(string.Empty, nameof(EnumWithDuplicates.None)), "0"), + new KeyValuePair(new EnumGroupAndName(string.Empty, nameof(EnumWithDuplicates.One)), "1"), + new KeyValuePair(new EnumGroupAndName(string.Empty, nameof(EnumWithDuplicates.Duece)), "2"), + new KeyValuePair(new EnumGroupAndName(string.Empty, nameof(EnumWithDuplicates.Two)), "2"), + new KeyValuePair(new EnumGroupAndName(string.Empty, nameof(EnumWithDuplicates.MoreThanTwo)), "3"), + new KeyValuePair(new EnumGroupAndName(string.Empty, nameof(EnumWithDuplicates.Three)), "3"), } }, { typeof(EnumWithFlags), - new List> + new List> { - new KeyValuePair(nameof(EnumWithFlags.Zero), "0"), - new KeyValuePair(nameof(EnumWithFlags.One), "1"), - new KeyValuePair(nameof(EnumWithFlags.Two), "2"), - new KeyValuePair(nameof(EnumWithFlags.Four), "4"), - new KeyValuePair(nameof(EnumWithFlags.All), "-1"), + new KeyValuePair(new EnumGroupAndName(string.Empty, nameof(EnumWithFlags.Zero)), "0"), + new KeyValuePair(new EnumGroupAndName(string.Empty, nameof(EnumWithFlags.One)), "1"), + new KeyValuePair(new EnumGroupAndName(string.Empty, nameof(EnumWithFlags.Two)), "2"), + new KeyValuePair(new EnumGroupAndName(string.Empty, nameof(EnumWithFlags.Four)), "4"), + new KeyValuePair(new EnumGroupAndName(string.Empty, nameof(EnumWithFlags.All)), "-1"), } }, { typeof(EnumWithFlags?), - new List> + new List> { - new KeyValuePair(nameof(EnumWithFlags.Zero), "0"), - new KeyValuePair(nameof(EnumWithFlags.One), "1"), - new KeyValuePair(nameof(EnumWithFlags.Two), "2"), - new KeyValuePair(nameof(EnumWithFlags.Four), "4"), - new KeyValuePair(nameof(EnumWithFlags.All), "-1"), + new KeyValuePair(new EnumGroupAndName(string.Empty, nameof(EnumWithFlags.Zero)), "0"), + new KeyValuePair(new EnumGroupAndName(string.Empty, nameof(EnumWithFlags.One)), "1"), + new KeyValuePair(new EnumGroupAndName(string.Empty, nameof(EnumWithFlags.Two)), "2"), + new KeyValuePair(new EnumGroupAndName(string.Empty, nameof(EnumWithFlags.Four)), "4"), + new KeyValuePair(new EnumGroupAndName(string.Empty, nameof(EnumWithFlags.All)), "-1"), } }, { typeof(EnumWithFields), - new List> + new List> { - new KeyValuePair(nameof(EnumWithFields.Zero), "0"), - new KeyValuePair(nameof(EnumWithFields.One), "1"), - new KeyValuePair(nameof(EnumWithFields.Two), "2"), - new KeyValuePair(nameof(EnumWithFields.Three), "3"), - new KeyValuePair(nameof(EnumWithFields.MinusTwo), "-2"), - new KeyValuePair(nameof(EnumWithFields.MinusOne), "-1"), + new KeyValuePair(new EnumGroupAndName(string.Empty, nameof(EnumWithFields.Zero)), "0"), + new KeyValuePair(new EnumGroupAndName(string.Empty, nameof(EnumWithFields.One)), "1"), + new KeyValuePair(new EnumGroupAndName(string.Empty, nameof(EnumWithFields.Two)), "2"), + new KeyValuePair(new EnumGroupAndName(string.Empty, nameof(EnumWithFields.Three)), "3"), + new KeyValuePair(new EnumGroupAndName(string.Empty, nameof(EnumWithFields.MinusTwo)), "-2"), + new KeyValuePair(new EnumGroupAndName(string.Empty, nameof(EnumWithFields.MinusOne)), "-1"), } }, { typeof(EnumWithFields?), - new List> + new List> { - new KeyValuePair(nameof(EnumWithFields.Zero), "0"), - new KeyValuePair(nameof(EnumWithFields.One), "1"), - new KeyValuePair(nameof(EnumWithFields.Two), "2"), - new KeyValuePair(nameof(EnumWithFields.Three), "3"), - new KeyValuePair(nameof(EnumWithFields.MinusTwo), "-2"), - new KeyValuePair(nameof(EnumWithFields.MinusOne), "-1"), + new KeyValuePair(new EnumGroupAndName(string.Empty, nameof(EnumWithFields.Zero)), "0"), + new KeyValuePair(new EnumGroupAndName(string.Empty, nameof(EnumWithFields.One)), "1"), + new KeyValuePair(new EnumGroupAndName(string.Empty, nameof(EnumWithFields.Two)), "2"), + new KeyValuePair(new EnumGroupAndName(string.Empty, nameof(EnumWithFields.Three)), "3"), + new KeyValuePair(new EnumGroupAndName(string.Empty, nameof(EnumWithFields.MinusTwo)), "-2"), + new KeyValuePair(new EnumGroupAndName(string.Empty, nameof(EnumWithFields.MinusOne)), "-1"), } }, }; @@ -524,9 +524,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata [Theory] [MemberData(nameof(EnumDisplayNamesData))] - public void GetDisplayMetadata_EnumDisplayNamesAndValues_ReflectsModelType( + public void GetDisplayMetadata_EnumGroupedDisplayNamesAndValues_ReflectsModelType( Type type, - IEnumerable> expectedKeyValuePairs) + IEnumerable> expectedKeyValuePairs) { // Arrange var provider = new DataAnnotationsMetadataProvider(); @@ -541,8 +541,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata // Assert // OrderBy is used because the order of the results may very depending on the platform / client. Assert.Equal( - expectedKeyValuePairs?.OrderBy(item => item.Key, StringComparer.Ordinal), - context.DisplayMetadata.EnumDisplayNamesAndValues?.OrderBy(item => item.Key, StringComparer.Ordinal)); + expectedKeyValuePairs?.OrderBy(item => item.Key.Group, StringComparer.Ordinal) + .ThenBy(item => item.Key.Name, StringComparer.Ordinal), + context.DisplayMetadata.EnumGroupedDisplayNamesAndValues?.OrderBy(item => item.Key.Group, StringComparer.Ordinal) + .ThenBy(item => item.Key.Name, StringComparer.Ordinal)); } [Fact] @@ -708,10 +710,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata [Display(ShortName = "uno")] One = 1, - [Display(Name = "")] + [Display(Name = "", GroupName = "Zero")] Zero = 0, - [Display(Name = "menos uno")] + [Display(Name = "menos uno", GroupName = "Negatives")] MinusOne = -1, #if USE_REAL_RESOURCES diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/MvcSampleTests.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/MvcSampleTests.cs index fa2c2c03b7..6027d34bf7 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/MvcSampleTests.cs +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/MvcSampleTests.cs @@ -112,8 +112,8 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests "\"http://schemas.datacontract.org/2004/07/MvcSample.Web.Models\">I like playing Football" + "
My address
13true" + "
Dependents address
0false" + - "0Dependents name" + - "13.37" + + "Zero0Dependents name" + + "
Zero13.37" + "My nameSoftware Engineer", await response.Content.ReadAsStringAsync()); }