Links
ASP.NET Core 9 integration
|
dotnet add package Scalar.AspNetCore
|
Program.cs
|
builder.Services.AddOpenApi();
if (app.Environment.IsDevelopment())
{
app.MapOpenApi();
// add the following lines
app.MapScalarApiReference(options =>
{
options
.WithTitle("MyApp API Reference")
.WithTheme(ScalarTheme.Solarized)
.WithClientButton(false)
.WithDocumentDownloadType(DocumentDownloadType.Json);
//.WithDefaultOpenAllTags(true)
});
}
|
URL: http://localhost:5000/scalar http://localhost:5000/openapi/v1.json
Properties/launchSettings.json
|
{
"profiles": {
"http": {
"launchBrowser": true,
"launchUrl": "http://localhost:5000/scalar",
"applicationUrl": "http://localhost:5000"
}
}
}
|
Configure OpenApi
OpenApiConfiguration.cs
|
public static class OpenApiConfiguration
{
public static IMvcBuilder ConfigureOpenApi(this IMvcBuilder builder)
{
builder.Services.AddOpenApi();
return builder;
}
public static void MapScalar(this WebApplication app)
{
if (!app.Environment.IsProduction())
{
app.MapOpenApi();
app.MapScalarApiReference(options =>
{
options
.WithTitle("My App API Reference")
.WithTheme(ScalarTheme.Solarized)
.WithClientButton(false)
.WithDocumentDownloadType(DocumentDownloadType.Json);
});
}
}
}
|
Program.cs
|
builder.Services.ConfigureOpenApi();
app.MapScalar();
|
Customize the document
|
builder.Services.AddOpenApi(options =>
{
options.AddDocumentTransformer<CustomOpenApiDocumentTransformer>();
});
private sealed class CustomOpenApiDocumentTransformer : IOpenApiDocumentTransformer
{
public Task TransformAsync(
OpenApiDocument document,
OpenApiDocumentTransformerContext context,
CancellationToken cancellationToken)
{
document.Info = new()
{
Title = "My App",
Version = $"{context.DocumentName}.0.0",
Description = "My description"
};
// remove the servers property
document.Servers = [];
document.Servers = document.Servers
.Select(server =>
{
server.Url = server.Url.Replace("http:", "https:");
return server;
})
.ToList();
return Task.CompletedTask;
}
}
|
Handle Enum as String
|
builder.Services.AddOpenApi(options =>
{
options.AddSchemaTransformer<EnumAsStringSchemaTransformer>();
});
private sealed class EnumAsStringSchemaTransformer : IOpenApiSchemaTransformer
{
public Task TransformAsync(
OpenApiSchema schema,
OpenApiSchemaTransformerContext context,
CancellationToken cancellationToken)
{
if (context.JsonTypeInfo.Type.IsEnum)
{
schema.Type = "string";
schema.Enum.Clear();
foreach (var name in Enum.GetNames(context.JsonTypeInfo.Type))
schema.Enum.Add(new OpenApiString(name));
}
return Task.CompletedTask;
}
}
|
OAuth2
OpenApiConfiguration.cs
|
public static class OpenApiConfiguration
{
private const string oAuth2Scheme = "OAuth2";
public static IMvcBuilder ConfigureOpenApi(this IMvcBuilder builder)
{
builder.Services.AddOpenApi(options =>
{
options.AddDocumentTransformer<OAuth2OpenApiDocumentTransformer>();
});
}
public static void MapScalar(this WebApplication app, AzureAdConfiguration configuration)
{
app.MapScalarApiReference(options =>
{
options
.AddPreferredSecuritySchemes(oAuth2Scheme)
.AddAuthorizationCodeFlow(oAuth2Scheme, flow =>
{
flow.ClientId = configuration.SwaggerClientId;
flow.Pkce = Pkce.Sha256;
flow.SelectedScopes = [$"api://{configuration.ClientId}/ReadWrite.All"];
})
.WithPersistentAuthentication(true):
});
}
}
|
OAuth2OpenApiDocumentTransformer.cs
|
public class OAuth2OpenApiDocumentTransformer(IConfiguration configuration) : IOpenApiDocumentTransformer
{
public Task TransformAsync(
OpenApiDocument document,
OpenApiDocumentTransformerContext context,
CancellationToken cancellationToken)
{
var azureAdConfiguration = configuration.Get<MyAppConfiguration>().AzureAd;
document.Components ??= new OpenApiComponents();
document.Components.SecuritySchemes.Add(oAuth2Scheme, new OpenApiSecurityScheme
{
Type = SecuritySchemeType.OAuth2,
Flows = new OpenApiOAuthFlows
{
AuthorizationCode = new OpenApiOAuthFlow
{
AuthorizationUrl = new Uri($"https://login.microsoftonline.com/{azureAdConfiguration.TenantId}/oauth2/v2.0/authorize"),
TokenUrl = new Uri($"https://login.microsoftonline.com/{azureAdConfiguration.TenantId}/oauth2/v2.0/token"),
Scopes = new Dictionary<string, string>
{
{ $"api://{azureAdConfiguration.ClientId}/ReadWrite.All", "API access" }
}
}
}
});
return Task.CompletedTask;
}
}
|
Multiple versions
OpenApiConfiguration.cs
|
public static class OpenApiConfiguration
{
public static IMvcBuilder ConfigureOpenApi(this IMvcBuilder builder)
{
builder.Services.AddOpenApi("v1");
builder.Services.AddOpenApi("v2");
return builder;
}
}
|
Program.cs
|
app.MapScalarApiReference(options =>
{
options
.AddDocument("v1", "Version 1")
.AddDocument("v2", "Version 2");
});
|