国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

由ASP.NET Core讀取Response.Body引發(fā)的思考

這篇具有很好參考價(jià)值的文章主要介紹了由ASP.NET Core讀取Response.Body引發(fā)的思考。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問。

前言

????前幾天有群友在群里問如何在我之前的文章《ASP.NET Core WebApi返回結(jié)果統(tǒng)一包裝實(shí)踐》的時(shí)候有點(diǎn)疑問,主要的疑問點(diǎn)就是關(guān)于Respouse的讀取的問題。在之前的文章《深入探究ASP.NET Core讀取Request.Body的正確方式》曾分析過關(guān)于Request的讀取問題,需要讀取Response的場(chǎng)景同樣經(jīng)常遇到,比如讀取輸出信息或者包裝一下輸出結(jié)果等。無獨(dú)有偶Response的讀取同樣存在類似的問題,本文我們便來分析一下如何進(jìn)行Response的Body讀取。

使用方式

我們?cè)谌粘5氖褂弥惺侨绾巫x取流呢?很簡單,直接使用StreamReader去讀取,方式如下

public override void OnResultExecuted(ResultExecutedContext context)
{
    //操作流之前恢復(fù)一下操作位
    context.HttpContext.Response.Body.Position = 0;

    StreamReader stream = new StreamReader(context.HttpContext.Response.Body);
    string body = stream.ReadToEnd();
    _logger.LogInformation("body content:" + body);

    context.HttpContext.Response.Body.Position = 0;
    base.OnResultExecuted(context);
}

代碼很簡單,直接讀取即可,可是這樣讀取是有問題的會(huì)拋出異常System.ArgumentException:“Stream was not readable.”異常信息就是的意思是當(dāng)前Stream不可讀,也就是Respouse的Body是不可以被讀取的。關(guān)于StreamReader到底和Stream有啥關(guān)聯(lián),我們?cè)谥暗奈恼律钊胩骄緼SP.NET Core讀取Request.Body的正確方式一文中有過源碼分析,這里就不在贅述了,有興趣的同學(xué)可以自行翻閱,強(qiáng)烈建議在閱讀本文之前可以看一下那篇文章,方便更容易了解。
如何解決上面的問題呢?方式也很簡單,比如你想在你的程序中保證Response的Body都是可讀的,你可以定義一個(gè)中間件解決這個(gè)問題。

public static IApplicationBuilder UseResponseBodyRead(this IApplicationBuilder app)
{
    return app.Use(async (context, next) =>
    {
        //獲取原始的Response Body
        var originalResponseBody = context.Response.Body;
        try
        {
            //聲明一個(gè)MemoryStream替換Response Body
            using var swapStream = new MemoryStream();
            context.Response.Body = swapStream;
            await next(context);
            //重置標(biāo)識(shí)位
            context.Response.Body.Seek(0, SeekOrigin.Begin);
            //把替換后的Response Body復(fù)制到原始的Response Body
            await swapStream.CopyToAsync(originalResponseBody);
        }
        finally
        {
            //無論異常與否都要把原始的Body給切換回來
            context.Response.Body = originalResponseBody;
        }
    });
}

本質(zhì)就是先用一個(gè)可操作的Stream比如咱們這里的MemoryStream替換默認(rèn)的ResponseBody,讓后續(xù)對(duì)ResponseBody的操作都是針對(duì)新的ResponseBody進(jìn)行操作,完成之后把替換后的ResponseBody復(fù)制到原始的ResponseBody。最終無論異常與否都要把原始的Body給切換回來。需要注意的是,這個(gè)中間件的位置盡量要放在比較靠前的位置注冊(cè),至少也要保證在你所有要操作ResponseBody之前的位置注冊(cè)。如下所示

var app = builder.Build();
app.UseResponseBodyRead();

源碼探究

通過上面我們了解到了ResponseBody是不可以被讀取的,至于為什么呢,這個(gè)我們需要通過相關(guān)源碼了解一下。通過HttpContext類的源碼我們可以看到相關(guān)定義

public abstract class HttpContext
{
    public abstract HttpResponse Response { get; }
}

這里看到HttpContext本身是個(gè)抽象類,看一下它的屬性HttpResponse類的定義也是一個(gè)抽象類

public abstract class HttpResponse
{
}

由上面可知Response屬性是抽象的,所以抽象類HttpResponse必然包含一個(gè)子類去實(shí)現(xiàn)它,否則沒辦法直接操作相關(guān)方法。這里我們介紹一個(gè)網(wǎng)站https://source.dot.net用它可以更輕松的閱讀微軟類庫的源碼,比如CLR、ASP.NET Core、EF Core等等,雙擊一個(gè)類或者屬性方法可以查找引用和定義它們的地方,非常方便,它的源碼都是最新版本的,來源就是GitHub上的相關(guān)倉庫。找到實(shí)例化HttpResponse的為位置在HttpContext的子類DefaultHttpContext類中[點(diǎn)擊查看源碼??]

public sealed class DefaultHttpContext : HttpContext
{
    private readonly DefaultHttpRequest _request;
    private readonly DefaultHttpResponse _response;

    public DefaultHttpContext(IFeatureCollection features)
    {
        _features.Initalize(features);
        _request = new DefaultHttpRequest(this);
        _response = new DefaultHttpResponse(this);
    }

    public override HttpRequest Request => _request;
    public override HttpResponse Response => _response;
}

防止大家比較繞解釋一下,因?yàn)?code>HttpContext是抽象類,它包含了抽象屬性HttpResponse類型的屬性Response,所以HttpContext必然有子類去集成它,由于HttpResponse也是抽象類,所以也必須包含了子類去繼承它。

尋找HttpResponse Body定義

通過上面的代碼我們可以看到HttpResponse的子類為DefaultHttpResponse類。找到類中Body屬性定義的地方[點(diǎn)擊查看源碼??]看一下具體實(shí)現(xiàn)

internal sealed class DefaultHttpResponse : HttpResponse
{
    private static readonly Func<IFeatureCollection, IHttpResponseBodyFeature?> _nullResponseBodyFeature = f => null;

    private readonly DefaultHttpContext _context;
    private FeatureReferences<FeatureInterfaces> _features;

    public DefaultHttpResponse(DefaultHttpContext context)
    {
        _context = context;
        _features.Initalize(context.Features);
    }

    //在FeatureReferences<FeatureInterfaces>中取出ResponseBody的交互操作IHttpResponseBodyFeature
    private IHttpResponseBodyFeature HttpResponseBodyFeature => _features.Fetch(ref _features.Cache.ResponseBody, _nullResponseBodyFeature)!;

    //Body本身是Stream它是抽象類
    public override Stream Body
    {
        //在IHttpResponseBodyFeature實(shí)例中查找Stream
        get { return HttpResponseBodyFeature.Stream; }
        set
        {
            var otherFeature = _features.Collection.GetRequiredFeature<IHttpResponseBodyFeature>();

            if (otherFeature is StreamResponseBodyFeature streamFeature
                && streamFeature.PriorFeature != null
                && object.ReferenceEquals(value, streamFeature.PriorFeature.Stream))
            {
                _features.Collection.Set(streamFeature.PriorFeature);
                return;
            }

            _features.Collection.Set<IHttpResponseBodyFeature>(new StreamResponseBodyFeature(value, otherFeature));
        }
    }
}

Body本身是Stream但是Stream是抽象類,但是這里并沒有對(duì)Stream的子類直接進(jìn)行定義,而是引入了IHttpResponseBodyFeature去和Stream交互,主要原因還是因?yàn)镽esponseBody涉及到一個(gè)交互體系,比如包含PipeWriter、SendFile等操作。所以這里我們只能順著IHttpResponseBodyFeature的操作找到相關(guān)的實(shí)現(xiàn)類,通過查找引用關(guān)系我找到了實(shí)現(xiàn)類HttpProtocol[點(diǎn)擊查看源碼??]我們看一下它的定義

 internal partial class HttpProtocol : IFeatureCollection,
                                          IHttpRequestFeature,
                                          IHttpResponseFeature,
                                          IHttpResponseBodyFeature,
                                          IRouteValuesFeature,
                                          IEndpointFeature,
                                          IHttpRequestIdentifierFeature,
                                          IHttpRequestTrailersFeature,
                                          IHttpExtendedConnectFeature,
                                          IHttpUpgradeFeature,
                                          IRequestBodyPipeFeature,
                                          IHttpConnectionFeature,
                                          IHttpRequestLifetimeFeature,
                                          IHttpBodyControlFeature,
                                          IHttpMaxRequestBodySizeFeature,
                                          IHttpRequestBodyDetectionFeature,
                                          IHttpWebTransportFeature,
                                          IBadRequestExceptionFeature
{
    internal protected IHttpResponseBodyFeature? _currentIHttpResponseBodyFeature;
    private void FastReset()
    {
        //省略一部分代碼
        _currentIHttpResponseBodyFeature = this;
        //省略一部分代碼
    }
}

它實(shí)現(xiàn)了很多接口,其中包含了IHttpResponseBodyFeature接口和IFeatureCollection接口,這兩個(gè)接口在DefaultHttpResponse類中都有涉獵,是Response輸出的交互類,可以理解為Response類是門面,實(shí)際的操作都是調(diào)用的具體類。我們可以分析一下包含獲取具體類型實(shí)例的操作,第一個(gè)便是它的索引器操作

internal protected IHttpResponseBodyFeature? _currentIHttpResponseBodyFeature;
object? IFeatureCollection.this[Type key]
{
    get
    {
        object? feature = null;
        //省略一部分代碼
        if (key == typeof(IHttpResponseBodyFeature))
        {
            feature = _currentIHttpResponseBodyFeature;
        }
        //省略一部分代碼
        return feature ?? ConnectionFeatures?[key];
    }
    set
    {
        _featureRevision++;
        //省略一部分代碼
        if (key == typeof(IHttpResponseBodyFeature))
        {
            _currentIHttpResponseBodyFeature = (IHttpResponseBodyFeature?)value;
        }
        //省略一部分代碼
    }
}

它本身也提供Get和Set相關(guān)的類來操作和獲取具體的相關(guān)的類型

TFeature? IFeatureCollection.Get<TFeature>() where TFeature : default
{
    TFeature? feature = default;
    if (typeof(TFeature) == typeof(IHttpResponseBodyFeature))
    {
        feature = Unsafe.As<IHttpResponseBodyFeature?, TFeature?>(ref _currentIHttpResponseBodyFeature);
    }
    return feature;
}

void IFeatureCollection.Set<TFeature>(TFeature? feature) where TFeature : default
{
    _featureRevision++;
    if (typeof(TFeature) == typeof(IHttpResponseBodyFeature))
    {
        _currentIHttpResponseBodyFeature = Unsafe.As<TFeature?, IHttpResponseBodyFeature?>(ref feature);
    }
}

為什么會(huì)這樣的,相信大家已經(jīng)猜到了HttpProtocol實(shí)現(xiàn)了很多的接口,意味著它有很多接口的能力。提供的這幾個(gè)方法可以根據(jù)類型快速的獲取想得到的實(shí)例。因?yàn)樵?code>HttpProtocol定義了許多變量承載它實(shí)現(xiàn)的接口的變量來承載當(dāng)前實(shí)例,所以在DefaultHttpResponse看到了類似緩存的效果獲取具體接口的對(duì)應(yīng)實(shí)例。我們知道了HttpProtocol實(shí)現(xiàn)了IHttpResponseBodyFeature接口,所以我們?cè)?code>HttpProtocol類中查找給IHttpResponseBodyFeature的Stream屬性賦值的地方即可,通過上面HttpProtocol類的定義方式我們可以看到它是partial也就是部分類,在另一個(gè)部分類中找到了賦值的地方[點(diǎn)擊查看源碼??]

Stream IHttpResponseBodyFeature.Stream => ResponseBody;
PipeWriter IHttpResponseBodyFeature.Writer => ResponseBodyPipeWriter;

Stream IHttpResponseFeature.Body
{
    get => ResponseBody;
    set => ResponseBody = value;
}

通過這個(gè)代碼我們可以看到IHttpResponseBodyFeature.Stream來自ResponseBody屬性,找到給HttpProtocol屬性ResponseBody賦值的地方[點(diǎn)擊查看源碼??]

protected BodyControl? _bodyControl;
public Stream ResponseBody { get; set; } = default!;
public PipeWriter ResponseBodyPipeWriter { get; set; } = default!;
public void InitializeBodyControl(MessageBody messageBody)
{
    if (_bodyControl == null)
    {
        _bodyControl = new BodyControl(bodyControl: this, this);
    }

    (RequestBody, ResponseBody, RequestBodyPipeReader, ResponseBodyPipeWriter) = _bodyControl.Start(messageBody);
}

上面的代碼我們可以看到ResponseBody定義和賦值的地方,我們可以看到給ResponseBody賦值來自BodyControl實(shí)例的Start方法里這個(gè)方法傳遞的是當(dāng)前HttpProtocol實(shí)例,所以直接找到BodyControl.Start方法定義的地方[點(diǎn)擊查看源碼??]查看實(shí)現(xiàn)

internal sealed class BodyControl
{
    //HttpResponseStream
    private readonly HttpResponseStream _response;
    private readonly HttpResponsePipeWriter _responseWriter;

    private readonly HttpRequestPipeReader _requestReader;
    private readonly HttpRequestStream _request;

    public BodyControl(IHttpBodyControlFeature bodyControl, IHttpResponseControl responseControl)
    {
        _requestReader = new HttpRequestPipeReader();
        _request = new HttpRequestStream(bodyControl, _requestReader);

        _responseWriter = new HttpResponsePipeWriter(responseControl);
        //實(shí)例化HttpResponseStream的地方
        _response = new HttpResponseStream(bodyControl, _responseWriter);
    }

    public (Stream request, Stream response, PipeReader reader, PipeWriter writer) Start(MessageBody body)
    {
        //省略代碼
        if (body.RequestUpgrade)
        {
        //默認(rèn)走不到暫時(shí)忽略
        }
        else if (body.ExtendedConnect)
        {
        //默認(rèn)走不到暫時(shí)忽略
        }
        else
        {
            //默認(rèn)走到這里
            return (_request, _response, _requestReader, _responseWriter);
        }
    }
}

好了,饒了這么多的彎,我們水落石出了找到了HttpResponse.Body的最終來源來自HttpResponseStream類的實(shí)例。所以結(jié)論就是HttpResponse的Body是HttpResponseStream實(shí)例??偨Y(jié)一下

  • HttpResponse的Body是Stream類型的,在DefaultHttpResponse中并未給Body直接賦值,而是在IHttpResponseBodyFeature實(shí)例中獲取Stream屬性,這個(gè)類負(fù)責(zé)是ResponseBody相關(guān)的交互。
  • IHttpResponseBodyFeature的實(shí)現(xiàn)類是HttpProtocol,這是一個(gè)部分類。在這里IHttpResponseBodyFeature.Stream屬性來自HttpProtocol類ResponseBody屬性。
  • HttpProtocol類ResponseBody屬性賦值來自BodyControl的Start方法,它返回的是BodyControl類的_response屬性,這個(gè)屬性的是HttpResponseStream類型的。
  • 所以得到結(jié)論HttpResponse.Body也就是Stream類型的,來自HttpResponseStream類的實(shí)例。

HttpResponseStream類定義

上面饒了這么大的圈找到了HttpResponse.Body實(shí)例的類型HttpResponseStream類,找到類定義的地方看一下里面的實(shí)現(xiàn)[點(diǎn)擊查看源碼??]

internal sealed partial class HttpResponseStream : Stream
{
    //說明不支持讀,如果想知道流是否可讀可以使用這個(gè)屬性先判斷
    public override bool CanRead => false;
    //流不可查找
    public override bool CanSeek => false;
    //支持寫
    public override bool CanWrite => true;
    //不能獲取流的長度否則拋出異常
    public override long Length => throw new NotSupportedException(SR.net_noseek);

    //不可讀取和設(shè)置位置否則拋出異常
    public override long Position
    {
        get => throw new NotSupportedException(SR.net_noseek);
        set => throw new NotSupportedException(SR.net_noseek);
    }
    //不支持設(shè)置Seek否則拋出異常
    public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(SR.net_noseek);
    //不支持Length否則拋出異常
    public override void SetLength(long value) => throw new NotSupportedException(SR.net_noseek);
    //不支持讀取操作否則拋出異常
    public override int Read(byte[] buffer, int offset, int size) => throw new InvalidOperationException(SR.net_writeonlystream);
    //不支持讀讀相關(guān)的操作
    public override IAsyncResult BeginRead(byte[] buffer, int offset, int size, AsyncCallback? callback, object? state)
    {
        throw new InvalidOperationException(SR.net_writeonlystream);
    }
    public override int EndRead(IAsyncResult asyncResult) => throw new InvalidOperationException(SR.net_writeonlystream);

    //省略寫相關(guān)方法和釋放相關(guān)的方法,只看設(shè)計(jì)到讀相關(guān)的地方
}

通過HttpResponseStream類的定義我們可以看到,HttpResponseStream本身是Stream抽象類的子類。涉及到讀相關(guān)的方法是直接拋出異常,也就是最開始我們直接讀取HttpResponse.Body讀取直接拋出異常的原因。不僅僅是讀取的方法不可用Postion、Length、Seek相關(guān)的方法都是不可操作的,操作了都會(huì)拋出異常。

UseHttpLogging的解決方式

從ASP.NET Core6.0之后開始,推出了HTTP日志記錄功能,使用方式如下

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHttpLogging(logging =>
{
    logging.LoggingFields = HttpLoggingFields.ResponseBody;
    logging.RequestBodyLogLimit = 4096;
});

var app = builder.Build();
app.UseHttpLogging();

不過我們通過上面看到了HttpResponse.Body默認(rèn)情況下是不可以讀取的,但是輸出Http日志時(shí)候是可以讀取ResponseBody的,所以我們可以看一下里面的相關(guān)實(shí)現(xiàn),在HttpLoggingMiddleware中間件里,因?yàn)檫@個(gè)中間件里涉及到Http日志記錄的相關(guān)邏輯實(shí)現(xiàn),而ResponseBody只是其中的一個(gè)選項(xiàng),所以咱們只關(guān)注這一部分的實(shí)現(xiàn)[點(diǎn)擊查看源碼??]

ResponseBufferingStream? responseBufferingStream = null;
IHttpResponseBodyFeature? originalBodyFeature = null;
try
{
    //獲取原始的response
    var response = context.Response;

    if (options.LoggingFields.HasFlag(HttpLoggingFields.ResponseBody))
    {
        //保存原始的IHttpResponseBodyFeature也就是上面提到的ResponseBody交互類
        originalBodyFeature = context.Features.Get<IHttpResponseBodyFeature>()!;
        //實(shí)例化ResponseBufferingStream
        responseBufferingStream = new ResponseBufferingStream(originalBodyFeature,
            options.ResponseBodyLogLimit,
            _logger,
            context,
            options.MediaTypeOptions.MediaTypeStates,
            options);
        //用ResponseBufferingStream實(shí)例替換原始ResponseBody
        response.Body = responseBufferingStream;
        //將responseBufferingStream設(shè)置到當(dāng)前的IHttpResponseBodyFeature
        context.Features.Set<IHttpResponseBodyFeature>(responseBufferingStream);
    }

    await _next(context);

    //輸出日志
    if (requestBufferingStream?.HasLogged == false)
    {
        requestBufferingStream.LogRequestBody();
    }
    if (responseBufferingStream != null)
    {
        var responseBody = responseBufferingStream.GetString(responseBufferingStream.Encoding);
        if (!string.IsNullOrEmpty(responseBody))
        {
            _logger.ResponseBody(responseBody);
        }
    }
}
finally
{
    responseBufferingStream?.Dispose();
    if (originalBodyFeature != null)
    {
        //還原原始的IHttpResponseBodyFeature
        context.Features.Set(originalBodyFeature);
    }
}

通過上面的代碼我們可以看到,其實(shí)也是實(shí)現(xiàn)了類似的操作,用ResponseBufferingStream替換掉原始的HttpResponseStream類型,替換的邏輯要在中間件執(zhí)行next()之前,操作完成之后也就是執(zhí)行了next()之后再把原始的IHttpResponseBodyFeature替換回來,有關(guān)具體的ResponseBufferingStream實(shí)現(xiàn)方式咱們這里不做詳細(xì)描述了,不是本文重點(diǎn)。

ResponseBufferingStream的實(shí)現(xiàn)并不是使用MemoryStream這種可讀取的流替換掉默認(rèn)的HttpResponseStream,ResponseBufferingStreamLogRequestBody()方法使用ILogger輸出日志并沒有直接去讀取Stream,而是反其道重寫了Stream的Write()方法,因?yàn)閷?duì)HttpResponseBody實(shí)例
HttpResponseStream的輸出寫操作本質(zhì)是調(diào)用Stream的Write()方法,重寫了Write()方法之后會(huì)把寫入的內(nèi)容記錄到Buffer中,LogRequestBody()方法通過讀取Buffer中的內(nèi)容得到字符串,使用ILogger輸出日志。

答疑解惑

在之前的討論中有許多小伙伴對(duì)用MemoryStream替換ResponseBody存在一個(gè)疑惑,就是既然已經(jīng)替換掉了,一直用MemoryStream不就好了嘛,為啥還要把ResponseBody原始值記錄下來,結(jié)束后再替換回來。這個(gè)疑問咋一聽確實(shí)也沒毛病,但是等大致了解了它的使用過程之后才恍然大悟,原來是這么回事,在這里咱們就看一下為啥會(huì)是這樣。
首先說一下結(jié)論,如果把ResponseBody替換為MemoryStream之后,不對(duì)原始的ResponseBody進(jìn)行操作的話,在這個(gè)中間件(類似上面說的到的UseResponseBodyRead中間件)之后的操作,可能是后續(xù)的其它中間件或者是各種終結(jié)點(diǎn)比如Controller的Action亦或者是MinimalApi的Map方法等,是可以讀取和寫入值的,也就是在替換中間件的范圍內(nèi),也就是大家經(jīng)常說的套娃模式,被它套進(jìn)去的是一直生效的,沒任何問題,終結(jié)點(diǎn)本身也是中間件。下面這張圖相信大家經(jīng)常看到打個(gè)比方如果我的UseResponseBodyRead中間件是圖里的Middleware1把ResponseBody替換為MemoryStream,那么后續(xù)的操作比如Middleware2和Middleware3還有后續(xù)的終結(jié)點(diǎn)之類的讀取ResponseBody是完全沒有問題的。但是最終Http的輸出結(jié)果肯定是不符合預(yù)期的,這主要涉及到HttpResponseStream.Write()的問題,我們知道最終我們輸出的結(jié)果會(huì)體現(xiàn)在Write()方法上[點(diǎn)擊查看源碼??],核心代碼如下所示

internal sealed class HttpResponseStream : Stream
{
    private readonly HttpResponsePipeWriter _pipeWriter;
    private readonly IHttpBodyControlFeature _bodyControl;

    public HttpResponseStream(IHttpBodyControlFeature bodyControl, HttpResponsePipeWriter pipeWriter)
    {
        _bodyControl = bodyControl;
        _pipeWriter = pipeWriter;
    }

    //重寫Stream的Write操作
    public override void Write(byte[] buffer, int offset, int count)
    {
        if (!_bodyControl.AllowSynchronousIO)
        {
            throw new InvalidOperationException(CoreStrings.SynchronousWritesDisallowed);
        }
        //調(diào)用WriteAsync方法
        WriteAsync(buffer, offset, count, default).GetAwaiter().GetResult();
    }

    public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
    {
        //本質(zhì)調(diào)用了HttpResponsePipeWriter的寫方法
        return _pipeWriter.WriteAsync(new ReadOnlyMemory<byte>(buffer, offset, count), cancellationToken).GetAsTask();
    }
)

通過上面我們可以看到HttpResponseStreamWrite()方法本質(zhì)是調(diào)用了HttpResponsePipeWriterWriteAsync()方法,HttpResponseStream本身不存儲(chǔ)寫入的數(shù)據(jù)。而HttpResponsePipeWriter實(shí)例的構(gòu)建是在BodyControl類中上面咱們已經(jīng)粘貼過實(shí)例化的源碼了,可自行翻閱上去看看HttpResponsePipeWriter類的定義相關(guān)。所以上面把ResponseBody替換為MemoryStream,最終的結(jié)果要體現(xiàn)在HttpResponseStream實(shí)例中,否則的話沒有辦法正常輸出。可以用一個(gè)偽代碼例子演示一下這個(gè)原理

Order order1 = new Order 
{
    Address = "北京市海淀區(qū)"
};

SetOrder(order1);

Console.WriteLine($"最后地址:{order1.Address}");

public void SetOrder(Order order2)
{
    order2 = new Order
    {
        Address = "上海市閔行區(qū)"
    };
    Console.WriteLine($"設(shè)置地址:{order2.Address}");
}

這個(gè)示例中即使SetOrder方法中設(shè)置了新的Address,但是脫離了SetOrder方法作用域后,外面的最后地址依然是北京市海淀區(qū)。在調(diào)用SetOrder進(jìn)入方法的時(shí)候order1和方法形參order2都指向的是Address = "北京市海淀區(qū)",在SetOrder方法內(nèi)部完成實(shí)例化之后order2指向的是Address = "上海市閔行區(qū)",但是order1依然指向的是Address = "北京市海淀區(qū)",因?yàn)橐脗鬟f形參本身只是存儲(chǔ)的引用地址,更換了引用地址就和原來的地址脫鉤了,如果想讓內(nèi)外行為一直必須要體現(xiàn)到原始值上面去。我們替換ResponseBody的時(shí)候也是同理,最終Write本質(zhì)還是要依賴HttpResponseStream里的HttpResponsePipeWriter屬性,但是MemoryStream可沒有HttpResponsePipeWriter。你可能會(huì)有疑問,我上面也沒把MemoryStream結(jié)果Write()HttpResponseStream里去???但是上面使用了CopyToAsync方法與原始的的ResponseBody類型HttpResponseStream交互,CopyToAsync方法本質(zhì)就是在調(diào)用WriteAsync()方法,口說無憑直接上代碼[點(diǎn)擊查看源碼??],核心代碼如下所示

public virtual Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken)
{
    //省略一部分代碼

    return Core(this, destination, bufferSize, cancellationToken);

    static async Task Core(Stream source, Stream destination, int bufferSize, CancellationToken cancellationToken)
    {
        //使用了對(duì)象池復(fù)用空間
        byte[] buffer = ArrayPool<byte>.Shared.Rent(bufferSize);
        try
        {
            int bytesRead;
            while ((bytesRead = await source.ReadAsync(new Memory<byte>(buffer), cancellationToken).ConfigureAwait(false)) != 0)
            {
                //最終也是調(diào)用的目標(biāo)流的WriteAsync方法
                await destination.WriteAsync(new ReadOnlyMemory<byte>(buffer, 0, bytesRead), cancellationToken).ConfigureAwait(false);
            }
        }
        finally
        {
            ArrayPool<byte>.Shared.Return(buffer);
        }
    }
}

總結(jié)

????本文主要講解了如何讀取ResponseBody,默認(rèn)情況下是不可以讀取的,需要我們使用了中間件結(jié)合MemoryStream自行處理一下,同時(shí)我們結(jié)合和Http日志記錄中間件里的處理方式對(duì)比了一下,最終答疑了為了要把替換的結(jié)果還得繼續(xù)體現(xiàn)在原始的ResponseBody上面去,整體來說這方面還是相對(duì)容易理解的,只是找起來可能比較麻煩。大致總結(jié)一下

  • ResponseBody默認(rèn)不可讀取,因?yàn)樗膶?shí)例是HttpResponseStream這個(gè)類重寫了Stream的Read相關(guān)的方法,但是實(shí)現(xiàn)是拋出異常的,所以我們需要可讀的類來替換默認(rèn)的操作,MemoryStream可以輔助實(shí)現(xiàn)。
  • UseHttpLogging中間件也可以讀取ResponseBody里的結(jié)果,但是它是使用的重寫Stream的Write相關(guān)的方法,在Write方法里使用Buffer記錄了寫過的數(shù)據(jù),然后通過GetString()方法讀取Buffer里的內(nèi)容實(shí)現(xiàn)記錄要輸出的值。
  • MemoryStream解決的是我們?cè)趯懘a過程中對(duì)ResponseBody的讀取或?qū)懭氩僮鳎浅绦蛱幚硗曛笠?code>MemoryStream的結(jié)果在體現(xiàn)到HttpResponseStream中去,否則雖然程序中讀取寫入Body沒問題,但是輸出的結(jié)果會(huì)出問題。

????說句題外話,ChatGTP的發(fā)布對(duì)人們心里的沖擊還是挺大的,因?yàn)樗憩F(xiàn)出來的強(qiáng)大效果讓人眼前一亮,很多博主和企業(yè)也借此風(fēng)口尋找新的出路,甚至有人會(huì)擔(dān)心會(huì)不會(huì)被替代失業(yè)。個(gè)人以為新的技術(shù)大行其道必然會(huì)帶來新的產(chǎn)業(yè),新的產(chǎn)業(yè)的新的崗位同時(shí)也是需要更多的人參與進(jìn)來。所以保持對(duì)新事物的好奇心多多參與。工具不會(huì)替代人,能替代人的是會(huì)使用工具的人。

文章來源地址http://www.zghlxwxcb.cn/news/detail-409348.html

??歡迎掃碼關(guān)注我的公眾號(hào)?? 由ASP.NET Core讀取Response.Body引發(fā)的思考

到了這里,關(guān)于由ASP.NET Core讀取Response.Body引發(fā)的思考的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場(chǎng)。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請(qǐng)注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請(qǐng)點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • ASP.NET和ASP.NET Core的區(qū)別

    ASP.NET和ASP.NET Core是兩個(gè)不同的Web應(yīng)用程序框架,它們都是由Microsoft開發(fā)的。ASP.NET是Microsoft推出的第一個(gè)Web應(yīng)用程序框架,而ASP.NET Core是其最新版本。本文將介紹ASP.NET和ASP.NET Core的簡介和區(qū)別。 ASP.NET的簡介 ASP.NET是一個(gè)基于.NET框架的Web應(yīng)用程序框架,它是Microsoft推出的第一

    2024年02月16日
    瀏覽(96)
  • Asp.Net VS ASP.NET Core 請(qǐng)求管道

    Asp.Net VS ASP.NET Core 請(qǐng)求管道

    參考鏈接 ASP.NET CORE 啟動(dòng)過程及源碼解讀 請(qǐng)求進(jìn)入Asp.Net工作進(jìn)程后,由進(jìn)程創(chuàng)建HttpWorkRequest對(duì)象,封裝此次請(qǐng)求有關(guān)的所有信息,然后進(jìn)入HttpRuntime類進(jìn)行進(jìn)一步處理。HttpRuntime通過請(qǐng)求信息創(chuàng)建HttpContext上下文對(duì)象,此對(duì)象將貫穿整個(gè)管道,直到響應(yīng)結(jié)束。同時(shí)創(chuàng)建或從應(yīng)用

    2024年02月04日
    瀏覽(100)
  • 【ASP.NET Core 基礎(chǔ)知識(shí)】--最佳實(shí)踐和進(jìn)階主題--設(shè)計(jì)模式在ASP.NET Core中的應(yīng)用

    一、設(shè)計(jì)模式概述 1.1 什么是設(shè)計(jì)模式 設(shè)計(jì)模式是在軟件設(shè)計(jì)過程中反復(fù)出現(xiàn)的、經(jīng)過驗(yàn)證的、可重用的解決問題的方法。它們是針對(duì)特定問題的通用解決方案,提供了一種在軟件開發(fā)中可靠的指導(dǎo)和標(biāo)準(zhǔn)化方法。設(shè)計(jì)模式通常描述了一種在特定情景下的解決方案,包括了問

    2024年02月21日
    瀏覽(850)
  • ASP.NET Core SingleR Core:WebApi + .net 客戶端開發(fā)

    ASP.NET Core SingleR Core:WebApi + .net 客戶端開發(fā)

    我之前稍微研究了一下SignalR Core。用起來還行。簡單來說SignalR就是用來解決實(shí)時(shí)通訊的問題的。 ASP.NET Core SingleR:初次體驗(yàn)和簡單項(xiàng)目搭建 SignalR支持三種客戶端,C#,Java,JavaScirpt?;緣蛴昧恕1旧砭褪俏④涢_發(fā)的,肯定支持自己的語言。因?yàn)槭荳ebsocket的上層封裝,所以也要支

    2024年01月20日
    瀏覽(575)
  • ASP.NET Core —配置系統(tǒng)

    ASP.NET Core —配置系統(tǒng)

    一個(gè)應(yīng)用要運(yùn)行起來,往往需要讀取很多的預(yù)設(shè)好的配置信息,根據(jù)約定好的信息或方式執(zhí)行一定的行為。 配置的本質(zhì)就是軟件運(yùn)行的參數(shù),在一個(gè)軟件實(shí)現(xiàn)中需要的參數(shù)非常多,如果我們以 Hard Code (硬編碼)的方式寫在應(yīng)用代碼中,這樣配置就會(huì)很亂,而且后續(xù)也不容易修

    2024年02月08日
    瀏覽(20)
  • Asp.Net Core 6 - 概述

    Q: 什么是 .NET? A:.NET 是一個(gè)開發(fā)人員平臺(tái),由工具、編程語言、庫組成,用于構(gòu)建許多不同類型的應(yīng)用程序。使用 .NET,可以使用多種語言、編輯器和庫來構(gòu)建 Web、移動(dòng)、桌面、游戲和 IoT 等,可以使用 C#、F# 或 Visual Basic 編寫 .NET 應(yīng)用。 .NET 發(fā)展至今,出現(xiàn)了兩種實(shí)現(xiàn) n

    2024年02月06日
    瀏覽(24)
  • ASP.NET Core 8 基礎(chǔ)

    ASP.NET Core 8 基礎(chǔ)

    2023年11月將發(fā)布發(fā)布.NET 8,基于.NET 8 的 ASP.NET Core 8.0也會(huì)一并發(fā)布,這是繼ASP.NET Core 6.0之后,又一個(gè)重要版本,因?yàn)橐肓薾ativeAOT,在性能上有很大提升,所以系統(tǒng)地學(xué)習(xí)一下這項(xiàng)技術(shù)。 ASP.NET Core 的幾個(gè)主要優(yōu)勢(shì): 跨平臺(tái),支持 Windows, macOS, Linux,Docker,Azure和AWS等云服務(wù)自

    2024年02月11日
    瀏覽(50)
  • .NET Core 引發(fā)的異常: “SqlSugar.SqlSugarException“ 位于 System.Private.CoreLib.dll 中

    在使用.NET Core開發(fā)應(yīng)用程序時(shí),有時(shí)候會(huì)遇到各種不同的異常情況。其中一種常見的異常是 “SqlSugar.SqlSugarException”。這個(gè)異常通常在與數(shù)據(jù)庫交互的過程中出現(xiàn),特別是在使用SqlSugar庫時(shí)。 SqlSugar是一個(gè)流行的ORM(對(duì)象關(guān)系映射)框架,它簡化了與數(shù)據(jù)庫的交互操作。它提

    2024年02月04日
    瀏覽(22)
  • ASP.NET Core 的 HttpContex

    HttpContext 類封裝了HTTP Request 和 HTTP Response。 當(dāng)收到一條HTTP Request 請(qǐng)求時(shí),就會(huì)實(shí)例化一個(gè)HttpContext對(duì)象。HttpContext對(duì)象可以被中間件訪問。 注意:HttpContext 不是線程安全的。 從Razer Page 讀取 從Razer Page 的cshtml 讀取 從 Controller 讀取 從 Minimal API 讀取 HttpContext.Request 可以獲取客

    2024年02月11日
    瀏覽(36)
  • asp.net core之實(shí)時(shí)應(yīng)用

    asp.net core之實(shí)時(shí)應(yīng)用

    本文將介紹ASP.NET Core SignalR,這是一個(gè)強(qiáng)大的實(shí)時(shí)通信庫,用于構(gòu)建實(shí)時(shí)、雙向通信應(yīng)用程序。我們將探討SignalR的基本概念、架構(gòu)和工作原理,并提供一些示例代碼來幫助讀者更好地理解和使用SignalR。 ASP.NET Core SignalR提供了一種簡單而強(qiáng)大的方式來構(gòu)建實(shí)時(shí)通信應(yīng)用程序。

    2024年02月14日
    瀏覽(22)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請(qǐng)作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包