41

Until now, I had a GET method that looked like the following:

protected override async Task<IHttpActionResult> GetAll(QueryData query)
{
     // ... Some operations

     //LINQ Expression based on the query parameters
     Expression<Func<Entity, bool>> queryExpression = BuildQueryExpression(query);

     //Begin to count all the entities in the repository
     Task<int> countingEntities = repo.CountAsync(queryExpression);

     //Reads an entity that will be the page start
     Entity start = await repo.ReadAsync(query.Start);

     //Reads all the entities starting from the start entity
     IEnumerable<Entity> found = await repo.BrowseAllAsync(start, queryExpression);

     //Truncates to page size
     found = found.Take(query.Size);

     //Number of entities returned in response
     int count = found.Count();

     //Number of total entities (without pagination)
     int total = await countingEntities;

     return Ok(new {
          Total = total,
          Count = count,
          Last = count > 0 ? GetEntityKey(found.Last()) : default(Key),
          Data = found.Select(e => IsResourceOwner(e) ? MapToOwnerDTO(e) : MapToDTO(e)).ToList()
     });
}

This worked like a charm and it was good. However, I was told recently to send the response metadata (that is, Total, Count and Last properties) as response custom headers instead of the response body.

I cannot manage to access the Response from the ApiController. I thought of a filter or attribute, but how would I get the metadata values?

I can keep all this information on the response and then have a filter that will deserialize the response before being sent to the client, and create a new one with the headers, but that seems troublesome and bad.

Is there a way to add custom headers directly from this method on an ApiController?

Matias Cicero
  • 25,439
  • 13
  • 82
  • 154
  • 2
    Should be as simple as [that](http://stackoverflow.com/questions/13487012/mvc-4-web-api-add-custom-response-http-header) – Andrei Aug 14 '15 at 19:55
  • @Andrei I don't have a `HttpContext` property, but I do have an `ActionContext` one. However, `Response` property of that object is `null` and I cannot operate with it. – Matias Cicero Aug 14 '15 at 20:01
  • you need to use ActionContext.Request.CreateResponse() to actually create a response and then set values in response as a strongly typed object rather than strings – harishr Aug 15 '15 at 05:18
  • @entre I would like for the Web Api to serialize my anonymous object (i.e. using the Web Api `Ok(T t)` method. That also includes setting some headers for me). If I create a response I have to serialize my object and I have to set all the headers manually. – Matias Cicero Aug 16 '15 at 02:39
  • move all header setting part in a method and use that method at both places – harishr Aug 16 '15 at 04:02
  • @harishr His signature is async `Task`, which cannot be implicitly casted to `HttpResponseMessage`. What he needs is: `public IHttpActionResult Get() { return base.ResponseMessage(Request.CreateResponse()); }` – nkalfov Aug 03 '17 at 16:03

6 Answers6

43

You can explicitly add custom headers in a method like so:

[HttpGet]
[Route("home/students")]
public HttpResponseMessage GetStudents()
{
       // Get students from Database

       // Create the response
        var response = Request.CreateResponse(HttpStatusCode.OK, students);
    
        // Set headers for paging
        response.Headers.Add("X-Students-Total-Count", students.Count());
       
       return response;
}

For more information read this article: http://www.jerriepelser.com/blog/paging-in-aspnet-webapi-http-headers/

David Klempfner
  • 8,700
  • 20
  • 73
  • 153
Seagull
  • 1,063
  • 1
  • 11
  • 18
  • I'm doing this, but the headers get stripped – weagle08 Feb 22 '17 at 16:37
  • 1
    @weagle08 Does your request go through proxies? If so you can read this: http://stackoverflow.com/questions/20820572/under-what-conditions-are-http-request-headers-removed-by-proxies – Seagull Feb 23 '17 at 06:54
  • Worked for me but no proxy involved in our connection –  Aug 16 '17 at 14:06
33

I have entered comments, here is my complete answer.

You will need to create a custom filter and apply that to your controller .

public class CustomHeaderFilter : ActionFilterAttribute
{
    public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
    {
       var count = actionExecutedContext.Request.Properties["Count"];
       actionExecutedContext.Response.Content.Headers.Add("totalHeader", count);
    }
}

In your Controller

  public class AddressController : ApiController
        {
            public async Task<Address> Get()
            {
               Request.Properties["Count"] = "123";
            }
    }
Yousuf
  • 3,105
  • 7
  • 28
  • 38
  • 6
    This works perfectly but is it the correct way to do this? My metadata should be a property of the response, not the request. I mean, it works as a solution, but is it conceptually right? – Matias Cicero Aug 14 '15 at 22:05
  • 2
    This looks like double work to me. You may add a header [directly](https://stackoverflow.com/a/45489505/2156743) – nkalfov Aug 03 '17 at 16:12
  • @Nikola but then you lose the strongly typed response, which the OP didn't use but is still an option with this approach. I'm working on a web api project and not using strong types is causing issues - for one we can't easily generate correct swagger. Avoid returning untyped responses if you can – Sten Petrov Dec 04 '17 at 20:28
  • 2
    In my case I found this to be the best solution for returning response data in the headers, but you have to be careful where the action filter gets the data. I has to get the data for the request you are processing, I looked all over the place for data storage that is unique to the request and the only thing I could find is the "context.Request.Properties" table, which is most likely why @Yousuf used it. Keep in mine that the "context.Response" object does not exist while processing the action, so "context.Request" seems to be the only place you can store data like this. – Michael Erickson Nov 18 '18 at 18:40
  • Regarding strongly typed response, unfortunately that is the nature of the HTTP protocol, all data is text. You could consider some XML or JSON format that includes typing to verify the transfer of data. – Michael Erickson Nov 18 '18 at 18:40
23

Simple solution is to write just this:

HttpContext.Current.Response.Headers.Add("MaxRecords", "1000");
Deepak
  • 428
  • 5
  • 12
  • Thanks Darek, for code highlighting. I will make sure to do this from now onwards :) – Deepak Sep 11 '18 at 05:03
  • 5
    HttpContext would not be present in Controllers that derive ApiController. – ShellNinja May 03 '19 at 13:35
  • 1
    This is indeed to right answer if we're talking about a .net framework controller that derive from `system.web.http.apicontroller`. I'm personally just familiar with the core syntax, so this was great for this legacy project. cheers – Paul DeVito Oct 28 '20 at 13:51
  • 1
    This worked for me. My controller is inheriting from ApiController – Andrew Gale Mar 24 '21 at 15:08
  • 1
    Absolute lifesaver in my legacy project. – Kit Oct 11 '21 at 09:13
  • 1
    I used this in my class which derives from System.Web.Http.ApiController. (.NET Framework - 4.5.2.) Works perfectly. – Yossi G. Dec 27 '21 at 17:45
14

What you need is:

public async Task<IHttpActionResult> Get() 
{ 
    var response = Request.CreateResponse();
    response.Headers.Add("Lorem", "ipsum");

    return base.ResponseMessage(response); 
}

I hope this answers your question.

nkalfov
  • 439
  • 5
  • 12
10

Alternatively, it’s better to leverage DelegatingHandler if it is something you need to perform on every response. Because it will work on the request/response pipeline and not on the controller/action level. In my case I must add some headers with every response, so I did what I described. See code snippet below

public class Interceptor : DelegatingHandler
{
    protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        var response = await base.SendAsync(request, cancellationToken);
        response.Headers.Add("Access-Control-Allow-Origin", "*");
        response.Headers.Add("Access-Control-Allow-Methods", "GET,POST,PATCH,DELETE,PUT,OPTIONS");
        response.Headers.Add("Access-Control-Allow-Headers", "Origin, Content-Type, X-Auth-Token, content-type");
        return response;
    }

}

And you would be requiring to add this handler in WebApiConfig

    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            config.MessageHandlers.Add(new Interceptor());
        }
    } 
0

You can use a custom ActionFilter that will allow you to send custom headers and access the HttpContext:

public class AddCustomHeaderFilter : ActionFilterAttribute
{
    public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
    {
       actionExecutedContext.Response.Content.Headers.Add("name", "value");
    }
}
Chris Bohatka
  • 363
  • 1
  • 4
  • 14