Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Append Custom HTML output before and after response in asp.net core?

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();
             
        });
like image 873
Royi Namir Avatar asked Oct 19 '25 23:10

Royi Namir


2 Answers

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>();

like image 198
Peter Csala Avatar answered Oct 21 '25 13:10

Peter Csala


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());
});
like image 34
bfren Avatar answered Oct 21 '25 11:10

bfren