【WebAPI No.5】Core WebAPI中的自定義格式化
介紹
Web API為JSON和XML提供媒體型別格式化程式。框架預設將這些格式化程式插入管道中。客戶端可以在HTTP請求的Accept標頭中請求JSON或XML.
格式化資料這個東西,其實沒有什麼最好的資料,要看各種場景,最適合才是最好的,不是說json就比xml好,容易解析什麼的等。
廢話不多說了,概念的東西大家一百度一大堆。開始我們的正文吧,當然首先我們還是要建立一個WebAPI專案,不會建立請返回第一章:如何建立簡單的WebAPI專案
控制器的返回型別
特定型別:
首先我們最熟悉的就是特定型別了,比如stting或自定義物件型別等。就例如模版控制器的就是返回一個字串陣列型別:
[HttpGet] public ActionResult<IEnumerable<string>> Get() { return new string[] { "value1", "value2" }; }
這種的示例沒有已知條件,直接返回特定型別即可了 ,但是有些操作我們需要考慮已知的條件,這時候會返回多個返回結果。根據不同的條件返回對應結果,下面我們來看一下IActionResult 型別。
IActionResult 型別:
在多種情況條件返回多個不同結果時, 要支援此類操作,必須使用 IActionResult 或 ActionResult<T>。ActionResult型別表示多種的HTTP狀態碼。屬於此類別的一些常見返回型別包括:
在返回多個型別的時候我們如何返回不同的型別哪,我們可以藉助【ProducesResponseType】特性來幫助我們實現返回自定義多個型別。下面我們寫個簡單的get方法的同步和非同步的示例:
同步示例:
返回兩個情況當id為5我給你正確返回,不是5我就找不到。當然實際情況肯定不是這個樣子,但是就是打一個找資源的例子,找到就返回,找不到就返回404,。
[HttpGet("{id}")] [ProducesResponseType(200, Type = typeof(Person))] [ProducesResponseType(404View Code)] public IActionResult Get(int id) { if (id == 5) { return Ok(new Person { Id = "001", name = "姓名1", age = 18, Birthday = DateTime.Now, introduce = "介紹001" }); } else { return NotFound(); } }
下面是非同步的方法:
[HttpGet("{id}")] [ProducesResponseType(200, Type = typeof(Person))] [ProducesResponseType(400)] public async Task<IActionResult> Get(int id) { if (id == 5) { await Task.Run(()=>System.Threading.Thread.Sleep(1000)); return Ok(new Person { Id = "001", name = "姓名1", age = 18, Birthday = DateTime.Now, introduce = "介紹001" }); } else { return BadRequest(); } }View Code
CreatedAtAction方法:建立一個CreatedAtActionResult物件,該物件生成Status201Created
下面我們看一下請求結果:
id不是5的時候返回找不到:
id為5的時候正常返回咱們的物件:
ActionResult<T> 型別:
這個型別是從ASP.NET Core 2.1引入的,所有使用前請看下版本哦。它支援返回從 ActionResult 派生的型別或返回特定型別。 ActionResult<T>
通過 IActionResult 型別可提供以下優勢:
- 可排除 [ProducesResponseType] 特性的
Type
屬性 - 隱式強制轉換運算子支援將
T
和ActionResult
均轉換為ActionResult<T>
。 將T
轉換為 ObjectResult,也就是將return new ObjectResult(T);
簡化為return T,什麼個意思哪,說白了就是在定義的時候指定了型別直接return就可以了。
返回響應補充:
- OK:建立一個OkResult物件,該物件生成一個空的Status200OK響應。
- NoContentResult:沒有內容,為響應建立的NoContentResult物件
-
NotFound():沒有找到,為響應建立的NotFoundResult。
-
PhysicalFile(string【檔案的路徑】,string【內容型別】):返回由
physicalPath
(Status200OK)指定的檔案。 - Redirect(string【url】):重定向,為響應建立的RedirectResult。
- StatusCode(int【返回的狀態碼】,object【值】):自定義返回狀態,切附帶返回值。其實是為響應建立了ObjectResult物件。
我就寫部分常用的其他的有興趣可以去官網瞭解一下:返回狀態相應
自定義格式化程式
我們都知道WebAPI因為MVC的內建所以預設支援了json,xml和文字格式。那麼我們想使用其他格式怎麼辦哪,微軟總是不會讓我們失望,我們可以自定義啊。
首先建立自定義格式化程式大致步驟:
- 從相應的基類中派生類。
- 在建構函式中指定有效的媒體型別和編碼。
- 重寫
CanReadType
/CanWriteType
方法 - 重寫
ReadRequestBodyAsync
/WriteResponseBodyAsync
方法
從相應的基類中派生:
從那些類中派生官方給的解釋是;
對於文字媒體型別(例如,vCard),從 TextInputFormatter 或 TextOutputFormatter 基類派生。
對於二進位制型別,從 InputFormatter 或 OutputFormatter 基類派生。
例如官方示例:
public class VcardOutputFormatter : TextOutputFormatter
指定有效的媒體型別和編碼
在建構函式中,通過新增到 SupportedMediaTypes
和 SupportedEncodings
集合來指定有效的媒體型別和編碼。
public VcardOutputFormatter() { SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("text/vcard")); SupportedEncodings.Add(Encoding.UTF8); SupportedEncodings.Add(Encoding.Unicode); }
重寫 CanReadType/CanWriteType
通過重寫 CanReadType
或 CanWriteType
方法,指定可反序列化為或從其序列化的型別。 例如,可能只能從 Contact
型別建立 vCard 文字,反之亦然。
protected override bool CanWriteType(Type type) { if (typeof(Contact).IsAssignableFrom(type) || typeof(IEnumerable<Contact>).IsAssignableFrom(type)) { return base.CanWriteType(type); } return false; }
CanWriteResult方法不一定必須重寫,但是有時候確實必須的,必須重寫官方給的解釋是;
- 操作方法返回模型類。
- 具有可能在執行時返回的派生類。
- 需要知道操作在執行時返回了哪個派生類。
簡單的意思是如果你返回的型別是父類的話,但是實際返回值可能存在子型別的返回且子型別為多個。但是你僅僅希望處理其中一個子型別的返回。這個時候可以使用CanWriteResult提供的上下文來檢查物件型別。
重寫 ReadRequestBodyAsync/WriteResponseBodyAsync
實際的反序列化或序列化工作在 ReadRequestBodyAsync
或 WriteResponseBodyAsync
中執行。 以下示例中突出顯示的行展示瞭如何從依賴關係注入容器中獲取服務(不能從建構函式引數中獲取它們)
public override 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); } return response.WriteAsync(buffer.ToString()); }View Code
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}"); }View Code