From ee89ea34610b24560f6d439a69239b133250fa9d Mon Sep 17 00:00:00 2001 From: Steve Ashman Date: Thu, 1 Apr 2021 11:08:46 -0400 Subject: [PATCH] Provide extensibility for UI customizations (#74) --- .../Configurations/OpenApiCustomUIOptions.cs | 18 +++++ ...nsions.OpenApi.FunctionApp.V3Static.csproj | 7 ++ .../dist/my-custom.css | 3 + .../dist/my-custom.js | 1 + .../DefaultOpenApiCustomUIOptions.cs | 67 +++++++++++++++++++ .../IOpenApiCustomUIOptions.cs | 33 +++++++++ .../ISwaggerUI.cs | 3 +- .../Resolvers/OpenApiCustomUIResolver.cs | 35 ++++++++++ .../SwaggerUI.cs | 14 +++- .../dist/index.html | 4 +- .../IOpenApiHttpTriggerContext.cs | 5 ++ .../OpenApiEndpoints/OpenApiHttpTrigger.cs | 2 +- .../OpenApiHttpTriggerContext.cs | 4 ++ 13 files changed, 192 insertions(+), 4 deletions(-) create mode 100644 samples/Microsoft.Azure.WebJobs.Extensions.OpenApi.FunctionApp.V3Static/Configurations/OpenApiCustomUIOptions.cs create mode 100644 samples/Microsoft.Azure.WebJobs.Extensions.OpenApi.FunctionApp.V3Static/dist/my-custom.css create mode 100644 samples/Microsoft.Azure.WebJobs.Extensions.OpenApi.FunctionApp.V3Static/dist/my-custom.js create mode 100644 src/Microsoft.Azure.WebJobs.Extensions.OpenApi.Core/Configurations/DefaultOpenApiCustomUIOptions.cs create mode 100644 src/Microsoft.Azure.WebJobs.Extensions.OpenApi.Core/IOpenApiCustomUIOptions.cs create mode 100644 src/Microsoft.Azure.WebJobs.Extensions.OpenApi.Core/Resolvers/OpenApiCustomUIResolver.cs diff --git a/samples/Microsoft.Azure.WebJobs.Extensions.OpenApi.FunctionApp.V3Static/Configurations/OpenApiCustomUIOptions.cs b/samples/Microsoft.Azure.WebJobs.Extensions.OpenApi.FunctionApp.V3Static/Configurations/OpenApiCustomUIOptions.cs new file mode 100644 index 0000000..13ab6f6 --- /dev/null +++ b/samples/Microsoft.Azure.WebJobs.Extensions.OpenApi.FunctionApp.V3Static/Configurations/OpenApiCustomUIOptions.cs @@ -0,0 +1,18 @@ +using System.Reflection; + +using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Configurations; + +namespace Microsoft.Azure.WebJobs.Extensions.OpenApi.FunctionApp.V3Static.Configurations +{ + public class OpenApiCustomUIOptions : DefaultOpenApiCustomUIOptions + { + public OpenApiCustomUIOptions(Assembly assembly) + : base(assembly) + { + } + + public override string CustomStylesheetPath { get; } = "dist.my-custom.css"; + + public override string CustomJavaScriptPath { get; } = "dist.my-custom.js"; + } +} diff --git a/samples/Microsoft.Azure.WebJobs.Extensions.OpenApi.FunctionApp.V3Static/Microsoft.Azure.WebJobs.Extensions.OpenApi.FunctionApp.V3Static.csproj b/samples/Microsoft.Azure.WebJobs.Extensions.OpenApi.FunctionApp.V3Static/Microsoft.Azure.WebJobs.Extensions.OpenApi.FunctionApp.V3Static.csproj index 1b1dca2..c715532 100644 --- a/samples/Microsoft.Azure.WebJobs.Extensions.OpenApi.FunctionApp.V3Static/Microsoft.Azure.WebJobs.Extensions.OpenApi.FunctionApp.V3Static.csproj +++ b/samples/Microsoft.Azure.WebJobs.Extensions.OpenApi.FunctionApp.V3Static/Microsoft.Azure.WebJobs.Extensions.OpenApi.FunctionApp.V3Static.csproj @@ -40,6 +40,13 @@ + + + + + + + diff --git a/samples/Microsoft.Azure.WebJobs.Extensions.OpenApi.FunctionApp.V3Static/dist/my-custom.css b/samples/Microsoft.Azure.WebJobs.Extensions.OpenApi.FunctionApp.V3Static/dist/my-custom.css new file mode 100644 index 0000000..c742d13 --- /dev/null +++ b/samples/Microsoft.Azure.WebJobs.Extensions.OpenApi.FunctionApp.V3Static/dist/my-custom.css @@ -0,0 +1,3 @@ +.swagger-ui .topbar { + background-color: #003399 +} diff --git a/samples/Microsoft.Azure.WebJobs.Extensions.OpenApi.FunctionApp.V3Static/dist/my-custom.js b/samples/Microsoft.Azure.WebJobs.Extensions.OpenApi.FunctionApp.V3Static/dist/my-custom.js new file mode 100644 index 0000000..3ecd5df --- /dev/null +++ b/samples/Microsoft.Azure.WebJobs.Extensions.OpenApi.FunctionApp.V3Static/dist/my-custom.js @@ -0,0 +1 @@ +console.log("Custom UI: Open API Sample on Azure Functions (STATIC)"); diff --git a/src/Microsoft.Azure.WebJobs.Extensions.OpenApi.Core/Configurations/DefaultOpenApiCustomUIOptions.cs b/src/Microsoft.Azure.WebJobs.Extensions.OpenApi.Core/Configurations/DefaultOpenApiCustomUIOptions.cs new file mode 100644 index 0000000..00a6176 --- /dev/null +++ b/src/Microsoft.Azure.WebJobs.Extensions.OpenApi.Core/Configurations/DefaultOpenApiCustomUIOptions.cs @@ -0,0 +1,67 @@ +using System.IO; +using System.Reflection; +using System.Threading.Tasks; + +using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Abstractions; +using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Extensions; + +namespace Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Configurations +{ + /// + /// Default implementation of , providing + /// empty replacements for custom javascript and stylesheets + /// + public class DefaultOpenApiCustomUIOptions : IOpenApiCustomUIOptions + { + private readonly Assembly _assembly; + + /// + /// Initializes a new instance of the class. + /// + /// instance. + public DefaultOpenApiCustomUIOptions(Assembly assembly) + { + this._assembly = assembly.ThrowIfNullOrDefault(); + } + + /// + public virtual string CustomStylesheetPath { get; } = "dist.custom.css"; + + /// + public virtual string CustomJavaScriptPath { get; } = "dist.custom.js"; + + /// + public virtual async Task GetStylesheetAsync() + { + using (var stream = this._assembly.GetManifestResourceStream($"{this._assembly.GetName().Name}.{this.CustomStylesheetPath}")) + { + if (stream.IsNullOrDefault()) + { + return string.Empty; + } + + using (var reader = new StreamReader(stream)) + { + return await reader.ReadToEndAsync(); + } + } + } + + /// + public virtual async Task GetJavaScriptAsync() + { + using (var stream = this._assembly.GetManifestResourceStream($"{this._assembly.GetName().Name}.{this.CustomJavaScriptPath}")) + { + if (stream.IsNullOrDefault()) + { + return string.Empty; + } + + using (var reader = new StreamReader(stream)) + { + return await reader.ReadToEndAsync(); + } + } + } + } +} diff --git a/src/Microsoft.Azure.WebJobs.Extensions.OpenApi.Core/IOpenApiCustomUIOptions.cs b/src/Microsoft.Azure.WebJobs.Extensions.OpenApi.Core/IOpenApiCustomUIOptions.cs new file mode 100644 index 0000000..d8ef7e2 --- /dev/null +++ b/src/Microsoft.Azure.WebJobs.Extensions.OpenApi.Core/IOpenApiCustomUIOptions.cs @@ -0,0 +1,33 @@ +using System.Threading.Tasks; + +namespace Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Abstractions +{ + /// + /// Interface for a custom UI provider that can provide custom javascript + /// and CSS to be populated on a page + /// + public interface IOpenApiCustomUIOptions + { + /// + /// Gets the filepath for stylesheet for custom UI. + /// + string CustomStylesheetPath { get; } + + /// + /// Gets filepath for JavaScript for custom UI. + /// + string CustomJavaScriptPath { get; } + + /// + /// Gets the stylesheet to be rendered on the page. + /// + /// The stylesheet string for custom UI. + Task GetStylesheetAsync(); + + /// + /// Gets the javascript to be rendered on the page. + /// + /// The JavaScript string for custom UI. + Task GetJavaScriptAsync(); + } +} diff --git a/src/Microsoft.Azure.WebJobs.Extensions.OpenApi.Core/ISwaggerUI.cs b/src/Microsoft.Azure.WebJobs.Extensions.OpenApi.Core/ISwaggerUI.cs index b7080df..7c80dc4 100644 --- a/src/Microsoft.Azure.WebJobs.Extensions.OpenApi.Core/ISwaggerUI.cs +++ b/src/Microsoft.Azure.WebJobs.Extensions.OpenApi.Core/ISwaggerUI.cs @@ -30,8 +30,9 @@ namespace Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Abstractions /// /// Builds Swagger UI document. /// + /// instance. /// instance. - Task BuildAsync(); + Task BuildAsync(IOpenApiCustomUIOptions options = null); /// /// Builds OAuth2 Redirect document. diff --git a/src/Microsoft.Azure.WebJobs.Extensions.OpenApi.Core/Resolvers/OpenApiCustomUIResolver.cs b/src/Microsoft.Azure.WebJobs.Extensions.OpenApi.Core/Resolvers/OpenApiCustomUIResolver.cs new file mode 100644 index 0000000..64dc469 --- /dev/null +++ b/src/Microsoft.Azure.WebJobs.Extensions.OpenApi.Core/Resolvers/OpenApiCustomUIResolver.cs @@ -0,0 +1,35 @@ +using System; +using System.Linq; +using System.Reflection; + +using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Abstractions; +using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Configurations; +using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Extensions; + +namespace Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Resolvers +{ + /// + /// This represents the resolver entity for . + /// + public static class OpenApiCustomUIResolver + { + /// + /// Gets the instance from the given assembly. + /// + /// The executing assembly instance. + /// Returns the instance resolved. + public static IOpenApiCustomUIOptions Resolve(Assembly assembly) + { + var type = assembly.GetTypes() + .SingleOrDefault(p => p.GetInterface("IOpenApiCustomUIOptions", ignoreCase: true).IsNullOrDefault() == false); + if (type.IsNullOrDefault()) + { + return new DefaultOpenApiCustomUIOptions(assembly); + } + + var options = Activator.CreateInstance(type, assembly); + + return options as IOpenApiCustomUIOptions; + } + } +} diff --git a/src/Microsoft.Azure.WebJobs.Extensions.OpenApi.Core/SwaggerUI.cs b/src/Microsoft.Azure.WebJobs.Extensions.OpenApi.Core/SwaggerUI.cs index 8fff4e5..13e62c9 100644 --- a/src/Microsoft.Azure.WebJobs.Extensions.OpenApi.Core/SwaggerUI.cs +++ b/src/Microsoft.Azure.WebJobs.Extensions.OpenApi.Core/SwaggerUI.cs @@ -18,7 +18,9 @@ namespace Microsoft.Azure.WebJobs.Extensions.OpenApi.Core { private const string SwaggerUITitlePlaceholder = "[[SWAGGER_UI_TITLE]]"; private const string SwaggerUICssPlaceholder = "[[SWAGGER_UI_CSS]]"; + private const string SwaggerUICustomCssPlaceholder = "[[SWAGGER_UI_CUSTOM_CSS]]"; private const string SwaggerUIBundleJsPlaceholder = "[[SWAGGER_UI_BUNDLE_JS]]"; + private const string SwaggerUICustomJsPlaceholder = "[[SWAGGER_UI_CUSTOM_JS]]"; private const string SwaggerUIStandalonePresetJsPlaceholder = "[[SWAGGER_UI_STANDALONE_PRESET_JS]]"; private const string SwaggerUIApiPrefix = "[[SWAGGER_UI_API_PREFIX]]"; private const string SwaggerUrlPlaceholder = "[[SWAGGER_URL]]"; @@ -32,7 +34,9 @@ namespace Microsoft.Azure.WebJobs.Extensions.OpenApi.Core private OpenApiInfo _info; private string _baseUrl; private string _swaggerUiCss; + private string _swaggerUiCustomCss; private string _swaggerUiBundleJs; + private string _swaggerUiCustomJs; private string _swaggerUiStandalonePresetJs; private string _swaggerUiApiPrefix; private string _indexHtml; @@ -73,10 +77,16 @@ namespace Microsoft.Azure.WebJobs.Extensions.OpenApi.Core } /// - public async Task BuildAsync() + public async Task BuildAsync(IOpenApiCustomUIOptions options = null) { var assembly = Assembly.GetExecutingAssembly(); + if (!options.IsNullOrDefault()) + { + this._swaggerUiCustomCss = await options.GetStylesheetAsync(); + this._swaggerUiCustomJs = await options.GetJavaScriptAsync(); + } + using (var stream = assembly.GetManifestResourceStream(swaggerUiCss)) using (var reader = new StreamReader(stream)) { @@ -152,7 +162,9 @@ namespace Microsoft.Azure.WebJobs.Extensions.OpenApi.Core var html = this._indexHtml.Replace(SwaggerUITitlePlaceholder, swaggerUiTitle) .Replace(SwaggerUICssPlaceholder, this._swaggerUiCss) + .Replace(SwaggerUICustomCssPlaceholder, this._swaggerUiCustomCss) .Replace(SwaggerUIBundleJsPlaceholder, this._swaggerUiBundleJs) + .Replace(SwaggerUICustomJsPlaceholder, this._swaggerUiCustomJs) .Replace(SwaggerUIStandalonePresetJsPlaceholder, this._swaggerUiStandalonePresetJs) .Replace(SwaggerUrlPlaceholder, swaggerUrl); diff --git a/src/Microsoft.Azure.WebJobs.Extensions.OpenApi.Core/dist/index.html b/src/Microsoft.Azure.WebJobs.Extensions.OpenApi.Core/dist/index.html index 3ea1bb6..23c7a31 100644 --- a/src/Microsoft.Azure.WebJobs.Extensions.OpenApi.Core/dist/index.html +++ b/src/Microsoft.Azure.WebJobs.Extensions.OpenApi.Core/dist/index.html @@ -28,12 +28,14 @@ margin:0; background: #fafafa; } + + [[SWAGGER_UI_CUSTOM_CSS]]
- +