Found it! Need to add ApiController attribute to all routes (also derive from controllerbase)

This commit is contained in:
2019-10-18 18:57:50 +00:00
parent 0d42f163fc
commit c5bd733302
4 changed files with 787 additions and 57 deletions

View File

@@ -11,6 +11,15 @@ UPDATE all the things before commencing work
- https://github.com/domaindrivendev/Swashbuckle.AspNetCore#swashbuckleaspnetcoreannotations - https://github.com/domaindrivendev/Swashbuckle.AspNetCore#swashbuckleaspnetcoreannotations
- -
Metrics and swagger broken after update.
Need to consider re-thinking versioning system of api to remove the complexity of swagger bullshit when using versioned api.
Test out versioning in the test project I made following the Swashbuckle guide for multiple docs:
https://github.com/domaindrivendev/Swashbuckle.AspNetCore#list-multiple-swagger-documents
Because maybe this is the way to go?
Need a sprint to get to a fully testable client with entry form, list and as much as possible all features from COMMON-* specs list Need a sprint to get to a fully testable client with entry form, list and as much as possible all features from COMMON-* specs list
Do the stuff in the Client todo first then back to the server as required. Do the stuff in the Client todo first then back to the server as required.

View File

@@ -14,7 +14,8 @@ namespace AyaNova.Api.Controllers
[ApiVersion("8.0")] [ApiVersion("8.0")]
[Route("api/v{version:apiVersion}/")] [Route("api/v{version:apiVersion}/")]
[AllowAnonymous] [AllowAnonymous]
public class ApiMetaController : Controller [ApiController]
public class ApiMetaController : ControllerBase
{ {
private readonly ApiServerState serverState; private readonly ApiServerState serverState;
private readonly ILogger<ApiMetaController> _log; private readonly ILogger<ApiMetaController> _log;

View File

@@ -14,14 +14,17 @@ using Microsoft.AspNetCore.Http;
using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Models;
using Microsoft.Extensions.Options;
using Swashbuckle.AspNetCore.Swagger;
using Swashbuckle.AspNetCore.SwaggerGen;
using AyaNova.Models; using AyaNova.Models;
using AyaNova.Util; using AyaNova.Util;
using AyaNova.Generator; using AyaNova.Generator;
using AyaNova.Biz; using AyaNova.Biz;
using Swashbuckle.AspNetCore.Swagger;
using Swashbuckle.AspNetCore.SwaggerGen;
using Swashbuckle.AspNetCore.SwaggerUI; using Swashbuckle.AspNetCore.SwaggerUI;
using System.IO; using System.IO;
@@ -88,10 +91,7 @@ namespace AyaNova
// add the versioned api explorer, which also adds IApiVersionDescriptionProvider service
// note: the specified format code will format the version as "'v'major[.minor][-status]"
_log.LogDebug("BOOT: init ApiExplorer service");
services.AddVersionedApiExplorer(o => o.GroupNameFormat = "'v'VVV");
_log.LogDebug("BOOT: ensuring user and backup folders exist and are separate locations..."); _log.LogDebug("BOOT: ensuring user and backup folders exist and are separate locations...");
FileUtil.EnsureUserAndUtilityFoldersExistAndAreNotIdentical(_hostingEnvironment.ContentRootPath); FileUtil.EnsureUserAndUtilityFoldersExistAndAreNotIdentical(_hostingEnvironment.ContentRootPath);
@@ -147,15 +147,562 @@ namespace AyaNova
#endregion #endregion
_log.LogDebug("BOOT: init ApiVersioning service");
// services.AddApiVersioning(o => o.ReportApiVersions = true);
services
.AddApiVersioning(options =>
{
options.AssumeDefaultVersionWhenUnspecified = true;
options.DefaultApiVersion = Microsoft.AspNetCore.Mvc.ApiVersion.Parse("8.0"); #region Swagger
options.ReportApiVersions = true;
}); // services
// .AddApiVersioning(options =>
// {
// options.AssumeDefaultVersionWhenUnspecified = true;
// options.DefaultApiVersion = Microsoft.AspNetCore.Mvc.ApiVersion.Parse("8.0");
// options.ReportApiVersions = true;
// });
// services.AddVersionedApiExplorer(o => o.GroupNameFormat = "'v'VVV");
services.AddApiVersioning();
services.AddVersionedApiExplorer(options => options.GroupNameFormat = "'v'VVV");
// services.AddSwaggerGen(
// c =>
// {
// c.SwaggerDoc("v8", new OpenApiInfo { Title = "XXX API - V8", Version = "v8" });
// c.DocInclusionPredicate((docName, apiDesc) =>
// {
// if (!apiDesc.TryGetMethodInfo(out MethodInfo methodInfo)) return false;
// var versions = methodInfo.DeclaringType
// .GetCustomAttributes(true)
// .OfType<ApiVersionAttribute>()
// .SelectMany(attr => attr.Versions);
// return versions.Any(v => $"v{v.ToString()}" == docName);
// });
// });
services.AddTransient<IConfigureOptions<SwaggerGenOptions>, ConfigureSwaggerOptions>();
services.AddSwaggerGen();
#endregion
#region JWT AUTHENTICATION
//get the key if specified
var secretKey = ServerBootConfig.AYANOVA_JWT_SECRET;
//If no key specified make a unique one
//This means the jwt creds won't survive a server reboot
//so in that case users need to specify an AyaNova_JWT_SECRET environment variable
if (string.IsNullOrWhiteSpace(secretKey))
{
secretKey = Util.Hasher.GenerateSalt();
}
//WAS "UNLICENSED5G*QQJ8#bQ7$Xr_@sXfHq4"
//If secretKey is less than 32 characters, pad it
if (secretKey.Length < 32)
{
secretKey = secretKey.PadRight(32, '-');
}
ServerBootConfig.AYANOVA_JWT_SECRET = secretKey;
var signingKey = new SymmetricSecurityKey(System.Text.Encoding.ASCII.GetBytes(ServerBootConfig.AYANOVA_JWT_SECRET));
_log.LogDebug("BOOT: init Authorization service");
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
// options.AutomaticAuthenticate = true;
// options.AutomaticChallenge = true;
options.TokenValidationParameters = new TokenValidationParameters
{
// Token signature will be verified using a private key.
ValidateIssuerSigningKey = true,
RequireSignedTokens = true,
IssuerSigningKey = signingKey,
ValidateIssuer = true,
ValidIssuer = "ayanova.com",
ValidateAudience = false,
//ValidAudience = "http://localhost:7575/"
// Token will only be valid if not expired yet, with 5 minutes clock skew.
ValidateLifetime = true,
RequireExpirationTime = true,
ClockSkew = new TimeSpan(0, 5, 0),
};
});
#endregion
_log.LogDebug("BOOT: init Generator service");
services.AddSingleton<IHostedService, GeneratorService>();
}
////////////////////////////////////////////////////////////
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
//
public void Configure(IApplicationBuilder app, Microsoft.AspNetCore.Hosting.IWebHostEnvironment env,
AyContext dbContext, IApiVersionDescriptionProvider provider, AyaNova.Api.ControllerHelpers.ApiServerState apiServerState, IServiceProvider serviceProvider)
{
_log.LogDebug("BOOT: configuring request pipeline...");
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
//Store a reference to the dependency injection service for static classes
ServiceProviderProvider.Provider = app.ApplicationServices;
//Enable ability to handle reverse proxy
app.UseForwardedHeaders(new ForwardedHeadersOptions
{
ForwardedHeaders = Microsoft.AspNetCore.HttpOverrides.ForwardedHeaders.XForwardedFor | Microsoft.AspNetCore.HttpOverrides.ForwardedHeaders.XForwardedProto
});
#region STATIC FILES
_log.LogDebug("BOOT: pipeline - static files");
app.UseDefaultFiles();
app.UseStaticFiles();
//Might need the following if the page doesn't update in the client properly
//however the vue build process will automatically uniquify each build file names so maybe not required
// app.UseStaticFiles(new StaticFileOptions
// {
// OnPrepareResponse = context =>
// {
// if (context.File.Name == "index.html")
// {
// context.Context.Response.Headers.Add("Cache-Control", "no-cache, no-store");
// context.Context.Response.Headers.Add("Expires", "-1");
// }
// }
// });
#endregion
_log.LogDebug("BOOT: pipeline - ROUTING");
app.UseRouting();//this wasn't here for 2.2 but added for 3.0, needs to come before the stuff after
_log.LogDebug("BOOT: pipeline - CORS");
app.UseCors("CorsPolicy");
#region AUTH / ROLES
_log.LogDebug("BOOT: pipeline - authentication");
//Use authentication middleware
app.UseAuthentication();
_log.LogDebug("BOOT: pipeline - authorization");
app.UseAuthorization();
//Custom middleware to get user roles and put them into the request so
//they can be authorized in routes.
app.Use(async (context, next) =>
{
if (!context.User.Identity.IsAuthenticated)
{
context.Request.HttpContext.Items["AY_ROLES"] = 0;
}
else
{
//Get user ID from claims
long userId = Convert.ToInt64(context.User.FindFirst(c => c.Type == "id").Value);
//Get the database context
var ct = context.RequestServices.GetService<AyContext>();
//get the user record
var u = ct.User.AsNoTracking().Where(a => a.Id == userId).Select(m => new { roles = m.Roles, name = m.Name, id = m.Id, localeId = m.LocaleId }).First();
context.Request.HttpContext.Items["AY_ROLES"] = u.roles;
context.Request.HttpContext.Items["AY_USERNAME"] = u.name;
context.Request.HttpContext.Items["AY_USER_ID"] = u.id;
context.Request.HttpContext.Items["AY_LOCALE_ID"] = u.localeId;
}
await next.Invoke();
});
#endregion
_log.LogDebug("BOOT: pipeline - ENDPOINTS");
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
#region SWAGGER
_log.LogDebug("BOOT: pipeline - api explorer");
// Enable middleware to serve generated Swagger as a JSON endpoint.
// app.UseSwagger();
// app.UseSwaggerUI(c =>
// {
// // build a swagger endpoint for each discovered API version
// foreach (var description in provider.ApiVersionDescriptions)
// {
// c.SwaggerEndpoint($"/swagger/{description.GroupName}/swagger.json", description.GroupName.ToUpperInvariant());
// }
// //clean up the swagger explorer UI page and remove the branding
// //via our own css
// //NOTE: this broke when updated to v2.x of swagger and it can be fixed according to docs:
// //https://github.com/domaindrivendev/Swashbuckle.AspNetCore#inject-custom-css
// // c.InjectStylesheet("/api/sw.css");
// c.DefaultModelsExpandDepth(-1);
// c.DocumentTitle = "AyaNova API explorer";
// c.RoutePrefix = "api-docs";
// });
app.UseSwagger();
app.UseSwaggerUI(
options =>
{
foreach (var description in provider.ApiVersionDescriptions)
{
options.SwaggerEndpoint(
$"/swagger/{description.GroupName}/swagger.json",
description.GroupName.ToUpperInvariant());
}
options.DocumentTitle = "AyaNova API explorer";
options.RoutePrefix = "api-docs";
});
#endregion swagger
//According to https://docs.microsoft.com/en-us/aspnet/core/migration/22-to-30?view=aspnetcore-2.2&tabs=visual-studio#migrate-startupconfigure
//replace use mvc with use endpoints
// //USE MVC
// _log.LogDebug("BOOT: pipeline - MVC");
// app.UseMvc();
// _log.LogDebug("BOOT: pipeline - ENDPOINTS");
// app.UseEndpoints(endpoints => {
// endpoints.MapControllers();
// });
// ******************************************************************
// ******************** TESTING WIPE DB *****************************
//
//Set this to true to wipe the db and reinstall a trial license and re-seed the data
var TESTING_REFRESH_DB = false;//#######################################################################################
#if (DEBUG)
//TESTING
if (TESTING_REFRESH_DB)
ServerBootConfig.AYANOVA_PERMANENTLY_ERASE_DATABASE = TESTING_REFRESH_DB;
//TESTING
#endif
if (ServerBootConfig.AYANOVA_PERMANENTLY_ERASE_DATABASE)
{
_log.LogWarning("BOOT: AYANOVA_PERMANENTLY_ERASE_DATABASE is true, dropping and recreating database");
Util.DbUtil.DropAndRecreateDb(_log);
AySchema.CheckAndUpdate(dbContext, _log);
}
//Check schema
_log.LogDebug("BOOT: db schema check");
AySchema.CheckAndUpdate(dbContext, _log);
//Check database integrity
_log.LogDebug("BOOT: db integrity check");
DbUtil.CheckFingerPrint(AySchema.EXPECTED_COLUMN_COUNT, AySchema.EXPECTED_INDEX_COUNT, _log);
//Initialize license
AyaNova.Core.License.Initialize(apiServerState, dbContext, _log);
//Ensure locales are present, not missing any keys and that there is a server default locale that exists
LocaleBiz lb = new LocaleBiz(dbContext, 1, ServerBootConfig.AYANOVA_DEFAULT_LANGUAGE_ID, AuthorizationRoles.OpsAdminFull);
lb.ValidateLocales();
#if (DEBUG)
//TESTING
if (TESTING_REFRESH_DB)
{
AyaNova.Core.License.Fetch(apiServerState, dbContext, _log);
Util.Seeder.SeedDatabase(Util.Seeder.SeedLevel.SmallOneManShopTrialDataSet, -7);//#############################################################################################
}
//TESTING
#endif
//AUTOID VALUES INITIALIZATION
ServerBootConfig.SetMostRecentAutoIdValuesFromDatabase(dbContext);
//SPA FALLBACK ROUTE
app.Use(async (context, next) =>
{
//to support html5 pushstate routing in spa
//this ensures that a refresh at the client will not 404 but rather force back to the index.html app page and then handled internally by the client
await next();
if (!context.Response.HasStarted && context.Request.Path.Value != "/docs" && context.Response.StatusCode == 404 && !Path.HasExtension(context.Request.Path.Value))
{
context.Request.Path = "/index.html";
context.Response.StatusCode = 200;
context.Response.ContentType = "text/html";
await context.Response.SendFileAsync(Path.Combine(env.WebRootPath, "index.html"));
}
});
//Log the active user count so it's in the log record
_log.LogInformation($"BOOT: Active techs - {UserBiz.ActiveCount}");
//Log the license info so it's on the record
_log.LogInformation($"BOOT: License -\r\n=-=-=-=-=-=-=-=-=-=-\r\n{AyaNova.Core.License.LicenseInfo}=-=-=-=-=-=-=-=-=-=-");
//Open up the server for visitors
apiServerState.SetOpen();
//final startup log
_log.LogInformation("BOOT: COMPLETED - SERVER IS NOW OPEN");
}
#region Swagger and API Versioning utilities
static string XmlCommentsFilePath
{
get
{
//Obsolete, used new method: https://developers.de/blogs/holger_vetter/archive/2017/06/30/swagger-includexmlcomments-platformservices-obsolete-replacement.aspx
//var basePath = PlatformServices.Default.Application.ApplicationBasePath;
var basePath = AppContext.BaseDirectory;
var fileName = typeof(Startup).GetTypeInfo().Assembly.GetName().Name + ".xml";
return Path.Combine(basePath, fileName);
}
}
// static Microsoft.OpenApi.Models.OpenApiInfo CreateInfoForApiVersion(ApiVersionDescription description)
// {
// var info = new Microsoft.OpenApi.Models.OpenApiInfo()
// {
// Title = $"AyaNova API {description.ApiVersion}",
// Version = description.ApiVersion.ToString()
// };
// if (description.IsDeprecated)
// {
// info.Description += " This API version has been deprecated.";
// }
// return info;
// }
//update for v3 from here https://github.com/microsoft/aspnet-api-versioning/wiki/API-Documentation#aspnet-core
//and example here: https://github.com/microsoft/aspnet-api-versioning/blob/master/samples/aspnetcore/SwaggerSample/ConfigureSwaggerOptions.cs
// public class ConfigureSwaggerOptions : IConfigureOptions<SwaggerGenOptions>
// {
// readonly IApiVersionDescriptionProvider provider;
// public ConfigureSwaggerOptions(IApiVersionDescriptionProvider provider) =>
// this.provider = provider;
// public void Configure(SwaggerGenOptions options)
// {
// foreach (var description in provider.ApiVersionDescriptions)
// {
// options.SwaggerDoc(
// description.GroupName,
// new OpenApiInfo()
// {
// Title = $"AyaNova API {description.ApiVersion}",
// Version = description.ApiVersion.ToString(),
// });
// }
// }
// }
#endregion
}
}
/*
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.ApiExplorer;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using Microsoft.Extensions.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.OpenApi.Models;
using Microsoft.Extensions.Options;
using Swashbuckle.AspNetCore.Swagger;
using Swashbuckle.AspNetCore.SwaggerGen;
using AyaNova.Models;
using AyaNova.Util;
using AyaNova.Generator;
using AyaNova.Biz;
using Swashbuckle.AspNetCore.SwaggerUI;
using System.IO;
using System.Reflection;
using System.Linq;
using System;
using System.Collections.Generic;
using Newtonsoft.Json.Serialization;
namespace AyaNova
{
public class Startup
{
/////////////////////////////////////////////////////////////
//
public Startup(ILogger<Startup> logger, ILoggerFactory logFactory, Microsoft.AspNetCore.Hosting.IWebHostEnvironment hostingEnvironment)
{
_log = logger;
_hostingEnvironment = hostingEnvironment;
AyaNova.Util.ApplicationLogging.LoggerFactory = logFactory;
//this must be set here
ServerBootConfig.AYANOVA_CONTENT_ROOT_PATH = hostingEnvironment.ContentRootPath;
}
private readonly ILogger<Startup> _log;
private string _connectionString = "";
private readonly Microsoft.AspNetCore.Hosting.IWebHostEnvironment _hostingEnvironment;
////////////////////////////////////////////////////////////
// This method gets called by the runtime. Use this method to add services to the container.
//
public void ConfigureServices(IServiceCollection services)
{
_log.LogDebug("BOOT: initializing services...");
//Server state service for shutting people out of api
_log.LogDebug("BOOT: init ApiServerState service");
services.AddSingleton(new AyaNova.Api.ControllerHelpers.ApiServerState());
//Init controllers
_log.LogDebug("BOOT: init controllers");
var MvcBuilder = services.AddControllers(config =>
{
config.Filters.Add(new AyaNova.Api.ControllerHelpers.ApiCustomExceptionFilter(AyaNova.Util.ApplicationLogging.LoggerFactory));
});
_log.LogDebug("BOOT: init JSON");
MvcBuilder.AddNewtonsoftJson(options =>
{
options.SerializerSettings.DateTimeZoneHandling = Newtonsoft.Json.DateTimeZoneHandling.Utc;
});
//2019-10-17 METRICS will not work just yet with .netcore 3.1 see here https://github.com/AppMetrics/AppMetrics/issues/480
//awaiting a new release from them
//_log.LogDebug("BOOT: init Metrics service");
//MvcBuilder.AddMetrics();
_log.LogDebug("BOOT: ensuring user and backup folders exist and are separate locations...");
FileUtil.EnsureUserAndUtilityFoldersExistAndAreNotIdentical(_hostingEnvironment.ContentRootPath);
#region DATABASE
_connectionString = ServerBootConfig.AYANOVA_DB_CONNECTION;
//Check DB server exists and can be connected to
_log.LogDebug("BOOT: Testing database server connection...");
//parse the connection string properly
DbUtil.ParseConnectionString(_log, _connectionString);
//Probe for database server
//Will retry every 10 seconds for up to 5 minutes before bailing
if (!DbUtil.DatabaseServerExists(_log, "BOOT: waiting for db server "))
{
var err = $"BOOT: E1000 - AyaNova can't connect to the database server after trying for 5 minutes (connection string is:\"{DbUtil.DisplayableConnectionString}\")";
_log.LogCritical(err);
throw new System.ApplicationException(err);
}
_log.LogInformation("BOOT: Connected to database server - {0}", DbUtil.DisplayableConnectionString);
//ensure database is ready and present
DbUtil.EnsureDatabaseExists(_log);
bool LOG_SENSITIVE_DATA = false;
#if (DEBUG)
// LOG_SENSITIVE_DATA = true;
#endif
_log.LogDebug("BOOT: init EF service");
services.AddEntityFrameworkNpgsql().AddDbContext<AyContext>(
options => options.UseNpgsql(_connectionString
//,opt => opt.EnableRetryOnFailure()//REMOVED THIS BECAUSE IT WAS INTEFERING WITH TRANSACTIONS BUT THEN DIDN'T USE THE TRANSACTION BUT IT SEEMS FASTER WITHOUT IT AS WELL SO...??
)//http://www.npgsql.org/efcore/misc.html?q=execution%20strategy#execution-strategy
.ConfigureWarnings(warnings => //https://livebook.manning.com/#!/book/entity-framework-core-in-action/chapter-12/v-10/85
warnings.Throw( //Throw an exception on client eval, not necessarily an error but a smell
// Microsoft.EntityFrameworkCore.Diagnostics.RelationalEventId.QueryClientEvaluationWarning
))
.EnableSensitiveDataLogging(LOG_SENSITIVE_DATA)
);
#endregion
// Add service and create Policy with options // Add service and create Policy with options
@@ -175,6 +722,23 @@ namespace AyaNova
#region Swagger #region Swagger
_log.LogDebug("BOOT: init ApiVersioning service");
// services.AddApiVersioning(o => o.ReportApiVersions = true);
services
.AddApiVersioning(options =>
{
options.AssumeDefaultVersionWhenUnspecified = true;
options.DefaultApiVersion = Microsoft.AspNetCore.Mvc.ApiVersion.Parse("8.0");
options.ReportApiVersions = true;
});
// add the versioned api explorer, which also adds IApiVersionDescriptionProvider service
// note: the specified format code will format the version as "'v'major[.minor][-status]"
_log.LogDebug("BOOT: init ApiExplorer service");
services.AddVersionedApiExplorer(o => o.GroupNameFormat = "'v'VVV");
services.AddTransient<IConfigureOptions<SwaggerGenOptions>, ConfigureSwaggerOptions>();
//https://docs.microsoft.com/en-us/aspnet/core/tutorials/web-api-help-pages-using-swagger?tabs=visual-studio-code //https://docs.microsoft.com/en-us/aspnet/core/tutorials/web-api-help-pages-using-swagger?tabs=visual-studio-code
//https://swagger.io/ //https://swagger.io/
//https://github.com/domaindrivendev/Swashbuckle.AspNetCore //https://github.com/domaindrivendev/Swashbuckle.AspNetCore
@@ -183,20 +747,61 @@ namespace AyaNova
services.AddSwaggerGen( services.AddSwaggerGen(
c => c =>
{ {
// resolve the IApiVersionDescriptionProvider service
// note: that we have to build a temporary service provider here because one has not been created yet
var provider = services.BuildServiceProvider().GetRequiredService<IApiVersionDescriptionProvider>();
// add a swagger document for each discovered API version
// note: you might choose to skip or document deprecated API versions differently // c.DocInclusionPredicate((docName, apiDesc) =>
foreach (var description in provider.ApiVersionDescriptions) // {
{ // if (!apiDesc.TryGetMethodInfo(out MethodInfo methodInfo)) return false;
c.SwaggerDoc(description.GroupName, CreateInfoForApiVersion(description));
} // var versions = methodInfo.DeclaringType
// .GetCustomAttributes(true)
// .OfType<ApiVersionAttribute>()
// .SelectMany(attr => attr.Versions);
// return versions.Any(v => $"v{v.ToString()}" == docName);
// });
// c.DocInclusionPredicate((docName, apiDesc) =>
// {
// if (!apiDesc.TryGetMethodInfo(out MethodInfo methodInfo)) return false;
// // Get the MapToApiVersion attributes of the action
// var mapApiVersions = methodInfo
// .GetCustomAttributes(true)
// .OfType<MapToApiVersionAttribute>()
// .SelectMany(attr => attr.Versions);
// //if it contains MapToApiVersion attributes, then we should check those as the ApiVersion ones are ignored
// if (mapApiVersions.Any() && mapApiVersions.Any(v => $"v{v.ToString()}" == docName))
// return true;
// // Get the ApiVersion attributes of the controller
// var versions = methodInfo.DeclaringType
// .GetCustomAttributes(true)
// .OfType<ApiVersionAttribute>()
// .SelectMany(attr => attr.Versions);
// return versions.Any(v => $"v{v.ToString()}" == docName);
// });
// resolve the IApiVersionDescriptionProvider service
//v3 commented this out
// // note: that we have to build a temporary service provider here because one has not been created yet
// var provider = services.BuildServiceProvider().GetRequiredService<IApiVersionDescriptionProvider>();
// // add a swagger document for each discovered API version
// // note: you might choose to skip or document deprecated API versions differently
// foreach (var description in provider.ApiVersionDescriptions)
// {
// c.SwaggerDoc(description.GroupName, CreateInfoForApiVersion(description));
// }
// add a custom operation filter which sets default values // add a custom operation filter which sets default values
//Removed because will no longer compile the SwaggerDefaultValues but may be needed, not sure it's all a bit muddled //Removed because will no longer compile the SwaggerDefaultValues but may be needed, not sure it's all a bit muddled
// c.OperationFilter<SwaggerDefaultValues>(); c.OperationFilter<SwaggerDefaultValues>();
// integrate xml comments // integrate xml comments
c.IncludeXmlComments(XmlCommentsFilePath); c.IncludeXmlComments(XmlCommentsFilePath);
@@ -218,11 +823,6 @@ namespace AyaNova
//Obsolete way
// c.AddSecurityRequirement(new System.Collections.Generic.Dictionary<string, System.Collections.Generic.IEnumerable<string>>
// {
// { "Bearer", new string[] { } }
// });
//https://stackoverflow.com/questions/56234504/migrating-to-swashbuckle-aspnetcore-version-5 //https://stackoverflow.com/questions/56234504/migrating-to-swashbuckle-aspnetcore-version-5
//First we define the security scheme //First we define the security scheme
@@ -431,6 +1031,9 @@ namespace AyaNova
c.DocumentTitle = "AyaNova API explorer"; c.DocumentTitle = "AyaNova API explorer";
c.RoutePrefix = "api-docs"; c.RoutePrefix = "api-docs";
}); });
#endregion swagger #endregion swagger
@@ -547,25 +1150,53 @@ namespace AyaNova
} }
} }
static Microsoft.OpenApi.Models.OpenApiInfo CreateInfoForApiVersion(ApiVersionDescription description) // static Microsoft.OpenApi.Models.OpenApiInfo CreateInfoForApiVersion(ApiVersionDescription description)
{ // {
var info = new Microsoft.OpenApi.Models.OpenApiInfo() // var info = new Microsoft.OpenApi.Models.OpenApiInfo()
{ // {
Title = $"AyaNova API {description.ApiVersion}", // Title = $"AyaNova API {description.ApiVersion}",
Version = description.ApiVersion.ToString() // Version = description.ApiVersion.ToString()
}; // };
if (description.IsDeprecated) // if (description.IsDeprecated)
{ // {
info.Description += " This API version has been deprecated."; // info.Description += " This API version has been deprecated.";
} // }
// return info;
// }
//update for v3 from here https://github.com/microsoft/aspnet-api-versioning/wiki/API-Documentation#aspnet-core
//and example here: https://github.com/microsoft/aspnet-api-versioning/blob/master/samples/aspnetcore/SwaggerSample/ConfigureSwaggerOptions.cs
// public class ConfigureSwaggerOptions : IConfigureOptions<SwaggerGenOptions>
// {
// readonly IApiVersionDescriptionProvider provider;
// public ConfigureSwaggerOptions(IApiVersionDescriptionProvider provider) =>
// this.provider = provider;
// public void Configure(SwaggerGenOptions options)
// {
// foreach (var description in provider.ApiVersionDescriptions)
// {
// options.SwaggerDoc(
// description.GroupName,
// new OpenApiInfo()
// {
// Title = $"AyaNova API {description.ApiVersion}",
// Version = description.ApiVersion.ToString(),
// });
// }
// }
// }
return info;
}
#endregion #endregion
} }
} }
*/

View File

@@ -1,8 +1,11 @@
// namespace AyaNova // namespace AyaNova
// { // {
// using Microsoft.OpenApi.Models;
// using Swashbuckle.AspNetCore.SwaggerGen; // using Swashbuckle.AspNetCore.SwaggerGen;
// using System.Linq; // using System.Linq;
// using Microsoft.AspNetCore.Authorization;
// //https://github.com/domaindrivendev/Swashbuckle.AspNetCore/releases/tag/v5.0.0-rc3
// /// <summary> // /// <summary>
// /// Represents the Swagger/Swashbuckle operation filter used to document the implicit API version parameter. // /// Represents the Swagger/Swashbuckle operation filter used to document the implicit API version parameter.
@@ -10,38 +13,124 @@
// /// <remarks>This <see cref="IOperationFilter"/> is only required due to bugs in the <see cref="SwaggerGenerator"/>. // /// <remarks>This <see cref="IOperationFilter"/> is only required due to bugs in the <see cref="SwaggerGenerator"/>.
// /// Once they are fixed and published, this class can be removed.</remarks> // /// Once they are fixed and published, this class can be removed.</remarks>
// public class SwaggerDefaultValues : IOperationFilter // public class SwaggerDefaultValues : IOperationFilter
// { // {
// /// <summary> // /// <summary>
// /// Applies the filter to the specified operation using the given context. // /// Applies the filter to the specified operation using the given context.
// /// </summary> // /// </summary>
// /// <param name="operation">The operation to apply the filter to.</param> // /// <param name="operation">The operation to apply the filter to.</param>
// /// <param name="context">The current operation filter context.</param> // /// <param name="context">The current operation filter context.</param>
// public void Apply( Operation operation, OperationFilterContext context ) // public void Apply(OpenApiOperation operation, OperationFilterContext context)
// { // {
// // REF: https://github.com/domaindrivendev/Swashbuckle.AspNetCore/issues/412 // // REF: https://github.com/domaindrivendev/Swashbuckle.AspNetCore/issues/412
// // REF: https://github.com/domaindrivendev/Swashbuckle.AspNetCore/pull/413 // // REF: https://github.com/domaindrivendev/Swashbuckle.AspNetCore/pull/413
// foreach ( var parameter in operation.Parameters.OfType<NonBodyParameter>() ) // foreach (var parameter in operation.Parameters)
// { // {
// var description = context.ApiDescription.ParameterDescriptions.First( p => p.Name == parameter.Name ); // var description = context.ApiDescription.ParameterDescriptions.First(p => p.Name == parameter.Name);
// var routeInfo = description.RouteInfo; // var routeInfo = description.RouteInfo;
// if ( parameter.Description == null ) // if (parameter.Description == null)
// { // {
// parameter.Description = description.ModelMetadata?.Description; // parameter.Description = description.ModelMetadata?.Description;
// } // }
// if ( routeInfo == null ) // if (routeInfo == null)
// { // {
// continue; // continue;
// } // }
// if ( parameter.Default == null ) // // if (parameter.Default == null)
// { // // {
// parameter.Default = routeInfo.DefaultValue; // // parameter.Default = routeInfo.DefaultValue;
// } // // }
// if (parameter.Schema.Default == null) { parameter.Schema.Default = new OpenApiString(description.DefaultValue.ToString()); }
// parameter.Required |= !routeInfo.IsOptional; // parameter.Required |= !routeInfo.IsOptional;
// } // }
// } // }
// } // }
// }
namespace AyaNova
{
using Microsoft.AspNetCore.Mvc.ApiExplorer;
using Microsoft.OpenApi.Any;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
using System.Linq;
/// <summary>
/// Represents the Swagger/Swashbuckle operation filter used to document the implicit API version parameter.
/// </summary>
/// <remarks>This <see cref="IOperationFilter"/> is only required due to bugs in the <see cref="SwaggerGenerator"/>.
/// Once they are fixed and published, this class can be removed.</remarks>
public class SwaggerDefaultValues : IOperationFilter
{
/// <summary>
/// Applies the filter to the specified operation using the given context.
/// </summary>
/// <param name="operation">The operation to apply the filter to.</param>
/// <param name="context">The current operation filter context.</param>
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
var apiDescription = context.ApiDescription;
operation.Deprecated |= apiDescription.IsDeprecated();
if (operation.Parameters == null)
{
return;
}
// REF: https://github.com/domaindrivendev/Swashbuckle.AspNetCore/issues/412
// REF: https://github.com/domaindrivendev/Swashbuckle.AspNetCore/pull/413
foreach (var parameter in operation.Parameters)
{
var description = apiDescription.ParameterDescriptions.First(p => p.Name == parameter.Name);
if (parameter.Description == null)
{
parameter.Description = description.ModelMetadata?.Description;
}
if (parameter.Schema.Default == null && description.DefaultValue != null)
{
parameter.Schema.Default = new OpenApiString(description.DefaultValue.ToString());
}
parameter.Required |= description.IsRequired;
}
}
}
}
// public void ConfigureServices(IServiceCollection services)
// {
// #region Swagger
// services
// .AddApiVersioning(options =>
// {
// options.AssumeDefaultVersionWhenUnspecified = true;
// options.DefaultApiVersion = Microsoft.AspNetCore.Mvc.ApiVersion.Parse("8.0");
// options.ReportApiVersions = true;
// });
// services.AddVersionedApiExplorer(o => o.GroupNameFormat = "'v'VVV");
// services.AddTransient<IConfigureOptions<SwaggerGenOptions>, ConfigureSwaggerOptions>();
// services.AddSwaggerGen(
// c =>
// {
// c.OperationFilter<SwaggerDefaultValues>();
// });
// #endregion