c# - 将框架升级到 .NET Core 3.1 后出现 Swagger 问题
问题描述
我们有一个框架 2.1 上的 .NET Core Web API。我将它更新到 .NET Core 3.1,当然这涉及到一些代码更改以实现兼容性。目前我可以从 VS 在本地运行它并且它工作正常 - 我可以使用 Swagger 和所有查看索引页面。但是在将其部署到开发服务器后,它不会从服务器 URL (https://[my app].[domain].com/swagger/index.html) 加载。我得到一个页面:
...尽管开发人员工具中没有列出任何错误。
服务器上的输出日志是这样说的:
信息:Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager[0]
用户配置文件可用。使用“C:\Windows\system32\config\systemprofile\AppData\Local\ASP.NET\DataProtection-Keys”作为密钥存储库和 Windows DPAPI 来加密静态密钥。
信息:Microsoft.Hosting.Lifetime[0]
应用程序已启动。按 Ctrl+C 关闭。
信息:Microsoft.Hosting.Lifetime[0]
托管环境:生产信息:Microsoft.Hosting.Lifetime[0] 内容根路径:E:\Websites[my app] 信息:Microsoft.AspNetCore.Hosting.Diagnostics 1
请求启动 HTTP/ 2.0 获取 https://[我的应用程序].[域].com/swagger/index.html
信息:Microsoft.AspNetCore.StaticFiles.StaticFileMiddleware[2] 发送文件。请求路径:'/swagger/index.html'。物理路径:'E:\Websites[my app]\wwwroot\swagger\index.html' 信息:Microsoft.AspNetCore.Hosting.Diagnostics[2] 请求在 21.5241 毫秒内完成 200 文本/html 信息:Microsoft.AspNetCore.Hosting。诊断1请求开始 HTTP/2.0 GET https://[my app].[domain].com/swagger/custom.css 信息:Microsoft.AspNetCore.StaticFiles.StaticFileMiddleware[2] 发送文件。请求路径:'/swagger/custom.css'。物理路径:'E:\Websites[my app]\wwwroot\swagger\custom.css' 信息:Microsoft.AspNetCore.Hosting.Diagnostics[2] 请求在 1.0042 毫秒内完成 200 文本/css 信息:Microsoft.AspNetCore.Hosting。诊断1请求开始 HTTP/2.0 GET https://[my app].[domain].com/swagger/favicon.ico 信息:Microsoft.AspNetCore.StaticFiles.StaticFileMiddleware[2] 发送文件。请求路径:'/swagger/favicon.ico'。物理路径:'E:\Websites[my app]\wwwroot\swagger\favicon.ico' 信息:Microsoft.AspNetCore.Hosting.Diagnostics[2] 请求在 1.8643 毫秒内完成 200 图像/x-icon 信息:Microsoft.AspNetCore。 Hosting.Diagnostics 1请求开始 HTTP/2.0 GET https://[my app].[domain].com/swagger/v3/swagger.json 信息:Microsoft.AspNetCore.Hosting.Diagnostics 1请求开始 HTTP/2.0 GET https://[my app].[domain].com/swagger/favicon-32x32.png 信息:Microsoft.AspNetCore.StaticFiles.StaticFileMiddleware[2] 发送文件。请求路径:'/swagger/favicon-32x32.png'。物理路径:'E:\Websites[my app]\wwwroot\swagger\favicon-32x32.png' 信息:Microsoft.AspNetCore.Hosting.Diagnostics[2] 请求在 2.0548 毫秒内完成 200 图像/png 失败:Microsoft.AspNetCore。服务器.IIS.Core.IISHttpServer[2]连接 ID “17437937763084179141”,请求 ID “800096c6-0001-f200-b63f-84710c7967bb”:应用程序引发了未处理的异常。System.UriFormatException:无效的 URI:无法确定 URI 的格式。在 System.Uri.CreateThis(String uri, Boolean dontEscape, UriKind uriKind) 在 System.Uri..ctor(String uriString) 在 ICE.API.Startup.<>c__DisplayClass4_0.b__5(SwaggerGenOptions swagger) 在 D:\ADO\ADOAgent1 \Work\206\s[repo][project]\Startup.cs:line 119 at Microsoft.Extensions.Options.ConfigureNamedOptions
1.Configure(String name, TOptions options) at Microsoft.Extensions.Options.OptionsFactory
1.Create(String name) at Microsoft.Extensions.Options.OptionsManager`1.<>c__DisplayClass5_0.b__0 ()....
引用的包包括(但不限于):
- Microsoft.AspNetCore.Mvc.NewtonsoftJson (3.1.15)
- Swashbuckle.AspNetCore (6.1.5)
- Swashbuckle.AspNetCore.Newtonsoft (6.1.5)
这是 Startup.cs 的内容,其中第 119 行(在上面的错误中指定)是swagger.SwaggerDoc("v3", new OpenApiInfo
:
public void ConfigureServices(IServiceCollection services)
{
string[] allowedOrigins = Configuration.GetSection("appsettings:AllowedPortalUrlCsv").Value.Trim().Split(',');
services.AddOptions();
services.AddCors(options =>
{
options.AddPolicy("CorsPolicy",
builder =>
builder
.WithOrigins(allowedOrigins)
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials()
);
});
services.Configure<AppSettings>(Configuration.GetSection("AppSettings"));
services.AddSignalR();
services.AddControllers();
//Add DB Context:
services.AddDbContext<ActiveDBContext>(options => options.UseOracle(Configuration["appsettings:connection:activedb"]));
services.AddDistributedMemoryCache();
int timeout;
var success = int.TryParse(Configuration["appsettings:sessionidletimeout"], out timeout);
timeout = (success ? timeout : 10);
services.AddSession(options =>
{
options.IdleTimeout = TimeSpan.FromMinutes(timeout);
options.Cookie.HttpOnly = true;
options.Cookie.IsEssential = true;
options.Cookie.SameSite = SameSiteMode.None;
});
//Higher Level Dependency Mappings:
services.AddTransient<IValidateRequest, ValidateRequest>();
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddSingleton<IFileProvider>(sp => sp.GetRequiredService<IWebHostEnvironment>().ContentRootFileProvider);
//Add Service Dependencies:
services.AddScoped<[interface], [service]>();
services.AddAuthentication(IISDefaults.AuthenticationScheme);
services.Configure<IISOptions>(options =>
{
options.AutomaticAuthentication = true;
});
services.AddSwaggerGen(swagger =>
{
swagger.SwaggerDoc("v3", new OpenApiInfo
{
Version = Configuration.GetSection("appsettings:info:version").Value,
Title = Configuration.GetSection("appsettings:info:title").Value,
Description = Configuration.GetSection("appsettings:info:description").Value,
TermsOfService = new Uri(Configuration.GetSection("appsettings:info:termsofservice").Value),
Contact = new OpenApiContact
{
Name = Configuration.GetSection("appsettings:info:contact:name").Value,
Email = Configuration.GetSection("appsettings:info:contact:email").Value,
Url = new Uri(Configuration.GetSection("appsettings:info:contact:url").Value),
},
License = new OpenApiLicense
{
Name = Configuration.GetSection("appsettings:info:license:name").Value,
Url = new Uri(Configuration.GetSection("appsettings:info:license:url").Value),
}
});
var filePath = Path.Combine(PlatformServices.Default.Application.ApplicationBasePath, "[project].xml");
swagger.IncludeXmlComments(filePath);
swagger.AddSecurityDefinition("ApiKey", new OpenApiSecurityScheme
{
Description = $"ApiKey for authorization across API (Example: {Configuration.GetSection("appsettings:apikey").Value})",
Name = "ApiKey",
In = ParameterLocation.Header,
Type = SecuritySchemeType.ApiKey
});
swagger.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "ApiKey"
}
},
new string[] { }
}
});
});
services.AddSwaggerGenNewtonsoftSupport();
services.AddSingleton(Configuration);
services.AddMvc().AddJsonOptions(o =>
{
o.JsonSerializerOptions.PropertyNamingPolicy = null;
o.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory)
{
//UseStaticFiles will allow the site to check wwwroot for overriding files to load, such as icons, images, styles, etc...
app.UseStaticFiles();
//For .NET Core 3.1, IHostingEnvironment changed to IWebHostEnvironment...but IsDevelopment is in the Microsoft.Extensions.Hosting namespace, so that was added up top
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
//Needed for setting if no TNSNames and Local Client is installed:
OracleConfiguration.OracleDataSources.Add(Configuration.GetSection("appsettings:connection:tnsname").Value, Configuration.GetSection("appsettings:connection:descriptor").Value);
loggerFactory.AddDBLogger(new DBLoggerConfiguration()
{
DBConnection = Configuration.GetSection("appsettings:connection:activedb").Value,
LogLevelAsString = Configuration.GetSection("appsettings:internalloglevel").Value.ToLower(),
EnableFallBackLogger = true
});
//Swagger API Documentation:
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("./swagger/v3/swagger.json", $"{Configuration.GetSection("appsettings:info:title").Value} {Configuration.GetSection("appsettings:info:version").Value}");
});
app.UseMiddleware<ApiKeyValidatorsMiddleware>();
app.UseCors("CorsPolicy");
app.UseSession();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapHub<Domain.Helpers.NotifyHub>("/notify");
endpoints.MapControllers();
});
}
}
这是 index.html 的内容:
<!-- HTML for static distribution bundle build -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>[my website]</title>
<link href="https://fonts.googleapis.com/css?family=Open+Sans:400,700|Source+Code+Pro:300,600|Titillium+Web:400,600,700" rel="stylesheet">
<link rel="stylesheet" type="text/css" href="https://unpkg.com/swagger-ui/dist/swagger-ui.css">
<link rel="stylesheet" type="text/css" href="./custom.css">
<link rel="icon" type="image/png" href="./favicon.ico">
<style>
html {
box-sizing: border-box;
overflow: -moz-scrollbars-vertical;
overflow-y: scroll;
}
*,
*:before,
*:after {
box-sizing: inherit;
}
body {
margin: 0;
background: #fafafa;
}
</style>
<link href='/swagger/custom.css' rel='stylesheet' media='screen' type='text/css' />
</head>
<body>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="position:absolute;width:0;height:0">
<defs>
<symbol viewBox="0 0 20 20" id="unlocked">
<path d="M15.8 8H14V5.6C14 2.703 12.665 1 10 1 7.334 1 6 2.703 6 5.6V6h2v-.801C8 3.754 8.797 3 10 3c1.203 0 2 .754 2 2.199V8H4c-.553 0-1 .646-1 1.199V17c0 .549.428 1.139.951 1.307l1.197.387C5.672 18.861 6.55 19 7.1 19h5.8c.549 0 1.428-.139 1.951-.307l1.196-.387c.524-.167.953-.757.953-1.306V9.199C17 8.646 16.352 8 15.8 8z"></path>
</symbol>
<symbol viewBox="0 0 20 20" id="locked">
<path d="M15.8 8H14V5.6C14 2.703 12.665 1 10 1 7.334 1 6 2.703 6 5.6V8H4c-.553 0-1 .646-1 1.199V17c0 .549.428 1.139.951 1.307l1.197.387C5.672 18.861 6.55 19 7.1 19h5.8c.549 0 1.428-.139 1.951-.307l1.196-.387c.524-.167.953-.757.953-1.306V9.199C17 8.646 16.352 8 15.8 8zM12 8H8V5.199C8 3.754 8.797 3 10 3c1.203 0 2 .754 2 2.199V8z"/>
</symbol>
<symbol viewBox="0 0 20 20" id="close">
<path d="M14.348 14.849c-.469.469-1.229.469-1.697 0L10 11.819l-2.651 3.029c-.469.469-1.229.469-1.697 0-.469-.469-.469-1.229 0-1.697l2.758-3.15-2.759-3.152c-.469-.469-.469-1.228 0-1.697.469-.469 1.228-.469 1.697 0L10 8.183l2.651-3.031c.469-.469 1.228-.469 1.697 0 .469.469.469 1.229 0 1.697l-2.758 3.152 2.758 3.15c.469.469.469 1.229 0 1.698z"/>
</symbol>
<symbol viewBox="0 0 20 20" id="large-arrow">
<path d="M13.25 10L6.109 2.58c-.268-.27-.268-.707 0-.979.268-.27.701-.27.969 0l7.83 7.908c.268.271.268.709 0 .979l-7.83 7.908c-.268.271-.701.27-.969 0-.268-.269-.268-.707 0-.979L13.25 10z"/>
</symbol>
<symbol viewBox="0 0 20 20" id="large-arrow-down">
<path d="M17.418 6.109c.272-.268.709-.268.979 0s.271.701 0 .969l-7.908 7.83c-.27.268-.707.268-.979 0l-7.908-7.83c-.27-.268-.27-.701 0-.969.271-.268.709-.268.979 0L10 13.25l7.418-7.141z"/>
</symbol>
<symbol viewBox="0 0 24 24" id="jump-to">
<path d="M19 7v4H5.83l3.58-3.59L8 6l-6 6 6 6 1.41-1.41L5.83 13H21V7z"/>
</symbol>
<symbol viewBox="0 0 24 24" id="expand">
<path d="M10 18h4v-2h-4v2zM3 6v2h18V6H3zm3 7h12v-2H6v2z"/>
</symbol>
</defs>
</svg>
<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="https://unpkg.com/swagger-ui-dist@3/swagger-ui-bundle.js" charset="utf-8"></script>
<script src="https://unpkg.com/swagger-ui-dist@3/swagger-ui-standalone-preset.js" charset="utf-8"></script>
<script>
window.onload = function () {
var configObject = JSON.parse('{"urls":[{"url":"/swagger/v3/swagger.json","name":"[my website] v3"}],"validatorUrl":null}');
var oauthConfigObject = JSON.parse('{}');
/*Apply mandatory parameters*/
configObject.dom_id = "#swagger-ui";
configObject.presets = [SwaggerUIBundle.presets.apis, SwaggerUIStandalonePreset];
configObject.layout = "StandaloneLayout";
/*If oauth2RedirectUrl isn't specified, use the built-in default*/
if (!configObject.hasOwnProperty("oauth2RedirectUrl"))
configObject.oauth2RedirectUrl = window.location.href.replace("index.html", "oauth2-redirect.html");
/*Build a system*/
const ui = SwaggerUIBundle(configObject);
/*Apply OAuth config*/
ui.initOAuth(oauthConfigObject);
}
</script>
</body>
</html>
一定和AddSwaggerGen
Startup里面的内容有关。我尝试用谷歌搜索,但看不到任何特定问题,所以我不确定我还需要做什么。请注意 .NET Core 2.1 上的版本在 URL 中加载 swagger 就好了。
解决方案
推荐阅读
- excel - vba中图像控制变量中的变量未定义错误
- elasticsearch - Grafana lucene 查询:fields.someFieldname = xxx 和 fields.someFieldname: xxx 之间的区别
- c# - 如何在不同的控制器中设置相同的路由模板
- python - 如何漂亮地格式化熊猫中的列和值,使其具有相同的宽度?
- angular - 后恢复密码服务不起作用
- mapbox-gl-js - Mapbox-gl popup.on('open')没有触发
- javascript - React + Lottie 动画滚动火
- azure - Azure B2C 密码字段值可见按钮?
- postgresql - 如何在postgresql中获取按行分组的ID并使用结果?
- netsuite - 创建一个贯穿每个项目的循环