在ApiController中添加自定义响应头

43

到目前为止,我有一个看起来像下面这样的GET方法:

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

这个方法非常有效且好用。然而,最近有人告诉我应该将响应元数据(即TotalCountLast属性)发送到响应自定义头部而不是响应正文。

我无法从ApiController中访问Response。我想过使用过滤器或属性,但如何获取元数据值呢?

我可以将所有这些信息保存在响应中,然后使用一个过滤器,在将响应发送给客户端之前反序列化响应,并创建一个新的带有头部的响应,但这似乎很麻烦而且不好。

是否有一种方法可以直接从ApiController的此方法添加自定义标头?


2
应该像这样简单:链接 - Andrei
@Andrei 我没有 HttpContext 属性,但我有一个 ActionContext 属性。然而,该对象的 Response 属性是 null,我无法操作它。 - Matias Cicero
你需要使用ActionContext.Request.CreateResponse()来创建响应,然后将值设置为强类型对象而不是字符串。 - harishr
@entre 我希望Web Api能够序列化我的匿名对象(即使用Web Api的Ok<T>(T t)方法。这也包括为我设置一些标头)。如果我创建一个响应,我必须手动序列化我的对象并设置所有标头。 - Matias Cicero
将所有的头部设置部分移动到一个方法中,并在两个地方使用该方法。 - harishr
@harishr 他的签名是异步 Task<IHttpActionResult>,不能隐式转换为 HttpResponseMessage。他需要的是:public IHttpActionResult Get() { return base.ResponseMessage(Request.CreateResponse()); } - nkalfov
6个回答

43

您可以在方法中明确添加自定义标头,例如:

[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;
}

了解更多信息,请阅读此文章:http://www.jerriepelser.com/blog/paging-in-aspnet-webapi-http-headers/


我正在做这个,但是头部被剥离了。 - weagle08
1
@weagle08,您的请求是否通过代理?如果是,您可以阅读此文:https://dev59.com/WWEi5IYBdhLWcg3w7__P - Seagull
对我来说有效,但我们的连接中没有代理。 - user755404

33

我已经添加了注释,这是我的完整答案。

您需要创建一个自定义过滤器并将其应用于您的控制器。

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

在你的控制器中

  public class AddressController : ApiController
        {
            public async Task<Address> Get()
            {
               Request.Properties["Count"] = "123";
            }
    }

6
这个方法运行良好,但它是正确的做法吗?我的元数据应该是响应的属性,而不是请求的属性。我的意思是,这个方法虽然可以解决问题,但从概念上讲,它是正确的吗? - Matias Cicero
2
这对我来说看起来像是重复的工作。你可以直接添加一个标题。 - nkalfov
@Nikola 但是这样你就失去了强类型响应,虽然OP没有使用,但这种方法仍然是一个选项。我正在开发一个Web API项目,不使用强类型会导致问题 - 其中一个问题是我们无法轻松生成正确的Swagger。如果可以,请避免返回未经分类的响应。 - Sten Petrov
2
在我的情况下,我发现在头文件中返回响应数据是最好的解决方案,但是你必须小心操作筛选器获取的数据位置。它必须获取您正在处理的请求的数据,我到处寻找独特于请求的数据存储位置,唯一能找到的就是“context.Request.Properties”表,这很可能是@Yousuf使用它的原因。请记住,在处理操作时不存在“context.Response”对象,因此“context.Request”似乎是您可以存储此类数据的唯一位置。 - Michael Erickson
关于强类型响应,不幸的是这是HTTP协议的本质,所有数据都是文本。您可以考虑一些包含类型验证的XML或JSON格式来验证数据的传输。 - Michael Erickson

23

简单的解决方案就是只写这个:

HttpContext.Current.Response.Headers.Add("MaxRecords", "1000");

谢谢 Darek 提供的代码高亮功能。我会从现在开始确保使用它 :) - Deepak
5
派生自ApiController的控制器中将不会存在HttpContext。 - ShellNinja
1
如果我们谈论的是从system.web.http.apicontroller派生的.NET框架控制器,那么这确实是正确的答案。我个人只熟悉核心语法,所以对于这个遗留项目来说非常好。干杯! - Paul DeVito
1
这对我有用。我的控制器继承自ApiController。 - Andrew Gale
1
在我的遗留项目中,绝对是救命稻草。 - Kit
1
我用这个类在我的课堂上,该类从 System.Web.Http.ApiController 派生而来。(.NET Framework - 4.5.2.)它完美地工作。 - Yossi G.

14

你所需要的是:

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

    return base.ResponseMessage(response); 
}

我希望这回答了你的问题。


12

如果您需要对每个响应执行操作,最好利用DelegatingHandler。因为它可以在请求/响应管道上工作,而不是控制器/操作级别上工作。在我的情况下,我必须在每个响应中添加一些头信息,所以我做了我描述的事情。请参见下面的代码片段

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;
    }

}

你需要在WebApiConfig中添加此处理程序。

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

四年过去了,现在这正是我正在寻找的东西。 - undefined

0
您可以使用自定义的ActionFilter,来发送自定义头部以及访问HttpContext。
public class AddCustomHeaderFilter : ActionFilterAttribute
{
    public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
    {
       actionExecutedContext.Response.Content.Headers.Add("name", "value");
    }
}

但是我该如何获取头部的“值”? - Matias Cicero
1
有趣的问题。看起来您可以在控制器中设置属性 Request.Properties["Count"] = "123",然后在筛选器中使用它。 - Yousuf
1
在过滤器中,您可以通过 actionContext.Request.Properties["Count"] 访问它。 - Yousuf

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接