|
|
@@ -2,9 +2,13 @@
|
|
|
// The .NET Foundation licenses this file to you under the MIT license.
|
|
|
|
|
|
using System.Collections.ObjectModel;
|
|
|
+using System.ComponentModel;
|
|
|
using System.ComponentModel.DataAnnotations;
|
|
|
+using System.Diagnostics.CodeAnalysis;
|
|
|
+using System.Globalization;
|
|
|
using System.Reflection;
|
|
|
using System.Text;
|
|
|
+using System.Xml.Linq;
|
|
|
using Microsoft.AspNetCore.Http;
|
|
|
using Microsoft.AspNetCore.Mvc.Abstractions;
|
|
|
using Microsoft.AspNetCore.Mvc.ActionConstraints;
|
|
|
@@ -1579,6 +1583,120 @@ public class DefaultApiDescriptionProviderTest
|
|
|
Assert.Equal(typeof(string), id.Type);
|
|
|
}
|
|
|
|
|
|
+ [Fact]
|
|
|
+ public void GetApiDescription_ParameterDescription_ParsablePrimitiveType()
|
|
|
+ {
|
|
|
+ // Arrange
|
|
|
+ var action = CreateActionDescriptor(nameof(AcceptsTryParsablePrimitiveType));
|
|
|
+ var parameterDescriptor = action.Parameters.Single();
|
|
|
+
|
|
|
+ // Act
|
|
|
+ var descriptions = GetApiDescriptions(action);
|
|
|
+
|
|
|
+ // Assert
|
|
|
+ var description = Assert.Single(descriptions);
|
|
|
+ Assert.Equal(1, description.ParameterDescriptions.Count);
|
|
|
+
|
|
|
+ var id = Assert.Single(description.ParameterDescriptions, p => p.Name == "id");
|
|
|
+ Assert.Same(BindingSource.Query, id.Source);
|
|
|
+ Assert.Equal(typeof(Guid), id.Type);
|
|
|
+ }
|
|
|
+
|
|
|
+ [Fact]
|
|
|
+ public void GetApiDescription_ParameterDescription_NullableParsablePrimitiveType()
|
|
|
+ {
|
|
|
+ // Arrange
|
|
|
+ var action = CreateActionDescriptor(nameof(AcceptsTryParsableNullablePrimitiveType));
|
|
|
+ var parameterDescriptor = action.Parameters.Single();
|
|
|
+
|
|
|
+ // Act
|
|
|
+ var descriptions = GetApiDescriptions(action);
|
|
|
+
|
|
|
+ // Assert
|
|
|
+ var description = Assert.Single(descriptions);
|
|
|
+ Assert.Equal(1, description.ParameterDescriptions.Count);
|
|
|
+
|
|
|
+ var id = Assert.Single(description.ParameterDescriptions, p => p.Name == "id");
|
|
|
+ Assert.Same(BindingSource.Query, id.Source);
|
|
|
+ Assert.Equal(typeof(Guid?), id.Type);
|
|
|
+ }
|
|
|
+
|
|
|
+ [Fact]
|
|
|
+ public void GetApiDescription_ParameterDescription_ParsableType()
|
|
|
+ {
|
|
|
+ // Arrange
|
|
|
+ var action = CreateActionDescriptor(nameof(AcceptsTryParsableEmployee));
|
|
|
+ var parameterDescriptor = action.Parameters.Single();
|
|
|
+
|
|
|
+ // Act
|
|
|
+ var descriptions = GetApiDescriptions(action);
|
|
|
+
|
|
|
+ // Assert
|
|
|
+ var description = Assert.Single(descriptions);
|
|
|
+ Assert.Equal(1, description.ParameterDescriptions.Count);
|
|
|
+
|
|
|
+ var id = Assert.Single(description.ParameterDescriptions, p => p.Name == "employee");
|
|
|
+ Assert.Same(BindingSource.Query, id.Source);
|
|
|
+ Assert.Equal(typeof(string), id.Type);
|
|
|
+ }
|
|
|
+
|
|
|
+ [Fact]
|
|
|
+ public void GetApiDescription_ParameterDescription_NullableParsableType()
|
|
|
+ {
|
|
|
+ // Arrange
|
|
|
+ var action = CreateActionDescriptor(nameof(AcceptsNullableTryParsableEmployee));
|
|
|
+ var parameterDescriptor = action.Parameters.Single();
|
|
|
+
|
|
|
+ // Act
|
|
|
+ var descriptions = GetApiDescriptions(action);
|
|
|
+
|
|
|
+ // Assert
|
|
|
+ var description = Assert.Single(descriptions);
|
|
|
+ Assert.Equal(1, description.ParameterDescriptions.Count);
|
|
|
+
|
|
|
+ var id = Assert.Single(description.ParameterDescriptions, p => p.Name == "employee");
|
|
|
+ Assert.Same(BindingSource.Query, id.Source);
|
|
|
+ Assert.Equal(typeof(string), id.Type);
|
|
|
+ }
|
|
|
+
|
|
|
+ [Fact]
|
|
|
+ public void GetApiDescription_ParameterDescription_ConvertibleType()
|
|
|
+ {
|
|
|
+ // Arrange
|
|
|
+ var action = CreateActionDescriptor(nameof(AcceptsConvertibleEmployee));
|
|
|
+ var parameterDescriptor = action.Parameters.Single();
|
|
|
+
|
|
|
+ // Act
|
|
|
+ var descriptions = GetApiDescriptions(action);
|
|
|
+
|
|
|
+ // Assert
|
|
|
+ var description = Assert.Single(descriptions);
|
|
|
+ Assert.Equal(1, description.ParameterDescriptions.Count);
|
|
|
+
|
|
|
+ var id = Assert.Single(description.ParameterDescriptions, p => p.Name == "employee");
|
|
|
+ Assert.Same(BindingSource.Query, id.Source);
|
|
|
+ Assert.Equal(typeof(string), id.Type);
|
|
|
+ }
|
|
|
+
|
|
|
+ [Fact]
|
|
|
+ public void GetApiDescription_ParameterDescription_NullableConvertibleType()
|
|
|
+ {
|
|
|
+ // Arrange
|
|
|
+ var action = CreateActionDescriptor(nameof(AcceptsNullableConvertibleEmployee));
|
|
|
+ var parameterDescriptor = action.Parameters.Single();
|
|
|
+
|
|
|
+ // Act
|
|
|
+ var descriptions = GetApiDescriptions(action);
|
|
|
+
|
|
|
+ // Assert
|
|
|
+ var description = Assert.Single(descriptions);
|
|
|
+ Assert.Equal(1, description.ParameterDescriptions.Count);
|
|
|
+
|
|
|
+ var id = Assert.Single(description.ParameterDescriptions, p => p.Name == "employee");
|
|
|
+ Assert.Same(BindingSource.Query, id.Source);
|
|
|
+ Assert.Equal(typeof(string), id.Type);
|
|
|
+ }
|
|
|
+
|
|
|
[Fact]
|
|
|
public void GetApiDescription_ParameterDescription_FromQueryManager()
|
|
|
{
|
|
|
@@ -2374,6 +2492,34 @@ public class DefaultApiDescriptionProviderTest
|
|
|
{
|
|
|
}
|
|
|
|
|
|
+ private void AcceptsTryParsablePrimitiveType([FromQuery] Guid id)
|
|
|
+ {
|
|
|
+ }
|
|
|
+
|
|
|
+ private void AcceptsTryParsableEmployee([FromQuery] TryParsableEmployee employee)
|
|
|
+ {
|
|
|
+ }
|
|
|
+
|
|
|
+ private void AcceptsConvertibleEmployee([FromQuery] ConvertibleEmployee employee)
|
|
|
+ {
|
|
|
+ }
|
|
|
+
|
|
|
+#nullable enable
|
|
|
+
|
|
|
+ private void AcceptsNullableTryParsableEmployee([FromQuery] TryParsableEmployee? employee)
|
|
|
+ {
|
|
|
+ }
|
|
|
+
|
|
|
+ private void AcceptsTryParsableNullablePrimitiveType([FromQuery] Guid? id)
|
|
|
+ {
|
|
|
+ }
|
|
|
+
|
|
|
+ private void AcceptsNullableConvertibleEmployee([FromQuery] ConvertibleEmployee? employee)
|
|
|
+ {
|
|
|
+ }
|
|
|
+
|
|
|
+#nullable restore
|
|
|
+
|
|
|
private void AcceptsOrderDTO(OrderDTO dto)
|
|
|
{
|
|
|
}
|
|
|
@@ -2499,6 +2645,33 @@ public class DefaultApiDescriptionProviderTest
|
|
|
public string Name { get; set; }
|
|
|
}
|
|
|
|
|
|
+ [TypeConverter(typeof(EmployeeConverter))]
|
|
|
+ private class ConvertibleEmployee
|
|
|
+ {
|
|
|
+ public string Name { get; set; }
|
|
|
+ }
|
|
|
+
|
|
|
+ private struct TryParsableEmployee : IParsable<TryParsableEmployee>
|
|
|
+ {
|
|
|
+ public string Name { get; set; }
|
|
|
+
|
|
|
+ public static TryParsableEmployee Parse(string s, IFormatProvider provider)
|
|
|
+ {
|
|
|
+ if (TryParse(s, provider, out var result))
|
|
|
+ {
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ throw new FormatException($"{nameof(s)} is not in the correct format");
|
|
|
+ }
|
|
|
+
|
|
|
+ public static bool TryParse([NotNullWhen(true)] string s, IFormatProvider provider, [MaybeNullWhen(false)] out TryParsableEmployee result)
|
|
|
+ {
|
|
|
+ result = new() { Name = s };
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
private class Manager
|
|
|
{
|
|
|
[FromQuery(Name = "managerid")]
|
|
|
@@ -2727,4 +2900,22 @@ public class DefaultApiDescriptionProviderTest
|
|
|
|
|
|
public bool IsOptional => false;
|
|
|
}
|
|
|
+
|
|
|
+ private class EmployeeConverter : TypeConverter
|
|
|
+ {
|
|
|
+ public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
|
|
|
+ {
|
|
|
+ return sourceType == typeof(string) || base.CanConvertFrom(context, sourceType);
|
|
|
+ }
|
|
|
+
|
|
|
+ public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
|
|
|
+ {
|
|
|
+ if (value is string input)
|
|
|
+ {
|
|
|
+ return new ConvertibleEmployee() { Name = input };
|
|
|
+ }
|
|
|
+
|
|
|
+ return base.ConvertFrom(context, culture, value);
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|