How to inject HttpHeader value in controller? - asp.net-core

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?

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

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

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.

Related

Session.IsNewSession in ASP.NET Core

I am migrating an ASP.NET MVC application to ASP.NET Core 3.1.
And I have a code to check if the session was timed out in my controller, like this:
if (Session.IsNewSession) {
How can I check it in ASP.NET Core?
Thanks
The default implementation of ISession is DistributedSession. This does not expose any property for IsNewSession although its constructor accepts a parameter named isNewSessionKey. So you can use reflection to get that private field of _isNewSessionKey to check it. But that way is not very standard, the name may be changed in future without notifying you any design-time error.
You have several points to intercept and get the info here. The first point is to create a custom ISessionStore (default by DistributedSessionStore) to intercept the call to ISessionStore.Create which gives access to isNewSessionKey. You can capture that value into a request feature just like how the framework set the ISessionFeature after creating the session. Here's the code:
//create the feature interface & class
public interface ISessionExFeature {
bool IsNewSession { get; }
}
public class SessionExFeature : ISessionExFeature {
public SessionExFeature(bool isNewSession){
IsNewSession = isNewSession;
}
public bool IsNewSession { get; }
}
//the custom ISessionStore
public class CustomDistributedSessionStore : DistributedSessionStore, ISessionStore
{
readonly IHttpContextAccessor _httpContextAccessor;
public CustomDistributedSessionStore(IDistributedCache cache,
ILoggerFactory loggerFactory,
IHttpContextAccessor httpContextAccessor) : base(cache, loggerFactory)
{
_httpContextAccessor = httpContextAccessor;
}
ISession ISessionStore.Create(string sessionKey, TimeSpan idleTimeout, TimeSpan ioTimeout, Func<bool> tryEstablishSession, bool isNewSessionKey)
{
var httpContext = _httpContextAccessor.HttpContext;
if(httpContext != null)
{
var sessionExFeature = new SessionExFeature(isNewSessionKey);
httpContext.Features.Set<ISessionExFeature>(sessionExFeature);
}
return Create(sessionKey, idleTimeout, ioTimeout, tryEstablishSession, isNewSessionKey);
}
}
//register the custom ISessionStore inside Startup.ConfigureServices
services.Replace(new ServiceDescriptor(typeof(ISessionStore), typeof(CustomDistributedSessionStore), ServiceLifetime.Transient));
//an extension method to help get the ISessionExFeature conveniently
public static class SessionExFeatureHttpContextExtensions {
public static bool HasNewSession(this HttpContext context){
return context.Features.Get<ISessionExFeature>()?.IsNewSession ?? false;
}
}
To use it in your code:
if (HttpContext.HasNewSession()) {
//...
}
Another point to intercept and get the info is customize both the ISessionStore and ISession. Which means you create a sub class of DistributedSession and expose the property for IsNewSession. That may require more code but it looks more like the old way of getting the info (directly from the Session not kind of via an extension method on HttpContext).

IHttpClientFactory using in ActionFilterAttribute [duplicate]

I am trying to inject a service into my action filter but I am not getting the required service injected in the constructor. Here is what I have:
public class EnsureUserLoggedIn : ActionFilterAttribute
{
private readonly ISessionService _sessionService;
public EnsureUserLoggedIn()
{
// I was unable able to remove the default ctor
// because of compilation error while using the
// attribute in my controller
}
public EnsureUserLoggedIn(ISessionService sessionService)
{
_sessionService = sessionService;
}
public override void OnActionExecuting(ActionExecutingContext context)
{
// Problem: _sessionService is null here
if (_sessionService.LoggedInUser == null)
{
context.HttpContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
context.Result = new JsonResult("Unauthorized");
}
}
}
And I am decorating my controller like so:
[Route("api/issues"), EnsureUserLoggedIn]
public class IssueController : Controller
{
}
Startup.cs
services.AddScoped<ISessionService, SessionService>();
Using these articles as reference:
ASP.NET Core Action Filters
Action filters, service filters and type filters in ASP.NET 5 and MVC 6
Using the filter as a ServiceFilter
Because the filter will be used as a ServiceType, it needs to be registered with the framework IoC. If the action filters were used directly, this would not be required.
Startup.cs
public void ConfigureServices(IServiceCollection services) {
services.AddMvc();
services.AddScoped<ISessionService, SessionService>();
services.AddScoped<EnsureUserLoggedIn>();
...
}
Custom filters are added to the MVC controller method and the controller class using the ServiceFilter attribute like so:
[ServiceFilter(typeof(EnsureUserLoggedIn))]
[Route("api/issues")]
public class IssueController : Controller {
// GET: api/issues
[HttpGet]
[ServiceFilter(typeof(EnsureUserLoggedIn))]
public IEnumerable<string> Get(){...}
}
There were other examples of
Using the filter as a global filter
Using the filter with base controllers
Using the filter with an order
Take a look, give them a try and see if that resolves your issue.
Hope this helps.
Global filters
You need to implement IFilterFactory:
public class AuthorizationFilterFactory : IFilterFactory
{
public bool IsReusable => false;
public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
{
// manually find and inject necessary dependencies.
var context = (IMyContext)serviceProvider.GetService(typeof(IMyContext));
return new AuthorizationFilter(context);
}
}
In Startup class instead of registering an actual filter you register your filter factory:
services.AddMvc(options =>
{
options.Filters.Add(new AuthorizationFilterFactory());
});
One more way for resolving this problem. You can get your service via Context as in the following code:
public override void OnActionExecuting(ActionExecutingContext context)
{
_sessionService = context.HttpContext.RequestServices.GetService<ISessionService>();
if (_sessionService.LoggedInUser == null)
{
context.HttpContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
context.Result = new JsonResult("Unauthorized");
}
}
Please note that you have to register this service in Startup.cs
services.AddTransient<ISessionService, SessionService>();
Example
private ILoginService _loginService;
public override void OnActionExecuting(ActionExecutingContext context)
{
_loginService = (ILoginService)context.HttpContext.RequestServices.GetService(typeof(ILoginService));
}
Hope it helps.
After reading this article ASP.NET Core - Real-World ASP.NET Core MVC Filters (Aug 2016) I implemented it like this:
In Starup.cs / ConfigureServices:
services.AddScoped<MyService>();
In MyFilterAttribute.cs:
public class MyFilterAttribute : TypeFilterAttribute
{
public MyFilterAttribute() : base(typeof (MyFilterAttributeImpl))
{
}
private class MyFilterAttributeImpl : IActionFilter
{
private readonly MyService _sv;
public MyFilterAttributeImpl(MyService sv)
{
_sv = sv;
}
public void OnActionExecuting(ActionExecutingContext context)
{
_sv.MyServiceMethod1();
}
public void OnActionExecuted(ActionExecutedContext context)
{
_sv.MyServiceMethod2();
}
}
}
In MyFooController.cs :
[MyFilter]
public IActionResult MyAction()
{
}
Edit: Passing arguments like [MyFilter("Something")] can be done using the Arguments property of the TypeFilterAttribute class: How do I add a parameter to an action filter in asp.net? (rboe's code also shows how to inject things (the same way))
While the question implicitly refers to "filters via attributes", it is still worth highlighting that adding filters "globally by type" supports DI out-of-the-box:
[For global filters added by type] any constructor dependencies will be populated by dependency injection (DI). Adding a filter by type is equivalent to filters.Add(new TypeFilterAttribute(typeof(MyFilter))).
https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/filters?view=aspnetcore-2.2#dependency-injection
With regards to attribute-based filters:
Filters that are implemented as attributes and added directly to controller classes or action methods cannot have constructor dependencies provided by dependency injection (DI). This is because attributes must have their constructor parameters supplied where they're applied. This is a limitation of how attributes work.
https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/filters?view=aspnetcore-2.2#dependency-injection
However, as mentioned in the previous answers to the OP, there are ways of indirection that can be used to achieve DI. For the sake of completeness, here are the links to the official docs:
ServiceFilterAttribute
TypeFilterAttribute
IFilterFactory implemented on your attribute

How can I make an ASP Core Claims Principal available in a non-controller object?

I am working on an ASP Core 2 project using JWT authentication and the Dapper ORM.
Like all ASP projects, I have a lot of controllers, each instantiating its associated data objects. Each data object inherits from an abstract DbObject class that provides database access services. I also have an AuthenticatedUser object that abstracts the JWT to make it's properties easier to use.
What I want is to do is create the AuthenticatedUser object in the constructor of DbObject. Of course, one method is to create it in the controller and pass it to every concrete data object but this is messy as it would have to be passed hundreds of times (and it just feels wrong).
Is there a way to use the ASP Core middleware to get the token after authentication and make it available through dependency injection in the DbObject?
Edit
Hopefully, this clarifies my intentions. I would like the controller to create data objects and use their properties and methods without regard to implementation (i.e. DbObject). But queries executed by DbObject will be filtered by information in the token of the logged in user.
public class ManufacturerController : Controller {
[HttpGet]
public async Task<IActionResult> Get() {
var manufacturers = await new Manufacturer().SelectMany();
return Ok(manufacturers);
}
[HttpGet("{id}")]
public async Task<IActionResult> Get(int id) {
var manufacturer = await new Manufacturer().SelectOne(id);
return Ok(manufacturer);
}...
public class Manufacturer : DbObject<Manufacturer> {
protected override string QrySelectOne => #"
Select *
From org.fn_Manufacturers ({0})
Where Id = {1}";
protected override string QrySelectMany => #"
Select *
From org.fn_Manufacturers ({0})";
public int Id { get; set; }
public string Name { get; set; }
public string Phone { get; set; }...
public abstract class DbObject<T> {
protected readonly AuthenticatedUser authenticatedUser;
public DbObject(IHttpContextAccessor contextAccessor) {
authenticatedUser = new
AuthenticatedUser(contextAccessor.HttpContext.User);
}
protected abstract string QrySelectOne { get; }
protected abstract string QrySelectMany { get; }
public async Task<T> SelectOne (int id) {...}
public async Task<T> SelectOne(params object[] ids) {...}
public async Task<IEnumerable<T>> SelectMany () {...}
public async Task<IEnumerable<T>> SelectMany (params object[] ids) {...}
I suppose one solution may be to create a static data object factory which has the IHttpContextAccessor injected??
ASP.NET Core provides IHttpContextAccessor interface for accessing HttpContext from non-controller objects.
The usage is fair simple. Inject IHttpContextAccessor into DbObject and access HttpContext by calling IHttpContextAccessor.HttpContext:
public abstract class DbObject
{
protected DbObject(IHttpContextAccessor contextAccessor)
{
var context = contextAccessor.HttpContext;
// Create instance of AuthenticatedUser based on context.User or other request data
}
}
EDIT
Your controllers instantiate data objects directly (with new operator), that's why you can't have IHttpContextAccessor injected out of the box. Here are possible solutions. I list them in order of my preference (from best to worst).
If each controller uses only one (or just several) types of data objects, the best options will be to avoid direct instantiation and move toward normal Dependency Injection.
So if ManufacturerController requires only Manufacturer like in your sample then, it's better to inject Manufacturer instance to controller, not to create it inside:
public class Manufacturer1Controller : Controller
{
private readonly Manufacturer manufacturer;
public Manufacturer1Controller(Manufacturer manufacturer)
{
this.manufacturer = manufacturer ?? throw new ArgumentNullException(nameof(manufacturer));
}
[HttpGet]
public async Task<IActionResult> Get()
{
var manufacturers = await manufacturer.SelectMany();
return Ok(manufacturers);
}
// ...
}
IHttpContextAccessor will be injected into Manufacturer and passed to base DbObject:
public class Manufacturer : DbObject<Manufacturer>
{
public Manufacturer(IHttpContextAccessor contextAccessor) : base(contextAccessor)
{
}
}
It's the cleanest solution in the list. You use DI in classic way and utilize all benefits DI provides.
If one controller could use dozens of different data objects, you could inject the factory object that creates instances of data objects. It could be simple implementation based on IServiceProvider:
public interface IDbObjectFactory
{
TDbObject Create<TDbObject>() where TDbObject : DbObject<TDbObject>;
}
public class DbObjectFactory : IDbObjectFactory
{
private readonly IServiceProvider serviceProvider;
public DbObjectFactory(IServiceProvider serviceProvider)
{
this.serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider));
}
public TDbObject Create<TDbObject>() where TDbObject : DbObject<TDbObject>
{
return serviceProvider.GetRequiredService<TDbObject>();
}
}
public class Manufacturer2Controller : Controller
{
private readonly IDbObjectFactory dbObjectFactory;
public Manufacturer2Controller(IDbObjectFactory dbObjectFactory)
{
this.dbObjectFactory = dbObjectFactory ?? throw new ArgumentNullException(nameof(dbObjectFactory));
}
[HttpGet]
public async Task<IActionResult> Get()
{
var manufacturer = dbObjectFactory.Create<Manufacturer>();
var manufacturers = await manufacturer.SelectMany();
return Ok(manufacturers);
}
}
The code for Manufacturer and DbObject does not change comparing to the first option.
I don't see any reason not to use option #1 or #2. However just to complete the picture, I'll describe another two options.
Inject IHttpContextAccessor into conroller and pass this instance (or IHttpContextAccessor.HttpContext.User) to Data Object constructor invoked with operator new:
public class Manufacturer3Controller : Controller
{
private readonly IHttpContextAccessor contextAccessor;
public Manufacturer3Controller(IHttpContextAccessor contextAccessor)
{
this.contextAccessor = contextAccessor ?? throw new ArgumentNullException(nameof(contextAccessor));
}
[HttpGet]
public async Task<IActionResult> Get()
{
var manufacturer = await new Manufacturer(contextAccessor).SelectMany();
// or
// var manufacturer = await new Manufacturer(contextAccessor.HttpContext.User).SelectMany();
return Ok(manufacturer);
}
}
It's a bad solution, because you don't use Dependency Injection for Manufacturer here and loose many advantages that DI provides.
And the worst option would be using of static object factory with injected IHttpContextAccessor. With this approach you also loose benefits of DI. In addition you get ugly code somewhere in Startup that initializes static instance of IHttpContextAccessor. When you come to this approach, you'll discover that theere is no quite elegant way to do this.
My advice: use option #1 untill you have good reasons against it. Then use option #2.
Here is Sample Project on GitHub with samples for approaches ##1-3.

Get Injected Object in ASP.NET vNext filter

I'm trying to create my custom authorize attribute, but in asp.net vnext using the default dependency injection framework I don't how to get the injected object. I need to get the injected object in the default ctor.
public class CustomAttribute
{
private IDb _db;
public CustomAttribute()
{
_db = null; // get injected object
}
public CustomAttribute(IDb db)
{
_db = db;
}
// apply all authentication logic
}
You can use the ServiceFilterAttribute for this purpose. The service filter attribute lets the DI system take care of instantiating and maintaining the lifetime of the filter CustomAuthorizeFilter and its any required services.
Example:
// register with DI
services.AddScoped<ApplicationDbContext>();
services.AddTransient<CustomAuthorizeFilter>();
//------------------
public class CustomAuthorizeFilter : IAsyncAuthorizationFilter
{
private readonly ApplicationDbContext _db;
public CustomAuthorizeFilter(ApplicationDbContext db)
{
_db = db;
}
public Task OnAuthorizationAsync(AuthorizationContext context)
{
//do something here
}
}
//------------------
[ServiceFilter(typeof(CustomAuthorizeFilter))]
public class AdminController : Controller
{
// do something here
}

Scope issues when using Ninject BindHttpFilter

I have a WebApi service that I am trying to add authentication to using Ninject BindHttpFilter.
Using the BindHttpFilter allows me to bind the authentication filter to a specific attribute. The AuthenticationFilter takes a constructor parameter (IAuthenticationService) which itself is created by Ninject.
kernel.BindHttpFilter<AuthenticationHttpFilter>(System.Web.Http.Filters.FilterScope.Action)
.WhenActionMethodHas<AuthenticationFilterAttribute>()
.WithConstructorArgument("service", x => x.Kernel.Get<IAuthenticationService>());
The concrete implementation of AuthenticationService takes a constructor parameter INonceRepository which is injected via Ninject:
public AuthenticationService(INonceRepository nonceRepository, ...)
The concrete implementation of NonceRepository takes a constructor ISession which is injected via Ninject:
public NonceRepository(ISession session)
Here is what the Ninject bindings look like:
kernel.Bind<INonceRepository>().To<NonceRepository>();
kernel.Bind<IAuthenticationService>().To<AuthenticationService>()
var session = sessionFactory.OpenSession();
Bind<ISession>().ToMethod(c => session).InRequestScope();
When the code runs the concrete implementation of AuthenticationService is only instantiated once and therefore NonceRepositiory is only instantiated once. This means that the ISession is valid and Open the first request but the ISession is closed on the second call and the constructor of AuthenticationService is never called the second time. It seems like it is a scoping issue but I can't figure out what doesn't have the correct scoping to make AuthenticationService get recreated per request.
I've tried to change the BindHttpScope request from FilterScope.Controller to FilterScope.Action (thinking that would cause the scope of AuthenticationService to be that it was create per Action call) but that didn't solve it.
Here is what the the interesting points of code looks like:
public class AuthenticationHttpFilter : IAuthenticationFilter
{
private readonly IAuthenticationService authenticationService;
public AuthenticationHttpFilter(IAuthenticationService service)
{
this.authenticationService = service;
}
public bool AllowMultiple { get; private set; }
public Task AuthenticateAsync(HttpAuthenticationContext authenticationContext, CancellationToken cancellationToken)
{
authenticationService.DoAuth();
return Task.FromResult(0);
}
public Task ChallengeAsync(HttpAuthenticationChallengeContext authenticationChallengeContext, CancellationToken cancellationToken)
{
...
}
}
public class AuthenticationService : IAuthenticationService
{
private readonly INonceRepository nonceRepo;
public AuthenticationService(INonceRepository nonceRepo){...}
public void DoAuth()
{
this.nonceRepo.Add(...);
}
}
public class NonceRepository : INonceRepository
{
private readonly ISession _session;
public NonceRepository(ISession session)
{
this._session = session;
}
public void Add(Nonce nonce)
{
this._session.Save(nonce);
}
}
I found that that when I am using WebApi 2, everything works fine. It behaves in RequestScope like I would expect. However, when using MVC 5, the filter stays cached in the pipeline, which keeps the constructor argument in a "Singleton" scope.
I fixed this problem by reaching into the configuration of the actionContext as such:
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
public class EventGridFilterAttribute : ActionFilterAttribute
{
private readonly Type serviceType = typeof(IEventGridService);
public EventGridFilterAttribute()
{
}
public override async Task OnActionExecutedAsync(HttpActionExecutedContext actionExecutedContext, CancellationToken cancellationToken)
{
var service = actionExecutedContext?.ActionContext?.ControllerContext?.Configuration?.DependencyResolver?.GetService(serviceType) as IEventGridService;
if (service != null && actionExecutedContext.Exception == null)
{
await service.PublishEventsAsync();
}
await base.OnActionExecutedAsync(actionExecutedContext, cancellationToken);
}
}
My dependency injector is saved here.
GlobalConfiguration.Configuration.DependencyResolver = new NinjectDependencyResolver(kernel);
I also do not use the BindHttpFilter, but register my filter like a normal filter.
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new EventGridFilterAttribute());
}