前言
我們講過ASP.NET Core Web APi路由綁定,本節我們來講講如何獲取客戶端請求過來的內容。
ASP.NET Core Web APi捕獲Request.Body內容
[HttpPost]
[Route("api/blog/jsonstring")]
public string Index([FromBody] string content)
{
return content;
}
//或者
[HttpPost("api/blog/jsonstring")]
public string Index([FromBody] string content)
{
return content;
}
由上圖我們能夠看到發出的為Post請求且Content-Type為application/json,所以此時在后台接受請求需要通過【FromBody】特性接受來自Post請求中的Body內容。這里需要特別說明的是:當在Vue利用axios發出Post請求且參數為簡單類型參數時,但是在后台只能利用對象接收,即不能按照如下形式接收參數。
[HttpPost("api/blog/jsonstring")]
public int Index([FromBody] int id)
{
return id;
}
對於簡單類型參數此時無需額外再定義對象來接收,我們可以采用變通的方式來接收即動態對象。
[HttpPost("api/blog/jsonstring")]
public int Index([FromBody] dynaminc dy)
{
var id = dy.id as int;
return id;
}
//或者
[HttpPost("api/blog/jsonstring")]
public int Index([FromBody] JObject jo)
{
JToken token = jo["id"];
if(token.Type == JTokenType.Null)
{
// Do your logic
}
......
}
上述兩種方式皆可,利用JObject好處在於可判斷參數是否為空情況,而dynamic可能會出現異常。如果我們想發送一個RAW字符串或者二進制數據,並且想要把它作為請求的一部分,那么這個時候就有意思,同時事情也會變得更加復雜。因為ASP.NET Core只會處理它所知道的信息,默認情況下是JSON和Form數據。 默認情況下原始數據不能直接映射到控制器上的方法參數上。什么意思呢?接下來我們若改變Content-Type為text/plain,此時將返回狀態為415,如下圖
那么對於此種情況,我們該如何獲取請求參數呢?不幸的是,ASP NET Core不允許我們僅僅通過方法參數以任何有意義的方式捕獲“原始”數據。因此我們需要通過處理Request.Body來獲取原始數據,然后反序列化它。
我們可以捕獲原始的Request.Body並從原始緩沖區中讀取參數。最簡而有效的方法是接受不帶參數的POST或PUT數據,然后從Request.Body讀取原始數據:
[HttpPost("api/blog/jsonstring")]
public async Task<string> Index()
{
var result = string.Empty;
using (var reader = new StreamReader(Request.Body, Encoding.UTF8))
{
result = await reader.ReadToEndAsync();
}
return result;
}
若我們想要讀取二進制數據,我們可如下操作。
[HttpPost("api/blog/bytes")]
public async Task<byte[]> RawBinaryData()
{
using (var ms = new MemoryStream(2048))
{
await Request.Body.CopyToAsync(ms);
return ms.ToArray();
}
}
代碼中的結果被捕獲為二進制字節並以JSON返回,這也就是為什么我們會從上圖看到base64編碼的結果字符串偽裝成二進制結果的原因。
像上述操作若很頻繁我們完全可以封裝起來,比如第三方調用我們接口時。封裝如下:
public static class HttpRequestExtensions
{
public static async Task<string> GetRawBodyStringAsync(this HttpRequest request, Encoding encoding = null)
{
if (encoding is null)
encoding = Encoding.UTF8;
using (var reader = new StreamReader(request.Body, encoding))
return await reader.ReadToEndAsync();
}
public static async Task<byte[]> GetRawBodyBytesAsync(this HttpRequest request)
{
using (var ms = new MemoryStream(2048))
{
await request.Body.CopyToAsync(ms);
return ms.ToArray();
}
}
}
如果我們期望對於原始參數使用確定的方法,那么我們還需要做更多額外的工作。也就是說需要自定義InputFormatter。在ASP.NET Core中通過使用InputFormatter來自定義格式化內容,並將自定義格式化內容注入到請求ASP.NET Core管道中,並根據特定參數類型來決定是否需要處理請求內容。最終請求運行通過則將請求主體進行反序列化。對於自定義InputFormatter需要滿足以下兩個條件。
(1)必須使用[FromBody]特性來觸發它。
(2) 對於對應的請求內容需自定義對應處理。
public class HandleRequestBodyFormatter : InputFormatter
{
public HandleRequestBodyFormatter()
{
SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/plain"));
SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/octet-stream"));
}
/// <summary>
/// 允許 text/plain, application/octet-stream和沒有Content-Type的參數類型解析到原始數據
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public override Boolean CanRead(InputFormatterContext context)
{
if (context == null) throw new ArgumentNullException(nameof(context));
var contentType = context.HttpContext.Request.ContentType;
if (string.IsNullOrEmpty(contentType) || contentType == "text/plain" ||
contentType == "application/octet-stream")
return true;
return false;
}
/// <summary>
/// 處理text/plain或者沒有Content-Type作為字符串結果
/// 處理application/octet-stream類型作為byte[]數組結果
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public override async Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context)
{
var request = context.HttpContext.Request;
var contentType = context.HttpContext.Request.ContentType;
if (string.IsNullOrEmpty(contentType) || contentType == "text/plain")
{
using (var reader = new StreamReader(request.Body))
{
var content = await reader.ReadToEndAsync();
return await InputFormatterResult.SuccessAsync(content);
}
}
if (contentType == "application/octet-stream")
{
using (var ms = new MemoryStream(2048))
{
await request.Body.CopyToAsync(ms);
var content = ms.ToArray();
return await InputFormatterResult.SuccessAsync(content);
}
}
return await InputFormatterResult.FailureAsync();
}
}
上述自定義InputFormatter使用CanRead方法來檢查要支持的請求內容類型,然后使用ReadRequestBodyAsync方法將內容進行讀取並反序列化為類型結果,該結果類型在控制器中的方法參數中返回。
最后需要做的則是將我們自定義的InputFormatter注入到MVC請求管道中一切大功告成。
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(o => o.InputFormatters.Insert(0, new HandleRequestBodyFormatter()));
}
通過對請求輸入參數的自定義格式化處理,我們現在可以使用來自客戶端Post或者Put請求且類型為text/plain, application/octet-stream或者沒有類型。。下面我們來通過例子說明。
[HttpPost("api/blog/rawstring")]
public string RawRequestBodyFormatter([FromBody] string rawString)
{
return rawString;
}
ASP.NET Core自身本來就支持application/json類型,我們通過自定義InputFormatter不過是多了對text/plain額外類型的支持而已。
[HttpPost("api/blog/rawbytes")]
public byte[] RawBytesFormatter([FromBody] byte[] rawData)
{
return rawData;
}
總結
本文比較詳細的講解了在ASP.NET Core WebAPi中如何獲取請求原始Body中的參數並添加了額外類型的支持,希望對閱讀本文的您有所幫助。