< Summary

Information
Class: ArturRios.Util.WebApi.Configuration.WebApiStartup
Assembly: ArturRios.Util.WebApi
File(s): D:\Repositories\dotnet-webapi-util\src\Configuration\WebApiStartup.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 137
Coverable lines: 137
Total lines: 243
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 30
Branch coverage: 0%
Method coverage
0%
Covered methods: 0
Fully covered methods: 0
Total methods: 15
Method coverage: 0%
Full method coverage: 0%

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%210%
Run()100%210%
BuildAndRun()100%210%
BuildApp()100%210%
AddDependencies()100%210%
ConfigureCors()100%210%
ConfigureSecurity()100%210%
ConfigureWebApi()100%210%
StartServices()100%210%
LoadConfiguration()0%2040%
AddMiddlewares(...)0%2040%
AddCustomInvalidModelStateResponse()0%2040%
UseSwagger(...)0%4260%
UseSwaggerGen(...)0%110100%
SetSwaggerConfigFromParameters()0%620%

File(s)

D:\Repositories\dotnet-webapi-util\src\Configuration\WebApiStartup.cs

#LineLine coverage
 1using ArturRios.Configuration.Enums;
 2using ArturRios.Configuration.Loaders;
 3using ArturRios.Configuration.Providers;
 4using ArturRios.Extensions;
 5using ArturRios.Output;
 6using ArturRios.Util.WebApi.Middleware;
 7using Microsoft.AspNetCore.Authentication.JwtBearer;
 8using Microsoft.AspNetCore.Builder;
 9using Microsoft.AspNetCore.Mvc;
 10using Microsoft.Extensions.Configuration;
 11using Microsoft.Extensions.DependencyInjection;
 12using Microsoft.Extensions.Logging;
 13using Microsoft.OpenApi;
 14using Swashbuckle.AspNetCore.SwaggerGen;
 15
 16namespace ArturRios.Util.WebApi.Configuration;
 17
 18/// <summary>
 19/// Base class for bootstrapping an ASP.NET Core web API: builds the <see cref="WebApplicationBuilder"/> and
 20/// <see cref="WebApplication"/>, wires up configuration, middlewares and Swagger, and exposes hooks
 21/// (<see cref="Build"/>, <see cref="ConfigureApp"/>, <see cref="AddDependencies"/>, etc.) for derived classes
 22/// to customize the pipeline.
 23/// </summary>
 24/// <param name="args">The command-line arguments passed to the application entry point.</param>
 025public abstract class WebApiStartup(string[] args)
 26{
 027    private readonly Action<SwaggerGenOptions> _swaggerGenJwtAuthentication = setup =>
 028    {
 029        var jwtSecurityScheme = new OpenApiSecurityScheme
 030        {
 031            BearerFormat = "JWT",
 032            Name = "JWT Authentication",
 033            In = ParameterLocation.Header,
 034            Type = SecuritySchemeType.Http,
 035            Scheme = JwtBearerDefaults.AuthenticationScheme,
 036            Description =
 037                "After getting a token from the Authentication route, put **_ONLY_** your JWT Bearer token on textbox be
 038        };
 039
 040        var jwtRequirement = new OpenApiSecurityRequirement
 041        {
 042            { new OpenApiSecuritySchemeReference(JwtBearerDefaults.AuthenticationScheme), [] }
 043        };
 044
 045        setup.AddSecurityDefinition(JwtBearerDefaults.AuthenticationScheme, jwtSecurityScheme);
 046        setup.AddSecurityRequirement(_ => jwtRequirement);
 047    };
 48
 49    /// <summary>The builder used to configure services before the application is built.</summary>
 050    protected readonly WebApplicationBuilder Builder = WebApplication.CreateBuilder(args);
 51
 52    /// <summary>The startup parameters parsed from the command-line arguments.</summary>
 053    protected readonly WebApiParameters Parameters = new(args);
 54
 055    private SettingsProvider _settings = null!;
 56
 57    /// <summary>The built application. Populated by <see cref="BuildApp"/>.</summary>
 058    protected WebApplication App = null!;
 59
 60    /// <summary>Performs the full startup sequence (configuration, services, middlewares, etc.). Implemented by derived
 61    public abstract void Build();
 62
 63    /// <summary>Runs the built application, blocking until it shuts down.</summary>
 064    public void Run() => App.Run();
 65
 66    /// <summary>Calls <see cref="Build"/> followed by <see cref="Run"/>.</summary>
 67    public void BuildAndRun()
 068    {
 069        Build();
 070        Run();
 071    }
 72
 73    /// <summary>Builds <see cref="App"/> from <see cref="Builder"/>.</summary>
 074    public void BuildApp() => App = Builder.Build();
 75
 76    /// <summary>Configures the built application's request pipeline. Implemented by derived classes.</summary>
 77    public abstract void ConfigureApp();
 78
 79    /// <summary>Registers application-specific dependencies. Override to add custom services.</summary>
 080    public virtual void AddDependencies() { }
 81
 82    /// <summary>Configures CORS policies. Override to enable and customize CORS.</summary>
 083    public virtual void ConfigureCors() { }
 84
 85    /// <summary>Configures authentication/authorization. Override to enable custom security.</summary>
 086    public virtual void ConfigureSecurity() { }
 87
 88    /// <summary>Configures web API-specific services (controllers, filters, etc.). Override to customize.</summary>
 089    public virtual void ConfigureWebApi() { }
 90
 91    /// <summary>Starts background or hosted services. Override to start custom services.</summary>
 092    public virtual void StartServices() { }
 93
 94    /// <summary>Loads application settings and/or the environment file according to <see cref="Parameters"/>,
 95    /// and registers the resulting <see cref="SettingsProvider"/>/<see cref="EnvironmentProvider"/> as services.</summa
 96    public void LoadConfiguration()
 097    {
 098        Builder.Services.AddSingleton(sp =>
 099            new ConfigurationLoader(Builder.Configuration, Builder.Environment.EnvironmentName,
 0100                null, sp.GetRequiredService<ILogger<ConfigurationLoader>>()));
 101
 0102        using var loggerFactory = LoggerFactory.Create(builder => builder.AddConsole());
 0103        var configurationLoader = new ConfigurationLoader(
 0104            Builder.Configuration, Builder.Environment.EnvironmentName, null,
 0105            loggerFactory.CreateLogger<ConfigurationLoader>());
 106
 0107        SetSwaggerConfigFromParameters();
 108
 0109        _settings = new SettingsProvider(Builder.Configuration);
 110
 0111        if (Parameters.UseAppSettings)
 0112        {
 0113            configurationLoader.LoadAppSettings();
 114
 0115            Builder.Services.AddSingleton<SettingsProvider>();
 0116        }
 117
 0118        if (Parameters.UseEnvFile)
 0119        {
 0120            configurationLoader.LoadEnvironment();
 121
 0122            Builder.Services.AddSingleton<EnvironmentProvider>();
 0123        }
 0124    }
 125
 126    /// <summary>Registers each given middleware type on the application pipeline, skipping any type that does
 127    /// not derive from <see cref="WebApiMiddleware"/>.</summary>
 128    /// <param name="middlewares">The middleware types to register, in pipeline order.</param>
 129    public void AddMiddlewares(Type[] middlewares)
 0130    {
 0131        foreach (var middleware in middlewares)
 0132        {
 0133            if (middleware.IsSubclassOf(typeof(WebApiMiddleware)))
 0134            {
 0135                App.UseMiddleware(middleware);
 0136            }
 0137        }
 0138    }
 139
 140    /// <summary>Replaces ASP.NET Core's default invalid-model-state response with a 400 result carrying a
 141    /// <see cref="DataOutput{T}"/> whose errors list each invalid parameter and its message.</summary>
 142    public void AddCustomInvalidModelStateResponse() =>
 0143        Builder.Services.Configure<ApiBehaviorOptions>(options =>
 0144        {
 0145            options.InvalidModelStateResponseFactory = context =>
 0146            {
 0147                var errors = context.ModelState
 0148                    .Where(e => e.Value?.Errors.Count > 0)
 0149                    .Select(e => $"Parameter: {e.Key} | Error: {e.Value?.Errors.First().ErrorMessage}").ToArray();
 0150
 0151                var output = DataOutput<string>.New
 0152                    .WithData(string.Empty)
 0153                    .WithErrors(errors);
 0154
 0155                return new BadRequestObjectResult(output);
 0156            };
 0157        });
 158
 159    /// <summary>Enables the Swagger middleware (JSON endpoint and UI) when the current environment is allowed,
 160    /// as determined by <paramref name="allowedEnvironments"/>, <see cref="WebApiParameters.GetSwaggerEnvironments"/>,
 161    /// or <see cref="AppSettingsKeys.SwaggerEnabled"/>, in that order of precedence.</summary>
 162    /// <param name="allowedEnvironments">Optional explicit list of environments in which Swagger should be served.</par
 163    public void UseSwagger(EnvironmentType[]? allowedEnvironments = null)
 0164    {
 165        bool useSwagger;
 0166        var currentEnv = Builder.Environment.EnvironmentName;
 0167        var swaggerEnvs = Parameters.GetSwaggerEnvironments();
 168
 0169        if (allowedEnvironments.IsNotEmpty())
 0170        {
 0171            useSwagger = allowedEnvironments!.Any(env =>
 0172                env.ToString().Equals(currentEnv, StringComparison.OrdinalIgnoreCase));
 0173        }
 0174        else if (swaggerEnvs.IsNotEmpty())
 0175        {
 0176            useSwagger = swaggerEnvs.Contains(currentEnv, StringComparer.OrdinalIgnoreCase);
 0177        }
 178        else
 0179        {
 0180            useSwagger = _settings.GetBool(AppSettingsKeys.SwaggerEnabled) ?? false;
 0181        }
 182
 0183        if (!useSwagger)
 0184        {
 0185            return;
 186        }
 187
 0188        App.UseSwagger();
 0189        App.UseSwaggerUI();
 0190    }
 191
 192    /// <summary>Registers the Swagger generator (<c>AddSwaggerGen</c>) when the current environment is allowed,
 193    /// optionally applying custom <see cref="SwaggerGenOptions"/> and/or JWT bearer security definitions.</summary>
 194    /// <param name="allowedEnvironments">Optional explicit list of environments in which Swagger docs should be generat
 195    /// <param name="swaggerGenOptions">Optional callback to further configure <see cref="SwaggerGenOptions"/>.</param>
 196    /// <param name="jwtAuthentication">Whether to add a JWT bearer security definition/requirement to the generated doc
 197    public void UseSwaggerGen(EnvironmentType[]? allowedEnvironments = null,
 198        Action<SwaggerGenOptions>? swaggerGenOptions = null, bool jwtAuthentication = false)
 0199    {
 0200        var useSwaggerDocs = false;
 0201        var currentEnv = Builder.Environment.EnvironmentName;
 0202        var swaggerEnvs = Parameters.GetSwaggerEnvironments();
 203
 0204        if (allowedEnvironments.IsNotEmpty())
 0205        {
 0206            useSwaggerDocs = allowedEnvironments!.Any(env =>
 0207                env.ToString().Equals(currentEnv, StringComparison.OrdinalIgnoreCase));
 0208        }
 0209        else if (swaggerEnvs.IsNotEmpty())
 0210        {
 0211            useSwaggerDocs = swaggerEnvs.Contains(currentEnv, StringComparer.OrdinalIgnoreCase);
 0212        }
 213
 0214        if (!useSwaggerDocs)
 0215        {
 0216            return;
 217        }
 218
 0219        Builder.Services.AddSwaggerGen(options =>
 0220        {
 0221            swaggerGenOptions?.Invoke(options);
 0222
 0223            if (jwtAuthentication)
 0224            {
 0225                _swaggerGenJwtAuthentication.Invoke(options);
 0226            }
 0227        });
 0228    }
 229
 230    private void SetSwaggerConfigFromParameters()
 0231    {
 0232        var currentEnv = Builder.Environment.EnvironmentName;
 233
 0234        if (!Parameters.SwaggerEnvironments.Contains(currentEnv, StringComparer.OrdinalIgnoreCase))
 0235        {
 0236            return;
 237        }
 238
 0239        var configValues = new Dictionary<string, string?> { [AppSettingsKeys.SwaggerEnabled] = "true" };
 240
 0241        Builder.Configuration.AddInMemoryCollection(configValues);
 0242    }
 243}