ASP.NET Core Web API 中的自訂格式器Custom formatters in ASP.NET Core Web API

作者:Tom DykstraBy Tom Dykstra

ASP.NET Core MVC 內建支援在 Web API 中使用 JSON 或 XML 的資料交換。ASP.NET Core MVC has built-in support for data exchange in web APIs by using JSON or XML. 本文說明如何藉由建立自訂的格式器來新增對其他格式的支援。This article shows how to add support for additional formats by creating custom formatters.

檢視或下載範例程式碼 (英文) (如何下載)View or download sample code (how to download)

自訂格式器的使用時機When to use custom formatters

如果您希望內容交涉程序支援某些內容類型,但內建的格式器 (JSON 和 XML) 不支援這些內容類型時,即可使用自訂的格式器。Use a custom formatter when you want the content negotiation process to support a content type that isn't supported by the built-in formatters (JSON and XML).

例如,您有些 Web API 用戶端可以處理 Protobuf 格式,因此您希望搭配使用 Protobuf 與這些用戶端,以便更有效率。For example, if some of the clients for your web API can handle the Protobuf format, you might want to use Protobuf with those clients because it's more efficient. 或者,您可能會想要 Web API 以 vCard 格式 (其為交換連絡人資料的常用格式) 來傳送連絡人名稱和地址。Or you might want your web API to send contact names and addresses in vCard format, a commonly used format for exchanging contact data. 本文提供的範例應用程式會實作簡單的 vCard 格式器。The sample app provided with this article implements a simple vCard formatter.

如何使用自訂格式器的概觀Overview of how to use a custom formatter

自訂格式器的建立與使用步驟如下:Here are the steps to create and use a custom formatter:

  • 如果您想要將資料序列化以傳送到用戶端,請建立輸出格式器類別。Create an output formatter class if you want to serialize data to send to the client.
  • 如果您想要將從用戶端接收的資料還原序列化,請建立輸入格式器類別。Create an input formatter class if you want to deserialize data received from the client.
  • 將您的格式器執行個體新增至 MvcOptions 中的 InputFormattersOutputFormatters 集合。Add instances of your formatters to the InputFormatters and OutputFormatters collections in MvcOptions.

下列各節將提供每個步驟的指引與程式碼範例。The following sections provide guidance and code examples for each of these steps.

如何建立自訂格式器類別How to create a custom formatter class

若要建立格式器:To create a formatter:

  • 請從適當的基底類別衍生類別。Derive the class from the appropriate base class.
  • 在建構函式中指定有效的媒體類型和編碼方式。Specify valid media types and encodings in the constructor.
  • 覆寫 CanReadType/CanWriteType 方法Override CanReadType/CanWriteType methods
  • 覆寫 ReadRequestBodyAsync/WriteResponseBodyAsync 方法Override ReadRequestBodyAsync/WriteResponseBodyAsync methods

從適當的基底類別衍生Derive from the appropriate base class

如需文字媒體類型 (例如 vCard),請從 TextInputFormatterTextOutputFormatter 基底類別來衍生。For text media types (for example, vCard), derive from the TextInputFormatter or TextOutputFormatter base class.

public class VcardOutputFormatter : TextOutputFormatter

若需要輸入格式器範例,請參考範例應用程式For an input formatter example, see the sample app.

如需二進位類型,請從 InputFormatterOutputFormatter 基底類別來衍生。For binary types, derive from the InputFormatter or OutputFormatter base class.

指定有效的媒體類型和編碼方式Specify valid media types and encodings

在建構函式中,您可以新增 SupportedMediaTypesSupportedEncodings 集合,以指定有效的媒體類型和編碼方式。In the constructor, specify valid media types and encodings by adding to the SupportedMediaTypes and SupportedEncodings collections.

public VcardOutputFormatter()
{
    SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("text/vcard"));

    SupportedEncodings.Add(Encoding.UTF8);
    SupportedEncodings.Add(Encoding.Unicode);
}

若需要輸入格式器範例,請參考範例應用程式For an input formatter example, see the sample app.

注意

您無法在格式器類別中執行建構函式的相依性插入。You can't do constructor dependency injection in a formatter class. 舉例來說,您無法藉由將記錄器參數新增至建構函式,而取得記錄器。For example, you can't get a logger by adding a logger parameter to the constructor. 若要存取服務,您必須使用可傳入方法的內容物件。To access services, you have to use the context object that gets passed in to your methods. 下列程式碼範例會示範這項操作。A code example below shows how to do this.

覆寫 CanReadType/CanWriteTypeOverride CanReadType/CanWriteType

您可以覆寫 CanReadTypeCanWriteType 方法,以指定要序列化或還原序列化的類型。Specify the type you can deserialize into or serialize from by overriding the CanReadType or CanWriteType methods. 例如,您可能只能透過 Contact 類型建立 vCard 文字,反之亦然。For example, you might only be able to create vCard text from a Contact type and vice versa.

protected override bool CanWriteType(Type type)
{
    if (typeof(Contact).IsAssignableFrom(type) 
        || typeof(IEnumerable<Contact>).IsAssignableFrom(type))
    {
        return base.CanWriteType(type);
    }
    return false;
}

若需要輸入格式器範例,請參考範例應用程式For an input formatter example, see the sample app.

CanWriteResult 方法The CanWriteResult method

在某些情況下,您必須覆寫 CanWriteResult 而不是 CanWriteTypeIn some scenarios you have to override CanWriteResult instead of CanWriteType. 如果符合下列所有條件,請使用 CanWriteResultUse CanWriteResult if the following conditions are true:

  • 您的動作方法會傳回模型類別。Your action method returns a model class.
  • 在執行階段期間,可能會傳回衍生的類別。There are derived classes which might be returned at runtime.
  • 您必須知道在執行階段期間,動作傳回的衍生類別是哪一個。You need to know at runtime which derived class was returned by the action.

例如,假設您的動作方法簽章傳回 Person 類型,但它可能會傳回衍生自 PersonStudentInstructor 類型。For example, suppose your action method signature returns a Person type, but it may return a Student or Instructor type that derives from Person. 如果您希望格式器只處理 Student 物件,請檢查您提供給 CanWriteResult 方法之內容物件中的物件類型。If you want your formatter to handle only Student objects, check the type of Object in the context object provided to the CanWriteResult method. 請注意,當動作方法傳回 IActionResult 時,您不需要使用 CanWriteResult;在此情況下,CanWriteType 方法會接收執行階段類型。Note that it's not necessary to use CanWriteResult when the action method returns IActionResult; in that case, the CanWriteType method receives the runtime type.

覆寫 ReadRequestBodyAsync/WriteResponseBodyAsyncOverride ReadRequestBodyAsync/WriteResponseBodyAsync

您可以在 ReadRequestBodyAsyncWriteResponseBodyAsync 中進行還原序列化或序列化的實際工作。You do the actual work of deserializing or serializing in ReadRequestBodyAsync or WriteResponseBodyAsync. 下列範例中,醒目標示的程式碼行示範如何透過相依性插入容器以取得服務 (您無法透過建構函式參數來取得)。The highlighted lines in the following example show how to get services from the dependency injection container (you can't get them from constructor parameters).

public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding)
{
    IServiceProvider serviceProvider = context.HttpContext.RequestServices;
    var logger = serviceProvider.GetService(typeof(ILogger<VcardOutputFormatter>)) as ILogger;

    var response = context.HttpContext.Response;

    var buffer = new StringBuilder();
    if (context.Object is IEnumerable<Contact>)
    {
        foreach (Contact contact in context.Object as IEnumerable<Contact>)
        {
            FormatVcard(buffer, contact, logger);
        }
    }
    else
    {
        var contact = context.Object as Contact;
        FormatVcard(buffer, contact, logger);
    }
    await response.WriteAsync(buffer.ToString());
}

private static void FormatVcard(StringBuilder buffer, Contact contact, ILogger logger)
{
    buffer.AppendLine("BEGIN:VCARD");
    buffer.AppendLine("VERSION:2.1");
    buffer.AppendFormat($"N:{contact.LastName};{contact.FirstName}\r\n");
    buffer.AppendFormat($"FN:{contact.FirstName} {contact.LastName}\r\n");
    buffer.AppendFormat($"UID:{contact.ID}\r\n");
    buffer.AppendLine("END:VCARD");
    logger.LogInformation($"Writing {contact.FirstName} {contact.LastName}");
}

若需要輸入格式器範例,請參考範例應用程式For an input formatter example, see the sample app.

如何設定 MVC 以使用自訂格式器How to configure MVC to use a custom formatter

若要使用自訂格式器,請將格式器類別的執行個體新增至 InputFormattersOutputFormatters 集合。To use a custom formatter, add an instance of the formatter class to the InputFormatters or OutputFormatters collection.

services.AddMvc(options =>
{
    options.InputFormatters.Insert(0, new VcardInputFormatter());
    options.OutputFormatters.Insert(0, new VcardOutputFormatter());
})
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

系統會依據您插入格式器的順序進行評估。Formatters are evaluated in the order you insert them. 第一個會優先使用。The first one takes precedence.

後續步驟Next steps

BEGIN:VCARD
VERSION:2.1
N:Davolio;Nancy
FN:Nancy Davolio
UID:20293482-9240-4d68-b475-325df4a83728
END:VCARD

若要查看 vCard 的輸出,請執行應用程式,並使用 Accept 標頭 "text/vcard" 將 Get 要求傳送給 http://localhost:63313/api/contacts/ (從 Visual Studio 執行時) 或 http://localhost:5000/api/contacts/ (從命令列執行時)。To see vCard output, run the application and send a Get request with Accept header "text/vcard" to http://localhost:63313/api/contacts/ (when running from Visual Studio) or http://localhost:5000/api/contacts/ (when running from the command line).

若要將 vCard 新增至連絡人的記憶體內部集合,請使用 Content-Type 標頭 "text/vcard" 與主體中的 vCard 文字 (如上述範例的格式),將 Post 要求傳送至相同的 URL。To add a vCard to the in-memory collection of contacts, send a Post request to the same URL, with Content-Type header "text/vcard" and with vCard text in the body, formatted like the example above.