How to get HttpContext from service layer - c#

I have requirement to add in a header on each request to a service a header MyHeader. MyHeader is the jwt I have received when the user is logging on.
I tried to read it from HttpContext.Request.Headers.
I am trying to access it in my service. I could get the result in the controller but not on the Service layer. Can anyone help me to get the same on service class.
I'm using Asp.net core

In Startup#ConfigureServices:
services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
In your service class:
public class YourServiceClass
{
private readonly IHttpContextAccessor _httpContextAccessor;
...
// constructor
public YourServiceClass(IHttpContextAccessor httpContextAccessor, ...)
{
_httpContextAccessor = httpContextAccessor;
...
}
public void YourServiceMethod()
{
var headers = _httpContextAccessor.HttpContext.Request.Headers;
...
}
...

Related

How to inject object of two different AddSingleton and AddScoped method in one class [duplicate]

I am creating a ASP.NET Core web application. I am using a Repository through a library project. I reference it in the web application project.
The repository interface is as below:
public interface IPushNotificationRepository
{
IQueryable<PushNotification> Notifications
{
get;
}
IQueryable<Client> Clients
{
get;
}
void Add(PushNotification notification);
void Add(Client client);
void AddRange(IList<PushNotification> notifications);
bool AddIfNotAlreadySent(PushNotification notification);
void UpdateDelivery(PushNotification notification);
bool CheckIfClientExists(string client);
Client FindClient(int? id);
void Update(Client client);
void Delete(Client client);
}
Within the repository I inject the db context
public class PushNotificationRepository : IPushNotificationRepository
{
private readonly PushNotificationsContext _context;
public PushNotificationRepository(PushNotificationsContext context)
{
_context = context;
}
}
The configure services of the start up class is as below:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
services.AddSingleton<IPushNotificationRepository, PushNotificationRepository>();
services.AddDbContextPool<PushNotificationsContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("PushNotificationsConnection")));
}
In the controller class I consume the repository:
public class ClientsController : Controller
{
//private readonly PushNotificationsContext _context;
private readonly IPushNotificationRepository _pushNotificationRepository;
public ClientsController(IPushNotificationRepository pushNotificationRepository)
{
_pushNotificationRepository = pushNotificationRepository;
}
}
The repository classes are in a separate library project which is referenced by the web application project. The error I receive is:
System.AggregateException: 'Some services are not able to be
constructed (Error while validating the service descriptor
'ServiceType:
Services.Messaging.Data.Abstract.IPushNotificationRepository Lifetime:
Singleton ImplementationType:
Services.Messaging.Data.PushNotificationRepository': Cannot consume
scoped service 'Services.Messaging.Data.PushNotificationsContext' from
singleton
'Services.Messaging.Data.Abstract.IPushNotificationRepository'.)'
Would really appreciate some advise on this
A singleton cannot reference a Scoped instance. The error message is clear.
Cannot consume scoped service
'Services.Messaging.Data.PushNotificationsContext' from singleton
PushNotificationsContext is considered as a scoped service. You should almost never consume scoped service or transient service from a singleton. You should also avoid consuming transient service from a scoped service. Consuming scoped services it's a good practice to inject what you need, it gets cleaned up after the request automatically.
Either
services.AddTransient < IPushNotificationRepository, PushNotificationRepository>();
or
services.AddScoped< IPushNotificationRepository, PushNotificationRepository>();
will work fine, but check your design. Maybe this is not the behaviour you are looking for.
services.AddDbContext<PushNotificationsContext>() registers the PushNotificationsContext as a service with ServiceLifetime.Scoped which means that your PushNotificationsContext is created per web request. It is disposed when request is completed.
You could inject IServiceScopeFactory which is singleton into your repository, then create a new scope using CreateScope() and request the PushNotificationsContext service from that scope
public class PushNotificationRepository : IPushNotificationRepository
{
IServiceScopeFactory _serviceScopeFactory;
public PushNotificationRepository(IServiceScopeFactory serviceScopeFactory)
{
_serviceScopeFactory = serviceScopeFactory;
}
public void Add(PushNotification notification);
{
using (var scope = _serviceScopeFactory.CreateScope())
{
var context = scope.ServiceProvider.GetRequiredService<PushNotificationsContext>();
//other logic
}
}
}
Refer to c# - DataContext disposed in ASP.NET Core scheduler

Multitenancy on ASP.NET Core in DbContext with Azure B2C

I have a .NET Core 3.1 Web API application that connects to different databases depending on what organization the user is from.
The organization is stored as a string value in their user account in Azure AD B2C, and upon performing a request, I need that user's organization to be identified and the correct connection string chosen based on their organization.
However, I'm having trouble getting the user's 'userId' in my DbContext.
I have tried two different implementations but neither are working.
Within the 'ConfigureServices' method in my Startup.cs file I have specified the following:
...
services.AddHttpContextAccessor();
services.AddTransient<IUserRepository, UserRepository>();
I then declared the Interface with its Service:
public interface IUserRepository
{
public void LogCurrentUser();
}
public class UserRepository : IUserRepository
{
private readonly IHttpContextAccessor _httpContextAccessor;
public UserRepository(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
public void LogCurrentUser()
{
var username = _httpContextAccessor.HttpContext.User.Identity.Name;
}
}
3)And finally, on my DbContext, I attempt to draw the userId from the claim:
public class ApplicationDbContext : DbContext
{
private readonly ApplicationTenantClient _tenant;
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options,
ITenantService tenantProvider,
IUserRepository userRepository,
IHttpContextAccessor httpContextAccessor) : base(options)
{
// Attempt (1)
userRepository.LogCurrentUser();
// Attempt (2)
var userAccessor = httpContextAccessor.HttpContext.User.FindFirst(ClaimTypes.NameIdentifier);
if(userAccessor != null)
{
var userId = userAccessor.Value;
}
_tenant = tenantProvider.GetTenant(); // <- ideally send userId here for lookup on AD Graph
}
...
After authenticating the user, then attempting to call an endpoint, the claim is empty:
First Result:
Second Result:
This is.. highly confusing, any help would be appreciated.
Articles that I have attempted to follow
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/http-context?view=aspnetcore-5.0
https://www.koskila.net/how-to-get-current-user-in-asp-net-core/

Need a way to get the current request URL to configure the database context in multi-tenant application

I am migrating a web app from asp.net mvc to .net core (.net 5), and this has got me stuck.
The site is configured in IIS to accept request from multiple URLs like site1.example.com and site2.example.com. Each site has its own database, accessed through entity framework core.
In the old .net framework, I was able to use one of the events in the global.asax.cs to parse the incoming request URL and lookup the correct tenant database from a configuration file. I'm trying to set up something similar in asp.net core mvc.
Here's the relevant part of my ConfigureServices method in the startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpContextAccessor();
services.AddSingleton<ITenantIdentifier, UrlTenantIdentifier>();
services.AddDbContext<myDbContext>((serviceProvider, dbContextBuilder) =>
{
var tenantIdentifier = serviceProvider.GetRequiredService<ITenantIdentifier>();
var connectionString = Configuration.GetConnectionString(tenantIdentifier.GetCurrentTenantId() + "myDataModel");
dbContextBuilder.UseSqlServer(connectionString);
}, ServiceLifetime.Scoped);
//other services configured below...
}
Then the tenant identifier looks like this:
public interface ITenantIdentifier
{
string GetCurrentTenantId();
}
public class UrlTenantIdentifier : ITenantIdentifier
{
readonly IHttpContextAccessor _httpContextAccessor;
readonly ILogger<UrlTenantIdentifier> _logger;
public UrlTenantIdentifier(IHttpContextAccessor httpContextAccessor, ILogger<UrlTenantIdentifier> logger)
{
_httpContextAccessor = httpContextAccessor;
_logger = logger;
}
public string GetCurrentTenantId()
{
//_httpContextAccessor is null here
//logic below for parsing URL and finding if we're site1 or site2
}
}
Is there a correct way of doing this now that I'm not aware of? How can I set up the entity framework database context for dependency injection when I don't know the connection string key until runtime? Am I going to be stuck configuring separate sites and virtual directories in IIS?
Refactor the DbContext to override the OnConfiguring member. Inject configuration and context accessor and perform configuration there.
public class myDbContext : DbContext {
private readonly ITenantIdentifier tenantIdentifier;
private readonly IConfiguration configuration;
public myDbContext(IConfiguration configuration, ITenantIdentifier tenantIdentifier) {
this.configuration = configuration;
this.tenantIdentifier = tenantIdentifier;
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) {
var connectionString = configuration
.GetConnectionString(tenantIdentifier.GetCurrentTenantId() + "myDataModel");
optionsBuilder.UseSqlServer(connectionString);
}
}
Trying to access the request context at the time the DbContext is being created/initialized is too early in the request flow to get access to the desired information. It needs to happen after the context has already been initialized and injected.
public void ConfigureServices(IServiceCollection services)
services.AddHttpContextAccessor();
services.AddSingleton<ITenantIdentifier, UrlTenantIdentifier>();
services.AddDbContext<myDbContext>(); //Simplified since configuration is internal
//other services configured below...
}
Reference DbContext Lifetime, Configuration, and Initialization

Scoped services in ASP.NET Core with SignalR Hubs

I have introduced SignalR into my ASP.NET Core 2 project, but I'm having some issues using a scoped service that I normally use in my controllers. I feel like the problem may be due to the difference in lifecycles between HTTP requests, websockets and hubs.
On each HTTP request, middleware reads the Authorization token and updates some properties (e.g. id, claims, etc.) on a scoped service (IUser) for the request. I use this service in all of my controllers with no issue. To get this to work with SignalR, I am sending an access_token query parameter and using some other middleware beforehand to add this query parameter as a header which works fine.
The problem arises when I am trying to access the IUser service in my SignalR hub. On construction of the hub, the IUser that gets injected has none of the properties set, despite the middleware for the /hub request just setting them.
If I set the service to be a singleton then it works, but IUser should never persist longer than a single request.
How should I go about setting an IUser for a specific SignalR connection?
// Startup.cs - User has several settable properties
services.AddScoped<IUser, User>();
// User Middleware
public class UserMiddleware
{
private readonly RequestDelegate _next;
public UserMiddleware(RequestDelegate next)
{
_next = next;
}
public Task Invoke(HttpContext context)
{
// retrieve service for this request
var user = context.RequestServices.GetService<IUser>();
// set some properties on the User from auth token
// e.g. User.Id = 1;
return _next(context);
}
}
[Authorize(Roles = Claim.Names.Read)]
public class MyHub : Hub
{
private readonly IUser _user;
public MyHub(IUser user)
{
// user that gets injected has null properties
_user = user;
}
public async Task Foo()
{
// do work with the _user credentials
}
}
public class DownloadController : Controller
{
private readonly IUser _user;
public DownloadController(IUser user)
{
// user gets injected and has properties set
_user = user;
}
}
Have you tried Context.GetHttpContext().RequestServices in your Hub-method? That should be the IServiceProvider of the ongoing request.
To use scope service in signalr hub, you could inject ServiceProvider and create a scope in it directly:
public class ChatHub : Hub
{
private IServiceProvider _serviceProvider;
public ChatHub(IServiceProvider serviceProvider)
{
_serviceProvider= serviceProvider;
}
public async Task Foo()
{
using (var scope = _serviceProvider.CreateScope())
{
var dbContext = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
var user = scope.ServiceProvider.GetRequiredService<IUser>();
}
}
}

Dependency injection with constructor parameter in .net core

I see a lot of code examples on how to use DI in .NET Core, however none of them use constructor parameters.
For example:
Create Authorization Service
Inject the current HTTP header(X-Api-Key) in constructor
In the implementation check if I have access
Here I need to not only use DI on my IAuthorizationService but also inject the token in the constructor. I know how to do it in Ninject, however have no experience in .NET Core DI.
Here is what I have as an example.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddEntityFrameworkSqlite();
services.AddDbContext<MainDbContext>();
services.AddScoped<IAuthorizationService, AuthorizationService>(); // Inject current HttpContext header value as a constructor?
}
I usually flow such values through a service where the data is set in a piece of middleware. For example:
An accessor class which can be injected:
public class ApiKeyAccessor
{
public string ApiKey { get; set; }
}
And a middleware which sets the API key at the beginning of the request:
public class ApiKeyMiddleware
{
private readonly RequestDelegate _next;
public ApiKeyMiddleware(RequestDelegate next)
{
_next = next;
}
public Task Invoke(HttpContext context, ApiKeyAccessor apiKeyAccessor)
{
StringValues key;
if (context.Request.Headers.TryGetValue("X-Api-Key", out key))
{
apiKeyAccessor.ApiKey = key;
return _next(context);
}
// todo: throw exception, etc..
}
}
Now all we have to is add the ApiKeyAccessor to the DI container with a scoped lifetime and add the ApiKeyMiddleware to the request execution pipeline, preferably as soon as possible.
When configured correctly, we can inject the ApiKeyAccessor instance in controllers or services:
public class AuthorizationService
{
private readonly string _apiKey;
public AuthorizationService(ApiKeyAccessor apiKeyAccessor)
{
_apiKey = apiKeyAccessor.ApiKey;
}
}

Categories