Added functionality to split det swagger doc into multiple definitions (#14126)

* Added functionality to split det swagger doc into multiple definitions. Most (maybe too much) is still shared between the apis.

* Fixed issue with duplicate operation ids when we have multiple versions of an endpoint

* use strong types instead of var

* Updated OpenApi.json

* Formatting

* formatting

* Delete old files that is not implemented in api.common

* Updated openAPi after merge

---------

Co-authored-by: Zeegaan <nge@umbraco.dk>
This commit is contained in:
Bjarke Berg
2023-04-20 12:38:31 +02:00
committed by GitHub
parent 518bb60a45
commit 38947e0870
31 changed files with 497 additions and 358 deletions

View File

@@ -0,0 +1,9 @@
namespace Umbraco.Cms.Api.Common.Attributes;
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class MapToApiAttribute : Attribute
{
public MapToApiAttribute(string apiName) => ApiName = apiName;
public string ApiName { get; }
}

View File

@@ -20,6 +20,6 @@ public class ConfigureApiExplorerOptions : IConfigureOptions<ApiExplorerOptions>
options.GroupNameFormat = "'v'VVV";
options.SubstituteApiVersionInUrl = true;
options.AddApiVersionParametersWhenVersionNeutral = true;
options.AssumeDefaultVersionWhenUnspecified = true;
options.AssumeDefaultVersionWhenUnspecified = false;
}
}

View File

@@ -11,7 +11,8 @@ public class ConfigureApiVersioningOptions : IConfigureOptions<ApiVersioningOpti
options.DefaultApiVersion = new ApiVersion(1, 0);
options.ReportApiVersions = true;
options.ApiVersionReader = new UrlSegmentApiVersionReader();
options.AssumeDefaultVersionWhenUnspecified = true;
options.ApiVersionSelector = new CurrentImplementationApiVersionSelector(options);
options.AssumeDefaultVersionWhenUnspecified = true; // This is required for the old backoffice to work
options.UseApiBehavior = false;
}
}

View File

@@ -1,70 +1,46 @@
using Microsoft.AspNetCore.Mvc.ApiExplorer;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
using Umbraco.Cms.Api.Management.Controllers.Security;
using Umbraco.Cms.Api.Management.DependencyInjection;
using Umbraco.Cms.Infrastructure.Serialization;
using Umbraco.Cms.Api.Common.Attributes;
using Umbraco.Cms.Api.Common.OpenApi;
using Umbraco.Extensions;
using OperationIdRegexes = Umbraco.Cms.Api.Common.OpenApi.OperationIdRegexes;
namespace Umbraco.Cms.Api.Management.OpenApi;
namespace Umbraco.Cms.Api.Common.Configuration;
internal sealed class ConfigureUmbracoSwaggerGenOptions : IConfigureOptions<SwaggerGenOptions>
public class ConfigureUmbracoSwaggerGenOptions : IConfigureOptions<SwaggerGenOptions>
{
private readonly IUmbracoJsonTypeInfoResolver _umbracoJsonTypeInfoResolver;
public ConfigureUmbracoSwaggerGenOptions(IUmbracoJsonTypeInfoResolver umbracoJsonTypeInfoResolver)
{
_umbracoJsonTypeInfoResolver = umbracoJsonTypeInfoResolver;
}
public void Configure(SwaggerGenOptions swaggerGenOptions)
{
swaggerGenOptions.SwaggerDoc(
ManagementApiConfiguration.DefaultApiDocumentName,
DefaultApiConfiguration.ApiName,
new OpenApiInfo
{
Title = ManagementApiConfiguration.ApiTitle,
Version = ManagementApiConfiguration.DefaultApiVersion.ToString(),
Description =
"This shows all APIs available in this version of Umbraco - including all the legacy apis that are available for backward compatibility"
Title = "Default API",
Version = "Latest",
Description = "All endpoints not defined under specific APIs"
});
swaggerGenOptions.AddSecurityDefinition(
"OAuth",
new OpenApiSecurityScheme
{
In = ParameterLocation.Header,
Name = "Umbraco",
Type = SecuritySchemeType.OAuth2,
Description = "Umbraco Authentication",
Flows = new OpenApiOAuthFlows
{
AuthorizationCode = new OpenApiOAuthFlow
{
AuthorizationUrl =
new Uri(Paths.BackOfficeApiAuthorizationEndpoint, UriKind.Relative),
TokenUrl = new Uri(Paths.BackOfficeApiTokenEndpoint, UriKind.Relative)
}
}
});
swaggerGenOptions.AddSecurityRequirement(new OpenApiSecurityRequirement
{
// this weird looking construct works because OpenApiSecurityRequirement
// is a specialization of Dictionary<,>
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference { Id = "OAuth", Type = ReferenceType.SecurityScheme }
},
new List<string>()
}
});
swaggerGenOptions.CustomOperationIds(CustomOperationId);
swaggerGenOptions.DocInclusionPredicate((_, api) => !string.IsNullOrWhiteSpace(api.GroupName));
swaggerGenOptions.DocInclusionPredicate((name, api) =>
{
if (string.IsNullOrWhiteSpace(api.GroupName))
{
return false;
}
if (api.ActionDescriptor is ControllerActionDescriptor controllerActionDescriptor)
{
return controllerActionDescriptor.MethodInfo.HasMapToApiAttribute(name);
}
return false;
});
swaggerGenOptions.TagActionsBy(api => new[] { api.GroupName });
swaggerGenOptions.OrderActionsBy(ActionOrderBy);
swaggerGenOptions.DocumentFilter<MimeTypeDocumentFilter>();
@@ -72,10 +48,11 @@ internal sealed class ConfigureUmbracoSwaggerGenOptions : IConfigureOptions<Swag
swaggerGenOptions.CustomSchemaIds(SchemaIdGenerator.Generate);
swaggerGenOptions.SupportNonNullableReferenceTypes();
swaggerGenOptions.OperationFilter<ReponseHeaderOperationFilter>();
swaggerGenOptions.UseOneOfForPolymorphism();
swaggerGenOptions.UseAllOfForInheritance();
swaggerGenOptions.SelectSubTypesUsing(_umbracoJsonTypeInfoResolver.FindSubTypes);
swaggerGenOptions.SelectDiscriminatorNameUsing(type =>
{
@@ -89,7 +66,7 @@ internal sealed class ConfigureUmbracoSwaggerGenOptions : IConfigureOptions<Swag
swaggerGenOptions.SelectDiscriminatorValueUsing(x => x.Name);
}
private static string CustomOperationId(ApiDescription api)
private static string CustomOperationId(ApiDescription api)
{
var httpMethod = api.HttpMethod?.ToLower().ToFirstUpper() ?? "Get";
@@ -127,12 +104,21 @@ internal sealed class ConfigureUmbracoSwaggerGenOptions : IConfigureOptions<Swag
.ToCamelCaseRegex()
.Replace(formattedOperationId, m => m.Groups[1].Value.ToUpper());
//Get map to version attribute
string? version = null;
if (api.ActionDescriptor is ControllerActionDescriptor controllerActionDescriptor)
{
version = controllerActionDescriptor.MethodInfo.GetMapToApiVersionAttributeValue();
}
// Return the operation ID with the formatted http method verb in front, e.g. GetTrackedReferenceById
return $"{httpMethod}{formattedOperationId.ToFirstUpper()}";
return $"{httpMethod}{formattedOperationId.ToFirstUpper()}{version}";
}
// see https://github.com/domaindrivendev/Swashbuckle.AspNetCore#change-operation-sort-order-eg-for-ui-sorting
private static string ActionOrderBy(ApiDescription apiDesc)
=>
$"{apiDesc.GroupName}_{apiDesc.ActionDescriptor.AttributeRouteInfo?.Template ?? apiDesc.ActionDescriptor.RouteValues["controller"]}_{apiDesc.ActionDescriptor.RouteValues["action"]}_{apiDesc.HttpMethod}";
// see https://github.com/domaindrivendev/Swashbuckle.AspNetCore#change-operation-sort-order-eg-for-ui-sorting
private static string ActionOrderBy(ApiDescription apiDesc)
=>
$"{apiDesc.GroupName}_{apiDesc.ActionDescriptor.AttributeRouteInfo?.Template ?? apiDesc.ActionDescriptor.RouteValues["controller"]}_{apiDesc.ActionDescriptor.RouteValues["action"]}_{apiDesc.HttpMethod}";
}

View File

@@ -0,0 +1,6 @@
namespace Umbraco.Cms.Api.Common.Configuration;
internal static class DefaultApiConfiguration
{
public const string ApiName = "default";
}

View File

@@ -0,0 +1,80 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
using Umbraco.Cms.Api.Common.Configuration;
using Umbraco.Cms.Api.Common.Serialization;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Web.Common.ApplicationBuilder;
using Umbraco.Extensions;
using IHostingEnvironment = Umbraco.Cms.Core.Hosting.IHostingEnvironment;
namespace Umbraco.Cms.Api.Common.DependencyInjection;
public static class UmbracoBuilderApiExtensions
{
public static IUmbracoBuilder AddUmbracoApiOpenApiUI(this IUmbracoBuilder builder)
{
builder.Services.ConfigureOptions<ConfigureApiVersioningOptions>();
builder.Services.AddApiVersioning();
builder.Services.ConfigureOptions<ConfigureApiExplorerOptions>();
builder.Services.AddVersionedApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.ConfigureOptions<ConfigureUmbracoSwaggerGenOptions>();
builder.Services.AddSingleton<IUmbracoJsonTypeInfoResolver, UmbracoJsonTypeInfoResolver>();
builder.Services.Configure<UmbracoPipelineOptions>(options =>
{
options.AddFilter(new UmbracoPipelineFilter(
"UmbracoApiCommon",
applicationBuilder =>
{
},
applicationBuilder =>
{
IServiceProvider provider = applicationBuilder.ApplicationServices;
IWebHostEnvironment webHostEnvironment = provider.GetRequiredService<IWebHostEnvironment>();
IOptions<SwaggerGenOptions> swaggerGenOptions = provider.GetRequiredService<IOptions<SwaggerGenOptions>>();
if (!webHostEnvironment.IsProduction())
{
GlobalSettings? settings = provider.GetRequiredService<IOptions<GlobalSettings>>().Value;
IHostingEnvironment hostingEnvironment = provider.GetRequiredService<IHostingEnvironment>();
var umbracoPath = settings.GetBackOfficePath(hostingEnvironment);
applicationBuilder.UseSwagger(swaggerOptions =>
{
swaggerOptions.RouteTemplate =
$"{umbracoPath.TrimStart(Constants.CharArrays.ForwardSlash)}/swagger/{{documentName}}/swagger.json";
});
applicationBuilder.UseSwaggerUI(
swaggerUiOptions =>
{
swaggerUiOptions.RoutePrefix = $"{umbracoPath.TrimStart(Constants.CharArrays.ForwardSlash)}/swagger";
foreach ((var name, OpenApiInfo? apiInfo) in swaggerGenOptions.Value.SwaggerGeneratorOptions.SwaggerDocs.OrderBy(x=>x.Value.Title))
{
swaggerUiOptions.SwaggerEndpoint($"{name}/swagger.json", $"{apiInfo.Title}");
}
});
}
},
applicationBuilder =>
{
}));
});
return builder;
}
}

View File

@@ -0,0 +1,32 @@
using System.Reflection;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Common.Attributes;
using Umbraco.Cms.Api.Common.Configuration;
namespace Umbraco.Extensions;
public static class MethodInfoApiCommonExtensions
{
public static string? GetMapToApiVersionAttributeValue(this MethodInfo methodInfo)
{
MapToApiVersionAttribute[] mapToApis = methodInfo.GetCustomAttributes(typeof(MapToApiVersionAttribute), inherit: true).Cast<MapToApiVersionAttribute>().ToArray();
return string.Join("|", mapToApis.SelectMany(x=>x.Versions));
}
public static string? GetMapToApiAttributeValue(this MethodInfo methodInfo)
{
MapToApiAttribute[] mapToApis = (methodInfo.DeclaringType?.GetCustomAttributes(typeof(MapToApiAttribute), inherit: true) ?? Array.Empty<object>()).Cast<MapToApiAttribute>().ToArray();
return mapToApis.SingleOrDefault()?.ApiName;
}
public static bool HasMapToApiAttribute(this MethodInfo methodInfo, string apiName)
{
var value = methodInfo.GetMapToApiAttributeValue();
return value == apiName
|| (value is null && apiName == DefaultApiConfiguration.ApiName);
}
}

View File

@@ -4,7 +4,7 @@ using Microsoft.OpenApi.Any;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
namespace Umbraco.Cms.Api.Management.OpenApi;
namespace Umbraco.Cms.Api.Common.OpenApi;
public class EnumSchemaFilter : ISchemaFilter
{

View File

@@ -2,7 +2,7 @@
using Swashbuckle.AspNetCore.SwaggerGen;
using Umbraco.Extensions;
namespace Umbraco.Cms.Api.Management.OpenApi;
namespace Umbraco.Cms.Api.Common.OpenApi;
/// <summary>
/// This filter explicitly removes all other mime types than application/json from the produced OpenAPI document when application/json is accepted.

View File

@@ -1,6 +1,6 @@
using System.Text.RegularExpressions;
namespace Umbraco.Cms.Api.Management.OpenApi;
namespace Umbraco.Cms.Api.Common.OpenApi;
/// <summary>
/// This is the regexes used to generate the operation IDs, the benefit of this being partial with GeneratedRegex

View File

@@ -1,7 +1,7 @@
using System.Text.RegularExpressions;
using Umbraco.Extensions;
namespace Umbraco.Cms.Api.Management.OpenApi;
namespace Umbraco.Cms.Api.Common.OpenApi;
internal static class SchemaIdGenerator
{

View File

@@ -1,6 +1,6 @@
using System.Text.Json.Serialization.Metadata;
namespace Umbraco.Cms.Infrastructure.Serialization;
namespace Umbraco.Cms.Api.Common.Serialization;
public interface IUmbracoJsonTypeInfoResolver : IJsonTypeInfoResolver
{

View File

@@ -3,7 +3,7 @@ using System.Text.Json;
using System.Text.Json.Serialization.Metadata;
using Umbraco.Cms.Core.Composing;
namespace Umbraco.Cms.Infrastructure.Serialization;
namespace Umbraco.Cms.Api.Common.Serialization;
public sealed class UmbracoJsonTypeInfoResolver : DefaultJsonTypeInfoResolver, IUmbracoJsonTypeInfoResolver
{

View File

@@ -11,8 +11,11 @@
<!-- <PackageReference Include="Microsoft.AspNetCore.App" Version="2.2.8" />-->
<PackageReference Include="Microsoft.AspNetCore.Mvc.Versioning" Version="5.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer" Version="5.0.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Umbraco.Core\Umbraco.Core.csproj" />
<ProjectReference Include="..\Umbraco.Web.Common\Umbraco.Web.Common.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,20 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
namespace Umbraco.Cms.Api.Delivery.Configuration;
public class ConfigureUmbracoDeliveryApiSwaggerGenOptions: IConfigureOptions<SwaggerGenOptions>
{
public void Configure(SwaggerGenOptions swaggerGenOptions)
{
swaggerGenOptions.SwaggerDoc(
DeliveryApiConfiguration.ApiName,
new OpenApiInfo
{
Title = DeliveryApiConfiguration.ApiTitle,
Version = "Latest",
});
}
}

View File

@@ -0,0 +1,8 @@
namespace Umbraco.Cms.Api.Delivery.Configuration;
internal static class DeliveryApiConfiguration
{
internal const string ApiTitle = "Umbraco Delivery API";
internal const string ApiName = "delivery";
}

View File

@@ -1,5 +1,7 @@
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Common.Attributes;
using Umbraco.Cms.Api.Common.Filters;
using Umbraco.Cms.Api.Delivery.Configuration;
using Umbraco.Cms.Api.Delivery.Filters;
using Umbraco.Cms.Core;
@@ -10,6 +12,7 @@ namespace Umbraco.Cms.Api.Delivery.Controllers;
[DeliveryApiAccess]
[JsonOptionsName(Constants.JsonOptionsNames.DeliveryApi)]
[LocalizeFromAcceptLanguageHeader]
[MapToApi(DeliveryApiConfiguration.ApiName)]
public abstract class DeliveryApiControllerBase : Controller
{
}

View File

@@ -4,6 +4,7 @@ using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Api.Common.Configuration;
using Umbraco.Cms.Api.Common.DependencyInjection;
using Umbraco.Cms.Api.Delivery.Accessors;
using Umbraco.Cms.Api.Delivery.Configuration;
using Umbraco.Cms.Api.Delivery.Json;
using Umbraco.Cms.Api.Delivery.Rendering;
using Umbraco.Cms.Api.Delivery.Services;
@@ -28,10 +29,8 @@ public static class UmbracoBuilderExtensions
builder.Services.AddSingleton<IApiAccessService, ApiAccessService>();
builder.Services.AddSingleton<IApiContentQueryService, ApiContentQueryService>();
builder.Services.ConfigureOptions<ConfigureApiVersioningOptions>();
builder.Services.AddApiVersioning();
builder.Services.ConfigureOptions<ConfigureApiExplorerOptions>();
builder.Services.AddVersionedApiExplorer();
builder.Services.ConfigureOptions<ConfigureUmbracoDeliveryApiSwaggerGenOptions>();
builder.AddUmbracoApiOpenApiUI();
builder
.Services

View File

@@ -0,0 +1,67 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
using Umbraco.Cms.Api.Common.Serialization;
using Umbraco.Cms.Api.Management.Controllers.Security;
using Umbraco.Cms.Api.Management.DependencyInjection;
using Umbraco.Cms.Api.Management.OpenApi;
namespace Umbraco.Cms.Api.Management.Configuration;
public class ConfigureUmbracoManagementApiSwaggerGenOptions : IConfigureOptions<SwaggerGenOptions>
{
private IUmbracoJsonTypeInfoResolver _umbracoJsonTypeInfoResolver;
public ConfigureUmbracoManagementApiSwaggerGenOptions(IUmbracoJsonTypeInfoResolver umbracoJsonTypeInfoResolver)
{
_umbracoJsonTypeInfoResolver = umbracoJsonTypeInfoResolver;
}
public void Configure(SwaggerGenOptions swaggerGenOptions)
{
swaggerGenOptions.SwaggerDoc(
ManagementApiConfiguration.ApiName,
new OpenApiInfo
{
Title = ManagementApiConfiguration.ApiTitle,
Version = "Latest",
Description = "This shows all APIs available in this version of Umbraco - including all the legacy apis that are available for backward compatibility",
});
swaggerGenOptions.OperationFilter<ResponseHeaderOperationFilter>();
swaggerGenOptions.SelectSubTypesUsing(_umbracoJsonTypeInfoResolver.FindSubTypes);
swaggerGenOptions.AddSecurityDefinition(
"OAuth",
new OpenApiSecurityScheme
{
In = ParameterLocation.Header,
Name = "Umbraco",
Type = SecuritySchemeType.OAuth2,
Description = "Umbraco Authentication",
Flows = new OpenApiOAuthFlows
{
AuthorizationCode = new OpenApiOAuthFlow
{
AuthorizationUrl =
new Uri(Paths.BackOfficeApiAuthorizationEndpoint, UriKind.Relative),
TokenUrl = new Uri(Paths.BackOfficeApiTokenEndpoint, UriKind.Relative)
}
}
});
swaggerGenOptions.AddSecurityRequirement(new OpenApiSecurityRequirement
{
// this weird looking construct works because OpenApiSecurityRequirement
// is a specialization of Dictionary<,>
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference { Id = "OAuth", Type = ReferenceType.SecurityScheme }
},
new List<string>()
}
});
}
}

View File

@@ -1,11 +1,14 @@
using System.Linq.Expressions;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Common.Attributes;
using Umbraco.Cms.Api.Common.Filters;
using Umbraco.Cms.Api.Management.DependencyInjection;
using Umbraco.Cms.Core.Security;
using Umbraco.New.Cms.Core;
namespace Umbraco.Cms.Api.Management.Controllers;
[MapToApi(ManagementApiConfiguration.ApiName)]
[JsonOptionsName(Constants.JsonOptionsNames.BackOffice)]
public class ManagementApiControllerBase : Controller
{

View File

@@ -8,7 +8,7 @@ using Umbraco.Cms.Api.Management.ViewModels.RecycleBin;
namespace Umbraco.Cms.Api.Management.Controllers.RecycleBin;
public abstract class RecycleBinControllerBase<TItem> : Controller
public abstract class RecycleBinControllerBase<TItem> : ManagementApiControllerBase
where TItem : RecycleBinItemResponseModel, new()
{
private readonly IEntityService _entityService;

View File

@@ -1,31 +0,0 @@
using Microsoft.AspNetCore.Mvc.Versioning;
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Core.DependencyInjection;
namespace Umbraco.Cms.Api.Management.DependencyInjection;
internal static class ApiVersioningBuilderExtensions
{
internal static IUmbracoBuilder AddApiVersioning(this IUmbracoBuilder builder)
{
builder.Services.AddApiVersioning(options =>
{
options.DefaultApiVersion = ManagementApiConfiguration.DefaultApiVersion;
options.ReportApiVersions = true;
options.ApiVersionReader = new UrlSegmentApiVersionReader();
options.AssumeDefaultVersionWhenUnspecified = true;
options.UseApiBehavior = false;
});
builder.Services.AddVersionedApiExplorer(options =>
{
options.DefaultApiVersion = ManagementApiConfiguration.DefaultApiVersion;
options.GroupNameFormat = "'v'VVV";
options.SubstituteApiVersionInUrl = true;
options.AddApiVersionParametersWhenVersionNeutral = true;
options.AssumeDefaultVersionWhenUnspecified = true;
});
return builder;
}
}

View File

@@ -52,37 +52,6 @@ internal static class ApplicationBuilderExtensions
}));
});
internal static IApplicationBuilder UseSwagger(this IApplicationBuilder applicationBuilder)
{
IServiceProvider provider = applicationBuilder.ApplicationServices;
IWebHostEnvironment webHostEnvironment = provider.GetRequiredService<IWebHostEnvironment>();
if (webHostEnvironment.IsProduction())
{
return applicationBuilder;
}
GlobalSettings settings = provider.GetRequiredService<IOptions<GlobalSettings>>().Value;
IHostingEnvironment hostingEnvironment = provider.GetRequiredService<IHostingEnvironment>();
var backOfficePath = settings.GetBackOfficePath(hostingEnvironment);
applicationBuilder.UseSwagger(swaggerOptions =>
{
swaggerOptions.RouteTemplate = $"{backOfficePath.TrimStart(Constants.CharArrays.ForwardSlash)}/swagger/{{documentName}}/swagger.json";
});
applicationBuilder.UseSwaggerUI(
swaggerUiOptions =>
{
swaggerUiOptions.SwaggerEndpoint($"{backOfficePath}/swagger/v1/swagger.json", $"{ManagementApiConfiguration.ApiTitle} {ManagementApiConfiguration.DefaultApiVersion}");
swaggerUiOptions.RoutePrefix = $"{backOfficePath.TrimStart(Constants.CharArrays.ForwardSlash)}/swagger";
swaggerUiOptions.OAuthClientId(New.Cms.Core.Constants.OauthClientIds.Swagger);
swaggerUiOptions.OAuthUsePkce();
});
return applicationBuilder;
}
internal static IApplicationBuilder UseEndpoints(this IApplicationBuilder applicationBuilder)
{
IServiceProvider provider = applicationBuilder.ApplicationServices;

View File

@@ -1,24 +0,0 @@
using Microsoft.AspNetCore.Mvc.ApiExplorer;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.OpenApi.Models;
using Umbraco.Cms.Api.Management.Controllers.Dictionary;
using Umbraco.Cms.Api.Management.Controllers.Security;
using Umbraco.Cms.Api.Management.OpenApi;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Infrastructure.Serialization;
using Umbraco.Extensions;
namespace Umbraco.Cms.Api.Management.DependencyInjection;
internal static class ManagementApiBuilderExtensions
{
internal static IUmbracoBuilder AddSwaggerGen(this IUmbracoBuilder builder)
{
builder.Services.AddSwaggerGen();
builder.Services.ConfigureOptions<ConfigureUmbracoSwaggerGenOptions>();
builder.Services.AddSingleton<IUmbracoJsonTypeInfoResolver, UmbracoJsonTypeInfoResolver>();
return builder;
}
}

View File

@@ -4,9 +4,7 @@ namespace Umbraco.Cms.Api.Management.DependencyInjection;
internal static class ManagementApiConfiguration
{
internal const string ApiTitle = "Umbraco Backoffice API";
internal const string ApiTitle = "Umbraco Management API";
internal const string DefaultApiDocumentName = "v1";
internal static ApiVersion DefaultApiVersion => new(1, 0);
internal const string ApiName = "management";
}

View File

@@ -4,6 +4,7 @@ using Umbraco.Cms.Core.Composing;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Api.Common.Configuration;
using Umbraco.Cms.Api.Common.DependencyInjection;
using Umbraco.Cms.Api.Management.Configuration;
using Umbraco.Cms.Api.Management.DependencyInjection;
using Umbraco.Cms.Api.Management.Serialization;
using Umbraco.Cms.Web.Common.ApplicationBuilder;
@@ -46,21 +47,11 @@ public class ManagementApiComposer : IComposer
.AddUserGroups()
.AddPackages()
.AddEntitys()
.AddBackOfficeAuthentication()
.AddApiVersioning()
.AddSwaggerGen();
.AddBackOfficeAuthentication();
services
.ConfigureOptions<ConfigureMvcOptions>()
.ConfigureOptions<ConfigureApiBehaviorOptions>()
.Configure<UmbracoPipelineOptions>(options =>
{
options.AddFilter(new UmbracoPipelineFilter(
"BackOfficeManagementApiFilter",
applicationBuilder => applicationBuilder.UseProblemDetailsExceptionHandling(),
applicationBuilder => applicationBuilder.UseSwagger(),
applicationBuilder => applicationBuilder.UseEndpoints()));
})
.AddControllers()
.AddJsonOptions(_ =>
{
@@ -69,6 +60,16 @@ public class ManagementApiComposer : IComposer
.AddJsonOptions(New.Cms.Core.Constants.JsonOptionsNames.BackOffice, _ => { });
services.ConfigureOptions<ConfigureUmbracoBackofficeJsonOptions>( );
services.ConfigureOptions<ConfigureUmbracoManagementApiSwaggerGenOptions>( );
services.Configure<UmbracoPipelineOptions>(options =>
{
options.AddFilter(new UmbracoPipelineFilter(
"BackOfficeManagementApiFilter",
applicationBuilder => applicationBuilder.UseProblemDetailsExceptionHandling(),
applicationBuilder => { },
applicationBuilder => applicationBuilder.UseEndpoints()));
});
// FIXME: when this is moved to core, make the AddUmbracoOptions extension private again and remove core InternalsVisibleTo for Umbraco.Cms.Api.Management
builder.AddUmbracoOptions<NewBackOfficeSettings>();

File diff suppressed because it is too large Load Diff

View File

@@ -1,13 +1,20 @@
using Microsoft.AspNetCore.Http;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
using Umbraco.Cms.Api.Management.DependencyInjection;
using Umbraco.Extensions;
namespace Umbraco.Cms.Api.Management.OpenApi;
internal class ReponseHeaderOperationFilter : IOperationFilter
internal class ResponseHeaderOperationFilter : IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
if (context.MethodInfo.HasMapToApiAttribute(ManagementApiConfiguration.ApiName) == false)
{
return;
}
foreach ((var key, OpenApiResponse? value) in operation.Responses)
{
switch (int.Parse(key))

View File

@@ -2,6 +2,7 @@ using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using Umbraco.Cms.Api.Common.Serialization;
using Umbraco.Cms.Infrastructure.Serialization;
namespace Umbraco.Cms.Api.Management.Serialization;

View File

@@ -35,7 +35,7 @@ internal sealed class OpenAPIContractTest : UmbracoTestServerTestBase
var officePath = GlobalSettings.GetBackOfficePath(HostingEnvironment);
var urlToContract = $"{officePath}/management/api/openapi.json";
var swaggerPath = $"{officePath}/swagger/v1/swagger.json";
var swaggerPath = $"{officePath}/swagger/management/swagger.json";
var apiContract = JObject.Parse(await Client.GetStringAsync(urlToContract));
var generatedJsonString = await Client.GetStringAsync(swaggerPath);

View File

@@ -80,7 +80,7 @@ namespace Umbraco.Cms.Tests.Integration.TestServerTest
builder.ConfigureTestServices(services =>
{
services.AddSingleton<IWebProfilerRepository, TestWebProfilerRepository>();
// Add a test auth scheme with a test auth handler to authn and assign the user
services.AddAuthentication(TestAuthHandler.TestAuthenticationScheme)
.AddScheme<AuthenticationSchemeOptions, TestAuthHandler>(
@@ -245,6 +245,7 @@ namespace Umbraco.Cms.Tests.Integration.TestServerTest
.AddWebsite()
.AddUmbracoSqlServerSupport()
.AddUmbracoSqliteSupport()
.AddDeliveryApi()
.AddTestServices(TestHelper); // This is the important one!
CustomTestSetup(builder);
@@ -256,7 +257,7 @@ namespace Umbraco.Cms.Tests.Integration.TestServerTest
/// </summary>
protected virtual void ConfigureTestServices(IServiceCollection services)
{
}
protected void Configure(IApplicationBuilder app)