4

I have a simple .net core app that emits an API output.

My Configure method is pretty simple :

public void Configure(IApplicationBuilder app, IWebHostEnvironment env  )
        {

            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            app.UseRouting();
            app.UseAuthorization();
            app.UseEndpoints(endpoints => { endpoints.MapControllers(); });
    }

This is the current output from the API :

enter image description here

Just for testing purpose, I want to add HTML tag before and after the response :

Something like ( edited manually in DOM ) :

enter image description here

So I've added this :

public void Configure(IApplicationBuilder app, IWebHostEnvironment env  )
        {
            

          app.Use(async (context, next) =>
         {
             await context.Response.WriteAsync("<b> Hi</b>");
             await next ();
             await context.Response.WriteAsync("<b> Bye </b>");
         });




            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            app.UseRouting();
            app.UseAuthorization();
            app.UseEndpoints(endpoints => { endpoints.MapControllers(); });
    }

But when I run it , I get :

fail: Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware1 An unhandled exception has occurred while executing the request. System.InvalidOperationException: Headers are read-only, response has already started. With this HTML :

enter image description here

I've been searching for a solution in SO but didn't find, how to do it.

Question:

Why is it happening? I thought I can control the pipeline and do whatever I want it via calling next() on the pipeline.

How can I add my custom HTML tags before and after?

Edit:

If I move the code to the end of the Configure method, I see the regular output , without getting the exception, but without the HTML tags.

Edit #2 :

I've also tried with OnStarting event , but still , no success (I get an empty page):

 app.Use(async (context, next) =>
        {
          
            context.Response.OnStarting(async state =>
            {
                if (state is HttpContext httpContext)
                {
                    var request = httpContext.Request;
                    var response = httpContext.Response;
                    await response .WriteAsync("<b> Bye </b>"); // <----
               }
            }, context);
            await next();
             
        });
6
  • Have you done View Source? Are you sure the browser isn't hiding the output? Commented May 16, 2021 at 15:58
  • @bcg yes. I did it. it writes the first statment only. i.imgur.com/RG0Ktwb.jpg ( for this code Commented May 17, 2021 at 14:13
  • @OP What's the content-type of the response body? Commented May 25, 2021 at 13:28
  • @PeterCsala application/json. ( i know that adding HTML tags before and after is a mismatch. but I'm after pure text contamination. ( i dont mind changing appication/json to text/html at response time....) Commented May 25, 2021 at 13:31
  • 1
    @PeterCsala Can you please have a look at this ? Commented Jun 4, 2021 at 10:16

2 Answers 2

4
+25

With the following middleware I was able to add html tags before and after the action result:

public class BeforeAfterMiddleware
{
    private readonly RequestDelegate _next;
    public BeforeAfterMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task Invoke(HttpContext context)
    {
        //Redirect response into a memorystream
        using var actionResponse = new MemoryStream();
        var httpResponse = context.Response.Body;
        context.Response.Body = actionResponse;

        //Call the handler action
        await _next.Invoke(context);

        //Read the handler response from the memory stream
        actionResponse.Seek(0, SeekOrigin.Begin);
        var reader = new StreamReader(actionResponse);
        using var bufferReader = new StreamReader(actionResponse);
        string body = await bufferReader.ReadToEndAsync();

        //Remove the handler's response from the memory stream
        context.Response.Clear();

        //Write data to the memorystream
        await context.Response.WriteAsync("<h1>HI</h1>");
        await context.Response.WriteAsync(body);
        await context.Response.WriteAsync("<h1>BYE</h1>");

        //Copy memorystream to the response stream
        context.Response.Body.Seek(0, SeekOrigin.Begin);
        await context.Response.Body.CopyToAsync(httpResponse);
        context.Request.Body = httpResponse;
    }
}
  1. It simply redirects the response to a MemoryStream
  2. then alters that with some text before and after
  3. finally redirects memoryStream back to the response stream

Usage: app.UseMiddleware<BeforeAfterMiddleware>();

Sign up to request clarification or add additional context in comments.

3 Comments

Nicely done...and it is a class instead of inline...
You may remove response headers set elsewhere with the context.Response.Clear(), which sometimes isn't desirable. How about using a using var writer = new StreamWriter(httpResponse) and then await writer.WriteAsync("<h1>HI</h1>") and so on? (If so, the ending part from "//Copy memorystream to the response stream" potentially could be removed.)
@JakobBagterp Yes, you are right that Clear resets not just the body but the headers and status code as well. Quite frankly I'm not sure I fully understand your comment's second half. Could you please post the suggested alternative to pastebin (or similar)?
2

OK, I think I have it! It's extremely challenging as you've worked out... the way I've done it is by writing a custom IOutputFormatter.

// in ConfigureServices()
services.AddControllers(opt =>
{
    opt.OutputFormatters.Clear();
    opt.OutputFormatters.Add(new AppendHtmlOutputFormatter());
});

// Formatter class
public class AppendHtmlOutputFormatter : IOutputFormatter
{
    public bool CanWriteResult(OutputFormatterCanWriteContext context) =>
        true; // add some logic here if you don't want to append all the time

    public Task WriteAsync(OutputFormatterWriteContext context)
    {
        var json = System.Text.Json.JsonSerializer.Serialize(context.Object);

        var modified = "<b>Hi!</b>" + json + "<b>Bye!</b>";
        return context.HttpContext.Response.WriteAsync(modified);
    }
}

Now when I run an API endpoint I get the following response:

<b>Hi!</b>{"Bar":42}<b>Bye!</b>

Is that what you're looking for?

Default Output Formatters

Be aware that the following default OutputFormatters are removed by .Clear() - in this order:

  1. HttpNoContentFormatter
  2. StringOutputFormatter
  3. StreamOutputFormatter
  4. SystemTextJsonOutputFormatter

The solution above replaces all these and uses AppendHtmlOutputFormatter for everything. Therefore the following may be a preferred option (though won't append the HTML output to everything):

// in ConfigureServices()
services.AddControllers(opt =>
{
    opt.OutputFormatters.Clear();
    opt.OutputFormatters.Add(new HttpNoContentOutputFormatter());
    opt.OutputFormatters.Add(new StreamOutputFormatter());
    opt.OutputFormatters.Add(new AppendHtmlOutputFormatter());
});

Alternative to .Clear()

If you don't remove the default formatters, .NET will use those and never reach the custom formatter. However, if you prefer not to remove all formatters (e.g. another feature is adding them in), you can also remove them one at a time by type:

services.AddControllers(opt =>
{
    opt.OutputFormatters.RemoveType<StringOutputFormatter>();
    opt.OutputFormatters.RemoveType<SystemTextJsonOutputFormatter>();
    opt.OutputFormatters.Add(new AppendHtmlOutputFormatter());
});

5 Comments

yeah. but how can i know the fefaults formatters ? BTW I've managed to do it in some way ....i.imgur.com/mcBANLP.jpg....I did replace there but again , it's the same ( replace vs add). one thing to mention here is that content.length had to be recalculated....
I definitely suggest trying to find a solution that does not involved reading or modifying the Response Body - because .NET Core does everything it can to stop you doing that.
I've added an alternative to Clear() which allows removal of specific OutputFormatters which may be preferable.
I wonder ....why must converter be removed ? why can'y i just add ?
If you just add, you'll find nothing changes because .NET uses SystemTextJsonOutputFormatter before it reaches the custom one. You could insert the custom formatter earlier in the list, but it makes more sense to me to remove it.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.