想做蜗牛的蝎 發表於 2025-10-17 17:28:00

使用Scalar.AspNetCore来管理你的OpenApi

<p><span style="font-size: 16px">一直觉得很好的一个组件,网上介绍少得可怜,没办法,只有自己爬官网了,又是对照git又是看doc文档,总算是玩明白了,现在完全抛弃那个谁谁谁了。因人喜好各取所长吧</span></p>
<p><span style="font-size: 16px">先来官方参考地址:</span></p>
<p><span style="font-size: 16px">https://learn.microsoft.com/zh-cn/aspnet/core/fundamentals/openapi/include-metadata?view=aspnetcore-9.0&amp;tabs=minimal-apis</span></p>
<p><span style="font-size: 16px">这是scalar的.net 集成文档地址</span></p>
<p><span style="font-size: 16px">https://guides.scalar.com/scalar/scalar-api-references/integrations/net-aspnet-core/integration</span></p>
<p><span style="font-size: 16px">github地址</span></p>
<p><span style="font-size: 16px">https://github.com/scalar/scalar</span></p>
<p><span style="font-size: 16px">先放个图,诱惑一下,集成了很多主题,还可以自定主题(留给前端去玩吧)</span></p>
<p><img src="https://img2024.cnblogs.com/blog/43211/202510/43211-20251017160114922-1289434129.png" alt="01" width="965" height="482" loading="lazy"></p>
<p>&nbsp;</p>
<p><span style="font-size: 16px"><strong>一、简单使用</strong></span></p>
<p>1.建立一个API项目,(最小,mvc都可)</p>
<p>2.引用包</p>
<p>&nbsp;dotnet add package Scalar.AspNetCore&nbsp;(当前版本2.9.0)</p>
<div>&nbsp;dotnet add package Microsoft.AspNetCore.OpenApi(当前版本10.0)</div>
<p>3.添加引用</p>
<p><span class="hljs-keyword">using Scalar.AspNetCore;</span></p>
<p><span class="hljs-keyword">4.添加配置,在Program.cs中添加下面配置</span></p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">builder.Services.AddOpenApi();

</span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (app.Environment.IsDevelopment())
{
    app.MapOpenApi();
    app.MapScalarApiReference();
}</span></pre>
</div>
<p>现在运行一下,看看,localhost:xxxx/scalar</p>
<p>是不是看到列出漂亮的界面了?</p>
<p><strong><span style="font-size: 16px">二、基本配置</span></strong></p>
<p>1.自定义路由</p>
<p>不想使用/salar,可以换成自己的地址</p>
<div class="cnblogs_code">
<pre>app.MapScalarApiReference(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">/api-docs</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
app.MapScalarApiReference(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">/docs</span><span style="color: rgba(128, 0, 0, 1)">"</span>);</pre>
</div>
<p>2.多文当或版本控制</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Chain multiple documents</span>
app.MapScalarApiReference(options =&gt;<span style="color: rgba(0, 0, 0, 1)">
{
    options.AddDocument(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">v1</span><span style="color: rgba(128, 0, 0, 1)">"</span>, <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Production API</span><span style="color: rgba(128, 0, 0, 1)">"</span>, <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">api/v1/openapi.json</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">)
         .AddDocument(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">v2-beta</span><span style="color: rgba(128, 0, 0, 1)">"</span>, <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Beta API</span><span style="color: rgba(128, 0, 0, 1)">"</span>, <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">api/v2-beta/openapi.json</span><span style="color: rgba(128, 0, 0, 1)">"</span>, isDefault: <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">)
         .AddDocument(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">internal</span><span style="color: rgba(128, 0, 0, 1)">"</span>, <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Internal API</span><span style="color: rgba(128, 0, 0, 1)">"</span>, <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">internal/openapi.json</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
});</span></pre>
</div>
<pre>isDefault: true是默认打开的页面<br>3.自定义文档的默认调试语言</pre>
<div class="cnblogs_code">
<pre>app.MapScalarApiReference(options =&gt;<span style="color: rgba(0, 0, 0, 1)">
{
    options.WithDefaultHttpClient(ScalarTarget.CSharp, ScalarClient.HttpClient);
});</span></pre>
</div>
<p><img src="https://img2024.cnblogs.com/blog/43211/202510/43211-20251017162848040-1986574236.png" alt="02" width="744" height="242" loading="lazy"></p>
<p>它对应右边窗口的语言,基本上都支持,java,php,rust,py,swift</p>
<p><strong><span style="font-size: 16px">三、高级配置</span></strong></p>
<p>之前的老版本使用的硬编码option加配置,2.9.0以后,在界面右上角菜单栏上出现了一个编辑配置功能</p>
<p><img src="https://img2024.cnblogs.com/blog/43211/202510/43211-20251017163326465-796338346.png" alt="03" width="346" height="612" loading="lazy"></p>
<p>根据自己的喜好,调试编辑完配置文件后,可以复制到文件中单独保存,真是太贴心了</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">{
</span>"title": "Aquxa API Documentation"<span style="color: rgba(0, 0, 0, 1)">,
</span>"slug": "aquxa-api-documentation"<span style="color: rgba(0, 0, 0, 1)">,
</span>"hideClientButton": <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">,
</span>"servers"<span style="color: rgba(0, 0, 0, 1)">: [
    {
      </span>"url": "http://localhost:5215"<span style="color: rgba(0, 0, 0, 1)">,
      </span>"description": "Development server"<span style="color: rgba(0, 0, 0, 1)">
    }
],
</span>"showSidebar": <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">,
</span>"showToolbar": "localhost"<span style="color: rgba(0, 0, 0, 1)">,<span style="color: rgba(255, 0, 0, 1)">//这里特别说明一下,编辑完后,不想出现这个菜单栏,就在这里可以关闭showToolbar: "never"
</span></span>"operationTitleSource": "summary"<span style="color: rgba(0, 0, 0, 1)">,
</span>"theme": "solarized"<span style="color: rgba(0, 0, 0, 1)">,<span style="color: rgba(255, 0, 0, 1)">//主题可以自己选,喜欢哪个选哪个
</span></span>"_integration": "dotnet"<span style="color: rgba(0, 0, 0, 1)">,
</span>"persistAuth": <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">,
</span>"telemetry": <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">,
</span>"layout": "modern"<span style="color: rgba(0, 0, 0, 1)">,
</span>"isEditable": <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">,
</span>"isLoading": <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">,
</span>"hideModels": <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">,
</span>"documentDownloadType": "both"<span style="color: rgba(0, 0, 0, 1)">,
</span>"hideTestRequestButton": <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">,
</span>"hideSearch": <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">,
</span>"showOperationId": <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">,
</span>"hideDarkModeToggle": <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">,
</span>"favicon": "favicon.svg"<span style="color: rgba(0, 0, 0, 1)">,
</span>"withDefaultFonts": <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">,
</span>"defaultOpenAllTags": <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">,
</span>"expandAllModelSections": <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">,
</span>"expandAllResponses": <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">,
</span>"orderSchemaPropertiesBy": "alpha"<span style="color: rgba(0, 0, 0, 1)">,
</span>"orderRequiredPropertiesFirst": <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">,
</span>"url": "http://localhost:5215/openapi/v1.json"<span style="color: rgba(0, 0, 0, 1)">
}</span></pre>
</div>
<pre><span>PS:这里特别说明一下,编辑完后,不想出现这个菜单栏,就在这里可以关闭showToolbar: "never"</span><br>得到这个文件,保存到wwwroot/js/scalar-config.js,注意,一定要保存到能访问的静态目录里,并在program.cs添加静态目录的配置<br><code class="t-editor__inline-code"></code></pre>
<div class="cnblogs_code">
<pre>app.UseStaticFiles(). <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">这个要放在scalar配置的前面,不然访问不到</span></pre>
</div>
<p>添加配置文件加载</p>
<div class="cnblogs_code">
<pre>.WithJavaScriptConfiguration(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">/js/scalar-config.js</span><span style="color: rgba(128, 0, 0, 1)">"</span>)   </pre>
</div>
<p>&nbsp;</p>
<p><img src="https://img2024.cnblogs.com/blog/43211/202510/43211-20251017164201407-1694509419.png" alt="04" loading="lazy"></p>
<p>这里费了好大的劲,查官方,看代码,因为官方文档还是老文档,只是简单的概括了一下。最后整出来了</p>
<p><span style="font-size: 16px"><strong>四、文档的编辑</strong></span></p>
<p>使用最重要的还是API文档编辑,其实它完全用的标准的OpenApi,只要参考这个表就可以完全配置了</p>
<p><img src="https://img2024.cnblogs.com/blog/43211/202510/43211-20251017165047566-1432136520.png" alt="05" loading="lazy"></p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">
    </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">)]
   
    <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 为整个控制器添加标签</span>
    <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> AdminController : ControllerBase
    {
      
      </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> IActionResult ReloadCache()
      {
            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 模拟重新加载缓存的操作</span>
            <span style="color: rgba(0, 0, 255, 1)">return</span> Ok(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Cache reloaded successfully</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
      }
      
      
      </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> IActionResult GetStats()
      {
            </span><span style="color: rgba(0, 0, 255, 1)">return</span> Ok(<span style="color: rgba(0, 0, 255, 1)">new</span> { Users = <span style="color: rgba(128, 0, 128, 1)">100</span>, Requests = <span style="color: rgba(128, 0, 128, 1)">1000</span><span style="color: rgba(0, 0, 0, 1)"> });
      }
    }</span></pre>
</div>
<p>下面说一下常用的特性</p>
<p>1.API分组</p>
<div>
<div class="cnblogs_code">
<pre>    </pre>
</div>
<p>这个比较熟悉,它可以分组,分版本,当你分好版本后/,会在scalar中左上角可以选择,当然,你也可以把它做为组来用</p>
<p><img src="https://img2024.cnblogs.com/blog/43211/202510/43211-20251017165845787-1124928420.png" alt="06" loading="lazy"></p>
<p>如果有不想显示的API也可以用来排除显示</p>
<div class="cnblogs_code">
<pre>

</span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> IActionResult PrivateEndpoint() {
    </span><span style="color: rgba(0, 0, 255, 1)">return</span> Ok(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">This is a private endpoint</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
}</span></pre>
</div>
<p>2.API分类</p>
<div class="cnblogs_code">
<pre></pre>
</div>
<p>分类的API,会归档在一起,方便查询,这样看起来没有那么乱了</p>
<div class="cnblogs_code">
<pre>)]

</span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> IResult Attributes()
{
    </span><span style="color: rgba(0, 0, 255, 1)">return</span> Results.Ok(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Hello world!</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
}</span></pre>
</div>
<p><img src="https://img2024.cnblogs.com/blog/43211/202510/43211-20251017170534464-553519957.png" alt="07" loading="lazy"></p>
<p>3.描述</p>
<div class="cnblogs_code">
<pre>


</span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> IResult Attributes()
{
    </span><span style="color: rgba(0, 0, 255, 1)">return</span> Results.Ok(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Hello world!</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
}</span></pre>
</div>
<p><img src="https://img2024.cnblogs.com/blog/43211/202510/43211-20251017170839444-1940993122.png" alt="08" loading="lazy"></p>
<p>4.过滤</p>
<p>不想显示的接口可以用</p>
<p>上面说的</p>
<pre><span>来关闭<br>还有一个就是根目录,如果在配置文件中有MapGet,</span></span>可以使用.ExcludeFromDescription();排除显示</pre>
<pre><span><span>&nbsp;</span></span></pre>
<div class="cnblogs_Highlighter">
<pre class="brush:csharp;gutter:true;">    // 默认打开首页
    app.MapGet("/", () =&gt; "Hangfire 服务运行中。访问 /hangfire 查看仪表盘,访问 /docs 查看API文档").ExcludeFromDescription();
//可以使用.ExcludeFromDescription();排除显示
</pre>
</div>
<p>  </p>
<pre><span><span><br><br></span></span></pre>
<p>更多编辑文档就看这里吧</p>
<p>https://learn.microsoft.com/zh-cn/aspnet/core/fundamentals/openapi/include-metadata?view=aspnetcore-9.0&amp;tabs=controllers</p>
<p><strong><span style="font-size: 16px">五、认证授权</span></strong></p>
<p>这里就使用自己的授权就可以,这里就偷懒找AI完成了。参考部分都有备注</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> Scalar.AspNetCore;
</span><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> Microsoft.AspNetCore.Authentication;
</span><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> Microsoft.Extensions.Options;
</span><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> System.Security.Claims;
</span><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> System.Text.Encodings.Web;
</span><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> Microsoft.AspNetCore.Mvc;
</span><span style="color: rgba(0, 0, 255, 1)">using</span> MyWebApi; <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 添加对WeatherForecast的引用</span>

<span style="color: rgba(0, 0, 255, 1)">var</span> builder =<span style="color: rgba(0, 0, 0, 1)"> WebApplication.CreateBuilder(args);

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Add services to the container.
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Learn more about configuring OpenAPI at </span><span style="color: rgba(0, 128, 0, 1); text-decoration: underline">https://aka.ms/aspnet/openapi</span>
builder.Services.AddOpenApi(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">v1</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
builder.Services.AddOpenApi(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">v2</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 添加控制器服务</span>
<span style="color: rgba(0, 0, 0, 1)">builder.Services.AddControllers();

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 添加身份验证服务</span>
builder.Services.AddAuthentication(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">BasicAuthentication</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">)
    .AddScheme</span>&lt;AuthenticationSchemeOptions, BasicAuthenticationHandler&gt;(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">BasicAuthentication</span><span style="color: rgba(128, 0, 0, 1)">"</span>, <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">);

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 添加授权服务</span>
builder.Services.AddAuthorization(options =&gt;<span style="color: rgba(0, 0, 0, 1)">
{
    options.AddPolicy(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">ScalarAccess</span><span style="color: rgba(128, 0, 0, 1)">"</span>, policy =&gt;<span style="color: rgba(0, 0, 0, 1)"> policy.RequireAuthenticatedUser());
});

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 配置服务器URL,避免端口冲突</span>
builder.WebHost.UseUrls(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">http://localhost:5215</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 0, 255, 1)">var</span> app =<span style="color: rgba(0, 0, 0, 1)"> builder.Build();

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Configure static file middleware to serve the JavaScript config file</span>
<span style="color: rgba(0, 0, 0, 1)">app.UseStaticFiles();

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 添加身份验证和授权中间件</span>
<span style="color: rgba(0, 0, 0, 1)">app.UseAuthentication();
app.UseAuthorization();

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Configure the HTTP request pipeline.</span>
<span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (app.Environment.IsDevelopment())
{
    app.MapOpenApi();
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Add Scalar for API management with JavaScript configuration and authorization</span>
    app.MapScalarApiReference(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">/scalar</span><span style="color: rgba(128, 0, 0, 1)">"</span>, options =&gt;<span style="color: rgba(0, 0, 0, 1)">
    {
      options.WithTitle(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">MyWebApi</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">)
               .WithJavaScriptConfiguration(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">/js/scalar-config.js</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">)               
               .AddDocument(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">v1</span><span style="color: rgba(128, 0, 0, 1)">"</span>, <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Aquxa API Documentation</span><span style="color: rgba(128, 0, 0, 1)">"</span>,isDefault: <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">)
               .AddDocument(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">v2</span><span style="color: rgba(128, 0, 0, 1)">"</span>, <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Beta API</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
    })
    .RequireAuthorization(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">ScalarAccess</span><span style="color: rgba(128, 0, 0, 1)">"</span>); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 应用授权策略</span>
<span style="color: rgba(0, 0, 0, 1)">}

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 添加控制器路由</span>
<span style="color: rgba(0, 0, 0, 1)">app.MapControllers();

app.Run();

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Basic Authentication Handler</span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span> BasicAuthenticationHandler : AuthenticationHandler&lt;AuthenticationSchemeOptions&gt;<span style="color: rgba(0, 0, 0, 1)">
{
    </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> BasicAuthenticationHandler(
      IOptionsMonitor</span>&lt;AuthenticationSchemeOptions&gt;<span style="color: rgba(0, 0, 0, 1)"> options,
      ILoggerFactory logger,
      UrlEncoder encoder)
      : </span><span style="color: rgba(0, 0, 255, 1)">base</span><span style="color: rgba(0, 0, 0, 1)">(options, logger, encoder)
    {
    }

    </span><span style="color: rgba(0, 0, 255, 1)">protected</span> <span style="color: rgba(0, 0, 255, 1)">override</span> <span style="color: rgba(0, 0, 255, 1)">async</span> Task&lt;AuthenticateResult&gt;<span style="color: rgba(0, 0, 0, 1)"> HandleAuthenticateAsync()
    {
      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 检查是否有Authorization头</span>
      <span style="color: rgba(0, 0, 255, 1)">if</span> (!Request.Headers.ContainsKey(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Authorization</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">))
            </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> AuthenticateResult.NoResult();

      </span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)">
      {
            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 解析Basic认证头</span>
            <span style="color: rgba(0, 0, 255, 1)">var</span> authHeader = Request.Headers[<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Authorization</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">].ToString();
            </span><span style="color: rgba(0, 0, 255, 1)">if</span> (!authHeader.StartsWith(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Basic </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">))
                </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> AuthenticateResult.NoResult();

            </span><span style="color: rgba(0, 0, 255, 1)">var</span> encodedCredentials = authHeader.Substring(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Basic </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">.Length).Trim();
            </span><span style="color: rgba(0, 0, 255, 1)">var</span> decodedCredentials =<span style="color: rgba(0, 0, 0, 1)"> System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(encodedCredentials));
            </span><span style="color: rgba(0, 0, 255, 1)">var</span> credentials = decodedCredentials.Split(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">:</span><span style="color: rgba(128, 0, 0, 1)">'</span>, <span style="color: rgba(128, 0, 128, 1)">2</span><span style="color: rgba(0, 0, 0, 1)">);
            
            </span><span style="color: rgba(0, 0, 255, 1)">var</span> username = credentials[<span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)">];
            </span><span style="color: rgba(0, 0, 255, 1)">var</span> password = credentials[<span style="color: rgba(128, 0, 128, 1)">1</span><span style="color: rgba(0, 0, 0, 1)">];

            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 验证用户名和密码(这里使用硬编码,实际应用中应从配置或数据库获取)</span>
            <span style="color: rgba(0, 0, 255, 1)">if</span> (username == <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">admin</span><span style="color: rgba(128, 0, 0, 1)">"</span> &amp;&amp; password == <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">password123</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">)
            {
                </span><span style="color: rgba(0, 0, 255, 1)">var</span> claims = <span style="color: rgba(0, 0, 255, 1)">new</span>[] { <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Claim(ClaimTypes.Name, username) };
                </span><span style="color: rgba(0, 0, 255, 1)">var</span> identity = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> ClaimsIdentity(claims, Scheme.Name);
                </span><span style="color: rgba(0, 0, 255, 1)">var</span> principal = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> ClaimsPrincipal(identity);
                </span><span style="color: rgba(0, 0, 255, 1)">var</span> ticket = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> AuthenticationTicket(principal, Scheme.Name);
               
                </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> AuthenticateResult.Success(ticket);
            }
            
            </span><span style="color: rgba(0, 0, 255, 1)">return</span> AuthenticateResult.Fail(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Invalid username or password</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
      }
      </span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)">
      {
            </span><span style="color: rgba(0, 0, 255, 1)">return</span> AuthenticateResult.Fail(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Invalid Authorization Header</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
      }
    }
   
    </span><span style="color: rgba(0, 0, 255, 1)">protected</span> <span style="color: rgba(0, 0, 255, 1)">override</span> <span style="color: rgba(0, 0, 255, 1)">async</span><span style="color: rgba(0, 0, 0, 1)"> Task HandleChallengeAsync(AuthenticationProperties properties)
    {
      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 发送WWW-Authenticate头以触发浏览器的认证对话框</span>
      Response.Headers[<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">WWW-Authenticate</span><span style="color: rgba(128, 0, 0, 1)">"</span>] = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Basic realm=\"Scalar API Documentation\"</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">;
      </span><span style="color: rgba(0, 0, 255, 1)">await</span> <span style="color: rgba(0, 0, 255, 1)">base</span><span style="color: rgba(0, 0, 0, 1)">.HandleChallengeAsync(properties);
    }
}</span></pre>
</div>
<p>&nbsp;<img src="https://img2024.cnblogs.com/blog/43211/202510/43211-20251017172606610-102964762.png" alt="09" loading="lazy"></p>
<p>&nbsp;</p>
</div>
<pre></pre><br><br>
来源:https://www.cnblogs.com/wangbin5542/p/19148516
頁: [1]
查看完整版本: 使用Scalar.AspNetCore来管理你的OpenApi