Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to read and change body of Response into overided OnActionExecuted method of Controller

I need to append some info to each JSON answer of every action of controller. For this, I make base controller inherited from standard MVC Controller class. I try like this. But I catch ArgumentException with message like this "Stream was not readable." Probably stream was closed when ActionResult object wrote data to body. What can I do?

public override void OnActionExecuted(ActionExecutedContext context)
{
    var respApi = new ResponseApiDTO();
    respApi.Comment = "You could!!!";
    JObject jRespApi = JObject.FromObject(respApi);
    Stream bodyStream = context.HttpContext.Response.Body;
    JObject jbody;

    context.Result = Json(jbody);
    using( var rd = new StreamReader(bodyStream))
    {
        string bodyText = rd.ReadToEnd();
        jbody = (JObject)JsonConvert.DeserializeObject(bodyText);
        jbody.Add(jRespApi);
        context.Result = Json(jbody);
    }
}
like image 710
trueboroda Avatar asked Jan 18 '17 14:01

trueboroda


1 Answers

I`ve find a solve. We need to replace the Body stream to MemoryStream object while executing MVC step in pipeline. And then we must return original stream object object into Response.Body.

// Extension method used to add the middleware to the HTTP request pipeline.
    public static class BufferedResponseBodyExtensions
    {

        public static IApplicationBuilder UseBufferedResponseBody(this IApplicationBuilder builder)
        {
            return builder.Use(async (context, next) =>
            {
                using (var bufferStream = new MemoryStream())
                {
                    var orgBodyStream = context.Response.Body;
                    context.Response.Body = bufferStream;

                    await next();//there is running MVC

                    bufferStream.Seek(0, SeekOrigin.Begin);
                    await bufferStream.CopyToAsync(orgBodyStream);    
                    context.Response.Body = orgBodyStream;
                }
            });
        }
    }

Then include this into the pipline. Class Startup, method Configure.

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
        {   
           ...
            app.UseBufferedResponseBody();
            app.UseMvc();           
           ...
        }

And after replacing responses body stream you can read and modify body contents via Result Filter.

 public class ResultFilterAttribute : Attribute, IResultFilter
    {
        public async void OnResultExecuted(ResultExecutedContext context)
        {
            Stream bodyStream = context.HttpContext.Response.Body;
            bodyStream.Seek(0, SeekOrigin.Begin);
            string bodyText = rd.ReadToEnd();
        }
     }

This attribute must be aplied to the target Controller class.

like image 169
trueboroda Avatar answered Sep 21 '22 18:09

trueboroda