如何在控制器中注入HttpHeader值?

时间:2016-09-12 22:27:37

标签: asp.net-core asp.net-core-webapi

我使用ASP.NET Core API开发了Web API。每个传入的请求都插入了自定义标头值。例如x-correlationid。控制器使用此值来记录和跟踪请求。 目前我正在读取每个控制器中的值,如下所示

[Route("api/[controller]")]
public class DocumentController : Controller
{
    private ILogger<TransformController> _logger;
    private string _correlationid = null;

    public DocumentController(ILogger<DocumentController > logger)
    {
        _logger = logger;
        _correlationid = HttpContext.Request.Headers["x-correlationid"];
    }

    [HttpPost]
    public async Task<intTransform([FromBody]RequestWrapper request)
    {
        _logger.LogInformation("Start task. CorrelationId:{0}", _correlationid);

         // do something here

        _logger.LogInformation("End task. CorrelationId:{0}", _correlationid);

        return result;
    }
}

我认为这违反了DI规则。

我想在控制器的构造函数中注入值,而不是读取控制器构造函数中的值。

中间件可以读取x-correlationid*somehow*使其可供所有控制器使用,因此我们不必将其注入任何控制器吗?

这里有什么更好的选择?

3 个答案:

答案 0 :(得分:10)

  

我想在控制器的构造函数中注入值,而不是读取控制器构造函数中的值。

您不能将值本身注入到api控制器的构造函数中,因为在构造时HttpContext将是null

一种“注入式”选项是在您的操作中使用FromHeaderAttribute

[HttpPost]
public async Task<int> Transform(
    [FromBody]RequestWrapper request,
    [FromHeader(Name="x-correlationid")] string correlationId)
{
    return result;
}
  

中间件是否可以读取x-correlationid而以某种方式使其可供所有控制器使用,因此我们不必将其注入任何控制器?

我认为中间件解决方案可能会因你需要而过度杀伤。相反,您可以创建一个派生自Controller的自定义基类,并让您的所有Api控制器都从中派生出来。

public class MyControllerBase : Controller
{
    protected string CorrelationId =>
        HttpContext?.Request.Headers["x-correlationid"] ?? string.Empty;
}

[Route("api/[controller]")]
public class DocumentController : MyControllerBase 
{
    private ILogger<TransformController> _logger;

    public DocumentController(ILogger<DocumentController> logger)
    {
        _logger = logger;
    }

    [HttpPost]
    public async Task<intTransform([FromBody]RequestWrapper request)
    {
        _logger.LogInformation($"Start task. CorrelationId:{CorrelationId}");

        // do something here

        _logger.LogInformation($"End task. CorrelationId:{CorrelationId}");
        return result;
    }
}

答案 1 :(得分:1)

这就是我想出的。我想我也可以对它进行单元测试。

public interface IRequestContext
{
    string CorrelationId { get; }
}

public sealed class RequestContextAdapter : IRequestContext
{
    private readonly IHttpContextAccessor _accessor;
    public RequestContextAdapter(IHttpContextAccessor accessor)
    {
        this._accessor = accessor;
    }

    public string CorrelationId
    {
        get
        {
            return this._accessor.HttpContext.Request.Headers[Constants.CORRELATIONID_KEY];
        }
    }
}

然后在startup的configureservice方法中注册适配器

 services.AddSingleton<IRequestContext, RequestContextAdapter>();

并将其注入控制器

[Route("api/[controller]")]
public class DocumentController : Controller
{
    private ILogger<TransformController> _logger;
    private IRequestContext _requestContext = null;

    public DocumentController(ILogger<DocumentController > logger,IRequestContext requestContext)
    {
        _logger = logger;
        _requestContext = requestContext;
    }

    [HttpPost]
    public async Task<intTransform([FromBody]RequestWrapper request)
    {
        _logger.LogInformation("Start task. CorrelationId:{0}", _requestContext.CorrelationId);

         // do something here

        _logger.LogInformation("End task. CorrelationId:{0}", _requestContext.CorrelationId);

        return result;
    }
}

答案 2 :(得分:1)

根据您的需求,以下一项是合适的:

  • 如果您需要在操作级别使用标头值,则使用FromHeaderAttribute听起来会更好(更轻松)。
  • 如果您需要在较低的层(例如存储库或DAL)中使用此标头值,这些标头值将在初始化Controller之前实例化,那么请考虑使用中间件来获取标头值进行初始化,并且可用于其他组件。