LP13
LP13

Reputation: 34079

How to inject HttpHeader value in controller?

I have Web API developed using ASP.NET Core API. Every incoming request has a custom header value inserted. eg x-correlationid. The controller use this value for logging and tracing the request. Currently I'm reading the value in each controller as below

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

I think this is against DI rules.

Instead of reading the value inside the controller's constructor, I want to inject the value in the controller's constructor.
Or
Can middleware read the x-correlationid and *somehow* make it available to all the controllers so we don't have to inject it in any controller?

What would be a better option here?

Upvotes: 10

Views: 6534

Answers (3)

Imran Javed
Imran Javed

Reputation: 12667

Depending on your needs one of following is suitable:

  • If you need your header values at action level, then using FromHeaderAttribute sounds better (lighter and easier).
  • If you need to use this header value in lower layers like Repository or DAL, which will be instantiated before Controller has been initialized, then consider to use middleware to get header values initialized and available for other components.

Upvotes: 2

Will Ray
Will Ray

Reputation: 10879

Instead of reading the value inside the controller's constructor, I want to inject the value in the controller's constructor.

You can't inject the value itself into the constructor of the api controller, because at the time of construction the HttpContext is going to be null.

One "injection-style" option would be to use the FromHeaderAttribute in your actions:

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

Can middleware read the x-correlationid and somehow make it available to all the controllers so we don't have to inject it in any controller?

I think a middleware solution would probably be overkill for what you need. Instead, you can create a custom base class that derives from Controller and have all your Api controllers derive from that.

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

Upvotes: 14

LP13
LP13

Reputation: 34079

This is what I came up with. I think i can also unit test it.

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

then in startup's configureservice method register the adapter

 services.AddSingleton<IRequestContext, RequestContextAdapter>();

and inject it in controller

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

Upvotes: 6

Related Questions