From 6c462f36462644186d5126ac423757d3b0570de9 Mon Sep 17 00:00:00 2001 From: martincostello Date: Sun, 14 Apr 2024 17:12:18 +0100 Subject: [PATCH] Support native AoT with SwaggerUI Add support for using SwaggerUI with native AoT. SwaggerGen still does not support native AoT, though it happens to work for simple cases. Resolves #2550. --- Swashbuckle.AspNetCore.sln | 15 ++++ .../Swashbuckle.AspNetCore.Swagger.csproj | 3 + .../JavascriptStringEnumConverter.cs | 18 ++++ ...JavascriptStringEnumEnumerableConverter.cs | 67 +++++++++++++++ .../SwaggerUIJsonSerializerContext.cs | 13 +++ .../SwaggerUIMiddleware.cs | 83 ++++++++++++++----- .../SwaggerUIOptions.cs | 24 +++++- .../Swashbuckle.AspNetCore.SwaggerUI.csproj | 4 + test/WebSites/WebApi.Aot/Program.cs | 51 ++++++++++++ .../WebApi.Aot/Properties/launchSettings.json | 15 ++++ test/WebSites/WebApi.Aot/WebApi.Aot.csproj | 17 ++++ test/WebSites/WebApi.Aot/WebApi.Aot.http | 9 ++ .../WebApi.Aot/appsettings.Development.json | 8 ++ test/WebSites/WebApi.Aot/appsettings.json | 9 ++ 14 files changed, 312 insertions(+), 24 deletions(-) create mode 100644 src/Swashbuckle.AspNetCore.SwaggerUI/JavascriptStringEnumConverter.cs create mode 100644 src/Swashbuckle.AspNetCore.SwaggerUI/JavascriptStringEnumEnumerableConverter.cs create mode 100644 src/Swashbuckle.AspNetCore.SwaggerUI/SwaggerUIJsonSerializerContext.cs create mode 100644 test/WebSites/WebApi.Aot/Program.cs create mode 100644 test/WebSites/WebApi.Aot/Properties/launchSettings.json create mode 100644 test/WebSites/WebApi.Aot/WebApi.Aot.csproj create mode 100644 test/WebSites/WebApi.Aot/WebApi.Aot.http create mode 100644 test/WebSites/WebApi.Aot/appsettings.Development.json create mode 100644 test/WebSites/WebApi.Aot/appsettings.json diff --git a/Swashbuckle.AspNetCore.sln b/Swashbuckle.AspNetCore.sln index a81e7991a3..c95003fafb 100644 --- a/Swashbuckle.AspNetCore.sln +++ b/Swashbuckle.AspNetCore.sln @@ -111,6 +111,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebApi", "test\WebSites\WebApi\WebApi.csproj", "{DE1D77F8-3916-4DEE-A57D-6DDC357F64C6}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebApi.Aot", "test\WebSites\WebApi.Aot\WebApi.Aot.csproj", "{07BB09CF-6C6F-4D00-A459-93586345C921}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -529,6 +531,18 @@ Global {DE1D77F8-3916-4DEE-A57D-6DDC357F64C6}.Release|x64.Build.0 = Release|Any CPU {DE1D77F8-3916-4DEE-A57D-6DDC357F64C6}.Release|x86.ActiveCfg = Release|Any CPU {DE1D77F8-3916-4DEE-A57D-6DDC357F64C6}.Release|x86.Build.0 = Release|Any CPU + {07BB09CF-6C6F-4D00-A459-93586345C921}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {07BB09CF-6C6F-4D00-A459-93586345C921}.Debug|Any CPU.Build.0 = Debug|Any CPU + {07BB09CF-6C6F-4D00-A459-93586345C921}.Debug|x64.ActiveCfg = Debug|Any CPU + {07BB09CF-6C6F-4D00-A459-93586345C921}.Debug|x64.Build.0 = Debug|Any CPU + {07BB09CF-6C6F-4D00-A459-93586345C921}.Debug|x86.ActiveCfg = Debug|Any CPU + {07BB09CF-6C6F-4D00-A459-93586345C921}.Debug|x86.Build.0 = Debug|Any CPU + {07BB09CF-6C6F-4D00-A459-93586345C921}.Release|Any CPU.ActiveCfg = Release|Any CPU + {07BB09CF-6C6F-4D00-A459-93586345C921}.Release|Any CPU.Build.0 = Release|Any CPU + {07BB09CF-6C6F-4D00-A459-93586345C921}.Release|x64.ActiveCfg = Release|Any CPU + {07BB09CF-6C6F-4D00-A459-93586345C921}.Release|x64.Build.0 = Release|Any CPU + {07BB09CF-6C6F-4D00-A459-93586345C921}.Release|x86.ActiveCfg = Release|Any CPU + {07BB09CF-6C6F-4D00-A459-93586345C921}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -571,6 +585,7 @@ Global {3BA087DA-788C-43D6-9D8B-1EF017014A4A} = {FA1B4021-0A97-4F68-B966-148191F6AAA8} {A0EC16BE-C520-4FCF-BB54-2D79CD255F00} = {3BA087DA-788C-43D6-9D8B-1EF017014A4A} {DE1D77F8-3916-4DEE-A57D-6DDC357F64C6} = {DB3F57FC-1472-4F03-B551-43394DA3C5EB} + {07BB09CF-6C6F-4D00-A459-93586345C921} = {DB3F57FC-1472-4F03-B551-43394DA3C5EB} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {36FC6A67-247D-4149-8EDD-79FFD1A75F51} diff --git a/src/Swashbuckle.AspNetCore.Swagger/Swashbuckle.AspNetCore.Swagger.csproj b/src/Swashbuckle.AspNetCore.Swagger/Swashbuckle.AspNetCore.Swagger.csproj index 3bb7621445..06fd4c19bb 100644 --- a/src/Swashbuckle.AspNetCore.Swagger/Swashbuckle.AspNetCore.Swagger.csproj +++ b/src/Swashbuckle.AspNetCore.Swagger/Swashbuckle.AspNetCore.Swagger.csproj @@ -9,6 +9,9 @@ true netstandard2.0;netcoreapp3.0;net5.0;net6.0;net7.0;net8.0 + + true + diff --git a/src/Swashbuckle.AspNetCore.SwaggerUI/JavascriptStringEnumConverter.cs b/src/Swashbuckle.AspNetCore.SwaggerUI/JavascriptStringEnumConverter.cs new file mode 100644 index 0000000000..3d9a3fd60e --- /dev/null +++ b/src/Swashbuckle.AspNetCore.SwaggerUI/JavascriptStringEnumConverter.cs @@ -0,0 +1,18 @@ +#if NET6_0_OR_GREATER +using System; +using System.Diagnostics.CodeAnalysis; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Swashbuckle.AspNetCore.SwaggerUI; + +internal sealed class JavascriptStringEnumConverter<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] TEnum>() : +#if NET8_0_OR_GREATER + JsonStringEnumConverter(JsonNamingPolicy.CamelCase, false) +#else + JsonStringEnumConverter(JsonNamingPolicy.CamelCase, false) +#endif + where TEnum : struct, Enum +{ +} +#endif diff --git a/src/Swashbuckle.AspNetCore.SwaggerUI/JavascriptStringEnumEnumerableConverter.cs b/src/Swashbuckle.AspNetCore.SwaggerUI/JavascriptStringEnumEnumerableConverter.cs new file mode 100644 index 0000000000..4f5ac37958 --- /dev/null +++ b/src/Swashbuckle.AspNetCore.SwaggerUI/JavascriptStringEnumEnumerableConverter.cs @@ -0,0 +1,67 @@ +#if NET6_0_OR_GREATER +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Swashbuckle.AspNetCore.SwaggerUI; + +internal sealed class JavascriptStringEnumEnumerableConverter<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] TEnum>() : + JsonConverterFactory + where TEnum : struct, Enum +{ + private readonly JavascriptStringEnumConverter _enumConverter = new(); + + public override bool CanConvert(Type typeToConvert) + => typeToConvert.IsAssignableFrom(typeof(IEnumerable)); + + public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) + { + if (!typeToConvert.IsAssignableFrom(typeof(IEnumerable))) + { + throw new NotSupportedException($"The type {typeToConvert} is not supported."); + } + + var valueConverter = (JsonConverter)_enumConverter.CreateConverter(typeof(TEnum), options); + return new EnumEnumerableConverter(valueConverter); + } + + private sealed class EnumEnumerableConverter(JsonConverter inner) : JsonConverter> + { + public override IEnumerable Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType != JsonTokenType.StartArray) + { + throw new JsonException("Expected start of a JSON array."); + } + + var result = new List(); + + while (reader.Read()) + { + if (reader.TokenType == JsonTokenType.EndArray) + { + return result; + } + + result.Add(inner.Read(ref reader, typeof(TEnum), options)); + } + + throw new JsonException("JSON array is malformed."); + } + + public override void Write(Utf8JsonWriter writer, IEnumerable value, JsonSerializerOptions options) + { + writer.WriteStartArray(); + + foreach (var item in value) + { + inner.Write(writer, item, options); + } + + writer.WriteEndArray(); + } + } +} +#endif diff --git a/src/Swashbuckle.AspNetCore.SwaggerUI/SwaggerUIJsonSerializerContext.cs b/src/Swashbuckle.AspNetCore.SwaggerUI/SwaggerUIJsonSerializerContext.cs new file mode 100644 index 0000000000..ed15c9df84 --- /dev/null +++ b/src/Swashbuckle.AspNetCore.SwaggerUI/SwaggerUIJsonSerializerContext.cs @@ -0,0 +1,13 @@ +#if NET6_0_OR_GREATER +using System.Text.Json.Serialization; + +namespace Swashbuckle.AspNetCore.SwaggerUI; + +[JsonSerializable(typeof(ConfigObject))] +[JsonSerializable(typeof(InterceptorFunctions))] +[JsonSerializable(typeof(OAuthConfigObject))] +[JsonSourceGenerationOptions( + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase)] +internal sealed partial class SwaggerUIOptionsJsonContext : JsonSerializerContext; +#endif diff --git a/src/Swashbuckle.AspNetCore.SwaggerUI/SwaggerUIMiddleware.cs b/src/Swashbuckle.AspNetCore.SwaggerUI/SwaggerUIMiddleware.cs index e5a5ae46b9..52dac4b73e 100644 --- a/src/Swashbuckle.AspNetCore.SwaggerUI/SwaggerUIMiddleware.cs +++ b/src/Swashbuckle.AspNetCore.SwaggerUI/SwaggerUIMiddleware.cs @@ -1,21 +1,20 @@ -using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Linq; using System.Reflection; -using System.Threading.Tasks; -using System.Text.RegularExpressions; using System.Text; -using System.Collections.Generic; -using System.IO; using System.Text.Json; using System.Text.Json.Serialization; +using System.Text.RegularExpressions; +using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.StaticFiles; +using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -using Microsoft.Extensions.FileProviders; -using Microsoft.AspNetCore.StaticFiles; -using Microsoft.AspNetCore.Http.Extensions; -using System.Linq; #if NETSTANDARD2_0 using IWebHostEnvironment = Microsoft.AspNetCore.Hosting.IHostingEnvironment; @@ -23,7 +22,7 @@ namespace Swashbuckle.AspNetCore.SwaggerUI { - public class SwaggerUIMiddleware + public partial class SwaggerUIMiddleware { private const string EmbeddedFileNamespace = "Swashbuckle.AspNetCore.SwaggerUI.node_modules.swagger_ui_dist"; @@ -41,14 +40,25 @@ public SwaggerUIMiddleware( _staticFileMiddleware = CreateStaticFileMiddleware(next, hostingEnv, loggerFactory, options); - _jsonSerializerOptions = new JsonSerializerOptions(); -#if NET6_0_OR_GREATER - _jsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull; + if (options.JsonSerializerOptions != null) + { + _jsonSerializerOptions = options.JsonSerializerOptions; + } +#if !NET6_0_OR_GREATER + else + { + _jsonSerializerOptions = new JsonSerializerOptions() + { +#if NET5_0_OR_GREATER + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, #else - _jsonSerializerOptions.IgnoreNullValues = true; + IgnoreNullValues = true, +#endif + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + Converters = { new JsonStringEnumConverter(JsonNamingPolicy.CamelCase, false) } + }; + } #endif - _jsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; - _jsonSerializerOptions.Converters.Add(new JsonStringEnumConverter(JsonNamingPolicy.CamelCase, false)); } public async Task Invoke(HttpContext httpContext) @@ -56,8 +66,10 @@ public async Task Invoke(HttpContext httpContext) var httpMethod = httpContext.Request.Method; var path = httpContext.Request.Path.Value; + var isGet = HttpMethods.IsGet(httpMethod); + // If the RoutePrefix is requested (with or without trailing slash), redirect to index URL - if (httpMethod == "GET" && Regex.IsMatch(path, $"^/?{Regex.Escape(_options.RoutePrefix)}/?$", RegexOptions.IgnoreCase)) + if (isGet && Regex.IsMatch(path, $"^/?{Regex.Escape(_options.RoutePrefix)}/?$", RegexOptions.IgnoreCase)) { // Use relative redirect to support proxy environments var relativeIndexUrl = string.IsNullOrEmpty(path) || path.EndsWith("/") @@ -68,7 +80,7 @@ public async Task Invoke(HttpContext httpContext) return; } - if (httpMethod == "GET" && Regex.IsMatch(path, $"^/{Regex.Escape(_options.RoutePrefix)}/?index.html$", RegexOptions.IgnoreCase)) + if (isGet && Regex.IsMatch(path, $"^/{Regex.Escape(_options.RoutePrefix)}/?index.html$", RegexOptions.IgnoreCase)) { await RespondWithIndexHtml(httpContext.Response); return; @@ -118,15 +130,42 @@ private async Task RespondWithIndexHtml(HttpResponse response) } } - private IDictionary GetIndexArguments() +#if NET5_0_OR_GREATER + [UnconditionalSuppressMessage( + "AOT", + "IL2026:RequiresUnreferencedCode", + Justification = "Method is only called if the user provides their own custom JsonSerializerOptions.")] + [UnconditionalSuppressMessage( + "AOT", + "IL3050:RequiresDynamicCode", + Justification = "Method is only called if the user provides their own custom JsonSerializerOptions.")] +#endif + private Dictionary GetIndexArguments() { + string configObject = null; + string oauthConfigObject = null; + string interceptors = null; + +#if NET6_0_OR_GREATER + if (_jsonSerializerOptions is null) + { + configObject = JsonSerializer.Serialize(_options.ConfigObject, SwaggerUIOptionsJsonContext.Default.ConfigObject); + oauthConfigObject = JsonSerializer.Serialize(_options.OAuthConfigObject, SwaggerUIOptionsJsonContext.Default.OAuthConfigObject); + interceptors = JsonSerializer.Serialize(_options.Interceptors, SwaggerUIOptionsJsonContext.Default.InterceptorFunctions); + } +#endif + + configObject ??= JsonSerializer.Serialize(_options.ConfigObject, _jsonSerializerOptions); + oauthConfigObject ??= JsonSerializer.Serialize(_options.OAuthConfigObject, _jsonSerializerOptions); + interceptors ??= JsonSerializer.Serialize(_options.Interceptors, _jsonSerializerOptions); + return new Dictionary() { { "%(DocumentTitle)", _options.DocumentTitle }, { "%(HeadContent)", _options.HeadContent }, - { "%(ConfigObject)", JsonSerializer.Serialize(_options.ConfigObject, _jsonSerializerOptions) }, - { "%(OAuthConfigObject)", JsonSerializer.Serialize(_options.OAuthConfigObject, _jsonSerializerOptions) }, - { "%(Interceptors)", JsonSerializer.Serialize(_options.Interceptors) }, + { "%(ConfigObject)", configObject }, + { "%(OAuthConfigObject)", oauthConfigObject }, + { "%(Interceptors)", interceptors }, }; } } diff --git a/src/Swashbuckle.AspNetCore.SwaggerUI/SwaggerUIOptions.cs b/src/Swashbuckle.AspNetCore.SwaggerUI/SwaggerUIOptions.cs index 0c30cc59d3..cd9aa9df42 100644 --- a/src/Swashbuckle.AspNetCore.SwaggerUI/SwaggerUIOptions.cs +++ b/src/Swashbuckle.AspNetCore.SwaggerUI/SwaggerUIOptions.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Linq; using System.Text.Json.Serialization; +using System.Text.Json; namespace Swashbuckle.AspNetCore.SwaggerUI { @@ -44,6 +45,11 @@ public class SwaggerUIOptions /// Gets the interceptor functions that define client-side request/response interceptors /// public InterceptorFunctions Interceptors { get; set; } = new InterceptorFunctions(); + + /// + /// Gets or sets the optional JSON serialization options to use to serialize options to the HTML document. + /// + public JsonSerializerOptions JsonSerializerOptions { get; set; } } public class ConfigObject @@ -81,6 +87,9 @@ public class ConfigObject /// Controls how the model is shown when the API is first rendered. /// (The user can always switch the rendering for a given model by clicking the 'Model' and 'Example Value' links) /// +#if NET6_0_OR_GREATER + [JsonConverter(typeof(JavascriptStringEnumConverter))] +#endif public ModelRendering DefaultModelRendering { get; set; } = ModelRendering.Example; /// @@ -92,6 +101,9 @@ public class ConfigObject /// Controls the default expansion setting for the operations and tags. /// It can be 'list' (expands only the tags), 'full' (expands the tags and operations) or 'none' (expands nothing) /// +#if NET6_0_OR_GREATER + [JsonConverter(typeof(JavascriptStringEnumConverter))] +#endif public DocExpansion DocExpansion { get; set; } = DocExpansion.List; /// @@ -126,7 +138,15 @@ public class ConfigObject /// List of HTTP methods that have the Try it out feature enabled. /// An empty array disables Try it out for all operations. This does not filter the operations from the display /// - public IEnumerable SupportedSubmitMethods { get; set; } = Enum.GetValues(typeof(SubmitMethod)).Cast(); +#if NET6_0_OR_GREATER + [JsonConverter(typeof(JavascriptStringEnumEnumerableConverter))] +#endif + public IEnumerable SupportedSubmitMethods { get; set; } = +#if NET5_0_OR_GREATER + Enum.GetValues(); +#else + Enum.GetValues(typeof(SubmitMethod)).Cast(); +#endif /// /// Controls whether the "Try it out" section should be enabled by default. @@ -252,4 +272,4 @@ public class InterceptorFunctions /// public string ResponseInterceptorFunction { get; set; } } -} \ No newline at end of file +} diff --git a/src/Swashbuckle.AspNetCore.SwaggerUI/Swashbuckle.AspNetCore.SwaggerUI.csproj b/src/Swashbuckle.AspNetCore.SwaggerUI/Swashbuckle.AspNetCore.SwaggerUI.csproj index 197b7177b0..a4c54ac56b 100644 --- a/src/Swashbuckle.AspNetCore.SwaggerUI/Swashbuckle.AspNetCore.SwaggerUI.csproj +++ b/src/Swashbuckle.AspNetCore.SwaggerUI/Swashbuckle.AspNetCore.SwaggerUI.csproj @@ -9,6 +9,10 @@ true netstandard2.0;netcoreapp3.0;net5.0;net6.0;net7.0;net8.0 + + true + true + diff --git a/test/WebSites/WebApi.Aot/Program.cs b/test/WebSites/WebApi.Aot/Program.cs new file mode 100644 index 0000000000..57c18d71bb --- /dev/null +++ b/test/WebSites/WebApi.Aot/Program.cs @@ -0,0 +1,51 @@ +using System.Text.Json.Serialization; +using Microsoft.OpenApi.Models; + +var builder = WebApplication.CreateSlimBuilder(args); + +builder.Services.ConfigureHttpJsonOptions(options => +{ + options.SerializerOptions.TypeInfoResolverChain.Insert(0, AppJsonSerializerContext.Default); +}); + +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(configure => +{ + configure.SwaggerDoc( + "v1", + new OpenApiInfo + { + Title = "Native AoT API V1", + Version = "v1", + Description = "A sample API for testing Swashbuckle with native AoT", + TermsOfService = new Uri("http://tempuri.org/terms") + }); +}); + +var app = builder.Build(); + +var sampleTodos = new Todo[] { + new(1, "Walk the dog"), + new(2, "Do the dishes", DateOnly.FromDateTime(DateTime.Now)), + new(3, "Do the laundry", DateOnly.FromDateTime(DateTime.Now.AddDays(1))), + new(4, "Clean the bathroom"), + new(5, "Clean the car", DateOnly.FromDateTime(DateTime.Now.AddDays(2))) +}; + +var todosApi = app.MapGroup("/todos"); +todosApi.MapGet("/", () => sampleTodos); +todosApi.MapGet("/{id}", (int id) => + sampleTodos.FirstOrDefault(a => a.Id == id) is { } todo + ? Results.Ok(todo) + : Results.NotFound()); + +app.UseSwagger(); +app.UseSwaggerUI(); +app.MapSwagger(); + +app.Run(); + +internal record Todo(int Id, string? Title, DateOnly? DueBy = null, bool IsComplete = false); + +[JsonSerializable(typeof(Todo[]))] +internal partial class AppJsonSerializerContext : JsonSerializerContext; diff --git a/test/WebSites/WebApi.Aot/Properties/launchSettings.json b/test/WebSites/WebApi.Aot/Properties/launchSettings.json new file mode 100644 index 0000000000..70d3513a72 --- /dev/null +++ b/test/WebSites/WebApi.Aot/Properties/launchSettings.json @@ -0,0 +1,15 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "http://localhost:5154", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/test/WebSites/WebApi.Aot/WebApi.Aot.csproj b/test/WebSites/WebApi.Aot/WebApi.Aot.csproj new file mode 100644 index 0000000000..f264d1d349 --- /dev/null +++ b/test/WebSites/WebApi.Aot/WebApi.Aot.csproj @@ -0,0 +1,17 @@ + + + + enable + true + enable + true + net8.0 + false + + + + + + + + diff --git a/test/WebSites/WebApi.Aot/WebApi.Aot.http b/test/WebSites/WebApi.Aot/WebApi.Aot.http new file mode 100644 index 0000000000..64b525737d --- /dev/null +++ b/test/WebSites/WebApi.Aot/WebApi.Aot.http @@ -0,0 +1,9 @@ +@WebApi.Aot_HostAddress = http://localhost:5154 + +GET {{WebApi.Aot_HostAddress}}/todos/ +Accept: application/json + +### + +GET {{WebApi.Aot_HostAddress}}/todos/1 +Accept: application/json diff --git a/test/WebSites/WebApi.Aot/appsettings.Development.json b/test/WebSites/WebApi.Aot/appsettings.Development.json new file mode 100644 index 0000000000..0c208ae918 --- /dev/null +++ b/test/WebSites/WebApi.Aot/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/test/WebSites/WebApi.Aot/appsettings.json b/test/WebSites/WebApi.Aot/appsettings.json new file mode 100644 index 0000000000..10f68b8c8b --- /dev/null +++ b/test/WebSites/WebApi.Aot/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +}