Headers are read only exception when writing to HttpResponse
Asked Answered
L

2

9

I have a controller that looks like this

IHttpWrite httpWrite;

[HttpPost]
public async Task<HttpResponse> Post(Request req)
{
    return await httpWrite.Write(req, Response);
}

that calls this

public async Task<HttpResponse> Write(object data, HttpResponse httpResponse)
{
    var json = JsonConvert.SerializeObject(data);

    httpResponse.OnStarting(() =>
    {
        httpResponse.Clear();
        httpResponse.ContentType = "application/json";
        return Task.CompletedTask;
    });

    await httpResponse.WriteAsync(json);
    return httpResponse;
}

But calling it gets this exception

System.InvalidOperationException: Headers are read-only, response has already started.
   at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpHeaders.ThrowHeadersReadOnlyException()

I understand you cannot set the headers once data is written to the response, but on my end I am setting the headers in the HttpResponse before writing anything to it.

From the debugging I've done, the exception for the headers is only being thrown after the controller returns the HttpResponse. This indicates to me the attempt to change the headers is being done on something outside of my code.

I'm using most of the boilerplate code Visual Studio sets up. This is the default Program

public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
        WebHost.CreateDefaultBuilder(args)
               .UseStartup<Startup>();

Which is calling the mostly-default Startup

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseHsts();
    }

    app.UseHttpsRedirection();
    app.UseStaticFiles();
    app.UseMvc();
}

I believe the solution either lies in changing something in the configuration to not automatically set the response headers after I return the HttpResponse, or potentially returning a different object instead so that I do not have to manually set the headers. Without being able to know the source of what's trying to set the headers after I write to the HttpResponse, I am unsure how to proceed.

Leighannleighland answered 31/10, 2018 at 14:20 Comment(0)
C
1

Why don't you just write your headers before writing the response? and even better: instead of manually set the response, why don't you declare the return as something more abstract and let the framework deal with these?

anyway, here a piece of code on how to add custom headers:

using System;
using System.Linq;
using System.Net;
using Microsoft.AspNetCore.Mvc;

namespace WebApplication.Controllers
{
    [Route("api")]
    public class ValuesController : Controller
    {
        [HttpGet]
        [Route("values/{top}")]
        public IActionResult Get(int top)
        {
            // Generate dummy values
            var list = Enumerable.Range(0, DateTime.Now.Second)
                             .Select(i => $"Value {i}")
                             .ToList();
            list.Reverse();

            var result = new ObjectResult(list.Take(top))
            {
                StatusCode = (int)HttpStatusCode.OK
            };

            Request.HttpContext.Response.Headers.Add("X-Total-Count", list.Count.ToString());

            return result;
        }
    }
}
Chantell answered 31/10, 2018 at 14:51 Comment(2)
Using the HttpResponse.OnStarting() callback should ensure the headers get added before writing any data (if I'm doing the callback correctly). I'll try returning ActionResult instead.Leighannleighland
IActionResult works. I wasn't aware there are different versions like JsonResult and FileContentResult that will set the headers and return the data without requiring me to send everything manually.Leighannleighland
H
0

Headers are set read only in HttpProtocol.cs. Here is the full stack trace:

HttpProtocol.CreateResponseHeaders(bool appCompleted) Line 1825 C#
HttpProtocol.FirstWriteAsyncInternal(System.ReadOnlyMemory<byte> data, System.Threading.CancellationToken cancellationToken) Line 2182  C#
HttpProtocol.WritePipeAsync(System.ReadOnlyMemory<byte> data, System.Threading.CancellationToken cancellationToken) Line 2144   C#
HttpResponsePipeWriter.WriteAsync(System.ReadOnlyMemory<byte> source, System.Threading.CancellationToken cancellationToken) Line 68 C#
HttpResponseStream.WriteAsync(byte[] buffer, int offset, int count, System.Threading.CancellationToken cancellationToken) Line 122  C#

Each of the lines in the stack trace starts with Microsoft.AspNetCore.Server.Kestrel.Core.dll!Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.

To obtain this stack trace I run

file.CopyToAsync(Response.Body).Wait();
Haywood answered 5/9 at 11:18 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.