本文說明如何在 ASP.NET Core 應用程式中加入 OpenAPI 元資料。
包含 OpenAPI 中的端點元數據
ASP.NET 從網頁應用程式的端點收集元資料,並用來產生 OpenAPI 文件。
在控制器型應用程式中,當控制器具有[EndpointDescription]時,會從[HttpPost]、[Produces]和[ApiController]等屬性中收集元數據。
在最小 API 中,元資料可從屬性收集,但也可透過擴充方法及其他策略(如從路由處理器回傳 TypedResults )設定。
下表提供所收集中繼資料的概覽,並說明設定中繼資料的策略。
| Metadata | Attribute | 擴展方式 | 其他策略 |
|---|---|---|---|
| 摘要 | [EndpointSummary] |
WithSummary | |
| description | [EndpointDescription] |
WithDescription | |
| tags | [Tags] |
WithTags | |
| operationId | [EndpointName] |
WithName | |
| parameters |
[FromQuery]、 、 [FromRoute]、 [FromHeader][FromForm] |
||
| 參數說明 | [Description] |
||
| requestBody | [FromBody] |
Accepts | |
| responses | [Produces] |
Produces、ProducesProblem | TypedResults |
| 排除端點 |
[ExcludeFromDescription]、[ApiExplorerSettings] |
ExcludeFromDescription |
ASP.NET Core 也能從 XML 文件註解中收集元資料。 欲了解更多資訊,請參閱 ASP.NET Core 中的 ASP.NET Core OpenAPI XML 文件註解支援詳細資訊。
下列各節示範如何在應用程式中包含中繼資料,以自訂產生的 OpenAPI 文件。
摘要和描述
端點摘要與描述可以使用屬性[EndpointSummary] 和 [EndpointDescription] 設定,或在 Minimal API 中使用擴充方法WithSummary 和 WithDescription 設定。
下列範例示範設定摘要和描述的不同策略。
請注意,屬性置放在委派方法上,而不是放在 app.MapGet 方法上。
app.MapGet("/extension-methods", () => "Hello world!")
.WithSummary("This is a summary.")
.WithDescription("This is a description.");
app.MapGet("/attributes",
[EndpointSummary("This is a summary.")]
[EndpointDescription("This is a description.")]
() => "Hello world!");
tags
OpenAPI 支援將每個端點上的標籤指定為分類形式。
在最小 API 中,標籤可以透過[Tags]屬性或WithTags擴充方法來設定。
下列範例示範設定標記的不同策略。
app.MapGet("/extension-methods", () => "Hello world!")
.WithTags("todos", "projects");
app.MapGet("/attributes",
[Tags("todos", "projects")]
() => "Hello world!");
operationId
OpenAPI 支援每個端點上的 operationId 作為作業的唯一識別碼或名稱。
在 Minimal API 中,操作 ID 可以透過[EndpointName]屬性或WithName擴充方法來設定。
下列範例示範設定 operationId 的不同策略。
app.MapGet("/extension-methods", () => "Hello world!")
.WithName("FromExtensionMethods");
app.MapGet("/attributes",
[EndpointName("FromAttributes")]
() => "Hello world!");
parameters
OpenAPI 支援 API 所使用的標註路徑、查詢字串、標頭和 cookie 參數。
架構會根據路由處理常式的簽章,自動推斷要求參數的型別。
[Description] 屬性可用來提供參數的描述。
下列範例示範如何設定參數的描述。
app.MapGet("/attributes",
([Description("This is a description.")] string name) => "Hello world!");
描述請求主體
OpenAPI 中的 requestBody 欄位描述了 API 用戶端可傳送給伺服器的請求內容,包括支援的內容類型及內容結構。
當端點處理方法接受從請求主體綁定的參數時,ASP.NET Core 會為 OpenAPI 文件中的操作產生對應的 requestBody。 您也可以使用屬性或擴充方法來指定要求主體的元數據。 可以使用 文件轉換器 或 作業轉換器來設定其他中繼資料。
如果端點沒有定義任何綁定到請求主體的參數,而是直接從 HttpContext 取用請求主體,ASP.NET Core 就會提供機制來指定請求主體的元資料。 處理請求主體為資料流的端點常見案例是這樣的。
某些請求正文的元數據可以由路由處理器方法中的FromBody或FromForm參數來確定。
您可以在參數的[Description]屬性上,使用FromBody或FromForm來設定請求本文的描述。
如果FromBody參數不可為 Null,且EmptyBodyBehavior在Allow屬性中未設定為FromBody,則要求的本文是必需的,且在產生的 OpenAPI 文件中,required的requestBody欄位會設定為true。
表單內容始終是必需的,並且已將 required 設定為 true。
使用 文件轉換器 或 作業轉換器 來設定 example、 examples或 encoding 欄位,或在產生的 OpenAPI 文件中新增要求內文的規格延伸。
設定要求本文元數據的其他機制取決於所開發的應用程式類型,如下幾節所述。
所產生的 OpenAPI 文件中,請求主體的內容類型是由綁定到請求主體的參數類型或使用 Accepts 擴充方法指定的參數類型決定的。
預設情況下,參數的內容類型 FromBody 為 , application/json 參數的內容類型 FromForm 為 multipart/form-dataapplication/x-www-form-urlencoded或 。
這些預設內容類型的支援內建於基本 API 中,而其他內容類型可以使用自定義系結來處理。 如需詳細資訊,請參閱最小 API 文件的 自訂繫結 主題。
可以指定請求主體的內容類型的幾種不同方法。
如果 FromBody 參數的類型實作了 IEndpointParameterMetadataProvider,ASP.NET Core 會使用此介面來決定請求主體中的內容類型。
框架利用 PopulateMetadata 此介面的方法來設定請求內容類型以及請求正文內容的類型。 例如,接收Todo 或 application/xml 內容類型的text/xml 類別,可使用IEndpointParameterMetadataProvider 將此資訊提供給架構。
public class Todo : IEndpointParameterMetadataProvider
{
public static void PopulateMetadata(
ParameterInfo parameter,
EndpointBuilder builder)
{
builder.Metadata.Add(
new AcceptsMetadata(
["application/xml", "text/xml"],
typeof(Todo)
)
);
}
}
Accepts 擴充方法也可以用來指定請求正文的內容類型。
在下列範例中,端點會在請求主體中接受 Todo 物件,並且期待內容型別為 application/xml。
app.MapPut("/todos/{id}", (int id, Todo todo) => ...)
.Accepts<Todo>("application/xml");
由於 application/xml 不是內建內容類型,因此 類別 Todo 必須實 IBindableFromHttpContext<TSelf> 作 介面,以提供要求主體的自定義系結。 例如:
public class Todo : IBindableFromHttpContext<Todo>
{
public static async ValueTask<Todo?> BindAsync(
HttpContext context,
ParameterInfo parameter)
{
var xmlDoc = await XDocument.LoadAsync(context.Request.Body, LoadOptions.None, context.RequestAborted);
var serializer = new XmlSerializer(typeof(Todo));
return (Todo?)serializer.Deserialize(xmlDoc.CreateReader());
}
}
如果端點未定義系結至要求本文的任何參數,請使用 Accepts 擴充方法指定端點接受的內容類型。
如果您指定 Accepts 多次,則只會使用最後一個的元數據-- 它們不會合併。
描述回應類型
OpenAPI 支援提供從 API 傳回的回應描述。 ASP.NET Core 提供多種策略來設定端點的回應元資料。 可設定的回應元資料包括狀態碼、回應主體的類型,以及回應的內容類型。 OpenAPI 中的回應可能會有其他元數據,例如描述、標頭、連結和範例。 可以使用 文件轉換器 或 作業轉換器來設定此額外的中繼資料。
設定回應元數據的特定機制取決於正在開發的應用程式類型。
在 Minimal API 應用程式中,ASP.NET Core 可以擷取端點擴充方法新增的回應元資料、路由處理器上的屬性,以及路由處理器的回傳型別。
- Produces擴充方法可在端點上指定狀態碼、回應體型態及端點回應的內容類型。
-
[ProducesResponseType]或 ProducesResponseTypeAttribute<T> 屬性可用來指定響應主體的類型。 - 路由處理器可用來回傳一個型別,該型別實作 IEndpointMetadataProvider 以指定回應體的型別與內容型別。
- ProducesProblem端點上的擴充方法可用來指定錯誤回應的狀態碼與內容類型。
請注意,Produces 和 ProducesProblem 擴充方法於 RouteHandlerBuilder 和 RouteGroupBuilder 都受支援。 例如,這允許針對群組中的所有作業定義一組常見的錯誤回應。
未由上述其中一個策略指定時,:
- 回應的狀態代碼預設為 200。
- 回應主體的架構可以從端點方法的隱含或明確傳回類型推斷,例如,從
T中 Task<TResult>推斷,否則會被視為未指定。 - 指定或推斷之響應主體的內容類型為 「application/json」。。
在「Minimal API」中,Produces 擴充方法和 [ProducesResponseType] 屬性只會設定端點的回應中繼資料。 它們不會修改或限制端點的行為,其可能會傳回與元數據指定的狀態代碼或響應主體類型不同的狀態代碼或響應主體類型,而且內容類型是由路由處理程式方法的傳回類型所決定,而不論屬性或擴充方法中指定的任何內容類型為何。
擴充 Produces 方法可以指定端點的回應類型,其預設狀態代碼為 200,預設內容類型為 application/json。 下面這個範例可說明這點:
app.MapGet("/todos", async (TodoDb db) => await db.Todos.ToListAsync())
.Produces<IList<Todo>>();
[ProducesResponseType]可用來將回應元數據新增至端點。 請注意,屬性是套用在路由處理方法上,而不是應用在建立路由的方法調用上,如下列範例所示:
app.MapGet("/todos",
[ProducesResponseType<List<Todo>>(200)]
async (TodoDb db) => await db.Todos.ToListAsync());
[ProducesResponseType]、 [Produces]和 [ProducesDefaultResponseType] 也支援稱為 的選擇性字串屬性 Description ,可用來描述回應。 這適用於說明客戶端預期特定回應的原因或時機:
app.MapGet("/todos/{id}",
[ProducesResponseType<Todo>(200,
Description = "Returns the requested Todo item.")]
[ProducesResponseType(404, Description = "Requested item not found.")]
[ProducesDefault(Description = "Undocumented status code.")]
async (int id, TodoDb db) => /* Code here */);
在實現端點路由處理器時使用 TypedResults,會自動包含端點回應類型的中繼資料。 例如,下列程式碼會自動以具有 200 內容型別的 application/json 狀態碼的回應來註釋端點。
app.MapGet("/todos", async (TodoDb db) =>
{
var todos = await db.Todos.ToListAsync();
return TypedResults.Ok(todos);
});
只有那些實作 IEndpointMetadataProvider 的傳回型別會在 OpenAPI 文件中建立一個 responses 條目。 以下是產生TypedResults項目之responses一些輔助方法的部分清單:
TypedResults 輔助方法 |
狀態碼 |
|---|---|
| Ok() | 200 |
| Created() | 201 |
| CreatedAtRoute() | 201 |
| Accepted() | 202 |
| AcceptedAtRoute() | 202 |
| NoContent() | 204 |
| BadRequest() | 400 |
| ValidationProblem() | 400 |
| NotFound() | 404 |
| Conflict() | 409 |
| UnprocessableEntity() | 422 |
| 檔案() | 200 |
所有這些方法,除了 NoContent 之外,都有一個可指定響應主體類型的泛型多載。
您可以實作 類別來設定端點元數據,並從路由處理程式傳回它。
描述二進位檔案回應
要描述 OpenAPI 文件中回傳二進位檔案回應的端點,請使用 Produces extension 方法,以 type FileContentResult 參數指定回應類型與內容類型:
app.MapPost("/filecontentresult", () =>
{
var content = "This endpoint returns a FileContentResult!"u8.ToArray();
return TypedResults.File(content);
})
.Produces<FileContentResult>(contentType: MediaTypeNames.Application.Octet);
這會產生一個具有 type: string 和 format: binary 的 FileContentResult 類型的 OpenAPI 架構。
產生的 OpenAPI 文件描述端點回應如下:
responses:
'200':
description: OK
content:
application/octet-stream:
schema:
$ref: '#/components/schemas/FileContentResult'
其中 FileContentResult 定義為 components/schemas :
components:
schemas:
FileContentResult:
type: string
format: binary
設定 ProblemDetails 的回應
為可能傳回 ProblemDetails 回應的端點設定回應類型時,可以使用下列專案來新增端點的適當回應元數據:
- ProducesProblem
- ProducesValidationProblem 擴充方法。
- TypedResults 的狀態代碼在(400-499)範圍內。
欲了解更多如何配置最小 API 應用程式以回傳 ProblemDetails 回應的資訊,請參見 Handle errors in ASP.NET Core APIs。
多個回應型別
如果端點可以在不同案例中傳回不同的回應型別,您可以透過下列方式提供中繼資料:
多次調用 Produces 擴充方法,如下列範例所示:
app.MapGet("/api/todoitems/{id}", async (int id, TodoDb db) => await db.Todos.FindAsync(id) is Todo todo ? Results.Ok(todo) : Results.NotFound()) .Produces<Todo>(StatusCodes.Status200OK) .Produces(StatusCodes.Status404NotFound);在簽章中使用 Results<TResult1,TResult2,TResult3,TResult4,TResult5,TResult6>,處理常式主體中使用 TypedResults,如下列範例所示:
app.MapGet("/book/{id}", Results<Ok<Book>, NotFound> (int id, List<Book> bookList) => { return bookList.FirstOrDefault((i) => i.Id == id) is Book book ? TypedResults.Ok(book) : TypedResults.NotFound(); });Results<TResult1,TResult2,TResultN>聯合類型聲明路由處理程序會返回多個實作IResult的具體類型,其中任何實作IEndpointMetadataProvider的類型都將為端點的中繼資料做出貢獻。聯合型別會實作隱含轉換運算子。 這些運算子可讓編譯器自動將泛型引數中指定的型別轉換為聯合型別的實例。 此功能的額外優點是提供編譯時檢查,以確保路由處理常式僅傳回它宣告的結果。 嘗試傳回未宣告為其中一個泛型引數的型別至
Results<TResult1,TResult2,TResultN>會產生編譯錯誤。
從生成的文件中排除端點
根據預設,應用程式中定義的所有端點都會記錄在產生的 OpenAPI 檔案中,但可以使用屬性或擴充方法從檔中排除端點。
指定應排除之端點的機制取決於所開發的應用程式類型。
最小 API 支援從 OpenAPI 檔排除指定端點的兩種策略:
下列範例示範從產生的 OpenAPI 文件中排除指定端點的不同策略。
app.MapGet("/extension-method", () => "Hello world!")
.ExcludeFromDescription();
app.MapGet("/attributes",
[ExcludeFromDescription]
() => "Hello world!");
包含用於數據類型的 OpenAPI 元數據
要求或回應主體中使用的 C# 類別或記錄會以所產生 OpenAPI 文件的結構描表示。
預設情況下,架構中只 public 表示屬性,但也可 JsonSerializerOptions 為欄位建立結構屬性。
當 PropertyNamingPolicy 設定為駝峰箱(這是 ASP.NET 網頁應用程式的預設值),結構中的屬性名稱即為類別或記錄屬性名稱的駝峰箱形式。
[JsonPropertyName] 可用於個別屬性,以指定結構描述中的屬性名稱。
類型和格式
數值類型
JSON 架構庫會將標準 C# 數值類型對應至 OpenAPItype 和format,根據應用程式中使用的NumberHandlingJsonSerializerOptions屬性。 在 ASP.NET Core Web API 應用程式中,此屬性的預設值為 JsonNumberHandling.AllowReadingFromString。
NumberHandling當 屬性設定為 JsonNumberHandling.AllowReadingFromString時,數值類型會對應如下:
| C# 類型 | 開放API type |
開放API format |
其他斷言 |
|---|---|---|---|
| int | [integer,string] | int32 | 模式 <digits> |
| long | [integer,string] | int64 | 模式 <digits> |
| short | [integer,string] | int16 | 模式 <digits> |
| 位元組 | [integer,string] | uint8 | 模式 <digits> |
| float | [number,string] | float | 模式 <digits with decimal > |
| double | [number,string] | double | 模式 <digits with decimal > |
| 十進位 | [number,string] | double | 模式 <digits with decimal > |
如果應用程式設定為產生 OpenAPI 3.0 或 OpenAPI v2 檔,其中 type 欄位不能有陣列值,type 則會被移除。
NumberHandling當 屬性設定為 JsonNumberHandling.Strict時,數值類型會對應如下:
| C# 類型 | 開放API type |
開放API format |
|---|---|---|
| int | 整數 | int32 |
| long | 整數 | int64 |
| short | 整數 | int16 |
| 位元組 | 整數 | uint8 |
| float | number | float |
| double | number | double |
| 十進位 | number | double |
字串類型
下表展示了 C# 類型如何映射到 string OpenAPI 文件中類型的屬性。
| C# 類型 | 開放API type |
開放API format |
其他斷言 |
|---|---|---|---|
| 字串 | 字串 | ||
| Char | 字串 | Char | 最小長度:1,最大長度:1 |
| byte[] | 字串 | 位元組 | |
| DateTimeOffset | 字串 | date-time | |
| DateOnly | 字串 | date | |
| TimeOnly | 字串 | time | |
| Uri | 字串 | uri | |
| Guid | 字串 | uuid |
其他類型
其他 C# 類型則在生成的 OpenAPI 文件中呈現,如下表所示。
| C# 類型 | 開放API type |
開放API format |
|---|---|---|
| bool | boolean | |
| 物件 | omitted | |
| dynamic | omitted |
使用屬性新增元數據
ASP.NET 利用類別或記錄屬性的元資料,來設定產生結構對應屬性的元資料。
下表總結了命名空間中提供產生結構元資料的屬性 System.ComponentModel 。
| Attribute | Description |
|---|---|
[Description] |
設定 description 架構中的屬性。 |
[Required] |
請將結構描述中的屬性標示為 required。 |
[DefaultValue] |
設定結構描述中屬性的 default 值。 |
[Range] |
設定整數或數字的 minimum 和 maximum 值。 |
[MinLength] |
設定字串的 minLength 或陣列的 minItems。 |
[MaxLength] |
設定字串的 maxLength 或陣列的 maxItems。 |
[RegularExpression] |
設定 pattern 字串。 |
請注意,在控制器型應用程式中,這些屬性會將篩選新增至作業,以驗證任何傳入的資料是否符合條件約束。 在最小 API 中,這些屬性會在產生的結構描述中設定中繼資料,但必須透過端點篩選、路由處理常式邏輯或透過第三方套件明確執行驗證。
屬性也可以放在記錄定義的參數清單中,但必須包含 property 修飾詞。 例如:
public record Todo(
[property: Required]
[property: Description("The unique identifier for the todo")]
int Id,
[property: Description("The title of the todo")]
[property: MaxLength(120)]
string Title,
[property: Description("Whether the todo has been completed")]
bool Completed
) {}
其他產生的結構描述的中繼資料來源
required
在類別、結構體或記錄中,帶有 [Required] 屬性或 required 修飾符的屬性總是 required 在對應的結構中。
您也可以根據類別、結構或記錄的建構函式(隱含和明確)來要求其他屬性。
- 對於具有單一公用建構函式的類別或記錄類別,在對應的架構中,任何作為建構函式參數且名稱和類型相同的屬性(不區分大小寫比對)都是必需的。
- 對於具有多個公用建構函式的類別或記錄類別,不需要其他屬性。
- 對於結構或記錄結構,不需要其他屬性,因為 C# 一律會定義結構的隱含無參數建構函式。
列舉
C# 中的列舉類型是以整數為基礎,但可以使用 JSON 中的 [JsonConverter] 和 JsonStringEnumConverter 表示為字串。 當列舉類型在 JSON 中以字串表示時,產生的結構描述將具有列舉字串值的 enum 屬性。
下列範例示範如何使用 JsonStringEnumConverter 來將列舉表示為 JSON 中的字串:
[JsonConverter(typeof(JsonStringEnumConverter<DayOfTheWeekAsString>))]
public enum DayOfTheWeekAsString
{
Sunday,
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday
}
特殊案例是當列舉類型具有 [Flags] 屬性時,表示列舉可以視為位字段,也就是一組旗標。 具有enum的旗標[JsonConverterAttribute]在生成的架構中被定義為type: string,且沒有enum屬性。 不會產生 enum 任何屬性,因為值可以是列舉值的任何組合。 例如,下列enum可能會有"Pepperoni, Sausage"或"Sausage, Mushrooms, Anchovies"之類的值:
[Flags, JsonConverter(typeof(JsonStringEnumConverter<PizzaToppings>))]
public enum PizzaToppings {
Pepperoni = 1,
Sausage = 2,
Mushrooms = 4,
Anchovies = 8
}
不含 [JsonConverter] 的列舉類型會在產生的結構描述中定義為 type: integer。
注意:[AllowedValues] 屬性不會設定某個屬性的 enum 值。
全域設定 JSON 選項 會顯示如何全域設定 JsonStringEnumConverter 。
可為 Null
定義為可為 Null 值或參考型別的屬性會在產生的架構中顯示,並使用關鍵字 type,其值為包含 null 作為其中一種型別的陣列。 這與 System.Text.Json 反序列化器的預設行為一致,它接受 null 作為可為 NULL 的屬性的有效值。
例如,定義為 string? 的 C# 屬性會在產生的架構中表示為:
"nullableString": {
"description": "A property defined as string?",
"type": [
"null",
"string"
]
},
如果應用程式設定為產生 OpenAPI v3.0 或 OpenAPI v2 檔,則產生的架構中有可為 Null 的值或參考類型 nullable: true ,因為這些 OpenAPI 版本不允許 type 字段成為陣列。
additionalProperties
結構定義預設不會產生 additionalProperties 判斷提示,這表示 true 的預設值。 這與 System.Text.Json 還原序列化程式的預設行為一致,它會默默忽略 JSON 物件中的其他屬性。
如果結構描述的其他屬性應該只有特定類型的值,請將屬性或類別定義為 Dictionary<string, type>。 字典的索引鍵類型必須是 string。 這會產生結構描述,其中 additionalProperties 指定 "type" 的結構描述為必要的實值類型。
多態型
使用父類別上的 [JsonPolymorphic] 和 [JsonDerivedType] 屬性來指定多型類型的歧視性字段和子類型。
[JsonDerivedType] 將鑑別子欄位新增到每個子類別的結構模式中,並通過列舉來指定每個子類別的特定鑑別子值。 這個屬性也會修改每個衍生類別的建構函式,以設定鑑別子值。
具有 [JsonPolymorphic] 屬性的抽象類別具有 discriminator 結構描述中的欄位,但具有 [JsonPolymorphic] 屬性的實體類別沒有 discriminator 欄位。 OpenAPI 要求鑑別子屬性是結構描述中的必要屬性,但由於實體基底類別中未定義鑑別子屬性,所以結構描述不能包含 discriminator 欄位。
使用架構轉換器新增元數據
結構描述轉換器可用來覆寫任何預設的中繼資料,或在生成的結構描述中新增其他中繼資料,例如 example 值。 如需詳細資訊,請參閱使用結構描述轉換器。
全域設定 JSON 串行化選項
下列程式代碼會全域設定一些 JSON 選項,適用於基本 API 和控制器型 API:
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.AspNetCore.Http.Json;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddOpenApi();
builder.Services.Configure<JsonOptions>(options =>
{
options.SerializerOptions.Converters.Add(
new JsonStringEnumConverter<DayOfTheWeekAsString>());
options.SerializerOptions.DefaultIgnoreCondition =
JsonIgnoreCondition.WhenWritingNull;
options.SerializerOptions.PropertyNamingPolicy =
JsonNamingPolicy.CamelCase;
});
builder.Services.AddControllers()
.AddJsonOptions(options =>
{
options.JsonSerializerOptions.Converters.Add(
new JsonStringEnumConverter<DayOfTheWeekAsString>());
options.JsonSerializerOptions.DefaultIgnoreCondition =
JsonIgnoreCondition.WhenWritingNull;
options.JsonSerializerOptions.PropertyNamingPolicy =
JsonNamingPolicy.CamelCase;
});
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.MapOpenApi();
}
app.UseHttpsRedirection();
app.MapGet("/", () =>
{
var day = DayOfTheWeekAsString.Friday;
return Results.Json(day);
});
app.MapPost("/", (DayOfTheWeekAsString day) =>
{
return Results.Json($"Received: {day}");
});
app.UseRouting();
app.MapControllers();
app.Run();
MVC JSON 選項和全域 JSON 選項
下表展示了 MVC JSON 選項與全域最小 API JSON 選項的主要差異。
| Aspect | MVC JSON 選項 | 全域 JSON 選項 |
|---|---|---|
| Scope | 限制為MVC控制器和端點。 | 最小 API 和 OpenAPI 檔。 |
| Configuration | AddControllers().AddJsonOptions() |
Configure<JsonOptions>() |
| Purpose | 處理 API 中 JSON 要求和回應的串行化和還原串行化。 | 定義最小化 API 和 OpenAPI 結構的全域 JSON 處理。 |
| 對 OpenAPI 的影響 | None | 直接影響 OpenAPI 架構產生。 |
其他資源
包含 OpenAPI 中的端點元數據
ASP.NET 從網頁應用程式的端點收集元資料,並用來產生 OpenAPI 文件。
在控制器型應用程式中,當控制器具有[EndpointDescription]時,會從[HttpPost]、[Produces]和[ApiController]等屬性中收集元數據。
在最小 API 中,元資料可從屬性收集,但也可透過擴充方法及其他策略(如從路由處理器回傳 TypedResults )設定。
下表提供所收集中繼資料的概覽,並說明設定中繼資料的策略。
| Metadata | Attribute | 擴展方式 | 其他策略 |
|---|---|---|---|
| 摘要 | [EndpointSummary] |
WithSummary | |
| description | [EndpointDescription] |
WithDescription | |
| tags | [Tags] |
WithTags | |
| operationId | [EndpointName] |
WithName | |
| parameters |
[FromQuery]、 、 [FromRoute]、 [FromHeader][FromForm] |
||
| 參數說明 | [Description] |
||
| requestBody | [FromBody] |
Accepts | |
| responses | [Produces] |
Produces、ProducesProblem | TypedResults |
| 排除端點 |
[ExcludeFromDescription]、[ApiExplorerSettings] |
ExcludeFromDescription |
ASP.NET Core 也能從 XML 文件註解中收集元資料。 欲了解更多資訊,請參閱 ASP.NET Core 中的 ASP.NET Core OpenAPI XML 文件註解支援詳細資訊。
下列各節示範如何在應用程式中包含中繼資料,以自訂產生的 OpenAPI 文件。
摘要和描述
端點摘要與描述可以使用屬性[EndpointSummary] 和 [EndpointDescription] 設定,或在 Minimal API 中使用擴充方法WithSummary 和 WithDescription 設定。
下列範例示範設定摘要和描述的不同策略。
請注意,屬性置放在委派方法上,而不是放在 app.MapGet 方法上。
app.MapGet("/extension-methods", () => "Hello world!")
.WithSummary("This is a summary.")
.WithDescription("This is a description.");
app.MapGet("/attributes",
[EndpointSummary("This is a summary.")]
[EndpointDescription("This is a description.")]
() => "Hello world!");
tags
OpenAPI 支援將每個端點上的標籤指定為分類形式。
在最小 API 中,標籤可以透過[Tags]屬性或WithTags擴充方法來設定。
下列範例示範設定標記的不同策略。
app.MapGet("/extension-methods", () => "Hello world!")
.WithTags("todos", "projects");
app.MapGet("/attributes",
[Tags("todos", "projects")]
() => "Hello world!");
operationId
OpenAPI 支援每個端點上的 operationId 作為作業的唯一識別碼或名稱。
在 Minimal API 中,操作 ID 可以透過[EndpointName]屬性或WithName擴充方法來設定。
下列範例示範設定 operationId 的不同策略。
app.MapGet("/extension-methods", () => "Hello world!")
.WithName("FromExtensionMethods");
app.MapGet("/attributes",
[EndpointName("FromAttributes")]
() => "Hello world!");
parameters
OpenAPI 支援 API 所使用的標註路徑、查詢字串、標頭和 cookie 參數。
架構會根據路由處理常式的簽章,自動推斷要求參數的型別。
[Description] 屬性可用來提供參數的描述。
下列範例示範如何設定參數的描述。
app.MapGet("/attributes",
([Description("This is a description.")] string name) => "Hello world!");
描述請求主體
OpenAPI 中的 requestBody 欄位描述了 API 用戶端可傳送給伺服器的請求內容,包括支援的內容類型及內容結構。
當端點處理方法接受從請求主體綁定的參數時,ASP.NET Core 會為 OpenAPI 文件中的操作產生對應的 requestBody。 您也可以使用屬性或擴充方法來指定要求主體的元數據。 可以使用 文件轉換器 或 作業轉換器來設定其他中繼資料。
如果端點沒有定義任何綁定到請求主體的參數,而是直接從 HttpContext 取用請求主體,ASP.NET Core 就會提供機制來指定請求主體的元資料。 處理請求主體為資料流的端點常見案例是這樣的。
某些請求正文的元數據可以由路由處理器方法中的FromBody或FromForm參數來確定。
您可以在參數的[Description]屬性上,使用FromBody或FromForm來設定請求本文的描述。
如果FromBody參數不可為 Null,且EmptyBodyBehavior在Allow屬性中未設定為FromBody,則要求的本文是必需的,且在產生的 OpenAPI 文件中,required的requestBody欄位會設定為true。
表單內容始終是必需的,並且已將 required 設定為 true。
使用 文件轉換器 或 作業轉換器 來設定 example、 examples或 encoding 欄位,或在產生的 OpenAPI 文件中新增要求內文的規格延伸。
設定要求本文元數據的其他機制取決於所開發的應用程式類型,如下幾節所述。
所產生的 OpenAPI 文件中,請求主體的內容類型是由綁定到請求主體的參數類型或使用 Accepts 擴充方法指定的參數類型決定的。
預設情況下,FromBody 參數的內容類型為 application/json,而 FromForm 參數的內容類型為 multipart/form-data 或 application/x-www-form-urlencoded。
這些預設內容類型的支援內建於基本 API 中,而其他內容類型可以使用自定義系結來處理。 如需詳細資訊,請參閱最小 API 文件的 自訂繫結 主題。
可以指定請求主體的內容類型的幾種不同方法。
如果 FromBody 參數的類型實作了 IEndpointParameterMetadataProvider,ASP.NET Core 會使用此介面來決定請求主體中的內容類型。
框架利用 PopulateMetadata 此介面的方法來設定請求內容類型以及請求正文內容的類型。 例如,接收Todo 或 application/xml 內容類型的text/xml 類別,可使用IEndpointParameterMetadataProvider 將此資訊提供給架構。
public class Todo : IEndpointParameterMetadataProvider
{
public static void PopulateMetadata(
ParameterInfo parameter,
EndpointBuilder builder)
{
builder.Metadata.Add(
new AcceptsMetadata(
["application/xml", "text/xml"],
typeof(Todo)
)
);
}
}
Accepts 擴充方法也可以用來指定請求正文的內容類型。
在下列範例中,端點會在請求主體中接受 Todo 物件,並且期待內容型別為 application/xml。
app.MapPut("/todos/{id}", (int id, Todo todo) => ...)
.Accepts<Todo>("application/xml");
由於 application/xml 不是內建內容類型,因此 類別 Todo 必須實 IBindableFromHttpContext<TSelf> 作 介面,以提供要求主體的自定義系結。 例如:
public class Todo : IBindableFromHttpContext<Todo>
{
public static async ValueTask<Todo?> BindAsync(
HttpContext context,
ParameterInfo parameter)
{
var xmlDoc = await XDocument.LoadAsync(context.Request.Body, LoadOptions.None, context.RequestAborted);
var serializer = new XmlSerializer(typeof(Todo));
return (Todo?)serializer.Deserialize(xmlDoc.CreateReader());
}
}
如果端點未定義系結至要求本文的任何參數,請使用 Accepts 擴充方法指定端點接受的內容類型。
如果您指定 Accepts 多次,則只會使用最後一個的元數據-- 它們不會合併。
描述回應類型
OpenAPI 支援提供從 API 傳回的回應描述。 ASP.NET Core 提供多種策略來設定端點的回應元資料。 可設定的回應元資料包括狀態碼、回應主體的類型,以及回應的內容類型。 OpenAPI 中的回應可能會有其他元數據,例如描述、標頭、連結和範例。 可以使用 文件轉換器 或 作業轉換器來設定此額外的中繼資料。
設定回應元數據的特定機制取決於正在開發的應用程式類型。
在 Minimal API 應用程式中,ASP.NET Core 可以擷取端點擴充方法新增的回應元資料、路由處理器上的屬性,以及路由處理器的回傳型別。
- Produces擴充方法可在端點上指定狀態碼、回應體型態及端點回應的內容類型。
-
[ProducesResponseType]或 ProducesResponseTypeAttribute<T> 屬性可用來指定響應主體的類型。 - 路由處理器可用來回傳一個型別,該型別實作 IEndpointMetadataProvider 以指定回應體的型別與內容型別。
- ProducesProblem端點上的擴充方法可用來指定錯誤回應的狀態碼與內容類型。
請注意,Produces 和 ProducesProblem 擴充方法於 RouteHandlerBuilder 和 RouteGroupBuilder 都受支援。 例如,這允許針對群組中的所有作業定義一組常見的錯誤回應。
未由上述其中一個策略指定時,:
- 回應的狀態代碼預設為 200。
- 回應主體的架構可以從端點方法的隱含或明確傳回類型推斷,例如,從
T中 Task<TResult>推斷,否則會被視為未指定。 - 指定或推斷之響應主體的內容類型為 「application/json」。。
在「Minimal API」中,Produces 擴充方法和 [ProducesResponseType] 屬性只會設定端點的回應中繼資料。 它們不會修改或限制端點的行為,其可能會傳回與元數據指定的狀態代碼或響應主體類型不同的狀態代碼或響應主體類型,而且內容類型是由路由處理程式方法的傳回類型所決定,而不論屬性或擴充方法中指定的任何內容類型為何。
擴充 Produces 方法可以指定端點的回應類型,其預設狀態代碼為 200,預設內容類型為 application/json。 下面這個範例可說明這點:
app.MapGet("/todos", async (TodoDb db) => await db.Todos.ToListAsync())
.Produces<IList<Todo>>();
[ProducesResponseType]可用來將回應元數據新增至端點。 請注意,屬性是套用在路由處理方法上,而不是應用在建立路由的方法調用上,如下列範例所示:
app.MapGet("/todos",
[ProducesResponseType<List<Todo>>(200)]
async (TodoDb db) => await db.Todos.ToListAsync());
[ProducesResponseType]、 [Produces]和 [ProducesDefaultResponseType] 也支援稱為 的選擇性字串屬性 Description ,可用來描述回應。 這適用於說明客戶端預期特定回應的原因或時機:
app.MapGet("/todos/{id}",
[ProducesResponseType<Todo>(200,
Description = "Returns the requested Todo item.")]
[ProducesResponseType(404, Description = "Requested item not found.")]
[ProducesDefault(Description = "Undocumented status code.")]
async (int id, TodoDb db) => /* Code here */);
在實現端點路由處理器時使用 TypedResults,會自動包含端點回應類型的中繼資料。 例如,下列程式碼會自動以具有 200 內容型別的 application/json 狀態碼的回應來註釋端點。
app.MapGet("/todos", async (TodoDb db) =>
{
var todos = await db.Todos.ToListAsync();
return TypedResults.Ok(todos);
});
只有那些實作 IEndpointMetadataProvider 的傳回型別會在 OpenAPI 文件中建立一個 responses 條目。 以下是產生TypedResults項目之responses一些輔助方法的部分清單:
TypedResults 輔助方法 |
狀態碼 |
|---|---|
| Ok() | 200 |
| Created() | 201 |
| CreatedAtRoute() | 201 |
| Accepted() | 202 |
| AcceptedAtRoute() | 202 |
| NoContent() | 204 |
| BadRequest() | 400 |
| ValidationProblem() | 400 |
| NotFound() | 404 |
| Conflict() | 409 |
| UnprocessableEntity() | 422 |
所有這些方法,除了 NoContent 之外,都有一個可指定響應主體類型的泛型多載。
您可以實作 類別來設定端點元數據,並從路由處理程式傳回它。
設定 ProblemDetails 的回應
為可能傳回 ProblemDetails 回應的端點設定回應類型時,可以使用下列專案來新增端點的適當回應元數據:
- ProducesProblem
- ProducesValidationProblem 擴充方法。
- TypedResults 的狀態代碼在(400-499)範圍內。
欲了解更多如何配置最小 API 應用程式以回傳 ProblemDetails 回應的資訊,請參見 Handle errors in ASP.NET Core APIs。
多個回應型別
如果端點可以在不同案例中傳回不同的回應型別,您可以透過下列方式提供中繼資料:
多次調用 Produces 擴充方法,如下列範例所示:
app.MapGet("/api/todoitems/{id}", async (int id, TodoDb db) => await db.Todos.FindAsync(id) is Todo todo ? Results.Ok(todo) : Results.NotFound()) .Produces<Todo>(StatusCodes.Status200OK) .Produces(StatusCodes.Status404NotFound);在簽章中使用 Results<TResult1,TResult2,TResult3,TResult4,TResult5,TResult6>,處理常式主體中使用 TypedResults,如下列範例所示:
app.MapGet("/book/{id}", Results<Ok<Book>, NotFound> (int id, List<Book> bookList) => { return bookList.FirstOrDefault((i) => i.Id == id) is Book book ? TypedResults.Ok(book) : TypedResults.NotFound(); });Results<TResult1,TResult2,TResultN>聯合類型聲明路由處理程序會返回多個實作IResult的具體類型,其中任何實作IEndpointMetadataProvider的類型都將為端點的中繼資料做出貢獻。聯合型別會實作隱含轉換運算子。 這些運算子可讓編譯器自動將泛型引數中指定的型別轉換為聯合型別的實例。 此功能的額外優點是提供編譯時檢查,以確保路由處理常式僅傳回它宣告的結果。 嘗試傳回未宣告為其中一個泛型引數的型別至
Results<TResult1,TResult2,TResultN>會產生編譯錯誤。
從生成的文件中排除端點
根據預設,應用程式中定義的所有端點都會記錄在產生的 OpenAPI 檔案中,但可以使用屬性或擴充方法從檔中排除端點。
指定應排除之端點的機制取決於所開發的應用程式類型。
最小 API 支援從 OpenAPI 檔排除指定端點的兩種策略:
下列範例示範從產生的 OpenAPI 文件中排除指定端點的不同策略。
app.MapGet("/extension-method", () => "Hello world!")
.ExcludeFromDescription();
app.MapGet("/attributes",
[ExcludeFromDescription]
() => "Hello world!");
包含用於數據類型的 OpenAPI 元數據
要求或回應主體中使用的 C# 類別或記錄會以所產生 OpenAPI 文件的結構描表示。
預設情況下,架構中只 public 表示屬性,但也可 JsonSerializerOptions 為欄位建立結構屬性。
當 PropertyNamingPolicy 設定為駝峰箱(這是 ASP.NET 網頁應用程式的預設值),結構中的屬性名稱即為類別或記錄屬性名稱的駝峰箱形式。
[JsonPropertyName] 可用於個別屬性,以指定結構描述中的屬性名稱。
類型和格式
數值類型
JSON 架構庫會將標準 C# 數值類型對應至 OpenAPItype 和format,根據應用程式中使用的NumberHandlingJsonSerializerOptions屬性。 在 ASP.NET Core Web API 應用程式中,此屬性的預設值為 JsonNumberHandling.AllowReadingFromString。
NumberHandling當 屬性設定為 JsonNumberHandling.AllowReadingFromString時,數值類型會對應如下:
| C# 類型 | 開放API type |
開放API format |
其他斷言 |
|---|---|---|---|
| int | [integer,string] | int32 | 模式 <digits> |
| long | [integer,string] | int64 | 模式 <digits> |
| short | [integer,string] | int16 | 模式 <digits> |
| 位元組 | [integer,string] | uint8 | 模式 <digits> |
| float | [number,string] | float | 模式 <digits with decimal > |
| double | [number,string] | double | 模式 <digits with decimal > |
| 十進位 | [number,string] | double | 模式 <digits with decimal > |
如果應用程式設定為產生 OpenAPI 3.0 或 OpenAPI v2 檔,其中 type 欄位不能有陣列值,type 則會被移除。
NumberHandling當 屬性設定為 JsonNumberHandling.Strict時,數值類型會對應如下:
| C# 類型 | 開放API type |
開放API format |
|---|---|---|
| int | 整數 | int32 |
| long | 整數 | int64 |
| short | 整數 | int16 |
| 位元組 | 整數 | uint8 |
| float | number | float |
| double | number | double |
| 十進位 | number | double |
字串類型
下表展示了 C# 類型如何映射到 string OpenAPI 文件中類型的屬性。
| C# 類型 | 開放API type |
開放API format |
其他斷言 |
|---|---|---|---|
| 字串 | 字串 | ||
| Char | 字串 | Char | 最小長度:1,最大長度:1 |
| byte[] | 字串 | 位元組 | |
| DateTimeOffset | 字串 | date-time | |
| DateOnly | 字串 | date | |
| TimeOnly | 字串 | time | |
| Uri | 字串 | uri | |
| Guid | 字串 | uuid |
其他類型
其他 C# 類型會在產生的 OpenAPI 檔中表示,如下表所示:
| C# 類型 | 開放API type |
開放API format |
|---|---|---|
| bool | boolean | |
| 物件 | omitted | |
| dynamic | omitted |
使用屬性新增元數據
ASP.NET 利用類別或記錄屬性的元資料,來設定產生結構對應屬性的元資料。
下表總結了命名空間中提供產生結構元資料的屬性 System.ComponentModel 。
| Attribute | Description |
|---|---|
[Description] |
設定 description 架構中的屬性。 |
[Required] |
請將結構描述中的屬性標示為 required。 |
[DefaultValue] |
設定結構描述中屬性的 default 值。 |
[Range] |
設定整數或數字的 minimum 和 maximum 值。 |
[MinLength] |
設定字串的 minLength 或陣列的 minItems。 |
[MaxLength] |
設定字串的 maxLength 或陣列的 maxItems。 |
[RegularExpression] |
設定 pattern 字串。 |
請注意,在控制器型應用程式中,這些屬性會將篩選新增至作業,以驗證任何傳入的資料是否符合條件約束。 在最小 API 中,這些屬性會在產生的結構描述中設定中繼資料,但必須透過端點篩選、路由處理常式邏輯或透過第三方套件明確執行驗證。
屬性也可以放在記錄定義的參數清單中,但必須包含 property 修飾詞。 例如:
public record Todo(
[property: Required]
[property: Description("The unique identifier for the todo")]
int Id,
[property: Description("The title of the todo")]
[property: MaxLength(120)]
string Title,
[property: Description("Whether the todo has been completed")]
bool Completed
) {}
其他產生的結構描述的中繼資料來源
required
在類別、結構體或記錄中,帶有 [Required] 屬性或 required 修飾符的屬性總是 required 在對應的結構中。
您也可以根據類別、結構或記錄的建構函式(隱含和明確)來要求其他屬性。
- 對於具有單一公用建構函式的類別或記錄類別,在對應的架構中,任何作為建構函式參數且名稱和類型相同的屬性(不區分大小寫比對)都是必需的。
- 對於具有多個公用建構函式的類別或記錄類別,不需要其他屬性。
- 對於結構或記錄結構,不需要其他屬性,因為 C# 一律會定義結構的隱含無參數建構函式。
列舉
C# 中的列舉類型是以整數為基礎,但可以使用 JSON 中的 [JsonConverter] 和 JsonStringEnumConverter 表示為字串。 當列舉類型在 JSON 中以字串表示時,產生的結構描述將具有列舉字串值的 enum 屬性。
下列範例示範如何使用 JsonStringEnumConverter 來將列舉表示為 JSON 中的字串:
[JsonConverter(typeof(JsonStringEnumConverter<DayOfTheWeekAsString>))]
public enum DayOfTheWeekAsString
{
Sunday,
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday
}
特殊案例是當列舉類型具有 [Flags] 屬性時,表示列舉可以視為位字段,也就是一組旗標。 具有enum的旗標[JsonConverterAttribute]在生成的架構中被定義為type: string,且沒有enum屬性。 不會產生 enum 任何屬性,因為值可以是列舉值的任何組合。 例如,下列enum可能會有"Pepperoni, Sausage"或"Sausage, Mushrooms, Anchovies"之類的值:
[Flags, JsonConverter(typeof(JsonStringEnumConverter<PizzaToppings>))]
public enum PizzaToppings {
Pepperoni = 1,
Sausage = 2,
Mushrooms = 4,
Anchovies = 8
}
不含 [JsonConverter] 的列舉類型會在產生的結構描述中定義為 type: integer。
注意:[AllowedValues] 屬性不會設定某個屬性的 enum 值。
全域設定 JSON 選項 會顯示如何全域設定 JsonStringEnumConverter 。
可為 Null
定義為可為 Null 值或參考型別的屬性會在產生的架構中顯示,並使用關鍵字 type,其值為包含 null 作為其中一種型別的陣列。 這與 System.Text.Json 反序列化器的預設行為一致,它接受 null 作為可為 NULL 的屬性的有效值。
例如,定義為 string? 的 C# 屬性會在產生的架構中表示為:
"nullableString": {
"description": "A property defined as string?",
"type": [
"null",
"string"
]
},
如果應用程式設定為產生 OpenAPI v3.0 或 OpenAPI v2 檔,則產生的架構中有可為 Null 的值或參考類型 nullable: true ,因為這些 OpenAPI 版本不允許 type 字段成為陣列。
additionalProperties
結構定義預設不會產生 additionalProperties 判斷提示,這表示 true 的預設值。 這與 System.Text.Json 還原序列化程式的預設行為一致,它會默默忽略 JSON 物件中的其他屬性。
如果結構描述的其他屬性應該只有特定類型的值,請將屬性或類別定義為 Dictionary<string, type>。 字典的索引鍵類型必須是 string。 這會產生結構描述,其中 additionalProperties 指定 "type" 的結構描述為必要的實值類型。
多態型
使用父類別上的 [JsonPolymorphic] 和 [JsonDerivedType] 屬性來指定多型類型的歧視性字段和子類型。
[JsonDerivedType] 將鑑別子欄位新增到每個子類別的結構模式中,並通過列舉來指定每個子類別的特定鑑別子值。 這個屬性也會修改每個衍生類別的建構函式,以設定鑑別子值。
具有 [JsonPolymorphic] 屬性的抽象類別具有 discriminator 結構描述中的欄位,但具有 [JsonPolymorphic] 屬性的實體類別沒有 discriminator 欄位。 OpenAPI 要求鑑別子屬性是結構描述中的必要屬性,但由於實體基底類別中未定義鑑別子屬性,所以結構描述不能包含 discriminator 欄位。
使用架構轉換器新增元數據
結構描述轉換器可用來覆寫任何預設的中繼資料,或在生成的結構描述中新增其他中繼資料,例如 example 值。 如需詳細資訊,請參閱使用結構描述轉換器。
全域設定 JSON 串行化選項
下列程式代碼會全域設定一些 JSON 選項,適用於基本 API 和控制器型 API:
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.AspNetCore.Http.Json;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddOpenApi();
builder.Services.Configure<JsonOptions>(options =>
{
options.SerializerOptions.Converters.Add(
new JsonStringEnumConverter<DayOfTheWeekAsString>());
options.SerializerOptions.DefaultIgnoreCondition =
JsonIgnoreCondition.WhenWritingNull;
options.SerializerOptions.PropertyNamingPolicy =
JsonNamingPolicy.CamelCase;
});
builder.Services.AddControllers()
.AddJsonOptions(options =>
{
options.JsonSerializerOptions.Converters.Add(
new JsonStringEnumConverter<DayOfTheWeekAsString>());
options.JsonSerializerOptions.DefaultIgnoreCondition =
JsonIgnoreCondition.WhenWritingNull;
options.JsonSerializerOptions.PropertyNamingPolicy =
JsonNamingPolicy.CamelCase;
});
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.MapOpenApi();
}
app.UseHttpsRedirection();
app.MapGet("/", () =>
{
var day = DayOfTheWeekAsString.Friday;
return Results.Json(day);
});
app.MapPost("/", (DayOfTheWeekAsString day) =>
{
return Results.Json($"Received: {day}");
});
app.UseRouting();
app.MapControllers();
app.Run();
MVC JSON 選項和全域 JSON 選項
下表展示了 MVC JSON 選項與全域最小 API JSON 選項的主要差異。
| Aspect | MVC JSON 選項 | 全域 JSON 選項 |
|---|---|---|
| Scope | 限制為MVC控制器和端點。 | 最小 API 和 OpenAPI 檔。 |
| Configuration | AddControllers().AddJsonOptions() |
Configure<JsonOptions>() |
| Purpose | 處理 API 中 JSON 要求和回應的串行化和還原串行化。 | 定義最小化 API 和 OpenAPI 結構的全域 JSON 處理。 |
| 對 OpenAPI 的影響 | None | 直接影響 OpenAPI 架構產生。 |
其他資源
包含 OpenAPI 中的端點元數據
ASP.NET 從網頁應用程式的端點收集元資料,並用來產生 OpenAPI 文件。
在控制器型應用程式中,當控制器具有[EndpointDescription]時,會從[HttpPost]、[Produces]和[ApiController]等屬性中收集元數據。
在最小 API 中,元資料可從屬性收集,但也可透過擴充方法及其他策略(如從路由處理器回傳 TypedResults )設定。
下表提供所收集中繼資料的概覽,並說明設定中繼資料的策略。
| Metadata | Attribute | 擴展方式 | 其他策略 |
|---|---|---|---|
| 摘要 | [EndpointSummary] |
WithSummary | |
| description | [EndpointDescription] |
WithDescription | |
| tags | [Tags] |
WithTags | |
| operationId | [EndpointName] |
WithName | |
| parameters |
[FromQuery]、 、 [FromRoute]、 [FromHeader][FromForm] |
||
| 參數說明 | [Description] |
||
| requestBody | [FromBody] |
Accepts | |
| responses | [Produces] |
Produces、ProducesProblem | TypedResults |
| 排除端點 |
[ExcludeFromDescription]、[ApiExplorerSettings] |
ExcludeFromDescription |
ASP.NET Core 不會從 XML 文件註解中收集元資料。
下列各節示範如何在應用程式中包含中繼資料,以自訂產生的 OpenAPI 文件。
摘要和描述
端點摘要與描述可以使用屬性[EndpointSummary] 和 [EndpointDescription] 設定,或在 Minimal API 中使用擴充方法WithSummary 和 WithDescription 設定。
下列範例示範設定摘要和描述的不同策略。
請注意,屬性置放在委派方法上,而不是放在 app.MapGet 方法上。
app.MapGet("/extension-methods", () => "Hello world!")
.WithSummary("This is a summary.")
.WithDescription("This is a description.");
app.MapGet("/attributes",
[EndpointSummary("This is a summary.")]
[EndpointDescription("This is a description.")]
() => "Hello world!");
tags
OpenAPI 支援將每個端點上的標籤指定為分類形式。
在最小 API 中,標籤可以透過[Tags]屬性或WithTags擴充方法來設定。
下列範例示範設定標記的不同策略。
app.MapGet("/extension-methods", () => "Hello world!")
.WithTags("todos", "projects");
app.MapGet("/attributes",
[Tags("todos", "projects")]
() => "Hello world!");
operationId
OpenAPI 支援每個端點上的 operationId 作為作業的唯一識別碼或名稱。
在 Minimal API 中,操作 ID 可以透過[EndpointName]屬性或WithName擴充方法來設定。
下列範例示範設定 operationId 的不同策略。
app.MapGet("/extension-methods", () => "Hello world!")
.WithName("FromExtensionMethods");
app.MapGet("/attributes",
[EndpointName("FromAttributes")]
() => "Hello world!");
parameters
OpenAPI 支援 API 所使用的標註路徑、查詢字串、標頭和 cookie 參數。
架構會根據路由處理常式的簽章,自動推斷要求參數的型別。
[Description] 屬性可用來提供參數的描述。
下列範例示範如何設定參數的描述。
app.MapGet("/attributes",
([Description("This is a description.")] string name) => "Hello world!");
描述請求主體
OpenAPI 中的 requestBody 欄位描述了 API 用戶端可傳送給伺服器的請求內容,包括支援的內容類型及內容結構。
當端點處理方法接受從請求主體綁定的參數時,ASP.NET Core 會為 OpenAPI 文件中的操作產生對應的 requestBody。 您也可以使用屬性或擴充方法來指定要求主體的元數據。 可以使用 文件轉換器 或 作業轉換器來設定其他中繼資料。
如果端點沒有定義任何綁定到請求主體的參數,而是直接從 HttpContext 取用請求主體,ASP.NET Core 就會提供機制來指定請求主體的元資料。 處理請求主體為資料流的端點常見案例是這樣的。
某些請求正文的元數據可以由路由處理器方法中的FromBody或FromForm參數來確定。
您可以在參數的[Description]屬性上,使用FromBody或FromForm來設定請求本文的描述。
如果FromBody參數不可為 Null,且EmptyBodyBehavior在Allow屬性中未設定為FromBody,則要求的本文是必需的,且在產生的 OpenAPI 文件中,required的requestBody欄位會設定為true。
表單內容始終是必需的,並且已將 required 設定為 true。
使用 文件轉換器 或 作業轉換器 來設定 example、 examples或 encoding 欄位,或在產生的 OpenAPI 文件中新增要求內文的規格延伸。
設定要求本文元數據的其他機制取決於所開發的應用程式類型,如下幾節所述。
所產生的 OpenAPI 文件中,請求主體的內容類型是由綁定到請求主體的參數類型或使用 Accepts 擴充方法指定的參數類型決定的。
預設情況下,FromBody 參數的內容類型為 application/json,而 FromForm 參數的內容類型為 multipart/form-data 或 application/x-www-form-urlencoded。
這些預設內容類型的支援內建於基本 API 中,而其他內容類型可以使用自定義系結來處理。 如需詳細資訊,請參閱最小 API 文件的 自訂繫結 主題。
可以指定請求主體的內容類型的幾種不同方法。
如果 FromBody 參數的類型實作了 IEndpointParameterMetadataProvider,ASP.NET Core 會使用此介面來決定請求主體中的內容類型。
框架利用 PopulateMetadata 此介面的方法來設定請求內容類型以及請求正文內容的類型。 例如,接收Todo 或 application/xml 內容類型的text/xml 類別,可使用IEndpointParameterMetadataProvider 將此資訊提供給架構。
public class Todo : IEndpointParameterMetadataProvider
{
public static void PopulateMetadata(ParameterInfo parameter, EndpointBuilder builder)
{
builder.Metadata.Add(new AcceptsMetadata(["application/xml", "text/xml"], typeof(Todo)));
}
}
Accepts 擴充方法也可以用來指定請求正文的內容類型。
在下列範例中,端點會在請求主體中接受 Todo 物件,並且期待內容型別為 application/xml。
app.MapPut("/todos/{id}", (int id, Todo todo) => ...)
.Accepts<Todo>("application/xml");
由於 application/xml 不是內建內容類型,因此 類別 Todo 必須實 IBindableFromHttpContext<TSelf> 作 介面,以提供要求主體的自定義系結。 例如:
public class Todo : IBindableFromHttpContext<Todo>
{
public static async ValueTask<Todo?> BindAsync(HttpContext context, ParameterInfo parameter)
{
var xmlDoc = await XDocument.LoadAsync(context.Request.Body, LoadOptions.None, context.RequestAborted);
var serializer = new XmlSerializer(typeof(Todo));
return (Todo?)serializer.Deserialize(xmlDoc.CreateReader());
}
}
如果端點未定義系結至要求本文的任何參數,請使用 Accepts 擴充方法指定端點接受的內容類型。
如果您指定 <AspNetCore.Http.OpenApiRouteHandlerBuilderExtensions.Accepts%2A> 多次,則只會使用最後一個來源的元數據 -- 它們不會合併。
描述回應類型
OpenAPI 支援提供從 API 傳回的回應描述。 ASP.NET Core 提供多種策略來設定端點的回應元資料。 可設定的回應元資料包括狀態碼、回應主體的類型,以及回應的內容類型。 OpenAPI 中的回應可能會有其他元數據,例如描述、標頭、連結和範例。 可以使用 文件轉換器 或 作業轉換器來設定此額外的中繼資料。
設定回應元數據的特定機制取決於正在開發的應用程式類型。
在 Minimal API 應用程式中,ASP.NET Core 可以擷取端點擴充方法新增的回應元資料、路由處理器上的屬性,以及路由處理器的回傳型別。
- Produces擴充方法可在端點上指定狀態碼、回應體型態及端點回應的內容類型。
-
[ProducesResponseType]或 ProducesResponseTypeAttribute<T> 屬性可用來指定響應主體的類型。 - 路由處理器可用來回傳一個型別,該型別實作 IEndpointMetadataProvider 以指定回應體的型別與內容型別。
- ProducesProblem端點上的擴充方法可用來指定錯誤回應的狀態碼與內容類型。
請注意,Produces 和 ProducesProblem 擴充方法於 RouteHandlerBuilder 和 RouteGroupBuilder 都受支援。 例如,這允許針對群組中的所有作業定義一組常見的錯誤回應。
未由上述其中一個策略指定時,:
- 回應的狀態代碼預設為 200。
- 回應主體的架構可以從端點方法的隱含或明確傳回類型推斷,例如,從
T中 Task<TResult>推斷,否則會被視為未指定。 - 指定或推斷之響應主體的內容類型為 「application/json」。。
在「Minimal API」中,Produces 擴充方法和 [ProducesResponseType] 屬性只會設定端點的回應中繼資料。 它們不會修改或限制端點的行為,其可能會傳回與元數據指定的狀態代碼或響應主體類型不同的狀態代碼或響應主體類型,而且內容類型是由路由處理程式方法的傳回類型所決定,而不論屬性或擴充方法中指定的任何內容類型為何。
擴充 Produces 方法可以指定端點的回應類型,其預設狀態代碼為 200,預設內容類型為 application/json。 下面這個範例可說明這點:
app.MapGet("/todos", async (TodoDb db) => await db.Todos.ToListAsync())
.Produces<IList<Todo>>();
[ProducesResponseType]可用來將回應元數據新增至端點。 請注意,屬性是套用在路由處理方法上,而不是應用在建立路由的方法調用上,如下列範例所示:
app.MapGet("/todos",
[ProducesResponseType<List<Todo>>(200)]
async (TodoDb db) => await db.Todos.ToListAsync());
在實現端點路由處理器時使用 TypedResults,會自動包含端點回應類型的中繼資料。 例如,下列程式碼會自動以具有 200 內容型別的 application/json 狀態碼的回應來註釋端點。
app.MapGet("/todos", async (TodoDb db) =>
{
var todos = await db.Todos.ToListAsync();
return TypedResults.Ok(todos);
});
只有那些實作 IEndpointMetadataProvider 的傳回型別會在 OpenAPI 文件中建立一個 responses 條目。 以下是產生TypedResults項目之responses一些輔助方法的部分清單:
TypedResults 輔助方法 |
狀態碼 |
|---|---|
| Ok() | 200 |
| Created() | 201 |
| CreatedAtRoute() | 201 |
| Accepted() | 202 |
| AcceptedAtRoute() | 202 |
| NoContent() | 204 |
| BadRequest() | 400 |
| ValidationProblem() | 400 |
| NotFound() | 404 |
| Conflict() | 409 |
| UnprocessableEntity() | 422 |
所有這些方法,除了 NoContent 之外,都有一個可指定響應主體類型的泛型多載。
您可以實作 類別來設定端點元數據,並從路由處理程式傳回它。
設定 ProblemDetails 的回應
為可能傳回 ProblemDetails 回應的端點設定回應類型時,可以使用下列專案來新增端點的適當回應元數據:
- ProducesProblem
- ProducesValidationProblem 擴充方法。
- TypedResults 的狀態代碼在(400-499)範圍內。
欲了解更多如何配置最小 API 應用程式以回傳 ProblemDetails 回應的資訊,請參見 Handle errors in ASP.NET Core APIs。
多個回應型別
如果端點可以在不同案例中傳回不同的回應型別,您可以透過下列方式提供中繼資料:
多次調用 Produces 擴充方法,如下列範例所示:
app.MapGet("/api/todoitems/{id}", async (int id, TodoDb db) => await db.Todos.FindAsync(id) is Todo todo ? Results.Ok(todo) : Results.NotFound()) .Produces<Todo>(StatusCodes.Status200OK) .Produces(StatusCodes.Status404NotFound);在簽章中使用 Results<TResult1,TResult2,TResult3,TResult4,TResult5,TResult6>,處理常式主體中使用 TypedResults,如下列範例所示:
app.MapGet("/book/{id}", Results<Ok<Book>, NotFound> (int id, List<Book> bookList) => { return bookList.FirstOrDefault((i) => i.Id == id) is Book book ? TypedResults.Ok(book) : TypedResults.NotFound(); });Results<TResult1,TResult2,TResultN>聯合類型聲明路由處理程序會返回多個實作IResult的具體類型,其中任何實作IEndpointMetadataProvider的類型都將為端點的中繼資料做出貢獻。聯合型別會實作隱含轉換運算子。 這些運算子可讓編譯器自動將泛型引數中指定的型別轉換為聯合型別的實例。 此功能的額外優點是提供編譯時檢查,以確保路由處理常式僅傳回它宣告的結果。 嘗試傳回未宣告為其中一個泛型引數的型別至
Results<TResult1,TResult2,TResultN>會產生編譯錯誤。
從生成的文件中排除端點
根據預設,應用程式中定義的所有端點都會記錄在產生的 OpenAPI 檔案中,但可以使用屬性或擴充方法從檔中排除端點。
指定應排除之端點的機制取決於所開發的應用程式類型。
最小 API 支援從 OpenAPI 檔排除指定端點的兩種策略:
下列範例示範從產生的 OpenAPI 文件中排除指定端點的不同策略。
app.MapGet("/extension-method", () => "Hello world!")
.ExcludeFromDescription();
app.MapGet("/attributes",
[ExcludeFromDescription]
() => "Hello world!");
包含用於數據類型的 OpenAPI 元數據
要求或回應主體中使用的 C# 類別或記錄會以所產生 OpenAPI 文件的結構描表示。
預設情況下,架構中只 public 表示屬性,但也可 JsonSerializerOptions 為欄位建立結構屬性。
當 PropertyNamingPolicy 設定為駝峰箱(這是 ASP.NET 網頁應用程式的預設值),結構中的屬性名稱即為類別或記錄屬性名稱的駝峰箱形式。
[JsonPropertyName] 可用於個別屬性,以指定結構描述中的屬性名稱。
類型和格式
JSON 結構描述程式庫會將標準 C# 類型對應至 OpenAPI type 與 format,如下所示:
| C# 類型 | 開放API type |
開放API format |
|---|---|---|
| int | 整數 | int32 |
| long | 整數 | int64 |
| short | 整數 | int16 |
| 位元組 | 整數 | uint8 |
| float | number | float |
| double | number | double |
| 十進位 | number | double |
| bool | boolean | |
| 字串 | 字串 | |
| Char | 字串 | Char |
| byte[] | 字串 | 位元組 |
| DateTimeOffset | 字串 | date-time |
| DateOnly | 字串 | date |
| TimeOnly | 字串 | time |
| Uri | 字串 | uri |
| Guid | 字串 | uuid |
| 物件 | omitted | |
| dynamic | omitted |
請注意,物件和動態類型在 OpenAPI 中 沒有定義類型 ,因為它們可以包含任何類型的數據,包括 int 或 string 等基本類型。
type和format也可以用結構轉換器來設定。 例如,您可能想要把 format 的十進位類型,用 decimal 來取代 double。
使用屬性新增元數據
ASP.NET 利用類別或記錄屬性的元資料,來設定產生結構對應屬性的元資料。
下表總結了命名空間中提供產生結構元資料的屬性 System.ComponentModel 。
| Attribute | Description |
|---|---|
[Description] |
設定 description 架構中的屬性。 |
[Required] |
請將結構描述中的屬性標示為 required。 |
[DefaultValue] |
設定結構描述中屬性的 default 值。 |
[Range] |
設定整數或數字的 minimum 和 maximum 值。 |
[MinLength] |
設定字串的 minLength 或陣列的 minItems。 |
[MaxLength] |
設定字串的 maxLength 或陣列的 maxItems。 |
[RegularExpression] |
設定 pattern 字串。 |
請注意,在控制器型應用程式中,這些屬性會將篩選新增至作業,以驗證任何傳入的資料是否符合條件約束。 在最小 API 中,這些屬性會在產生的結構描述中設定中繼資料,但必須透過端點篩選、路由處理常式邏輯或透過第三方套件明確執行驗證。
屬性也可以放在記錄定義的參數清單中,但必須包含 property 修飾詞。 例如:
public record Todo(
[property: Required]
[property: Description("The unique identifier for the todo")]
int Id,
[property: Description("The title of the todo")]
[property: MaxLength(120)]
string Title,
[property: Description("Whether the todo has been completed")]
bool Completed
) {}
其他產生的結構描述的中繼資料來源
required
在類別、結構體或記錄中,帶有 [Required] 屬性或 required 修飾符的屬性總是 required 在對應的結構中。
您也可以根據類別、結構或記錄的建構函式(隱含和明確)來要求其他屬性。
- 對於具有單一公用建構函式的類別或記錄類別,在對應的架構中,任何作為建構函式參數且名稱和類型相同的屬性(不區分大小寫比對)都是必需的。
- 對於具有多個公用建構函式的類別或記錄類別,不需要其他屬性。
- 對於結構或記錄結構,不需要其他屬性,因為 C# 一律會定義結構的隱含無參數建構函式。
列舉
C# 中的列舉類型是以整數為基礎,但可以使用 JSON 中的 [JsonConverter] 和 JsonStringEnumConverter 表示為字串。 當列舉類型在 JSON 中以字串表示時,產生的結構描述將具有列舉字串值的 enum 屬性。
下列範例示範如何使用 JsonStringEnumConverter 來將列舉表示為 JSON 中的字串:
[JsonConverter(typeof(JsonStringEnumConverter<DayOfTheWeekAsString>))]
public enum DayOfTheWeekAsString
{
Sunday,
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday
}
特殊案例是當列舉類型具有 [Flags] 屬性時,表示列舉可以視為位字段,也就是一組旗標。 在生成的架構中,具有 [JsonConverterAttribute] 的旗標列舉被定義為 type: string,不含 enum 屬性,這是由於其值可以是列舉值的任意組合。 例如,下列列舉:
[Flags, JsonConverter(typeof(JsonStringEnumConverter<PizzaToppings>))]
public enum PizzaToppings { Pepperoni = 1, Sausage = 2, Mushrooms = 4, Anchovies = 8 }
可能有值,例如"Pepperoni, Sausage"或"Sausage, Mushrooms, Anchovies"。
不含 [JsonConverter] 的列舉類型會在產生的結構描述中定義為 type: integer。
注意:[AllowedValues] 屬性不會設定某個屬性的 enum 值。
可為 Null
在產生的結構描述中,定義為可為 Null 的值或參考型別的屬性會出現 nullable: true。 這與 System.Text.Json 反序列化器的預設行為一致,它接受 null 作為可為 NULL 的屬性的有效值。
additionalProperties
結構定義預設不會產生 additionalProperties 判斷提示,這表示 true 的預設值。 這與 System.Text.Json 還原序列化程式的預設行為一致,它會默默忽略 JSON 物件中的其他屬性。
如果結構描述的其他屬性應該只有特定類型的值,請將屬性或類別定義為 Dictionary<string, type>。 字典的索引鍵類型必須是 string。 這會產生結構描述,其中 additionalProperties 指定 "type" 的結構描述為必要的實值類型。
多態型
使用父類別上的 [JsonPolymorphic] 和 [JsonDerivedType] 屬性來指定多型類型的歧視性字段和子類型。
[JsonDerivedType] 將鑑別子欄位新增到每個子類別的結構模式中,並通過列舉來指定每個子類別的特定鑑別子值。 這個屬性也會修改每個衍生類別的建構函式,以設定鑑別子值。
具有 [JsonPolymorphic] 屬性的抽象類別具有 discriminator 結構描述中的欄位,但具有 [JsonPolymorphic] 屬性的實體類別沒有 discriminator 欄位。 OpenAPI 要求鑑別子屬性是結構描述中的必要屬性,但由於實體基底類別中未定義鑑別子屬性,所以結構描述不能包含 discriminator 欄位。
使用架構轉換器新增元數據
結構描述轉換器可用來覆寫任何預設的中繼資料,或在生成的結構描述中新增其他中繼資料,例如 example 值。 如需詳細資訊,請參閱使用結構描述轉換器。