My project file acc.csproj
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<UserSecretsId>aspnet-acc-7EF42A0A-175F-4471-8CF7-F52B4BE2F02C</UserSecretsId>
</PropertyGroup>
<ItemGroup>
<Compile Remove="vy_note\**" />
<Content Remove="vy_note\**" />
<EmbeddedResource Remove="vy_note\**" />
<None Remove="vy_note\**" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="AspNetCoreRateLimit" Version="5.0.0" />
<PackageReference Include="AutoMapper" Version="12.0.0" />
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="12.0.0" />
<PackageReference Include="Marvin.Cache.Headers" Version="6.1.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="7.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="7.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="7.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Versioning" Version="5.0.0" />
<PackageReference Include="Microsoft.AspNetCore.OData" Version="8.0.12" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer" Version="5.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Abstractions" Version="7.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Analyzers" Version="7.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="7.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.Identity.Core" Version="7.0.0" />
<PackageReference Include="Microsoft.Identity.Web" Version="1.25.10" />
<PackageReference Include="Microsoft.IdentityModel.JsonWebTokens" Version="6.25.1" />
<PackageReference Include="Microsoft.IdentityModel.Logging" Version="6.25.1" />
<PackageReference Include="Microsoft.IdentityModel.Tokens" Version="6.25.1" />
<PackageReference Include="Microsoft.OData.Core" Version="7.13.0" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="7.0.0" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL.Design" Version="1.1.0" />
<PackageReference Include="Serilog" Version="2.12.0" />
<PackageReference Include="Serilog.AspNetCore" Version="6.1.0" />
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
<PackageReference Include="Serilog.Sinks.Seq" Version="5.2.2" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.25.1" />
<PackageReference Include="X.PagedList.Mvc.Core" Version="8.4.3" />
</ItemGroup>
<ItemGroup>
<Folder Include="logs\" />
<Folder Include="Migrations\" />
</ItemGroup>
</Project>
File Program.cs
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc.Versioning;
using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;
using Serilog;
using System.Text;
using acc.Configurations;
using acc.Contracts;
using acc.Data;
using acc.Middleware;
using acc.Repository;
using acc.Models;
using Microsoft.AspNetCore.OData;
using acc;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
// var connectionString = builder.Configuration.GetConnectionString("HotelListingDbConnectionString");
builder.Services.AddDbContext<Acc200Context>(options => options.UseNpgsql("Server=127.0.0.1;Port=5432;Database=acc200;User Id=postgres;Password=postgres;"));
builder.Services.AddControllers().AddJsonOptions(options => { options.JsonSerializerOptions.Converters.Add(new DateOnlyJsonConverter()); });
builder.Services.AddIdentityCore<ApiUser>()
.AddRoles<IdentityRole>()
.AddTokenProvider<DataProtectorTokenProvider<ApiUser>>("HotelListingApi")
.AddEntityFrameworkStores<Acc200Context>()
.AddDefaultTokenProviders();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddCors(options => {
options.AddPolicy("AllowAll",
b => b.AllowAnyHeader()
.AllowAnyOrigin()
.AllowAnyMethod());
});
builder.Services.AddApiVersioning(options =>
{
options.AssumeDefaultVersionWhenUnspecified = true;
options.DefaultApiVersion = new Microsoft.AspNetCore.Mvc.ApiVersion(1, 0);
options.ReportApiVersions = true;
options.ApiVersionReader = ApiVersionReader.Combine(
new QueryStringApiVersionReader("api-version"),
new HeaderApiVersionReader("X-Version"),
new MediaTypeApiVersionReader("ver")
);
});
builder.Services.AddVersionedApiExplorer(
options =>
{
options.GroupNameFormat = "'v'VVV";
options.SubstituteApiVersionInUrl = true;
});
builder.Host.UseSerilog((ctx, lc) => lc.WriteTo.Console().ReadFrom.Configuration(ctx.Configuration));
builder.Services.AddAutoMapper(typeof(MapperConfig));
builder.Services.AddScoped(typeof(IGenericRepository<>), typeof(GenericRepository<>));
builder.Services.AddScoped<ICountriesRepository, CountriesRepository>();
builder.Services.AddScoped<IHotelsRepository, HotelsRepository>();
builder.Services.AddScoped<IAuthManager, AuthManager>();
builder.Services.AddAuthentication(options => {
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; // "Bearer"
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options => {
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero,
ValidIssuer = builder.Configuration["JwtSettings:Issuer"],
ValidAudience = builder.Configuration["JwtSettings:Audience"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["JwtSettings:Key"]))
};
});
builder.Services.AddResponseCaching(options =>
{
options.MaximumBodySize = 1024;
options.UseCaseSensitivePaths = true;
});
builder.Services.AddControllers().AddOData(options =>
{
options.Select().Filter().OrderBy();
});
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseMiddleware<ExceptionMiddleware>();
app.UseHttpsRedirection();
app.UseCors("AllowAll");
app.UseResponseCaching();
app.Use(async (context, next) =>
{
context.Response.GetTypedHeaders().CacheControl =
new Microsoft.Net.Http.Headers.CacheControlHeaderValue()
{
Public = true,
MaxAge = TimeSpan.FromSeconds(10)
};
context.Response.Headers[Microsoft.Net.Http.Headers.HeaderNames.Vary] =
new string[] { "Accept-Encoding" };
await next();
});
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
// app.MapSwagger(); // VyDN 2022_12_10. https://github.com/domaindrivendev/Swashbuckle.AspNetCore/issues/2539
app.Run();
File ServiceExtensions.cs
using acc.Data;
using acc.Models;
using AspNetCoreRateLimit;
using Marvin.Cache.Headers;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Diagnostics;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Versioning;
using Microsoft.IdentityModel.Tokens;
using Microsoft.OpenApi.Models;
using Serilog;
using System.Reflection;
using System.Text;
namespace acc
{
public static class ServiceExtensions
{
public static void ConfigureIdentity(this IServiceCollection services)
{
var builder = services.AddIdentityCore<ApiUser>(q => { q.User.RequireUniqueEmail = true; });
builder = new IdentityBuilder(builder.UserType, typeof(IdentityRole), services);
builder.AddTokenProvider("HotelListingApi", typeof(DataProtectorTokenProvider<ApiUser>));
builder.AddEntityFrameworkStores<Acc200Context>().AddDefaultTokenProviders();
}
public static void ConfigureJWT(this IServiceCollection services, IConfiguration Configuration)
{
var jwtSettings = Configuration.GetSection("Jwt");
var key = Environment.GetEnvironmentVariable("KEY");
services.AddAuthentication(o =>
{
o.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
o.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(o =>
{
o.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = false,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = jwtSettings.GetSection("Issuer").Value,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(key)),
};
});
}
public static void ConfigureSwaggerDoc(this IServiceCollection services)
{
services.AddSwaggerGen(c =>
{
c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
Description = @"JWT Authorization header using the Bearer scheme.
Enter 'Bearer' [space] and then your token in the text input below.
Example: 'Bearer 12345abcdef'",
Name = "Authorization",
In = ParameterLocation.Header,
Type = SecuritySchemeType.ApiKey,
Scheme = "Bearer"
});
c.AddSecurityRequirement(new OpenApiSecurityRequirement() {
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference {
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
},
Scheme = "0auth2",
Name = "Bearer",
In = ParameterLocation.Header
},
new List<string>()
}
});
});
}
public static void ConfigureExceptionHandler(this IApplicationBuilder app)
{
app.UseExceptionHandler(error =>
{
error.Run(async context =>
{
context.Response.StatusCode = StatusCodes.Status500InternalServerError;
context.Response.ContentType = "application/json";
var contextFeature = context.Features.Get<IExceptionHandlerFeature>();
if (contextFeature != null)
{
Log.Error($"Something Went Wrong in the {contextFeature.Error}");
await context.Response.WriteAsync(new Error
{
StatusCode = context.Response.StatusCode,
Message = "Internal Server Error. Please Try Again Later."
}.ToString());
}
});
});
}
public static void ConfigureVersioning(this IServiceCollection services)
{
services.AddApiVersioning(opt =>
{
opt.ReportApiVersions = true;
opt.AssumeDefaultVersionWhenUnspecified = true;
opt.DefaultApiVersion = new ApiVersion(1, 0);
opt.ApiVersionReader = new HeaderApiVersionReader("api-version");
});
}
public static void ConfigureHttpCacheHeaders(this IServiceCollection services)
{
services.AddResponseCaching();
services.AddHttpCacheHeaders(
(expirationOpt) =>
{
expirationOpt.MaxAge = 120;
expirationOpt.CacheLocation = CacheLocation.Private;
},
(validationOpt) =>
{
validationOpt.MustRevalidate = true;
}
);
}
public static void ConfigureAutoMapper(this IServiceCollection services)
{
//services.AddAutoMapper(Assembly.GetExecutingAssembly());
}
public static void ConfigureRateLimiting(this IServiceCollection services)
{
var rateLimitRules = new List<RateLimitRule>
{
new RateLimitRule
{
Endpoint = "*",
Limit= 1,
Period = "5s"
}
};
services.Configure<IpRateLimitOptions>(opt =>
{
opt.GeneralRules = rateLimitRules;
});
services.AddSingleton<IRateLimitCounterStore, MemoryCacheRateLimitCounterStore>();
services.AddSingleton<IIpPolicyStore, MemoryCacheIpPolicyStore>();
services.AddSingleton<IRateLimitConfiguration, RateLimitConfiguration>();
}
}
}
File DateOnlyJsonConverter.cs
using System.Text.Json;
using System.Text.Json.Serialization;
namespace acc
{
public sealed class DateOnlyJsonConverter : JsonConverter<DateOnly>
{
public override DateOnly Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return DateOnly.FromDateTime(reader.GetDateTime());
}
public override void Write(Utf8JsonWriter writer, DateOnly value, JsonSerializerOptions options)
{
var isoDate = value.ToString("O");
writer.WriteStringValue(isoDate);
}
}
}
// https://stackoverflow.com/a/72613135/3728901
File appsetttings.Development.json
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"Jwt": {
"key": "thisisveryveryveryimportantkey",
"Issuer": "letsprogram",
"Audience": "http://localhost:4200/"
},
"AllowedHosts": "*",
"ConnectionStrings": {
"SqlServerConnStr": "Server=127.0.0.1;Port=5432;Database=acc200;User Id=postgres; Password=postgres;"
},
"JwtSettings": {
"Issuer": "acc",
"Audience": "accClient",
"DurationInMinutes": 10,
"Key": "YourSuperSecretKey"
},
"Serilog": {
"MinimumLevel": {
"Default": "Information",
"Override": {
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"WriteTo": [
{
"Name": "File",
"Args": {
"path": "./logs/log-.txt",
"rollingInterval": "Day"
}
},
{
"Name": "Seq",
"Application": "Hotel Listing API",
"Args": { "serverUrl": "http://localhost:5341" }
}
]
}
}
After upgrade to .NET 7
Empty page has source view-source:https://localhost:7283/swagger/index.html
<!-- HTML for static distribution bundle build -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Swagger UI</title>
<link rel="stylesheet" type="text/css" href="./swagger-ui.css">
<link rel="icon" type="image/png" href="./favicon-32x32.png" sizes="32x32" />
<link rel="icon" type="image/png" href="./favicon-16x16.png" sizes="16x16" />
<style>
html {
box-sizing: border-box;
overflow: -moz-scrollbars-vertical;
overflow-y: scroll;
}
*,
*:before,
*:after {
box-sizing: inherit;
}
body {
margin: 0;
background: #fafafa;
}
</style>
</head>
<body>
<div id="swagger-ui"></div>
<!-- Workaround for https://github.com/swagger-api/swagger-editor/issues/1371 -->
<script>
if (window.navigator.userAgent.indexOf("Edge") > -1) {
console.log("Removing native Edge fetch in favor of swagger-ui's polyfill")
window.fetch = undefined;
}
</script>
<script src="./swagger-ui-bundle.js"></script>
<script src="./swagger-ui-standalone-preset.js"></script>
<script>
/* Source: https://gist.github.com/lamberta/3768814
* Parse a string function definition and return a function object. Does not use eval.
* @param {string} str
* @return {function}
*
* Example:
* var f = function (x, y) { return x * y; };
* var g = parseFunction(f.toString());
* g(33, 3); //=> 99
*/
function parseFunction(str) {
if (!str) return void (0);
var fn_body_idx = str.indexOf('{'),
fn_body = str.substring(fn_body_idx + 1, str.lastIndexOf('}')),
fn_declare = str.substring(0, fn_body_idx),
fn_params = fn_declare.substring(fn_declare.indexOf('(') + 1, fn_declare.lastIndexOf(')')),
args = fn_params.split(',');
args.push(fn_body);
function Fn() {
return Function.apply(this, args);
}
Fn.prototype = Function.prototype;
return new Fn();
}
window.onload = function () {
var configObject = JSON.parse('{"urls":[{"url":"v1/swagger.json","name":"acc v1"}],"deepLinking":false,"persistAuthorization":false,"displayOperationId":false,"defaultModelsExpandDepth":1,"defaultModelExpandDepth":1,"defaultModelRendering":"example","displayRequestDuration":false,"docExpansion":"list","showExtensions":false,"showCommonExtensions":false,"supportedSubmitMethods":["get","put","post","delete","options","head","patch","trace"],"tryItOutEnabled":false}');
var oauthConfigObject = JSON.parse('{"scopeSeparator":" ","scopes":[],"useBasicAuthenticationWithAccessCodeGrant":false,"usePkceWithAuthorizationCodeGrant":false}');
// Workaround for https://github.com/swagger-api/swagger-ui/issues/5945
configObject.urls.forEach(function (item) {
if (item.url.startsWith("http") || item.url.startsWith("/")) return;
item.url = window.location.href.replace("index.html", item.url).split('#')[0];
});
// If validatorUrl is not explicitly provided, disable the feature by setting to null
if (!configObject.hasOwnProperty("validatorUrl"))
configObject.validatorUrl = null
// If oauth2RedirectUrl isn't specified, use the built-in default
if (!configObject.hasOwnProperty("oauth2RedirectUrl"))
configObject.oauth2RedirectUrl = (new URL("oauth2-redirect.html", window.location.href)).href;
// Apply mandatory parameters
configObject.dom_id = "#swagger-ui";
configObject.presets = [SwaggerUIBundle.presets.apis, SwaggerUIStandalonePreset];
configObject.layout = "StandaloneLayout";
// Parse and add interceptor functions
var interceptors = JSON.parse('{"RequestInterceptorFunction":null,"ResponseInterceptorFunction":null}');
if (interceptors.RequestInterceptorFunction)
configObject.requestInterceptor = parseFunction(interceptors.RequestInterceptorFunction);
if (interceptors.ResponseInterceptorFunction)
configObject.responseInterceptor = parseFunction(interceptors.ResponseInterceptorFunction);
// Begin Swagger UI call region
const ui = SwaggerUIBundle(configObject);
ui.initOAuth(oauthConfigObject);
// End Swagger UI call region
window.ui = ui
}
</script>
<!-- Visual Studio Browser Link -->
<script type="text/javascript" src="/_vs/browserLink" async="async" id="__browserLink_initializationData" data-requestId="5834594221a7498a826d19cb504fd321" data-requestMappingFromServer="false" data-connectUrl="http://localhost:57705/3770e3c7e28f44539ac8f4173388863e/browserLink"></script>
<!-- End Browser Link -->
<script src="/_framework/aspnetcore-browser-refresh.js"></script></body>
</html>
How to fix it?