Skip to content

Commit 3a4cdc7

Browse files
[release/8.0] Fix handling of default values for struct types in RDG (#53047)
* Fix handling of default values in RDG * Address feedback * Cover more test scenarios * Add tests for culture + decimal * Add UseCulture test attribute * Fix formatting --------- Co-authored-by: Safia Abdalla <[email protected]>
1 parent c655f85 commit 3a4cdc7

File tree

3 files changed

+199
-1
lines changed

3 files changed

+199
-1
lines changed

src/Http/Http.Extensions/test/RequestDelegateGenerator/RequestDelegateCreationTests.SpecialTypes.cs

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
using System.Text.Json;
1414
using Microsoft.AspNetCore.Http.Features;
1515
using Microsoft.AspNetCore.Http.RequestDelegateGenerator.StaticRouteHandlerModel;
16+
using Microsoft.AspNetCore.Testing;
1617
using Microsoft.Extensions.DependencyInjection;
1718
using Microsoft.Extensions.Primitives;
1819

@@ -148,4 +149,138 @@ static void TestAction([AsParameters] ParametersListWithHttpContext args)
148149
Assert.Same(httpContext.Request, httpContext.Items["request"]);
149150
Assert.Same(httpContext.Response, httpContext.Items["response"]);
150151
}
152+
153+
public static object[][] DefaultValues
154+
{
155+
get
156+
{
157+
return new[]
158+
{
159+
new object[] { "string?", "default", default(string), true },
160+
new object[] { "string", "\"test\"", "test", true },
161+
new object[] { "string", "\"a\" + \"b\"", "ab", true },
162+
new object[] { "DateOnly?", "default", default(DateOnly?), false },
163+
new object[] { "bool", "default", default(bool), true },
164+
new object[] { "bool", "false", false, true },
165+
new object[] { "bool", "true", true, true},
166+
new object[] { "System.Threading.CancellationToken", "default", default(CancellationToken), false },
167+
new object[] { "Todo?", "default", default(Todo), false },
168+
new object[] { "char", "\'a\'", 'a', true },
169+
new object[] { "int", "default", 0, true },
170+
new object[] { "int", "1234", 1234, true },
171+
new object[] { "int", "1234 * 4", 1234 * 4, true },
172+
new object[] { "double", "1.0", 1.0, true },
173+
new object[] { "double", "double.NaN", double.NaN, true },
174+
new object[] { "double", "double.PositiveInfinity", double.PositiveInfinity, true },
175+
new object[] { "double", "double.NegativeInfinity", double.NegativeInfinity, true },
176+
new object[] { "double", "double.E", double.E, true },
177+
new object[] { "double", "double.Epsilon", double.Epsilon, true },
178+
new object[] { "double", "double.NegativeZero", double.NegativeZero, true },
179+
new object[] { "double", "double.MaxValue", double.MaxValue, true },
180+
new object[] { "double", "double.MinValue", double.MinValue, true },
181+
new object[] { "double", "double.Pi", double.Pi, true },
182+
new object[] { "double", "double.Tau", double.Tau, true },
183+
new object[] { "float", "float.NaN", float.NaN, true },
184+
new object[] { "float", "float.PositiveInfinity", float.PositiveInfinity, true },
185+
new object[] { "float", "float.NegativeInfinity", float.NegativeInfinity, true },
186+
new object[] { "float", "float.E", float.E, true },
187+
new object[] { "float", "float.Epsilon", float.Epsilon, true },
188+
new object[] { "float", "float.NegativeZero", float.NegativeZero, true },
189+
new object[] { "float", "float.MaxValue", float.MaxValue, true },
190+
new object[] { "float", "float.MinValue", float.MinValue, true },
191+
new object[] { "float", "float.Pi", float.Pi, true },
192+
new object[] { "float", "float.Tau", float.Tau, true },
193+
new object[] {"decimal", "decimal.MaxValue", decimal.MaxValue, true },
194+
new object[] {"decimal", "decimal.MinusOne", decimal.MinusOne, true },
195+
new object[] {"decimal", "decimal.MinValue", decimal.MinValue, true },
196+
new object[] {"decimal", "decimal.One", decimal.One, true },
197+
new object[] {"decimal", "decimal.Zero", decimal.Zero, true },
198+
new object[] {"long", "long.MaxValue", long.MaxValue, true },
199+
new object[] {"long", "long.MinValue", long.MinValue, true },
200+
new object[] {"short", "short.MaxValue", short.MaxValue, true },
201+
new object[] {"short", "short.MinValue", short.MinValue, true },
202+
new object[] {"ulong", "ulong.MaxValue", ulong.MaxValue, true },
203+
new object[] {"ulong", "ulong.MinValue", ulong.MinValue, true },
204+
new object[] {"ushort", "ushort.MaxValue", ushort.MaxValue, true },
205+
new object[] {"ushort", "ushort.MinValue", ushort.MinValue, true },
206+
};
207+
}
208+
}
209+
210+
[Theory]
211+
[MemberData(nameof(DefaultValues))]
212+
public async Task RequestDelegatePopulatesParametersWithDefaultValues(string type, string defaultValue, object expectedValue, bool declareConst)
213+
{
214+
var source = string.Empty;
215+
if (declareConst)
216+
{
217+
source = $$"""
218+
const {{type}} defaultConst = {{defaultValue}};
219+
static void TestAction(
220+
HttpContext context,
221+
{{type}} parameterWithDefault = {{defaultValue}},
222+
{{type}} parameterWithConst = defaultConst)
223+
{
224+
context.Items.Add("parameterWithDefault", parameterWithDefault);
225+
context.Items.Add("parameterWithConst", parameterWithConst);
226+
}
227+
app.MapPost("/", TestAction);
228+
""";
229+
}
230+
else
231+
{
232+
source = $$"""
233+
static void TestAction(
234+
HttpContext context,
235+
{{type}} parameterWithDefault = {{defaultValue}})
236+
{
237+
context.Items.Add("parameterWithDefault", parameterWithDefault);
238+
}
239+
app.MapPost("/", TestAction);
240+
""";
241+
}
242+
243+
var (_, compilation) = await RunGeneratorAsync(source);
244+
var endpoint = GetEndpointFromCompilation(compilation);
245+
246+
var httpContext = CreateHttpContext();
247+
httpContext.User = new ClaimsPrincipal();
248+
249+
await endpoint.RequestDelegate(httpContext);
250+
251+
Assert.Equal(expectedValue, httpContext.Items["parameterWithDefault"]);
252+
if (declareConst)
253+
{
254+
Assert.Equal(expectedValue, httpContext.Items["parameterWithConst"]);
255+
}
256+
}
257+
258+
[Fact]
259+
[UseCulture("fr-FR")]
260+
public async Task RequestDelegatePopulatesDecimalWithDefaultValuesAndCultureSet()
261+
{
262+
var source = $$"""
263+
const decimal defaultConst = 3.15m;
264+
static void TestAction(
265+
HttpContext context,
266+
decimal parameterWithDefault = 2.15m,
267+
decimal parameterWithConst = defaultConst)
268+
{
269+
context.Items.Add("parameterWithDefault", parameterWithDefault);
270+
context.Items.Add("parameterWithConst", parameterWithConst);
271+
}
272+
app.MapPost("/", TestAction);
273+
""";
274+
275+
var (_, compilation) = await RunGeneratorAsync(source);
276+
var endpoint = GetEndpointFromCompilation(compilation);
277+
278+
var httpContext = CreateHttpContext();
279+
httpContext.User = new ClaimsPrincipal();
280+
281+
await endpoint.RequestDelegate(httpContext);
282+
283+
Assert.Equal(2.15m, httpContext.Items["parameterWithDefault"]);
284+
Assert.Equal(3.15m, httpContext.Items["parameterWithConst"]);
285+
}
151286
}

src/Shared/RoslynUtils/SymbolExtensions.cs

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.Collections.Immutable;
77
using System.Diagnostics;
88
using System.Diagnostics.CodeAnalysis;
9+
using System.Globalization;
910
using System.Linq;
1011
using System.Reflection.PortableExecutable;
1112
using Microsoft.CodeAnalysis;
@@ -176,7 +177,27 @@ public static string GetDefaultValueString(this IParameterSymbol parameterSymbol
176177
{
177178
return !parameterSymbol.HasExplicitDefaultValue
178179
? "null"
179-
: SymbolDisplay.FormatLiteral((parameterSymbol.ExplicitDefaultValue ?? "null").ToString(), parameterSymbol.ExplicitDefaultValue is string);
180+
: InnerGetDefaultValueString(parameterSymbol.ExplicitDefaultValue);
181+
}
182+
183+
private static string InnerGetDefaultValueString(object? defaultValue)
184+
{
185+
return defaultValue switch
186+
{
187+
string s => SymbolDisplay.FormatLiteral(s, true),
188+
char c => SymbolDisplay.FormatLiteral(c, true),
189+
bool b => b ? "true" : "false",
190+
null => "default",
191+
float f when f is float.NegativeInfinity => "float.NegativeInfinity",
192+
float f when f is float.PositiveInfinity => "float.PositiveInfinity",
193+
float f when f is float.NaN => "float.NaN",
194+
float f => $"{SymbolDisplay.FormatPrimitive(f, false, false)}F",
195+
double d when d is double.NegativeInfinity => "double.NegativeInfinity",
196+
double d when d is double.PositiveInfinity => "double.PositiveInfinity",
197+
double d when d is double.NaN => "double.NaN",
198+
decimal d => $"{SymbolDisplay.FormatPrimitive(d, false, false)}M",
199+
_ => SymbolDisplay.FormatPrimitive(defaultValue, false, false),
200+
};
180201
}
181202

182203
public static bool TryGetNamedArgumentValue<T>(this AttributeData attribute, string argumentName, out T? argumentValue)
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
using System;
4+
using System.Globalization;
5+
using System.Reflection;
6+
using Xunit.Sdk;
7+
8+
namespace Microsoft.AspNetCore.Testing;
9+
10+
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
11+
public sealed class UseCultureAttribute : BeforeAfterTestAttribute
12+
{
13+
private CultureInfo _originalCulture;
14+
private CultureInfo _originalUiCulture;
15+
public UseCultureAttribute(string culture)
16+
: this(culture, culture)
17+
{
18+
}
19+
20+
public UseCultureAttribute(string culture, string uiCulture)
21+
{
22+
Culture = new CultureInfo(culture);
23+
UiCulture = new CultureInfo(uiCulture);
24+
}
25+
26+
public CultureInfo Culture { get; }
27+
public CultureInfo UiCulture { get; }
28+
29+
public override void Before(MethodInfo methodUnderTest)
30+
{
31+
_originalCulture = CultureInfo.CurrentCulture;
32+
_originalUiCulture = CultureInfo.CurrentUICulture;
33+
CultureInfo.CurrentCulture = Culture;
34+
CultureInfo.CurrentUICulture = UiCulture;
35+
}
36+
37+
public override void After(MethodInfo methodUnderTest)
38+
{
39+
CultureInfo.CurrentCulture = _originalCulture;
40+
CultureInfo.CurrentUICulture = _originalUiCulture;
41+
}
42+
}

0 commit comments

Comments
 (0)