I need to use some current user's data in controller's constructor to initialize repositories, but looks like impossible to get current user's data, because all objects, which can give any data about the user (for example HttpContext or User) is nullable in constructor's area. How can I get needed data inside the constructor or any other way initialize repositories with user's data?
You can use IHttpContextAccessor to access HttpContext in constructor;
public class HomeController : Controller
{
public HomeController(IHttpContextAccessor httpContextAccessor)
{
var httpContext = httpContextAccessor.HttpContext;
}
}
If you are using ASP.NET Core 1.x register IHttpContextAccessor in configure method in startup class and for ASP.NET Core 2.0 I think it is not required.
services.TryAddScoped<IHttpContextAccessor, HttpContextAccessor>();
Why in the constructor?
There are four ways you can get the current user's data in MVC:
You can create a session for the current user's data (preferred)
You can store data in local storage.
Using cookies. But that depends on cookies being allowed in the user's browser.
MVC provides ViewBag. You can pass data by using it.
Related
I am writing an app ASP.Net Core (2.2) MVC. I need to filter some the data inside the DbContext by value of certain claims of the Logged in user. I inject IHttpContextAccessor, but when I try to access HttpContext.User.Identity - all properties are null and all claims are empty.
This is how I am trying to achieve that
I wire up IHttpContextAccessor. I use a standard method like that:
public void ConfigureServices(IServiceCollection services){
services.AddHttpContextAccessor();
...
}
Then I build a custom Provider to extract claims from the User:
public class GetClaimsFromUser : IGetClaimsProvider
{
public string UserId {get; private set;}
public GetClaimsFromUser(IHttpContextAccessor accessor)
{
UserId = accessor.HttpContext?.User.Claims.SingleOrDefault(x => x.Type == ClaimTypes.Name)?.Value;
}
}
Then I also inject it inside ConfigureServices method:
public void ConfigureServices(IServiceCollection services){
...
services.AddScoped<IGetClaimsProvider, GetClaimsFromUser>();
...
}
Afterwards I injected it inside the ApplicationDbContext and try to set the private _userId field inside the constructor:
public class ExpenseManagerDbContext: IdentityDbContext<ApplicationUser>
{
private string _userId;
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options, IGetClaimsProvider claimsProvider) : base(options)
{
_userId = claimsProvider.UserId;
...
}
...
}
And exactly here it is empty. When I access the HttpContext inside the controller, the User.Identity is not empty and everything is fine. However, when I need to access it outside the controller, it is empty.
Thanks for any help!!!
The full code can be found here:
https://github.com/dudelis/expense-manager/blob/master/ExpenseManager.DataAccess/Concrete/EntityFramework/ExpenseManagerDbContext.cs?
You are attempting to access the user in ExpenseManagerDbContext which is the application’s IdentityDbContext. As such, it itself is a dependency of the authentication system and will get resolved when the framework performs the authentication.
So the flow is somewhat like this:
Request comes in.
Authentication middleware runs to authenticate the user.
UserManager resolves ExpenseManagerDbContext.
ExpenseManagerDbContext resolves IGetClaimsProvider.
GetClaimsProvider resolves the HttpContext and attempts to access the user’s claims.
Authentication middleware performs the authentication and sets HttpContext.User with the result.
If you look at steps 5 and 6, you will see that the HttpContext is accessed before the authentication middleware is able to actually authenticate the user and update the user object on the context. And since the authentication middleware always runs at the beginning of a request, this will always be the case.
I would recommend you to rethink your ExpenseManagerDbContext since it probably shouldn’t depend on the currently signed-in user. It should be independent of that. If you have logic there that depends on the user id, then it should probably be a separate service.
Solved!
The problem was in the sharing of the same DbContext for IdentityDbContext and ApplicationDataDbContext.
In my controller I had the following code:
[Authorize]
public class AccountController : Controller
{
[HttpGet]
public IActionResult Index()
{
var accounts = _accountService.GetAll();
var models = _mapper.Map<List<AccountDto>>(accounts);
return View(models);
}
}
And when I tried to call the controller from the browser, the app initialized DbContext first time due to [Authorize] attribute. And this was done without any HttpContext. So when the application made a call to the DbContext in '_accountService.GetAll()', the DbContext was already instantiated and the Constructor method was not called, therefore, all my private fields remained empty!
So I created a second DbContext class only for authentication/authorization purposes.
public class ApplicationDbAuthContext : IdentityDbContext
{
public ApplicationDbAuthContext(DbContextOptions<ApplicationDbAuthContext> options) : base(options)
{
}
}
Due to this, during the request inside the controller the correct DbContext was instantiated when I made a call and it contained the HttpContext.
I will update my code in the repo to show the changes.
Meanwhile, thanks for all the answers.
I need to remove session from a controller because it adds unnecessarily load to my Redis server. I use Redis to store my session.
I have a controller that it's used from a Web-hook that its called rapidly and with high volume, the Web-hook doesn't use session and it would be better if I could completely remove the session from it.
As I google-searched I discover the attribute [ControllerSessionState] that removes the session from the controller but unfortunately it's only for Mvc3.
Is there something similar for Asp.Net Mvc Core?
There are two basic approaches
Middleware filter
Create a base controller from which your stateful controllers inherit from and decorate it with an middleware filter attribute which registers a session.
Once created you'd have a base class
public class SessionPipeline
{
public void Configure(IApplicationBuilder applicationBuilder)
{
applicationBuilder.UseSession();
}
}
[MiddlewareFilter(typeof(SessionPipeline))]
public class StatefulControllerBase : ControllerBase
{
}
and have your stateful Controllers inherit from StatefulControllerBase instead of ControllerBase/Controller
Use MapWhen to conditionally register the Session
This approach was more common in the first versions of ASP.NET Core 1.x, but isn't much used these days
app.MapWhen(context => !context.Request.Path.StartsWith("/hooks/"), branch =>
{
branch.UseSession();
});
This way session middleware will only be used for pathes not matching /hooks/ request path.
Here's my scenario:
I Have a single app, but I need to switch the database connection by route.
Example:
switch(route)
{
case(URL/A):
{
USE DATABASE 1
}
case(URL/B):
{
USE DATABASE 2
}
DEFAULT:
USE DATABASE DEFAULT
}
Is it possible?
Since you're using ASP.NET MVC, your routes depends on your controllers. Then you can imagine having ControllerA using DatabaseA and ControllerB using DatabaseB.
To use multiple database connections, you need a connection string for each one of them.
I would use the following pieces of code to inject instances of DbContextOptionsBuilder inside of Startup.ConfigureServices()
var ContextAOptionsBuilder = new DbContextOptionsBuilder<ContextA>();
var ContextBOptionsBuilder = new DbContextOptionsBuilder<ContextB>();
Then you can configure your builders this way (depending on your parameters)
ContextAOptionsBuilder.UseSqlServer(Configuration.GetConnectionString("ContextAConnectionString"), builder =>
{
builder.EnableRetryOnFailure(5, TimeSpan.FromSeconds(30), null);
});
ContextAOptionsBuilder.EnableSensitiveDataLogging();
Then you can inject them as singletons this way :
services.AddSingleton(typeof(DbContextOptionsBuilder<ContextA>),ContextAOptionsBuilder);
You can use a BaseController, whose constructor parameters can access to services this way :
public BaseController(IConfiguration configuration, IMemoryCache memoryCache,
IHttpContextAccessor contextAccessor,
DbContextOptionsBuilder<ContextA> ContextAOptionsBuilder,
DbContextOptionsBuilder<ContextB> ContextBOptionsBuilder){}
Of course, ControllerA and ControllerB being heir classes of BaseController, you can access desired builder quite simply.
public ControllerA(IConfiguration configuration,
IMemoryCache cache,
IHttpContextAccessor contextAccessor,
DbContextOptionsBuilder<ContextA> ContextAOptionsBuilder,
DbContextOptionsBuilder<ContextB> ContextBOptionsBuilder)
:base(configuration, cache, contextAccessor, ContextAOptionsBuilder,ContextBOptionsBuilder)
{
//Create your DbContext using the builder
}
This way you can use one, the other, or both database to build your context
A simpler way would have been injecting your configuration file and building your context from it's content but ppumkin's comment suggested it's a bad idea to do this at a controller level.
This solution is working for me in an ASP.NET Core MVC application, I am still learning the framework but maybe my answer gave you precisions about multiple DbContexts.
You can create 3 connection string also 3 data access Classes. First of your class uses for example DropCreateDatabaseIfModelChanges others use CreateDatabaseIfNotExists. When you call first class your database creates when you need others there will no need recreate it.
Register your context (as scoped, per request) and use factory method for dynamically creating context with specified connection string based on current route (which should be available from HttpContext or something similar). If the databases schemas are same and just data is different this should work easily. I can't provide a snippet for you because it's mostly depends on what DI framework you have.
Forgive me for my lack of coding knowledge as well as ability to ask the right question.
I'm rather new to this ASP.Net Web Application thing (Core), yet I still wondered..
In my current application, I have a class that has a property in which it gets it from a static variable, set when a user requests a controller. So the flow is: User sends a request with a variable in body, if not specified in body, the StaticClass.StaticProperty (example) is then set to the variable the user specified in the body (or default = 0), data is returned based upon the variable.
Yet I wondered, since there is no thread guarantee on this variable, whether or not this could be changed or messed up when the web application gets 50,000 requests at once?
I looked into sessions and tried the following:
service.AddSession(); //Not sure this even does anything?
HttpContext.Session.SetString //Setting this works in the controller, but I cant access it elsewhere by GetString
System.Web.HttpContext.Current.Session["test"] // Cant even access System.Web.Httpcontext, doesn't seem to exist.
HttpContext.Current //doesn't exist either
Session["test"] //doesn't exist either
Can I send a session over somewhere? I'm pretty lost.
Not sure if any of this made sense, I'll try to elaborate if needed.
Thank you in advance.
EDIT: Updated info.
I have added this to my startup.cs:
services.AddSingleton();
services.AddSession(options =>
{
options.IdleTimeout = TimeSpan.FromSeconds(10);
options.Cookie.HttpOnly = true;
});
and
app.UseSession();
Setting the Session variable:
https://i.imgur.com/CY8rcdk.png
Using the Session variable:
https://i.imgur.com/SuLJKzV.png
Variable is always null.
Thank you for trying to help.
HttpContext is accessible only from things that are request specific, since it's a context of one and only request. And new controller instances are created by the framework for each request, with injected HttpContext. It's the developers job to pass it further if the need arises.
I recommend reading this article about it: https://dotnetcoretutorials.com/2017/01/05/accessing-httpcontext-asp-net-core/
First in your startup.cs, you need to register IHttpContextAccessor as a service like so :
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
}
When you create a helper/service class, you can then inject in the IHttpContextAccessor and use it. It would look like something not too dissimilar to this :
public class UserService : IUserService
{
private readonly IHttpContextAccessor _httpContextAccessor;
public UserService(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
public bool IsUserLoggedIn()
{
var context = _httpContextAccessor.HttpContext;
return context.User.Identities.Any(x => x.IsAuthenticated);
}
}
I am using Session in .Net core, However i am able to set and get the Session data in Controller like
HttpContext.Session.SetString("User", "True");
var user = HttpContext.Session.GetString("User");
But when i am trying to use the same code in a concrete class i am not able to do so. It does not show GetString or SetString after HttpContext.Session.
It does not work after
HttpContext.Session
Please help
Thanks
To access session in non-controller class -
First, register the following service in Startup.ConfigureServices
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
Now, register a class (example - SessionManager) where you want to access the Session in Startup.ConfigureServices.
services.AddScoped<SessionManager>();
Now, in SessionManager class, add the following code.
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly ISession _session;
public SessionManager(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
_session = _httpContextAccessor.HttpContext.Session;
}
The above code is receiving IHttpContextAccessor object through dependency injection and then, it is storing Sessions in a local variable.
That's because HttpContext is a member of Controller, and outside a controller, it's a type name. See Access the current HttpContext in ASP.NET Core how to inject the IHttpContextAccessor into a class and access the session from there.
However, it's generally inadvisable to use the session in a class library. You'd better pass the particular values to your library call. So instead of accessing the settings in the library method, you do:
// controller code
var user = HttpContext.Session.GetString("User");
var libraryResult = _fooLibrary.Bar(user);
HttpContext.Session.SetString("UserResult", libraryResult);