Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ODS-5623] Make the Swagger Startup configurable #1204

Open
wants to merge 7 commits into
base: main-6x
Choose a base branch
from
Open

Conversation

mjaksn
Copy link
Contributor

@mjaksn mjaksn commented Jan 6, 2025

No description provided.

Copy link
Contributor

@semalaiappan semalaiappan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you need to have abstract class for StartupBase and inherit that class to DefaultStartup class , so that you can have custom startup class which you can configure in AppSettings.json like this "StartUpClassName": "ODSStartupFromAppSettings.EDFIStartup" Please refer ticket ODS-5622 attachment for further details

@mjaksn
Copy link
Contributor Author

mjaksn commented Jan 7, 2025

you need to have abstract class for StartupBase and inherit that class to DefaultStartup class , so that you can have custom startup class which you can configure in AppSettings.json like this "StartUpClassName": "ODSStartupFromAppSettings.EDFIStartup" Please refer ticket ODS-5622 attachment for further details

@semalaiappan , could you elaborate on why an abstract startup base class is necessary here? As long as we rely on the configuration value to provide the name of the custom startup class and are not trying to dynamically locate a custom startup class based on it being derived from the abstract base class, that change may only add unnecessary complexity to the project.

You can test the current implementation by adding a CustomStartup.cs file to the SwaggerUI project with the following content:

using System.Collections.Generic;
using System.Reflection;
using System.Text.Json;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Swashbuckle.AspNetCore.SwaggerUI;

namespace EdFi.Ods.SwaggerUI
{
    public class CustomStartup
    {
        private readonly bool _useReverseProxyHeaders;
        private readonly string _pathBase;
        private readonly string _routePrefix;
        private const string DefaultRoutePrefix = "swagger";

        public CustomStartup(IConfiguration configuration)
        {
            Configuration = configuration;
            _useReverseProxyHeaders = Configuration.GetValue<bool>("UseReverseProxyHeaders");

            var pathBase = Configuration.GetValue<string>("PathBase");
            _routePrefix = Configuration.GetValue("SwaggerUIOptions:RoutePrefix", DefaultRoutePrefix);

            if (!string.IsNullOrEmpty(pathBase))
            {
                _pathBase = "/" + pathBase.Trim('/');
            }
        }

        private IConfiguration Configuration { get; }

        public void ConfigureServices(IServiceCollection services)
        {
            if (_useReverseProxyHeaders)
            {
                services.Configure<ForwardedHeadersOptions>(
                    options =>
                    {
                        options.ForwardedHeaders = ForwardedHeaders.XForwardedFor
                                                   | ForwardedHeaders.XForwardedHost
                                                   | ForwardedHeaders.XForwardedProto;

                        // Accept forwarded headers from any network and proxy
                        options.KnownNetworks.Clear();
                        options.KnownProxies.Clear();
                    });
            }

            services.AddHealthChecks();
        }

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILogger<CustomStartup> logger)
        {
            logger.LogInformation("The configure method of the CustomStartup class was called.");
            
            string webApiUrl = Configuration.GetValue("WebApiVersionUrl", string.Empty);
            var years = Configuration.GetSection("Years").Get<List<Years>>() ?? new List<Years>();
            string swaggerStyleSheetPath = "../swagger.css";

            if (!string.IsNullOrEmpty(_pathBase))
            {
                app.UsePathBase(_pathBase);
            }

            if (_useReverseProxyHeaders)
            {
                app.UseForwardedHeaders();
            }

            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Error");
            }

            app.Map(
                "/appSettings.json", builder =>
                {
                    builder.Run(
                        async context =>
                        {
                            context.Response.ContentType = "application/json";

                            await context.Response.WriteAsync(
                                JsonSerializer.Serialize(
                                    new
                                    {
                                        WebApiVersionUrl = webApiUrl,
                                        RoutePrefix = _routePrefix,
                                        Years = years
                                    }));
                        });
                });
            
            logger.LogInformation($"WebApiUrl = '{webApiUrl}'");
            logger.LogInformation($"UseReverseProxyHeaders = '{_useReverseProxyHeaders}'");
            logger.LogInformation($"PathBase = '{_pathBase}'");
            logger.LogInformation($"SwaggerStyleSheetPath = '{swaggerStyleSheetPath}'");

            // routes for swagger: http://server/PATHBASE/ROUTEPREFIX/index.html
            app.UseSwaggerUI(
                options =>
                {
                    Configuration.Bind("SwaggerUIOptions", options);

                    options.DocumentTitle = "Ed-Fi ODS API Documentation";

                    options.OAuthScopeSeparator(" ");
                    options.OAuthAppName(Assembly.GetExecutingAssembly().GetName().Name);
                    options.DocExpansion(DocExpansion.None);
                    options.DisplayRequestDuration();
                    options.ShowExtensions();
                    options.EnableValidator();
                    options.InjectStylesheet(swaggerStyleSheetPath);

                    options.IndexStream = ()
                        => GetType().Assembly.GetManifestResourceStream("EdFi.Ods.SwaggerUI.Resources.Swashbuckle_index.html");

                    options.ConfigObject.AdditionalItems["WebApiVersionUrl"] = webApiUrl;
                    options.RoutePrefix = _routePrefix;
                });

            app.UseDefaultFiles();
            app.UseStaticFiles();
            app.UseRouting();
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapHealthChecks("/health");
            });
        }
    }
}

Also, update the contents of appsettings.json to:

{
  "WebApiVersionUrl": "",
  "SwaggerUIOptions": {
    "StartupClassName": "CustomStartup",
    "OAuthConfigObject": {
      "ClientId": "",
      "ClientSecret": ""
    }
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  }
}

Then, when running the SwaggerUI project, you should see the following message in the log confirming that the custom startup class was used:

info: EdFi.Ods.SwaggerUI.CustomStartup[0]
      The configure method of the CustomStartup class was called.

Next, either remove the StartupClassName entry from appsettings.json, or set its value to "StartupClassName": "Startup". After either of those changes, you should no longer see the log message indicating the invocation of the CustomStartup class configure method, showing that the default startup class Startup was used.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants