.NET9中Swagger平替Scalar使用詳解
本文分享Swagger中常用功能在Scalar中的使用,包括版本說(shuō)明、接口分類(lèi)、接口及參數(shù)描述、枚舉類(lèi)型、文件上傳和JWT認(rèn)證等,并提供相關(guān)代碼示例和效果展示,以及可能遇到的問(wèn)題和解決方案。
書(shū)接上回,上一章介紹了Swagger代替品Scalar,在使用中遇到不少問(wèn)題,今天單獨(dú)分享一下之前Swagger中常用的功能如何在Scalar中使用。
下面我們將圍繞文檔版本說(shuō)明、接口分類(lèi)、接口描述、參數(shù)描述、枚舉類(lèi)型、文件上傳、JWT認(rèn)證等方面詳細(xì)講解。

01、版本說(shuō)明
我們先來(lái)看看默認(rèn)添加后是什么樣子的。
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddOpenApi();
var app = builder.Build();
app.MapScalarApiReference();
app.MapOpenApi();
app.UseAuthorization();
app.MapControllers();
app.Run();
}效果如下:

我們可以直接修改builder.Services.AddOpenApi()這行代碼,修改這塊描述,代碼如下:
builder.Services.AddOpenApi(options =>
{
options.AddDocumentTransformer((document, context, cancellationToken) =>
{
document.Info = new()
{
Title = "訂單微服務(wù)",
Version = "v1",
Description = "訂單相關(guān)接口"
};
return Task.CompletedTask;
});
});我們?cè)賮?lái)看看效果。

02、接口分類(lèi)
通過(guò)上圖可以看到菜單左側(cè)排列著所有接口,現(xiàn)在我們可以通過(guò)Tags特性對(duì)接口進(jìn)行分類(lèi),如下圖我們把增刪改查4個(gè)方法分為冪等接口和非冪等接口兩類(lèi),如下圖:

然后我們看看效果,如下圖:

03、接口描述
之前使用Swagger我們都是通過(guò)生成的注釋XML來(lái)生成相關(guān)接口描述,現(xiàn)在則是通過(guò)編碼的方式設(shè)置元數(shù)據(jù)來(lái)生成相關(guān)描述。
可以通過(guò)EndpointSummary設(shè)置接口摘要,摘要不設(shè)置默認(rèn)為接口url,通過(guò)EndpointDescription設(shè)置接口描述,代碼如下:
//獲取
[HttpGet(Name = "")]
[Tags("冪等接口")]
[EndpointDescription("獲取訂單列表")]
public IEnumerable<Order> Get()
{
return null;
}
//刪除
[HttpDelete(Name = "{id}")]
[Tags("冪等接口")]
[EndpointSummary("刪除訂單")]
[EndpointDescription("根據(jù)訂單id,刪除相應(yīng)訂單")]
public bool Delete(string id)
{
return true;
}運(yùn)行效果如下:

04、參數(shù)描述
同樣可以通過(guò)Description特性來(lái)設(shè)置參數(shù)的描述,并且此特性可以直接作用于接口中參數(shù)之前,同時(shí)也支持作用于屬性上,可以看看下面示例代碼。
public class Order
{
[property: Description("創(chuàng)建日期")]
public DateOnly Date { get; set; }
[property: Required]
[property: DefaultValue(120)]
[property: Description("訂單價(jià)格")]
public int Price { get; set; }
[property: Description("訂單折扣價(jià)格")]
public int PriceF => (int)(Price * 0.5556);
[property: Description("商品名稱(chēng)")]
public string? Name { get; set; }
}
[HttpPut(Name = "{id}")]
[Tags("非冪等接口")]
public bool Put([Description("訂單Id")] string id, Order order)
{
return true;
}效果如下圖:

從上圖可以發(fā)現(xiàn)除了描述還有默認(rèn)值、必填項(xiàng)、可空等字樣,這些是通過(guò)其他元數(shù)據(jù)設(shè)置的,對(duì)于屬性還有以下元數(shù)據(jù)可以進(jìn)行設(shè)置。

05、枚舉類(lèi)型
對(duì)于枚舉類(lèi)型,我們正常關(guān)注兩個(gè)東西,其一為枚舉項(xiàng)以int類(lèi)型展示還是以字符串展示,其二為枚舉項(xiàng)顯示描述信息。
關(guān)于第一點(diǎn)比較簡(jiǎn)單只要對(duì)枚舉類(lèi)型使用JsonStringEnumConverter即可,代碼如下:
[JsonConverter(typeof(JsonStringEnumConverter<OrderStatus>))]
public enum OrderStatus
{
[Description("等待處理")]
Pending = 1,
[Description("處理中")]
Processing = 2,
[Description("已發(fā)貨")]
Shipped = 3,
[Description("已送達(dá)")]
Delivered = 4,
}效果如下:

通過(guò)上圖也可以引發(fā)關(guān)于第二點(diǎn)的需求,如何對(duì)每個(gè)枚舉項(xiàng)添加描述信息。
要達(dá)到這個(gè)目標(biāo)需要做兩件事,其一給每個(gè)枚舉項(xiàng)通過(guò)Description添加元數(shù)據(jù)定義,其二我們要修改文檔的數(shù)據(jù)結(jié)構(gòu)Schema。
修改builder.Services.AddOpenApi(),通過(guò)AddSchemaTransformer方法修改文檔數(shù)據(jù)結(jié)構(gòu),代碼如下:
options.AddSchemaTransformer((schema, context, cancellationToken) =>
{
//找出枚舉類(lèi)型
if (context.JsonTypeInfo.Type.BaseType == typeof(Enum))
{
var list = new List<IOpenApiAny>();
//獲取枚舉項(xiàng)
foreach (var enumValue in schema.Enum.OfType<OpenApiString>())
{
//把枚舉項(xiàng)轉(zhuǎn)為枚舉類(lèi)型
if (Enum.TryParse(context.JsonTypeInfo.Type, enumValue.Value, out var result))
{
//通過(guò)枚舉擴(kuò)展方法獲取枚舉描述
var description = ((Enum)result).ToDescription();
//重新組織枚舉值展示結(jié)構(gòu)
list.Add(new OpenApiString($"{enumValue.Value} - {description}"));
}
else
{
list.Add(enumValue);
}
}
schema.Enum = list;
}
return Task.CompletedTask;
});我們?cè)賮?lái)看看結(jié)果。

但是這也帶來(lái)了一個(gè)問(wèn)題,就是參數(shù)的默認(rèn)值也是添加描述的格式,顯然這樣的數(shù)據(jù)格式作為參數(shù)肯定是錯(cuò)誤的,因此我們需要自己注意,如下圖。目前我也沒(méi)有發(fā)現(xiàn)更好的方式即可以把每項(xiàng)枚舉描述加上,又不影響參數(shù)默認(rèn)值,有解決方案的希望可以不吝賜教。

06、文件上傳
下面我們來(lái)看看文件上傳怎么用,直接上代碼:
[HttpPost("upload/image")]
[EndpointDescription("圖片上傳接口")]
[DisableRequestSizeLimit]
public bool UploadImgageAsync(IFormFile file)
{
return true;
}然后我們測(cè)試一下效果。

首先我們可以看到請(qǐng)求示例中相關(guān)信息,這個(gè)相當(dāng)于告訴我們后面要怎么選擇文件上傳,我們繼續(xù)點(diǎn)擊Test Request。
首先請(qǐng)求體需要選擇multipart/form-data,上圖請(qǐng)求示例中已經(jīng)給出提示。

然后設(shè)置key為file,上圖請(qǐng)求示例中已經(jīng)給出提示,然后點(diǎn)擊File上傳圖片,最后點(diǎn)擊Send即可。

07、JWT認(rèn)證
最后我們來(lái)看看如何使用JWT認(rèn)證,
首先我們需要注入AddAuthentication及AddJwtBearer,具體代碼如下:
public class JwtSettingOption
{
//這個(gè)字符數(shù)量有要求,不能隨便寫(xiě),否則會(huì)報(bào)錯(cuò)
public static string Secret { get; set; } = "123456789qwertyuiopasdfghjklzxcb";
public static string Issuer { get; set; } = "asdfghjkkl";
public static string Audience { get; set; } = "zxcvbnm";
public static int Expires { get; set; } = 120;
public static string RefreshAudience { get; set; } = "zxcvbnm.2024.refresh";
public static int RefreshExpires { get; set; } = 10080;
}
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
//取出私鑰
var secretByte = Encoding.UTF8.GetBytes(JwtSettingOption.Secret);
options.TokenValidationParameters = new TokenValidationParameters()
{
//驗(yàn)證發(fā)布者
ValidateIssuer = true,
ValidIssuer = JwtSettingOption.Issuer,
//驗(yàn)證接收者
ValidateAudience = true,
ValidAudiences = new List<string> { JwtSettingOption.Audience, JwtSettingOption.Audience },
//驗(yàn)證是否過(guò)期
ValidateLifetime = true,
//驗(yàn)證私鑰
IssuerSigningKey = new SymmetricSecurityKey(secretByte),
ClockSkew = TimeSpan.FromHours(1), //過(guò)期時(shí)間容錯(cuò)值,解決服務(wù)器端時(shí)間不同步問(wèn)題(秒)
RequireExpirationTime = true,
};
});然后我們需要繼續(xù)修改builder.Services.AddOpenApi()這行代碼,在里面加上如下代碼:

其中BearerSecuritySchemeTransformer實(shí)現(xiàn)如下:
public sealed class BearerSecuritySchemeTransformer(IAuthenticationSchemeProvider authenticationSchemeProvider) : IOpenApiDocumentTransformer
{
public async Task TransformAsync(OpenApiDocument document, OpenApiDocumentTransformerContext context, CancellationToken cancellationToken)
{
var authenticationSchemes = await authenticationSchemeProvider.GetAllSchemesAsync();
if (authenticationSchemes.Any(authScheme => authScheme.Name == JwtBearerDefaults.AuthenticationScheme))
{
var requirements = new Dictionary<string, OpenApiSecurityScheme>
{
[JwtBearerDefaults.AuthenticationScheme] = new OpenApiSecurityScheme
{
Type = SecuritySchemeType.Http,
Scheme = JwtBearerDefaults.AuthenticationScheme.ToLower(),
In = ParameterLocation.Header,
BearerFormat = "Json Web Token"
}
};
document.Components ??= new OpenApiComponents();
document.Components.SecuritySchemes = requirements;
foreach (var operation in document.Paths.Values.SelectMany(path => path.Operations))
{
operation.Value.Security.Add(new OpenApiSecurityRequirement
{
[new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Id = JwtBearerDefaults.AuthenticationScheme,
Type = ReferenceType.SecurityScheme
}
}] = Array.Empty<string>()
});
}
}
}
}下面就可以通過(guò)[Authorize]開(kāi)啟接口認(rèn)證,并實(shí)現(xiàn)一個(gè)登錄接口獲取token用來(lái)測(cè)試。
[HttpPost("login")]
[EndpointDescription("登錄成功后生成token")]
[AllowAnonymous]
public string Login()
{
//登錄成功返回一個(gè)token
// 1.定義需要使用到的Claims
var claims = new[] { new Claim("UserId", "test") };
// 2.從 appsettings.json 中讀取SecretKey
var secretKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(JwtSettingOption.Secret));
// 3.選擇加密算法
var algorithm = SecurityAlgorithms.HmacSha256;
// 4.生成Credentials
var signingCredentials = new SigningCredentials(secretKey, algorithm);
var now = DateTime.Now;
var expires = now.AddMinutes(JwtSettingOption.Expires);
// 5.根據(jù)以上,生成token
var jwtSecurityToken = new JwtSecurityToken(
JwtSettingOption.Issuer, //Issuer
JwtSettingOption.Audience, //Audience
claims, //Claims,
now, //notBefore
expires, //expires
signingCredentials //Credentials
);
// 6.將token變?yōu)閟tring
var token = new JwtSecurityTokenHandler().WriteToken(jwtSecurityToken);
return token;
}下面我們先用登錄接口獲取一個(gè)token。

我們先用token調(diào)用接口,可以發(fā)現(xiàn)返回401。

然后我們把上面獲取的token放進(jìn)去,請(qǐng)求成功。

在這個(gè)過(guò)程中有可能會(huì)遇到一種情況:Auth Type后面的下拉框不可選,如下圖。

可能因以下原因?qū)е?,缺少[builder.Services.AddAuthentication().AddJwtBearer();]或[options.AddDocumentTransformer();]任意一行代碼。
注:測(cè)試方法代碼以及示例源碼都已經(jīng)上傳至代碼庫(kù),有興趣的可以看看。https://gitee.com/hugogoos/Planner
到此這篇關(guān)于.NET9中Swagger平替Scalar詳解(四)的文章就介紹到這了,更多相關(guān).NET9 Swagger平替Scalar內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
asp.net datalist綁定數(shù)據(jù)后可以上移下移實(shí)現(xiàn)示例
這篇文章主要介紹了asp.net datalist綁定數(shù)據(jù)后可以上移下移的示例代碼,需要的朋友可以參考下2014-02-02
深入解析.NET 許可證編譯器 (Lc.exe) 的原理與源代碼剖析
許可證編譯器 (Lc.exe) 的作用是讀取包含授權(quán)信息的文本文件,并產(chǎn)生一個(gè)可作為資源嵌入到公用語(yǔ)言運(yùn)行庫(kù)可執(zhí)行文件中的 .licenses 文件2013-07-07
AspNetPager分頁(yè)控件UrlRewritePattern參數(shù)設(shè)置的重寫(xiě)代碼
AspNetPager分頁(yè)控件UrlRewritePattern參數(shù)設(shè)置的重寫(xiě)代碼,需要的朋友可以參考一下2013-02-02
ASP.NET web.config中數(shù)據(jù)庫(kù)連接字符串connectionStrings節(jié)的配置方法
ASP.NET web.config中數(shù)據(jù)庫(kù)連接字符串connectionStrings節(jié)的配置方法,需要的朋友可以參考一下2013-05-05
.NET Core中創(chuàng)建和使用NuGet包的示例代碼
這篇文章主要介紹了.NET Core中創(chuàng)建和使用NuGet包的示例代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-04-04
JQuery實(shí)現(xiàn)Repeater無(wú)刷新批量刪除(附后臺(tái)asp.net源碼)
JQuery實(shí)現(xiàn)Repeater無(wú)刷新批量刪除(附后臺(tái)asp.net源碼) ,學(xué)習(xí)jquery的朋友可以參考下。2011-09-09

