Click here to Skip to main content
14,882,411 members
Please Sign up or sign in to vote.
0.00/5 (No votes)
Good day,
I have an issue with a custom response in API Gateway Ocelot with Middleware.
inside FormatResponse(context.Response) I change response for specific endpoint and I see the new response on debug but I receive the original Response in final result on postman.
ex :
original Response
JavaScript
{
"name":"mo"
}

after a change will be
JavaScript
{
"name":"mo123"
}


my code
C#
 // .NET Core 3.1
 public async Task InvokeAsync(HttpContext context)
 {
     context.Request.EnableBuffering();

     var builder = new StringBuilder();

     var request = await FormatRequest(context.Request);

     builder.Append("Request: ").AppendLine(request);
     builder.AppendLine("Request headers:");
     foreach (var header in context.Request.Headers)
     {
         builder.Append(header.Key).Append(':').AppendLine(header.Value);
     }

     //Copy a pointer to the original response body stream
     var originalBodyStream = context.Response.Body;

     //Create a new memory stream...
     using var responseBody = new MemoryStream();
     //...and use that for the temporary response body
     context.Response.Body = responseBody;

     //Continue down the Middleware pipeline, eventually returning to this class
     await _next(context);

     //Format the response from the server
     var response = await FormatResponse(context.Response); // here ,i see new respose
     builder.Append("Response: ").AppendLine(response);
     builder.AppendLine("Response headers: ");
     foreach (var header in context.Response.Headers)
     {
         builder.Append(header.Key).Append(':').AppendLine(header.Value);
     }

     //Save log to chosen datastore
     _logger.LogInformation(builder.ToString());

     //Copy the contents of the new memory stream (which contains the response) to the original
// stream, which is then returned to the client.
     await responseBody.CopyToAsync(originalBodyStream);
 }

 private async Task<string> FormatResponse(HttpResponse response)
 {
     //We need to read the response stream from the beginning...
     response.Body.Seek(0, SeekOrigin.Begin);
     //...and copy it into a string
     string text = await new StreamReader(response.Body).ReadToEndAsync();
     text = CustomRes(text); //************************ here, I change response
     //We need to reset the reader for the response so that the client can read it.
     response.Body.Seek(0, SeekOrigin.Begin);
     //Return the string for the response, including the status code (e.g. 200, 404, 401, etc.)
     return $"{response.StatusCode}: {text}";
 }

Reference: https://www.abhith.net/blog/dotnet-core-api-gateway-ocelot-logging-http-requests-response-including-headers-body/

What I have tried:

I tried to make changes inside InvokeAsync function
Posted
Updated 15-Feb-21 6:39am
Comments
RickZeeland 15-Feb-21 7:36am
   
Seems to be an Async problem, what happens if you make the FormatResponse() a normal method?
Dev Mo 15-Feb-21 9:27am
   
I make it a normal method, nothing happens
if you can help me I will be thankful

C#
//We need to read the response stream from the beginning...
response.Body.Seek(0, SeekOrigin.Begin);
//...and copy it into a string
string text = await new StreamReader(response.Body).ReadToEndAsync();
text = CustomRes(text); //************************ here, I change response
//We need to reset the reader for the response so that the client can read it.
response.Body.Seek(0, SeekOrigin.Begin);
The problem is, you are changing a copy of the response. The data in the underlying stream is never changed.

If you want to send your modified response back to the client, then you need to change the data that you send back:
C#
public async Task InvokeAsync(HttpContext context)
{
    context.Request.EnableBuffering();

    var builder = new StringBuilder();
    var request = await FormatRequest(context.Request);
    builder.Append("Request: ").AppendLine(request);
    builder.AppendLine("Request headers:");
    foreach (var header in context.Request.Headers)
    {
        builder.Append(header.Key).Append(':').AppendLine(header.Value);
    }
    
    var originalBodyStream = context.Response.Body;
    
    using var cachedResponseBody = new MemoryStream();
    context.Response.Body = cachedResponseBody;
    await _next(context);
    
    using var modifiedResponseBody = new MemoryStream();
    string response = await ModifyResponse(context.Response, modifiedResponseBody);
    builder.Append("Response: ").AppendLine(response);
    builder.AppendLine("Response headers: ");
    foreach (var header in context.Response.Headers)
    {
        builder.Append(header.Key).Append(':').AppendLine(header.Value);
    }

    // Save log to chosen datastore
    _logger.LogInformation(builder.ToString());

    // Copy the contents of the modified memory stream (which contains the response) 
    // to the original stream, which is then returned to the client.
    await modifiedResponseBody.CopyToAsync(originalBodyStream);
}

private async Task<string> ModifyResponse(HttpResponse response, Stream modifiedResponseBody)
{
    response.Body.Seek(0, SeekOrigin.Begin);
    string text = await new StreamReader(response.Body).ReadToEndAsync();
    
    // Modify the response body:
    text = CustomRes(text);
    
    using (var writer = new StreamWriter(modifiedResponseBody))
    {
        writer.Write(text);
        writer.Flush();
    }

    modifiedResponseBody.Seek(0, SeekOrigin.Begin);
    return $"{response.StatusCode}: {text}";
}
   
Comments
Dev Mo 16-Feb-21 2:34am
   
Dear Richard
thank you so much,
I have applied your code.
The following error appeared: Cannot access a closed Stream.
I edited this "using (var writer = new StreamWriter(modifiedResponseBody))"
to "using (var writer = new StreamWriter(modifiedResponseBody, Encoding.UTF8, -1, true))".
then The following error appeared: Response Content-Length mismatch: too many bytes written.
I added this "context.Response.ContentLength = modifiedResponseBody.Length;"
befor "await modifiedResponseBody.CopyToAsync(originalBodyStream);"

Now it is working fine.

thank you.
Here is a good example of how to use Async and Tasks: Asynchronous programming in C# | Microsoft Docs[^]

Also see: Best~model-for-async-programming-in-net[^]
   
v2
Comments
Dev Mo 15-Feb-21 9:37am
   
thank you I will read your example, for now just I need to return a new response what I can do, I believe that will be a simple tip
RickZeeland 15-Feb-21 10:04am
   
Calling Async "simple" would be the understatement of the year :)
Take a good look at how the example uses a combination of Tasks and Async.

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)




CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900