11 Commits

Author SHA1 Message Date
luoyunchong
089c238126 add Version 0.0.10 2021-10-23 00:15:55 +08:00
luoyunchong
99fd092b24 #26 fix RoutePrefix nginx代理路径无效的问题 2021-10-23 00:13:05 +08:00
luoyunchong
1e9fb7d195 update package 2021-09-02 23:28:12 +08:00
luoyunchong
66cd518872 Update Package 2021-06-11 20:12:24 +08:00
luoyunchong
b7ae3cf42e update 目录 更新文档 减少底层依赖。 2020-10-22 23:32:35 +08:00
IGeekFan
311a886830 Merge pull request #12 from 91651/master
feat: nswag swagger adaptation
2020-10-22 23:16:16 +08:00
LZH
3c584c3a95 feat: nswag swagger adaptation 2020-10-21 18:24:05 +08:00
luoyunchong
8a4066a40e add 验证码demo示例 2020-09-04 01:37:34 +08:00
luoyunchong
35fa68cffc fix server url base path 2020-08-25 03:29:10 +08:00
luoyunchong
8ea53827f5 #5 #4 处理枚举和 servers参数 2020-08-25 02:48:52 +08:00
luoyunchong
1d63f2c585 fix 0.0.4 2020-08-13 01:12:32 +08:00
84 changed files with 1351 additions and 156 deletions

View File

@@ -22,6 +22,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SwaggerUI_IndexStream_Knife
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IGeekFan.AspNetCore.Knife4jUI", "src\IGeekFan.AspNetCore.Knife4jUI\IGeekFan.AspNetCore.Knife4jUI.csproj", "{6C784918-BE29-4FEF-8AC3-9D34A38DE822}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IGeekFan.AspNetCore.Knife4jUI", "src\IGeekFan.AspNetCore.Knife4jUI\IGeekFan.AspNetCore.Knife4jUI.csproj", "{6C784918-BE29-4FEF-8AC3-9D34A38DE822}"
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "WebSites", "WebSites", "{86851B6C-3504-4879-8464-1DB422D46BA0}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OAuth2Integration", "test\WebSites\OAuth2Integration\OAuth2Integration.csproj", "{9E8D8F42-33F0-4F2D-9B56-1AB1B33DE1FA}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NSwag.Swagger.Knife4jUI", "test\WebSites\NSwag.Swagger.Knife4jUI\NSwag.Swagger.Knife4jUI.csproj", "{42B4C1C3-AE38-47C7-AAAA-FE0FDA7DADEB}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@@ -44,6 +50,14 @@ Global
{6C784918-BE29-4FEF-8AC3-9D34A38DE822}.Debug|Any CPU.Build.0 = Debug|Any CPU {6C784918-BE29-4FEF-8AC3-9D34A38DE822}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6C784918-BE29-4FEF-8AC3-9D34A38DE822}.Release|Any CPU.ActiveCfg = Release|Any CPU {6C784918-BE29-4FEF-8AC3-9D34A38DE822}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6C784918-BE29-4FEF-8AC3-9D34A38DE822}.Release|Any CPU.Build.0 = Release|Any CPU {6C784918-BE29-4FEF-8AC3-9D34A38DE822}.Release|Any CPU.Build.0 = Release|Any CPU
{9E8D8F42-33F0-4F2D-9B56-1AB1B33DE1FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9E8D8F42-33F0-4F2D-9B56-1AB1B33DE1FA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9E8D8F42-33F0-4F2D-9B56-1AB1B33DE1FA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9E8D8F42-33F0-4F2D-9B56-1AB1B33DE1FA}.Release|Any CPU.Build.0 = Release|Any CPU
{42B4C1C3-AE38-47C7-AAAA-FE0FDA7DADEB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{42B4C1C3-AE38-47C7-AAAA-FE0FDA7DADEB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{42B4C1C3-AE38-47C7-AAAA-FE0FDA7DADEB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{42B4C1C3-AE38-47C7-AAAA-FE0FDA7DADEB}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@@ -53,6 +67,9 @@ Global
{86A24FA0-E85D-4BDD-97D7-B990C50A40A9} = {75C51574-4CBD-403B-8182-8BF2A6DCFD43} {86A24FA0-E85D-4BDD-97D7-B990C50A40A9} = {75C51574-4CBD-403B-8182-8BF2A6DCFD43}
{1D6FD5CA-5D58-4895-8545-A93099CE1AD4} = {C146A419-15E0-4475-9623-706C5E2DCE0B} {1D6FD5CA-5D58-4895-8545-A93099CE1AD4} = {C146A419-15E0-4475-9623-706C5E2DCE0B}
{6C784918-BE29-4FEF-8AC3-9D34A38DE822} = {929BB2D7-C678-4BE8-8AA9-F271A2AE4545} {6C784918-BE29-4FEF-8AC3-9D34A38DE822} = {929BB2D7-C678-4BE8-8AA9-F271A2AE4545}
{86851B6C-3504-4879-8464-1DB422D46BA0} = {75C51574-4CBD-403B-8182-8BF2A6DCFD43}
{9E8D8F42-33F0-4F2D-9B56-1AB1B33DE1FA} = {86851B6C-3504-4879-8464-1DB422D46BA0}
{42B4C1C3-AE38-47C7-AAAA-FE0FDA7DADEB} = {86851B6C-3504-4879-8464-1DB422D46BA0}
EndGlobalSection EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {9D77CCB4-F597-421B-9EF9-52D4B0AC382D} SolutionGuid = {9D77CCB4-F597-421B-9EF9-52D4B0AC382D}

View File

@@ -20,11 +20,24 @@
### 🚀安装包 ### 🚀安装包
以下为使用Swashbuckle.AspNetCore.Swagger底层组件
1.Install the standard Nuget package into your ASP.NET Core application. 1.Install the standard Nuget package into your ASP.NET Core application.
``` ```
Package Manager : Install-Package IGeekFan.AspNetCore.Knife4jUI Package Manager :
CLI : dotnet add package IGeekFan.AspNetCore.Knife4jUI
Install-Package Swashbuckle.AspNetCore.Swagger
Install-Package Swashbuckle.AspNetCore.SwaggerGen
Install-Package IGeekFan.AspNetCore.Knife4jUI
OR
CLI :
dotnet add package Swashbuckle.AspNetCore.Swagger
dotnet add package Swashbuckle.AspNetCore.SwaggerGen
dotnet add package IGeekFan.AspNetCore.Knife4jUI
``` ```
2.In the ConfigureServices method of Startup.cs, register the Swagger generator, defining one or more Swagger documents. 2.In the ConfigureServices method of Startup.cs, register the Swagger generator, defining one or more Swagger documents.
@@ -92,6 +105,32 @@ c.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, "SwaggerDemo.xml"),t
### NSwag.AspNetCore
请参考目录test/WebSites/NSwag.Swagger.Knife4jUI
```
public void ConfigureServices(IServiceCollection services)
{
// 其它Service
services.AddOpenApiDocument();
}
```
```
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// 其它 Use
app.UseOpenApi();
app.UseKnife4UI(c =>
{
c.RoutePrefix = "";
c.SwaggerEndpoint("/swagger/v1/swagger.json");
});
}
```
即可使用 Knife4jUI
### 🔎 效果图 ### 🔎 效果图
运行项目,打开 https://localhost:5001/index.html#/home 运行项目,打开 https://localhost:5001/index.html#/home

View File

@@ -8,9 +8,9 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.10.9" /> <PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.10.9" />
<PackageReference Include="Swashbuckle.AspNetCore.Swagger" Version="5.5.1" /> <PackageReference Include="Swashbuckle.AspNetCore.Swagger" Version="6.1.4" />
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" Version="5.5.1" /> <PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" Version="6.1.4" />
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUi" Version="5.5.1" /> <PackageReference Include="Swashbuckle.AspNetCore.SwaggerUi" Version="6.1.4" />
</ItemGroup> </ItemGroup>

View File

@@ -1,39 +1,48 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>netstandard2.0;netcoreapp3.0</TargetFrameworks> <TargetFrameworks>netstandard2.0;netcoreapp3.1;net5.0</TargetFrameworks>
<Description>Middleware to expose an embedded version of the knife4j-vue-v3 from an ASP.NET Core application</Description> <Description>Middleware to expose an embedded version of the knife4j-vue-v3 from an ASP.NET Core application</Description>
<NoWarn>$(NoWarn);1591</NoWarn> <NoWarn>$(NoWarn);1591</NoWarn>
<GenerateDocumentationFile>true</GenerateDocumentationFile> <GenerateDocumentationFile>true</GenerateDocumentationFile>
<PackageId></PackageId> <PackageId>IGeekFan.AspNetCore.Knife4jUI</PackageId>
<PackageTags>swagger;documentation;discovery;help;webapi;aspnet;aspnetcore</PackageTags> <PackageTags>swagger;documentation;discovery;help;webapi;aspnet;aspnetcore</PackageTags>
<PackageProjectUrl>https://github.com/luoyunchong/IGeekFan.AspNetCore.Knife4jUI</PackageProjectUrl> <PackageProjectUrl>https://github.com/luoyunchong/IGeekFan.AspNetCore.Knife4jUI</PackageProjectUrl>
<RepositoryType>git</RepositoryType> <RepositoryType>git</RepositoryType>
<RepositoryUrl>https://github.com/luoyunchong/IGeekFan.AspNetCore.Knife4jUI.git</RepositoryUrl> <RepositoryUrl>https://github.com/luoyunchong/IGeekFan.AspNetCore.Knife4jUI.git</RepositoryUrl>
<RootNamespace>IGeekFan.AspNetCore.Knife4jUI</RootNamespace> <RootNamespace>IGeekFan.AspNetCore.Knife4jUI</RootNamespace>
<Version>0.0.3</Version> <Version>0.0.10</Version>
<Company /> <Company />
<Authors>igeekfan;xiaoym;</Authors> <Authors>igeekfan;xiaoym;</Authors>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild> <GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Copyright>Apache License 2.0</Copyright> <Copyright>Apache License 2.0</Copyright>
<PackageLicenseExpression></PackageLicenseExpression> <PackageLicenseExpression></PackageLicenseExpression>
<AssemblyVersion>0.0.3.0</AssemblyVersion> <AssemblyVersion>0.0.10.0</AssemblyVersion>
<FileVersion>0.0.10.0</FileVersion>
</PropertyGroup> </PropertyGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard2.0' "> <!-- Using SourceLink -->
<PropertyGroup>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0' ">
<PackageReference Include="Microsoft.AspNetCore.Routing" Version="2.1.0" /> <PackageReference Include="Microsoft.AspNetCore.Routing" Version="2.1.0" />
<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="2.1.0" /> <PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="2.1.0" />
<PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" Version="2.1.0" /> <PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" Version="2.1.0" />
<PackageReference Include="System.Text.Json" Version="4.6.0" /> <PackageReference Include="System.Text.Json" Version="4.6.0" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'netcoreapp3.0' "> <ItemGroup Condition="'$(TargetFramework)' != 'netstandard2.0' ">
<FrameworkReference Include="Microsoft.AspNetCore.App" /> <FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<PackageReference Include="Swashbuckle.AspNetCore.Swagger" Version="5.5.1" />
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" Version="5.5.1" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<EmbeddedResource Include="knife4j/**/*" /> <EmbeddedResource Include="knife4j/**/*" />

View File

@@ -136,6 +136,8 @@ namespace IGeekFan.AspNetCore.Knife4jUI
public string Url { get; set; } public string Url { get; set; }
public string Name { get; set; } public string Name { get; set; }
public string SwaggerVersion { get; set; } = "3.0";
} }
public enum ModelRendering public enum ModelRendering

View File

@@ -14,8 +14,8 @@ using System.Text.Json;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using System.Collections.Generic; using System.Collections.Generic;
#if NETCOREAPP3_0 #if NETSTANDARD2_0
using IHostingEnvironment = Microsoft.AspNetCore.Hosting.IWebHostEnvironment; using IWebHostEnvironment = Microsoft.AspNetCore.Hosting.IHostingEnvironment;
#endif #endif
namespace IGeekFan.AspNetCore.Knife4jUI namespace IGeekFan.AspNetCore.Knife4jUI
{ {
@@ -29,7 +29,7 @@ namespace IGeekFan.AspNetCore.Knife4jUI
public Knife4jUIMiddleware( public Knife4jUIMiddleware(
RequestDelegate next, RequestDelegate next,
IHostingEnvironment hostingEnv, IWebHostEnvironment hostingEnv,
ILoggerFactory loggerFactory, ILoggerFactory loggerFactory,
Knife4UIOptions options) Knife4UIOptions options)
{ {
@@ -45,6 +45,7 @@ namespace IGeekFan.AspNetCore.Knife4jUI
public async Task Invoke(HttpContext httpContext) public async Task Invoke(HttpContext httpContext)
{ {
var httpMethod = httpContext.Request.Method; var httpMethod = httpContext.Request.Method;
var path = httpContext.Request.Path.Value; var path = httpContext.Request.Path.Value;
@@ -66,7 +67,7 @@ namespace IGeekFan.AspNetCore.Knife4jUI
return; return;
} }
if (httpMethod == "GET" && Regex.IsMatch(path, $"^/v3/api-docs/swagger-config$")) if (httpMethod == "GET" && Regex.IsMatch(path, $"/swagger-resources$"))
{ {
await RespondWithConfig(httpContext.Response); await RespondWithConfig(httpContext.Response);
return; return;
@@ -74,15 +75,15 @@ namespace IGeekFan.AspNetCore.Knife4jUI
await _staticFileMiddleware.Invoke(httpContext); await _staticFileMiddleware.Invoke(httpContext);
} }
private async Task RespondWithConfig(HttpResponse response) private async Task RespondWithConfig(HttpResponse response)
{ {
await response.WriteAsync(JsonSerializer.Serialize(_options.ConfigObject, _jsonSerializerOptions)); await response.WriteAsync(JsonSerializer.Serialize(_options.ConfigObject.Urls, _jsonSerializerOptions));
} }
private StaticFileMiddleware CreateStaticFileMiddleware( private StaticFileMiddleware CreateStaticFileMiddleware(
RequestDelegate next, RequestDelegate next,
IHostingEnvironment hostingEnv, IWebHostEnvironment hostingEnv,
ILoggerFactory loggerFactory, ILoggerFactory loggerFactory,
Knife4UIOptions options) Knife4UIOptions options)
{ {

View File

@@ -6,20 +6,37 @@
<meta name=viewport content="width=device-width,initial-scale=1"> <meta name=viewport content="width=device-width,initial-scale=1">
<link rel=icon href=favicon.ico> <link rel=icon href=favicon.ico>
<title>%(DocumentTitle)</title> <title>%(DocumentTitle)</title>
<link href=knife4j/css/app.b932f742.css rel=preload> <link href=knife4j/css/chunk-51277dbe.57225f85.css rel=prefetch>
<link href=knife4j/js/app.920753bc.js rel=preload as=script> <link href=knife4j/js/chunk-069eb437.0b47243d.js rel=prefetch>
<link href=knife4j/js/chunk-vendors.e86fea24.js rel=preload as=script> <link href=knife4j/js/chunk-0fd67716.d57e2c41.js rel=prefetch>
<link href=knife4j/css/app.b932f742.css rel=stylesheet> <link href=knife4j/js/chunk-2d0af44e.c299c1d4.js rel=prefetch>
<link href=knife4j/js/chunk-2d0bd799.cc91c520.js rel=prefetch>
<link href=knife4j/js/chunk-2d0d0b98.cb1dea78.js rel=prefetch>
<link href=knife4j/js/chunk-2d0da532.dd3c929c.js rel=prefetch>
<link href=knife4j/js/chunk-2d22269d.bd9173e1.js rel=prefetch>
<link href=knife4j/js/chunk-3b888a65.8737ce4f.js rel=prefetch>
<link href=knife4j/js/chunk-3ec4aaa8.a79d19f8.js rel=prefetch>
<link href=knife4j/js/chunk-51277dbe.4335d8bb.js rel=prefetch>
<link href=knife4j/js/chunk-589faee0.b24e5f3d.js rel=prefetch>
<link href=knife4j/js/chunk-735c675c.76ef1019.js rel=prefetch>
<link href=knife4j/js/chunk-adb9e944.b888f4bd.js rel=prefetch>
<link href=knife4j/css/app.284871fa.css rel=preload as=style>
<link href=knife4j/css/chunk-vendors.3f2387de.css rel=preload as=style>
<link href=knife4j/js/app.703cb2b2.js rel=preload as=script>
<link href=knife4j/js/chunk-vendors.90e8ba20.js rel=preload as=script>
<link href=knife4j/css/chunk-vendors.3f2387de.css rel=stylesheet>
<link href=knife4j/css/app.284871fa.css rel=stylesheet>
%(HeadContent) %(HeadContent)
</head> </head>
<body> <body>
<noscript><strong>We're sorry but knife4j-vue doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id=app></div> <noscript><strong>We're sorry but knife4j-vue doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript>
<script src=knife4j/js/chunk-vendors.e86fea24.js></script> <div id=app></div>
<script src=knife4j/js/app.920753bc.js></script> <script src=knife4j/js/chunk-vendors.90e8ba20.js></script>
<script src=knife4j/js/app.703cb2b2.js></script>
<script> <script>
window.onload = function () { window.onload = function () {
} }
</script> </script>
</body> </body>
</html> </html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
.api-tab[data-v-7da2228c]{margin-top:15px}.api-tab .ant-tag[data-v-7da2228c]{height:32px;line-height:32px}.api-basic[data-v-7da2228c]{padding:11px}.api-basic-title[data-v-7da2228c]{font-size:14px;font-weight:700}.api-basic-body[data-v-7da2228c]{font-size:14px;font-family:-webkit-body}.api-description[data-v-7da2228c]{border-left:4px solid #ddd;line-height:30px}.api-body-desc[data-v-7da2228c]{padding:10px;min-height:35px;-webkit-box-sizing:border-box;box-sizing:border-box;border:1px solid #e8e8e8}.ant-card-body[data-v-7da2228c]{padding:5px}.api-title[data-v-7da2228c]{margin-top:10px;margin-bottom:5px;font-size:16px;font-weight:600;height:30px;line-height:30px;border-left:4px solid #00ab6d;text-indent:8px}.content-line[data-v-7da2228c]{height:25px;line-height:25px}.content-line-count[data-v-7da2228c]{height:35px;line-height:35px}.divider[data-v-7da2228c]{margin:4px 0}

File diff suppressed because one or more lines are too long

View File

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
(window.webpackJsonp=window.webpackJsonp||[]).push([["chunk-2d0af44e"],{"0e36":function(e,t,i){"use strict";i.r(t);var n={name:"EditorShow",components:{editor:i("7c9e")},props:{value:{type:String,required:!0,default:""},xmlMode:{type:Boolean,default:!1,required:!1}},data:function(){return{lang:"json",editor:null,editorHeight:200}},methods:{change:function(e){this.$emit("change",e)},resetEditorHeight:function(){var e=this;setTimeout((function(){var t=e.editor.session.getLength();1==t&&(t=10);var i=16*t;e.editorHeight=i}),300)},editorInit:function(e){var t=this;this.editor=e,i("2099"),i("818b"),i("0696"),this.xmlMode&&(this.lang="xml"),i("1d29"),this.resetEditorHeight(),this.editor.renderer.on("afterRender",(function(){t.$emit("showDescription","123")}))}}},o=i("2877"),r=Object(o.a)(n,(function(){var e=this,t=e.$createElement,i=e._self._c||t;return i("div",[i("editor",{attrs:{value:e.value,lang:e.lang,theme:"eclipse",width:"100%",height:e.editorHeight},on:{init:e.editorInit,input:e.change}})],1)}),[],!1,null,null,null);t.default=r.exports}}]);

View File

@@ -0,0 +1 @@
(window.webpackJsonp=window.webpackJsonp||[]).push([["chunk-2d0bd799"],{"2bc6":function(t,e,a){"use strict";a.r(e);var n={name:"DataType",props:{text:{type:String,default:"string",required:!0},record:{type:Object,required:!0}},data:function(){return{validators:[]}},created:function(){this.intiValidator()},methods:{intiValidator:function(){var t=this.record;if(null!=t.validateInstance)for(var e in this.getJsonKeyLength(t.validateInstance),t.validateInstance){var a=e+":"+t.validateInstance[e];this.validators.push({key:e,val:a})}},getJsonKeyLength:function(t){var e=0;if(null!=t)for(var a in t)t.hasOwnProperty(a)&&e++;return e}}},r=a("2877"),i=Object(r.a)(n,(function(){var t=this,e=t.$createElement,a=t._self._c||e;return a("div",[t.record.validateStatus?a("span",{staticClass:"knife4j-request-validate-jsr"},[a("a-tooltip",{attrs:{placement:"right"}},[a("template",{slot:"title"},t._l(t.validators,(function(e){return a("div",{key:e.key},[t._v(t._s(e.val))])})),0),t._v(" "+t._s(t.text)+" ")],2)],1):a("span",[t._v(t._s(null==t.text||""==t.text?"string":t.text))])])}),[],!1,null,null,null);e.default=i.exports}}]);

View File

@@ -0,0 +1 @@
(window.webpackJsonp=window.webpackJsonp||[]).push([["chunk-2d0d0b98"],{"68cc":function(n,e,t){"use strict";t.r(e);var s=t("0e54"),r=t.n(s);r.a.setOptions({gfm:!0,tables:!0,breaks:!1,pedantic:!1,sanitize:!1,smartLists:!0,smartypants:!1});var a={name:"Markdown",props:{source:{type:String}},computed:{markdownSource:function(){return r()(this.source)}}},o=t("2877"),c=Object(o.a)(a,(function(){var n=this,e=n.$createElement;return(n._self._c||e)("div",{staticClass:"knife4j-markdown",domProps:{innerHTML:n._s(n.markdownSource)}})}),[],!1,null,null,null);e.default=c.exports}}]);

View File

@@ -0,0 +1 @@
(window.webpackJsonp=window.webpackJsonp||[]).push([["chunk-2d0da532"],{"6ab3":function(e,n,t){"use strict";t.r(n);var u=(t("5609"),t("b1c7"),{name:"OAuth2",components:{},data:function(){return{api:null,swaggerInstance:null,debugSupport:!1}},computed:{swagger:function(){return this.$store.state.globals.swagger}},mounted:function(){},beforeCreate:function(){},created:function(){},methods:{}}),a=t("2877"),o=Object(a.a)(u,(function(){var e=this,n=e.$createElement;return(e._self._c||n)("a-row",[e._v(" 我是OAuth2回调页面 ")])}),[],!1,null,"5ccd4e78",null);n.default=o.exports}}]);

View File

@@ -0,0 +1 @@
(window.webpackJsonp=window.webpackJsonp||[]).push([["chunk-2d22269d"],{cf04:function(t,e,i){"use strict";i.r(e);var n={name:"EditorShow",components:{editor:i("7c9e")},props:{value:{type:String,required:!0,default:""}},data:function(){return{lang:"javascript",editor:null,editorHeight:200}},methods:{resetEditorHeight:function(){var t=this;setTimeout((function(){var e=t.editor.session.getLength();1==e&&(e=10);var i=16*e;t.editorHeight=i}),300)},change:function(t){this.$emit("change",t)},editorInit:function(t){var e=this;this.editor=t,i("2099"),i("bb36"),i("1d29"),this.resetEditorHeight(),this.editor.renderer.on("afterRender",(function(){e.$emit("showDescription","123")}))}}},r=i("2877"),o=Object(r.a)(n,(function(){var t=this,e=t.$createElement,i=t._self._c||e;return i("div",[i("editor",{attrs:{value:t.value,lang:t.lang,theme:"eclipse",width:"100%",height:t.editorHeight},on:{init:t.editorInit,input:t.change}})],1)}),[],!1,null,null,null);e.default=o.exports}}]);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,6 @@
/*!
* clipboard.js v2.0.6
* https://clipboardjs.com/
*
* Licensed MIT © Zeno Rocha
*/

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,6 @@
/*!
* clipboard.js v2.0.6
* https://clipboardjs.com/
*
* Licensed MIT © Zeno Rocha
*/

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,6 @@
/*!
* clipboard.js v2.0.6
* https://clipboardjs.com/
*
* Licensed MIT © Zeno Rocha
*/

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,77 @@
/*
object-assign
(c) Sindre Sorhus
@license MIT
*/
/*!
localForage -- Offline Storage, Improved
Version 1.9.0
https://localforage.github.io/localForage
(c) 2013-2017 Mozilla, Apache License 2.0
*/
/*!
* vue-router v3.4.3
* (c) 2020 Evan You
* @license MIT
*/
/*!
Copyright (c) 2017 Jed Watson.
Licensed under the MIT License (MIT), see
http://jedwatson.github.io/classnames
*/
/*!
* The buffer module from node.js, for the browser.
*
* @author Feross Aboukhadijeh <http://feross.org>
* @license MIT
*/
/*!
* Vue.js v2.6.12
* (c) 2014-2020 Evan You
* Released under the MIT License.
*/
/*!
* vue-i18n v8.21.0
* (c) 2020 kazuya kawaguchi
* Released under the MIT License.
*/
/*!
* vuex v3.5.1
* (c) 2020 Evan You
* @license MIT
*/
/*! http://mths.be/fromcodepoint v0.1.0 by @mathias */
/*! safe-buffer. MIT License. Feross Aboukhadijeh <https://feross.org/opensource> */
/**
* [js-md5]{@link https://github.com/emn178/js-md5}
*
* @namespace md5
* @version 0.7.3
* @author Chen, Yi-Cyuan [emn178@gmail.com]
* @copyright Chen, Yi-Cyuan 2014-2017
* @license MIT
*/
//! authors : Tim Wood, Iskren Chernev, Moment.js contributors
//! license : MIT
//! moment.js
//! moment.js language configuration
//! moment.js locale configuration
//! momentjs.com
//! version : 2.27.0

File diff suppressed because one or more lines are too long

View File

@@ -1,91 +0,0 @@
/*
object-assign
(c) Sindre Sorhus
@license MIT
*/
/*!
localForage -- Offline Storage, Improved
Version 1.7.3
https://localforage.github.io/localForage
(c) 2013-2017 Mozilla, Apache License 2.0
*/
/*!
* vue-router v3.1.6
* (c) 2020 Evan You
* @license MIT
*/
/*!
Copyright (c) 2017 Jed Watson.
Licensed under the MIT License (MIT), see
http://jedwatson.github.io/classnames
*/
/*!
* The buffer module from node.js, for the browser.
*
* @author Feross Aboukhadijeh <http://feross.org>
* @license MIT
*/
/*!
* Vue.js v2.6.11
* (c) 2014-2019 Evan You
* Released under the MIT License.
*/
/*!
* clipboard.js v2.0.6
* https://clipboardjs.com/
*
* Licensed MIT © Zeno Rocha
*/
/*!
* html2canvas 1.0.0-rc.5 <https://html2canvas.hertzen.com>
* Copyright (c) 2019 Niklas von Hertzen <https://hertzen.com>
* Released under MIT License
*/
/*!
* vue-i18n v8.17.4
* (c) 2020 kazuya kawaguchi
* Released under the MIT License.
*/
/*! *****************************************************************************
Copyright (c) Microsoft Corporation. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
this file except in compliance with the License. You may obtain a copy of the
License at http://www.apache.org/licenses/LICENSE-2.0
THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
MERCHANTABLITY OR NON-INFRINGEMENT.
See the Apache Version 2.0 License for specific language governing permissions
and limitations under the License.
***************************************************************************** */
/*! http://mths.be/fromcodepoint v0.1.0 by @mathias */
/*! https://mths.be/punycode v1.4.1 by @mathias */
/**
* [js-md5]{@link https://github.com/emn178/js-md5}
*
* @namespace md5
* @version 0.7.3
* @author Chen, Yi-Cyuan [emn178@gmail.com]
* @copyright Chen, Yi-Cyuan 2014-2017
* @license MIT
*/
/**
* vuex v3.3.0
* (c) 2020 Evan You
* @license MIT
*/

View File

@@ -1,13 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk.Web"> <Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup> <PropertyGroup>
<TargetFramework>netcoreapp3.0</TargetFramework> <TargetFramework>netcoreapp3.1</TargetFramework>
<NoWarn>$(NoWarn);1591</NoWarn> <NoWarn>$(NoWarn);1591</NoWarn>
<GenerateDocumentationFile>true</GenerateDocumentationFile> <GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="5.5.1" /> <PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="6.1.4" />
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUi" Version="6.1.4" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@@ -26,6 +26,13 @@ namespace Basic.Controllers
throw new NotImplementedException(); throw new NotImplementedException();
} }
[HttpPost("form-with-user")]
public IActionResult PostFormUser([FromForm] FormUser FormUser)
{
throw new NotImplementedException();
}
[HttpGet("{name}")] [HttpGet("{name}")]
[Produces("application/octet-stream", Type = typeof(FileResult))] [Produces("application/octet-stream", Type = typeof(FileResult))]
public FileResult GetFile(string name) public FileResult GetFile(string name)
@@ -47,4 +54,11 @@ namespace Basic.Controllers
public IFormFile File { get; set; } public IFormFile File { get; set; }
} }
public class FormUser
{
public string Name { get; set; }
public string User { get; set; }
}
} }

View File

@@ -99,21 +99,26 @@ namespace Basic
app.UseSwagger(c => app.UseSwagger(c =>
{ {
}); });
app.UseSwaggerUI(c =>
{
c.RoutePrefix = "swagger"; // serve the UI at root
c.SwaggerEndpoint("/v1/swagger.json", "V1 Docs");
c.SwaggerEndpoint("/gp/swagger.json", "<22><>¼ģ<C2BC><C4A3>");
});
app.UseKnife4UI(c => app.UseKnife4UI(c =>
{ {
//c.RoutePrefix = ""; // serve the UI at root c.RoutePrefix = ""; // serve the UI at root
c.SwaggerEndpoint("/v1/api-docs", "V1 Docs"); c.SwaggerEndpoint("/v1/swagger.json", "V1 Docs");
c.SwaggerEndpoint("/gp/api-docs", "<22><>¼ģ<C2BC><C4A3>"); c.SwaggerEndpoint("/gp/swagger.json", "<22><>¼ģ<C2BC><C4A3>");
}); });
app.UseEndpoints(endpoints => app.UseEndpoints(endpoints =>
{ {
endpoints.MapControllers(); endpoints.MapControllers();
endpoints.MapSwagger("{documentName}/api-docs"); endpoints.MapSwagger("{documentName}/swagger.json");
}); });
} }

View File

@@ -1,9 +1,8 @@
using System; using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
namespace Knife4jUIDemo.Controllers namespace Knife4jUIDemo.Controllers
{ {
@@ -11,7 +10,7 @@ namespace Knife4jUIDemo.Controllers
/// 中文这是一个Get请求这是一个Get请求 /// 中文这是一个Get请求这是一个Get请求
/// </summary> /// </summary>
[ApiController] [ApiController]
[Route("api/WeatherForecast")] [Route("api/WeatherForecast/[action]")]
public class WeatherForecastController : ControllerBase public class WeatherForecastController : ControllerBase
{ {
private static readonly string[] Summaries = new[] private static readonly string[] Summaries = new[]
@@ -26,6 +25,38 @@ namespace Knife4jUIDemo.Controllers
_logger = logger; _logger = logger;
} }
/// <summary>
/// 得到一个ErrorCode
/// </summary>
/// <returns></returns>
[HttpGet]
public ErrorCode GetErrorCode()
{
return ErrorCode.Success;
}
[HttpGet]
public ErrorCode GetErrorCode2(ErrorCode errorCode)
{
return errorCode;
}
[HttpGet]
public IActionResult GetErrorCode4(ErrorCode errorCode)
{
return new JsonResult(new PostErrorCodeDto() { Message="a",ErrorCode=errorCode});
}
/// <summary>
/// 发送一个Post
/// </summary>
/// <returns></returns>
[HttpPost]
public PostErrorCodeDto PostErrorCode([FromBody] PostErrorCodeDto PostErrorCodeDto)
{
return PostErrorCodeDto;
}
/// <summary> /// <summary>
/// 这是一个Get请求 /// 这是一个Get请求
/// </summary> /// </summary>
@@ -43,4 +74,19 @@ namespace Knife4jUIDemo.Controllers
.ToArray(); .ToArray();
} }
} }
/// <summary>
/// 请求实体
/// </summary>
public class PostErrorCodeDto
{
/// <summary>
/// 异常信息
/// </summary>
public string Message { get; set; }
/// <summary>
/// 状态码
/// </summary>
public ErrorCode ErrorCode { get; set; }
}
} }

View File

@@ -0,0 +1,81 @@
using System.ComponentModel;
namespace Knife4jUIDemo
{
/// <summary>
/// 注释ErrorCode
/// </summary>
public enum ErrorCode
{
/// <summary>
/// 操作成功
/// </summary>
Success = 0,
/// <summary>
/// 未知错误
/// </summary>
UnknownError = 1007,
/// <summary>
/// 服务器未知错误
/// </summary>
ServerUnknownError = 999,
/// <summary>
/// 失败
/// </summary>
Error = 1000,
/// <summary>
/// 认证失败
/// </summary>
AuthenticationFailed = 10000,
/// <summary>
/// 无权限
/// </summary>
NoPermission = 10001,
/// <summary>
/// 失败
/// </summary>
Fail = 9999,
/// <summary>
/// refreshToken异常
/// </summary>
RefreshTokenError = 10100,
/// <summary>
/// 资源不存在
/// </summary>
NotFound = 10020,
/// <summary>
/// 参数错误
/// </summary>
[Description("参数错误")]
ParameterError = 10030,
/// <summary>
/// 令牌失效
/// </summary>
[Description("令牌失效")]
TokenInvalidation = 10040,
/// <summary>
/// 令牌过期
/// </summary>
TokenExpired = 10050,
/// <summary>
/// 字段重复
/// </summary>
RepeatField = 10060,
/// <summary>
/// 禁止操作
/// </summary>
Inoperable = 10070,
//10080 请求方法不允许
//10110 文件体积过大
//10120 文件数量过多
//10130 文件扩展名不符合规范
//10140 请求过于频繁,请稍后重试
ManyRequests = 10140
}
}

View File

@@ -11,7 +11,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Swashbuckle.AspNetCore" Version="5.5.1" /> <PackageReference Include="Swashbuckle.AspNetCore" Version="6.1.4" />
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.10.9" /> <PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.10.9" />
</ItemGroup> </ItemGroup>

View File

@@ -9,11 +9,113 @@
中文这是一个Get请求这是一个Get请求 中文这是一个Get请求这是一个Get请求
</summary> </summary>
</member> </member>
<member name="M:Knife4jUIDemo.Controllers.WeatherForecastController.GetErrorCode">
<summary>
得到一个ErrorCode
</summary>
<returns></returns>
</member>
<member name="M:Knife4jUIDemo.Controllers.WeatherForecastController.PostErrorCode(Knife4jUIDemo.Controllers.PostErrorCodeDto)">
<summary>
发送一个Post
</summary>
<returns></returns>
</member>
<member name="M:Knife4jUIDemo.Controllers.WeatherForecastController.Get"> <member name="M:Knife4jUIDemo.Controllers.WeatherForecastController.Get">
<summary> <summary>
这是一个Get请求 这是一个Get请求
</summary> </summary>
<returns></returns> <returns></returns>
</member> </member>
<member name="T:Knife4jUIDemo.Controllers.PostErrorCodeDto">
<summary>
请求实体
</summary>
</member>
<member name="P:Knife4jUIDemo.Controllers.PostErrorCodeDto.Message">
<summary>
异常信息
</summary>
</member>
<member name="P:Knife4jUIDemo.Controllers.PostErrorCodeDto.ErrorCode">
<summary>
状态码
</summary>
</member>
<member name="T:Knife4jUIDemo.ErrorCode">
<summary>
注释ErrorCode
</summary>
</member>
<member name="F:Knife4jUIDemo.ErrorCode.Success">
<summary>
操作成功
</summary>
</member>
<member name="F:Knife4jUIDemo.ErrorCode.UnknownError">
<summary>
未知错误
</summary>
</member>
<member name="F:Knife4jUIDemo.ErrorCode.ServerUnknownError">
<summary>
服务器未知错误
</summary>
</member>
<member name="F:Knife4jUIDemo.ErrorCode.Error">
<summary>
失败
</summary>
</member>
<member name="F:Knife4jUIDemo.ErrorCode.AuthenticationFailed">
<summary>
认证失败
</summary>
</member>
<member name="F:Knife4jUIDemo.ErrorCode.NoPermission">
<summary>
无权限
</summary>
</member>
<member name="F:Knife4jUIDemo.ErrorCode.Fail">
<summary>
失败
</summary>
</member>
<member name="F:Knife4jUIDemo.ErrorCode.RefreshTokenError">
<summary>
refreshToken异常
</summary>
</member>
<member name="F:Knife4jUIDemo.ErrorCode.NotFound">
<summary>
资源不存在
</summary>
</member>
<member name="F:Knife4jUIDemo.ErrorCode.ParameterError">
<summary>
参数错误
</summary>
</member>
<member name="F:Knife4jUIDemo.ErrorCode.TokenInvalidation">
<summary>
令牌失效
</summary>
</member>
<member name="F:Knife4jUIDemo.ErrorCode.TokenExpired">
<summary>
令牌过期
</summary>
</member>
<member name="F:Knife4jUIDemo.ErrorCode.RepeatField">
<summary>
字段重复
</summary>
</member>
<member name="F:Knife4jUIDemo.ErrorCode.Inoperable">
<summary>
禁止操作
</summary>
</member>
</members> </members>
</doc> </doc>

View File

@@ -60,19 +60,20 @@ namespace Knife4jUIDemo
app.UseSwaggerUI(c => app.UseSwaggerUI(c =>
{ {
c.SwaggerEndpoint("/v1/api-docs", "LinCms"); c.SwaggerEndpoint("v1/swagger.json", "My API V1");
//c.SwaggerEndpoint("/v1/api-docs", "LinCms");
}); });
app.UseKnife4UI(c => app.UseKnife4UI(c =>
{ {
c.RoutePrefix = ""; // serve the UI at root c.RoutePrefix = ""; // serve the UI at root
c.SwaggerEndpoint("/v1/api-docs", "V1 Docs"); c.SwaggerEndpoint("/v1/swagger.json", "V1 Docs");
}); });
app.UseEndpoints(endpoints => app.UseEndpoints(endpoints =>
{ {
endpoints.MapControllers(); endpoints.MapControllers();
endpoints.MapSwagger("{documentName}/api-docs"); endpoints.MapSwagger("{documentName}/swagger.json");
}); });
} }
} }

View File

@@ -0,0 +1,39 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace NSwag.Swagger.Knife4jUI.Controllers
{
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
private readonly ILogger<WeatherForecastController> _logger;
public WeatherForecastController(ILogger<WeatherForecastController> logger)
{
_logger = logger;
}
[HttpGet]
public IEnumerable<WeatherForecast> Get()
{
var rng = new Random();
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = rng.Next(-20, 55),
Summary = Summaries[rng.Next(Summaries.Length)]
})
.ToArray();
}
}
}

View File

@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="NSwag.AspNetCore" Version="13.8.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\src\IGeekFan.AspNetCore.Knife4jUI\IGeekFan.AspNetCore.Knife4jUI.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,26 @@
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace NSwag.Swagger.Knife4jUI
{
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
}

View File

@@ -0,0 +1,31 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:2990",
"sslPort": 44362
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"NSwag.Swagger.Knife4jUI": {
"commandName": "Project",
"dotnetRunMessages": "true",
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "https://localhost:5001;http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

View File

@@ -0,0 +1,70 @@
using IGeekFan.AspNetCore.Knife4jUI;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
// using Microsoft.OpenApi.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace NSwag.Swagger.Knife4jUI
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddOpenApiDocument();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseOpenApi(settings =>
{
settings.PostProcess = (document, request) =>
{
document.Info.Title = Configuration["Project:Name"];
};
});
app.UseKnife4UI(c =>
{
c.RoutePrefix = ""; // serve the UI at root
// c.RoutePrefix = "/docs"; // serve the UI at root
c.SwaggerEndpoint("/swagger/v1/swagger.json", "V1 Docs");
});
// app.UseSwaggerUi3();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
}

View File

@@ -0,0 +1,15 @@
using System;
namespace NSwag.Swagger.Knife4jUI
{
public class WeatherForecast
{
public DateTime Date { get; set; }
public int TemperatureC { get; set; }
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
public string Summary { get; set; }
}
}

View File

@@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
}
}

View File

@@ -0,0 +1,10 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*"
}

View File

@@ -0,0 +1,57 @@
using System.Collections.Generic;
using IdentityServer4.Models;
using IdentityServer4.Test;
namespace OAuth2Integration.AuthServer
{
public static class Config
{
internal static IEnumerable<Client> Clients()
{
yield return new Client
{
ClientId = "test-id",
ClientName = "Test client (Code with PKCE)",
RedirectUris = new[] {
"http://localhost:55202/resource-server/swagger/oauth2-redirect.html", // IIS Express
"http://localhost:5000/resource-server/swagger/oauth2-redirect.html", // Kestrel
},
ClientSecrets = { new Secret("test-secret".Sha256()) },
RequireConsent = true,
AllowedGrantTypes = GrantTypes.Code,
RequirePkce = true,
AllowedScopes = new[] { "readAccess", "writeAccess" },
};
}
internal static IEnumerable<ApiResource> ApiResources()
{
yield return new ApiResource
{
Name = "api",
DisplayName = "API",
Scopes = new[]
{
new Scope("readAccess", "Access read operations"),
new Scope("writeAccess", "Access write operations")
}
};
}
internal static List<TestUser> TestUsers()
{
return new List<TestUser>
{
new TestUser
{
SubjectId = "joebloggs",
Username = "joebloggs",
Password = "pass123"
}
};
}
}
}

View File

@@ -0,0 +1,58 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using IdentityServer4;
using IdentityServer4.Test;
using NCaptcha.Abstractions;
using NCaptcha.AspNetCore.Extensions;
namespace OAuth2Integration.AuthServer.Controllers
{
[ApiExplorerSettings(IgnoreApi = true)]
[Route("account")]
public class AccountController : Controller
{
private readonly TestUserStore _userStore;
public AccountController()
{
_userStore = new TestUserStore(Config.TestUsers());
}
[HttpGet("login")]
public IActionResult Login(string returnUrl)
{
var viewModel = new LoginViewModel { Username = "joebloggs", Password = "pass123", ReturnUrl = returnUrl };
return View("/AuthServer/Views/Login.cshtml", viewModel);
}
[HttpPost("login")]
public async Task<IActionResult> Login([FromForm] LoginViewModel viewModel)
{
if (!_userStore.ValidateCredentials(viewModel.Username, viewModel.Password))
{
ModelState.AddModelError("", "Invalid username or password");
viewModel.Password = string.Empty;
return View("/AuthServer/Views/Login.cshtml", viewModel);
}
// Use an IdentityServer-compatible ClaimsPrincipal
var identityServerUser = new IdentityServerUser(viewModel.Username);
identityServerUser.DisplayName = viewModel.Username;
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, identityServerUser.CreatePrincipal());
return Redirect(viewModel.ReturnUrl);
}
}
public class LoginViewModel
{
public string ReturnUrl { get; set; }
public string Username { get; set; }
public string Password { get; set; }
}
}

View File

@@ -0,0 +1,68 @@
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Mvc;
using IdentityServer4.Stores;
using IdentityServer4.Services;
using IdentityServer4.Models;
namespace OAuth2Integration.AuthServer.Controllers
{
[ApiExplorerSettings(IgnoreApi = true)]
public class ConsentController : Controller
{
private readonly IIdentityServerInteractionService _interaction;
private readonly IClientStore _clientStore;
private readonly IResourceStore _resourceStore;
public ConsentController(
IIdentityServerInteractionService interaction,
IClientStore clientStore,
IResourceStore resourceStore)
{
_interaction = interaction;
_clientStore = clientStore;
_resourceStore = resourceStore;
}
[HttpGet("consent")]
public async Task<IActionResult> Consent(string returnUrl)
{
var request = await _interaction.GetAuthorizationContextAsync(returnUrl);
var client = await _clientStore.FindEnabledClientByIdAsync(request.ClientId);
var resource = await _resourceStore.FindApiResourceAsync("api");
var viewModel = new ConsentViewModel
{
ReturnUrl = returnUrl,
ClientName = client.ClientName,
ScopesRequested = resource.Scopes.Where(s => request.ScopesRequested.Contains(s.Name))
};
return View("/AuthServer/Views/Consent.cshtml", viewModel);
}
[HttpPost("consent")]
public async Task<IActionResult> Consent([FromForm]ConsentViewModel viewModel)
{
var request = await _interaction.GetAuthorizationContextAsync(viewModel.ReturnUrl);
// Communicate outcome of consent back to identityserver
var consentResponse = new ConsentResponse
{
ScopesConsented = viewModel.ScopesConsented
};
await _interaction.GrantConsentAsync(request, consentResponse);
return Redirect(viewModel.ReturnUrl);
}
}
public class ConsentViewModel
{
public string ReturnUrl { get; set; }
public string ClientName { get; set; }
public IEnumerable<Scope> ScopesRequested { get; set; }
public string[] ScopesConsented { get; set; }
}
}

View File

@@ -0,0 +1,25 @@
@model OAuth2Integration.AuthServer.Controllers.ConsentViewModel
<style>
</style>
<h1>Consent</h1>
<p>
@Model.ClientName is requesting your permission to ...
</p>
<form asp-controller="Consent" asp-action="Consent">
<input type="hidden" asp-for="ReturnUrl" />
<fieldset>
<ul>
@foreach (var scope in Model.ScopesRequested)
{
<li>
<label>
<input name="ScopesConsented" type="checkbox" value="@scope.Name" checked />@scope.DisplayName
</label>
</li>
}
</ul>
</fieldset>
<button type="submit">Grant Access</button>
</form>

View File

@@ -0,0 +1,23 @@
@model OAuth2Integration.AuthServer.Controllers.LoginViewModel
<style>
input {
display: block;
}
</style>
<h1>Login</h1>
<form asp-controller="Account" asp-action="Login">
<input type="hidden" asp-for="ReturnUrl" />
<fieldset>
<label>
Username:
<input type="text" asp-for="Username" />
</label>
<label>
Password:
<input type="text" asp-for="Password" />
</label>
</fieldset>
<button type="submit">Login</button>
</form>

View File

@@ -0,0 +1 @@
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

View File

@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="IdentityServer4" Version="3.0.2" />
<PackageReference Include="IdentityServer4.AccessTokenValidation" Version="3.0.1" />
<PackageReference Include="NCaptcha.AspNetCore.SessionImages" Version="0.1.0" />
<PackageReference Include="Swashbuckle.AspNetCore.Swagger" Version="6.1.4" />
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" Version="6.1.4" />
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUi" Version="6.1.4" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\src\IGeekFan.AspNetCore.Knife4jUI\IGeekFan.AspNetCore.Knife4jUI.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
namespace OAuth2Integration
{
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
}

View File

@@ -0,0 +1,30 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:55202",
"sslPort": 0
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "resource-server/swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"OAuth2Integration": {
"commandName": "Project",
"launchBrowser": true,
"launchUrl": "resource-server/swagger",
"applicationUrl": "http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

View File

@@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using NCaptcha.Abstractions;
using NCaptcha.AspNetCore.Extensions;
namespace OAuth2Integration.ResourceServer.Controllers
{
[Route("captcha")]
public class CaptchaController : Controller
{
private readonly ICaptchaGenerator _captchaGenerator;
public CaptchaController(ICaptchaGenerator captchaGenerator)
{
_captchaGenerator = captchaGenerator;
}
[Produces("image/gif", Type = typeof(FileContentResult))]
[HttpGet]
public async Task<IActionResult> OnGetCaptchaAsync() => await _captchaGenerator.GetCaptchaFileResultAsync();
}
}

View File

@@ -0,0 +1,58 @@
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
namespace OAuth2Integration.ResourceServer.Controllers
{
[Route("products")]
[Authorize(AuthenticationSchemes = "Bearer")]
public class ProductsController : Controller
{
[HttpGet]
[Authorize("readAccess")]
public IEnumerable<Product> GetProducts()
{
yield return new Product
{
Id = 1,
SerialNo = "ABC123",
};
}
[HttpGet("{id}")]
[Authorize("readAccess")]
public Product GetProduct(int id)
{
return new Product
{
Id = 1,
SerialNo = "ABC123",
};
}
[HttpPost]
[Authorize("writeAccess")]
public void CreateProduct([FromBody]Product product)
{
}
[HttpDelete("{id}")]
[Authorize("writeAccess")]
public void DeleteProduct(int id)
{
}
}
public class Product
{
public int Id { get; internal set; }
public string SerialNo { get; set; }
public ProductStatus Status { get; set; }
}
public enum ProductStatus
{
InStock, ComingSoon
}
}

View File

@@ -0,0 +1,40 @@
using System.Linq;
using System.Collections.Generic;
using Microsoft.AspNetCore.Authorization;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
namespace OAuth2Integration.ResourceServer.Swagger
{
public class SecurityRequirementsOperationFilter : IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
// Policy names map to scopes
var requiredScopes = context.MethodInfo
.GetCustomAttributes(true)
.OfType<AuthorizeAttribute>()
.Select(attr => attr.Policy)
.Distinct();
if (requiredScopes.Any())
{
operation.Responses.Add("401", new OpenApiResponse { Description = "Unauthorized" });
operation.Responses.Add("403", new OpenApiResponse { Description = "Forbidden" });
var oAuthScheme = new OpenApiSecurityScheme
{
Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "oauth2" }
};
operation.Security = new List<OpenApiSecurityRequirement>
{
new OpenApiSecurityRequirement
{
[ oAuthScheme ] = requiredScopes.ToList()
}
};
}
}
}
}

View File

@@ -0,0 +1,172 @@
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.OpenApi.Models;
using NCaptcha.AspNetCore.SessionImages;
using IGeekFan.AspNetCore.Knife4jUI;
using Microsoft.AspNetCore.Mvc.Controllers;
namespace OAuth2Integration
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// Register IdentityServer services to power OAuth2.0 flows
services.AddIdentityServer()
.AddDeveloperSigningCredential()
.AddInMemoryClients(AuthServer.Config.Clients())
.AddInMemoryApiResources(AuthServer.Config.ApiResources())
.AddTestUsers(AuthServer.Config.TestUsers());
// The auth setup is a little nuanced because this app provides the auth-server & the resource-server
// Use the "Cookies" scheme by default & explicitly require "Bearer" in the resource-server controllers
// See https://docs.microsoft.com/en-us/aspnet/core/security/authorization/limitingidentitybyscheme?tabs=aspnetcore2x
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie()
.AddIdentityServerAuthentication(c =>
{
c.Authority = "http://localhost:55202/auth-server/";
c.RequireHttpsMetadata = false;
c.ApiName = "api";
});
// Configure named auth policies that map directly to OAuth2.0 scopes
services.AddAuthorization(c =>
{
c.AddPolicy("readAccess", p => p.RequireClaim("scope", "readAccess"));
c.AddPolicy("writeAccess", p => p.RequireClaim("scope", "writeAccess"));
});
services.AddControllersWithViews();
services.AddSession();
services.AddSessionBasedImageCaptcha();
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Version = "v1", Title = "Test API V1" });
// Define the OAuth2.0 scheme that's in use (i.e. Implicit Flow)
c.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme
{
Type = SecuritySchemeType.OAuth2,
Flows = new OpenApiOAuthFlows
{
AuthorizationCode = new OpenApiOAuthFlow
{
AuthorizationUrl = new Uri("/auth-server/connect/authorize", UriKind.Relative),
TokenUrl = new Uri("/auth-server/connect/token", UriKind.Relative),
Scopes = new Dictionary<string, string>
{
{ "readAccess", "Access read operations" },
{ "writeAccess", "Access write operations" }
}
}
}
});
c.CustomOperationIds(apiDesc =>
{
var controllerAction = apiDesc.ActionDescriptor as ControllerActionDescriptor;
return controllerAction.ControllerName + "-" + controllerAction.ActionName;
});
c.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "oauth2" }
},
new[] { "readAccess", "writeAccess" }
}
});
// Assign scope requirements to operations based on AuthorizeAttribute
//c.OperationFilter<SecurityRequirementsOperationFilter>();
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseSession();
app.Map("/auth-server", authServer =>
{
authServer.UseRouting();
authServer.UseAuthentication();
authServer.UseIdentityServer();
authServer.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
});
app.Map("/resource-server", resourceServer =>
{
resourceServer.UseRouting();
resourceServer.UseAuthentication();
resourceServer.UseAuthorization();
resourceServer.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
resourceServer.UseSwagger();
app.UseKnife4UI(c =>
{
c.RoutePrefix = ""; //http://localhost:5000/index.html#/home
c.SwaggerEndpoint("/resource-server/swagger/v1/swagger.json", "My API V1");
c.EnableDeepLinking();
c.OAuthClientId("test-id");
c.OAuthClientSecret("test-secret");
c.OAuthAppName("test-app");
c.OAuthScopeSeparator(" ");
c.OAuthUsePkce();
});
resourceServer.UseSwaggerUI(c =>
{
//http://localhost:5000/resource-server/swagger/index.html
c.SwaggerEndpoint("/resource-server/swagger/v1/swagger.json", "My API V1");
c.EnableDeepLinking();
// Additional OAuth settings (See https://github.com/swagger-api/swagger-ui/blob/v3.10.0/docs/usage/oauth2.md)
c.OAuthClientId("test-id");
c.OAuthClientSecret("test-secret");
c.OAuthAppName("test-app");
c.OAuthScopeSeparator(" ");
c.OAuthUsePkce();
});
});
}
}
}

View File

@@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Debug",
"System": "Information",
"Microsoft": "Information"
}
}
}

View File

@@ -0,0 +1,10 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*"
}

View File

@@ -0,0 +1 @@
{"KeyId":"08433185bdf583112d2df84b15adc5fe","Parameters":{"D":"G5yB2Ha3ktEmpXH+1qIuJ/dJ8TJKFAj2j1qMLl/Zu0jI1GZJlCyeWHEM0B0IFlbkUq2W82ym9dJUaSGXC3FxSZ/JHwNzgonpv9ELQZDnAwvO0kpnJTw+SQWwDxUxItGNT8giqqm5LcSYWhv+QexepyEOwKr6Efh6hHrTsjxk/2t3xPhZJbutifcBrAaHb0wjV/mw442wXol1cjWmG5qVOj54n16kU2Pxr1i9oukZBJMYLltg8NB99KgvZSVvOoctXiPszEHel4kNdSv0R3yt2lBad+OEZykCgzgbZYmA2aIvUmB8r+yxFUiXEDuM9Wjp3gxBLdAWAdutPhtcXqPXGw==","DP":"fVcFyftVuEEFERT6UzfnIhDW/2A1ckzwutVQAYOjRwC+FyRIGyN6kzc1SSIhpuMECcibvPBp0wbyx0JsUbcUVlo6uKXaYx5h6Qcv4fIrqfFOUfVNuQ2W8mltwpDdFuquwOAEz08zMtkVavZxfghmJgYNeqqQvvuvetjAuL6Tdws=","DQ":"whD6E1yg5fe3FDcq+cVQnj3xeSWB7pYURNIYAI2Oo1y2fCfH7/TDaQbGpIr0nO/RiH2SxH1vJ5O5foKG0JES3wCe7qflJOviOzb3SNlXm3gCdvNZyAruA2k9wyro5hauDMU51saed6xAy3dL+Q5fKE+0fmKOBxVZr2RPMnglTc0=","Exponent":"AQAB","InverseQ":"tQt+mEXZQq9YpAU6Q7/8gpPBy+F3B52KmP3e5bcnvilSDIXOUjye8NMTd8WeX+NmOje+g+5aYW5kVyiXhwoVM1Bz3Iwyq51Kix3JMmaiFBPaC/r9jF/2JveiT3iKlzgi0RJcOM4rnA1pHakr3rz6nVoxpBGe8OMUTrg4cVkM4HU=","Modulus":"y6F+tcOmGz6DAYfooFDMtkeG5/7TqOpW3U3eSu4KmuoCMdc8hiJwspcwu6Yu6OGVbYKW1nZTEP2SizpTixy6W/TYFMIQZC08iBxR8mDlp2HXeGiOTMFJQFdgT+Qe0xgZ9yPdOSbBmih6dATK61k4QvDbrhAf8j7mv7JW4s5T6WCjmx0UOyo9medzP/YhCL6lnaxP3hWA61ncVK38BGHFzo/sZSyA79EXo/091CKm1V0VpZMhpZIt6wyXPg8bLyJh1edXFCqEYDXvVcfuFFhmILfB/g5CUz/YCJu2Z1VDG4OOXPtg6xJMAbcl/67yH0wu/HSNoUcLN1cJkYXAUsg1yQ==","P":"6Tl+cOLjTwUiQbTiVtC4v2uO4FcjsuKbWlqKiqJ7veMJQHdP/AOPNtLq9eKrpKfc6f0eRQHYLf//o1ntweFFBhZ3lgCAdD3gQw02meMCxSPt+BMgylFkNZXW0TfISx/a3q4XGBSLTmu0d5uN3FNxRDNmAKeL8xgvb/ikKQSHnGM=","Q":"34Qr0kjiZkXvxHY6pxfBmzwYitC/bBwxwOrLq8S30FN5G2QQ/KWc7EJnOJz3C+yX9Vep2Fslu5xYAoBjbEofVMRlvsu2T4c+d/KfUQLaualXNlQiR4TKqW13Dau+ZEDEh71NFdJwqhKzJb8ojiRMcMHD1M5AmDApDUfqAOoybuM="}}