ASP.NET Core Web APi獲取原始請求內容


前言

我們講過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中的參數並添加了額外類型的支持,希望對閱讀本文的您有所幫助。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM