How to access other entities from ASP.NET Core Identity classes? - asp.net-core

New to ASP.NET Core. I'm using the Identity framework for authentication, scaffolding among other the Register razor page. In the Register.cshtml.cs, I'd like to get data for populating a dropdown menu. The data is in another part of the Entity Framework tables. So the intention is to be able to select e.g. "Company" when registering a user.
I don't like too fiddle to much with the Register.cshtml.cs, i.e. modifying the constructor to take my own services and/or context objects. But how to access "my own" tables from within that page?
Can it be done? Or shouldn't it be done (why?)? And if not, any advice on making this general user admin stuff in combination with the Identity framework?
Thanks,
Palle

The ASP.NET Core Identity is used to manage users, passwords, profile data, roles, claims, tokens, email confirmation, and more. We can use the UserManager, RoleManager and SignInManager to manage the following tables:
More detail information about Identity, see Identity on ASP.NET Core.
To access other Entity Framework tables, I think you have to use the database context in the Register.cshtml.cs, like this:
public class RegisterModel : PageModel
{
private readonly SignInManager<ApplicationUser> _signInManager;
private readonly UserManager<ApplicationUser> _userManager;
private readonly ILogger<RegisterModel> _logger;
private readonly IEmailSender _emailSender;
private readonly ApplicationDbContext _context;
public RegisterModel(
UserManager<ApplicationUser> userManager,
SignInManager<ApplicationUser> signInManager,
ILogger<RegisterModel> logger,
ApplicationDbContext context,
IEmailSender emailSender)
{
_userManager = userManager;
_signInManager = signInManager;
_logger = logger;
_context = context;
_emailSender = emailSender;
}
[BindProperty]
public InputModel Input { get; set; }
public string ReturnUrl { get; set; }
[BindProperty]
public List<Company> Companies { get; set; }
public IList<AuthenticationScheme> ExternalLogins { get; set; }
public class InputModel
{
...
}
public async Task OnGetAsync(string returnUrl = null)
{
ReturnUrl = returnUrl;
ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
Companies = _context.Companies.ToList(); //get all companies from database.
}
Besides, if you still don't want to use the DB context. You could consider setting the select items with fixed options list or using an enumeration list, without getting them from the database.

Related

How can I extend the user table in identity in Blazor Server Side?

Is there any way to add additional fields in the User table in identity Blazor? because I have many user information such as firstname lastname and others and i want to bring all these when the user is logging in.
To add custom user properties in ASP.NET Core Identity User table, you need to do the following steps:
Create a Custom ApplicationUser class which inherits the IdentiyUser:
public class ApplicationUser: IdentityUser
{
public int Age { get; set; }
public string CustomTag { get; set; }
public bool IsApproved { get; set; }
}
Update the ApplicationDbContext as below:
public class ApplicationDbContext : IdentityDbContext
{
public DbSet<ApplicationUser> ApplicationUsers { get; set; }
public DbSet<ApplicationRole> ApplicationRoles { get; set; } //custom Idnetity Role
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
}
}
Update the Identity Configuration in the ConfigureServices method:
services.AddIdentity<ApplicationUser, ApplicationRole>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders()
.AddDefaultUI();
Enable migration and update the database with the following commands:
Add-Migration CustomUserData
Update-Database
Check the database via SSMS.
Use Scaffold Identity generate the relate identity pages, such as: login, logout. Then, you can update the page content to base on the ApplicationUser model.
[Note] In the Identity Page, they might still use the IdentityUser model, you need to change it to ApplicationUser.
More detail information, see the following tutorials:
Add, download, and delete custom user data to Identity in an ASP.NET Core project
How to add Custom User Properties in ASP.NET Core Identity

Current user (ASP.NET Identity) information within Generic repository pattern

I have implemented generic repository pattern over an EF Core context, for example;
public class GenericRepository<TEntity, TContext> : IGenericRepository<TEntity>
where TEntity : EntityBase
where TContext : DbContext
{
internal TContext context;
internal DbSet<TEntity> dbSet;
public GenericRepository(TContext context)
{
this.context = context;
dbSet = context.Set<TEntity>();
}
public virtual TEntity GetById(long id)
{
return dbSet.Find(id);
}
public virtual void Insert(TEntity entity)
{
entity.CreatedDate = DateTime.Now;
dbSet.Add(entity);
}
//... additional methods removed for brevity
}
All my models are using an EntityBase class that allows me to record when the record was created and by whom, for example;
public abstract class EntityBase {
public long Id { get; set; }
public DateTime CreatedDate { get; set; }
public string CreatedBy { get; set; }
//... additional fields removed for brevity
}
What are the different ways to access the current user within the repository, so I can add the user to the entity on creation;
public virtual void Insert(TEntity entity)
{
entity.CreatedBy = CurrentUser.Name; // TODO: get current user here?
entity.CreatedDate = DateTime.Now;
dbSet.Add(entity);
}
It feels like I should be able to do this via middleware and or injection.
I think, you should pass user information from controller to the repository method or set CreatedBy value inside the controller instead of Repository method. Moreover , in my opinion you should avoid depend your repository to the identity and keep it simple and independent.
In other words, referencing HttpContext.User.Identity inside your repository is not a good idea,and HttpContext belog to the Presentation Layer not Data Layer.

ihttpcontextaccessor.httpcontext.user.findfirst(claimtypes.nameidentifier).value is null

I have a service like this:
public class CurentUserService : ICurentUserService
{
private readonly IHttpContextAccessor _httpContextAccessor;
public string UserId { get; set; }
public CurentUserService(IHttpContextAccessor httpContextAccessor)
{
this._httpContextAccessor = httpContextAccessor;
this.UserId = this._httpContextAccessor.HttpContext?.User?.FindFirstValue(ClaimTypes.NameIdentifier);
}
and I add to startup
services.AddScoped<ICurentUserService, CurentUserService>();
//services.AddHttpContextAccessor();
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
but when à used via dependency injection is all time null when I was logged.
I use asp.net core version 3.1.0
Thanks you for your help.
Can't explain why but this code works
private readonly IHttpContextAccessor _httpContextAccessor;
public CurrentUserService(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
public string UserId => _httpContextAccessor.HttpContext?.User?.FindFirstValue(ClaimTypes.NameIdentifier);
In startup
services.AddSingleton<ICurrentUserService, CurrentUserService>();
services.AddHttpContextAccessor();
source: github.com/jasontaylordev/CleanArchitecture/issues/119

Reference entities from a different DbContext

I'm splitting my DbContext in two: ApplicationContext (only domain specific entities) and IdentityContext (only asp.net core identity entities).
However, I need to add a foreign key to the ApplicationUser entity (IdentityContext), pointing to an entity created in the ApplicationContext, called UserArea. This way, I'll have a table for all company areas and the Aspnetusers table will have a column referencing the UserArea.Id.
Is the duplication of entities (create the Area entity on both contexts) between context the only way to achieve this?
How can I deal with the order of execution of migrations as I may have a table depending on the IdentityContext migrations at the same time as a table from IdentityContext depending on another table from ApplicationContext?
The whole idea here is to have less coupling between the domain layer of my aplication and third party resources like Entity Framework and Identity.
public class ApplicationContext : DbContext
{
public virtual DbSet<UserArea> UserAreas { get; set; }
}
public class IdentityContext : IdentityDbContext<ApplicationUser>, IDisposable
{
private readonly IConfiguration _configuration;
public IdentityContext(DbContextOptions<IdentityContext> options, IConfiguration configuration)
: base(options)
{
_configuration = configuration;
}
public DbSet<UserAudit> UserAuditEvents { get; set; }
}
ApplicationUser class:
public class ApplicationUser : IdentityUser
{
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime DateRegistered { get; set; }
}
UserArea class:
public class UserArea
{
public int Id { get; set; }
public string Name { get; set; }
}
Treat those 2 DbContext as separate ones, without any relation between them. You can use manually assigned ID's (GUIDs) to represent relation between them.
Imagine a 3rd party Identity server and JWT authentication (Auth0, Okta, IdentityServer) - so it's basically an implementation of your IdentityDbContext. The back end will get an unique identifier for that user. See: https://www.rfc-editor.org/rfc/rfc7519#section-4 ant the sub value.
Your DB context will need to have:
an User class as well - it will have a unique ID (best to use integer or sequential guid) and a AuthID - which is the sub from the JWT - and based on you will do all the user lookups
UserArea object (or DB) - which either has a FK to User.ID or does have User.AreaID or there will be a ternary table for NxN
Anyhow, this should do the necessary decoupling.
Now to user creation:
either you decide to accept all users from the given identity server
or you will have to add a boolean: hasAccess flag to the User and also make sure user object is created in your DB when it is added on the identity DB (messaging, callbacks with retries).

ASP.NET Core: DbSet to executing raw stored procedure

I am using ASP.NET Core 2.0 and razor pages. The SQL table does not have a primary key and I will not be able to make changes to the SQL table. I am trying to use a stored procedure to retrieve the data from the table and show the resultant data in UI.
Since there is no primary key available, I am getting error as - Microsoft.EntityFrameworkCore.Infrastructure.ModelValidator.ValidateNonNullPrimaryKeys. I would like to move the code from DBSet to Raw sql as defined in https://www.learnentityframeworkcore.com/raw-sql
Below is my existing code :
//Data - myDbContext
public class MyDbContext : DbContext
{
public DbSet<LOB> lobs { get; set; }
public MyDbContext(DbContextOptions<MyDbContext> options) : base(options)
{
}
}
// Model
public class LOB
{
public string Desc { get; set; }
}
//Index.cshtml.cs
public class IndexModel : PageModel
{
private readonly MyDbContext _dbContext;
public IndexModel(MyDbContext dbContext)
{
_dbContext = dbContext;
}
public List<LOB> lOBs { get; set; } = new List<LOB>();
[BindProperty]
public string[] SelectedLOBs { get; set; }
public SelectList LOBOptions { get; set; }
public async Task OnGetAsync()
{
lOBs = await _dbContext.Set<LOB>().FromSql(
"EXECUTE sp")
.AsNoTracking()
.ToListAsync();
LOBOptions = new SelectList(lOBs, "Desc1");
}
}
// Index.cshtml
<select class="form-control" required multiple id="selLOB" asp-for="SelectedLOBs" asp-items="Model.LOBOptions"></select>
How to fill the dropdown using context.database property ?
Thanks
For Asp.Net Core and EF Core are different. Asp.Net Core 2.0 is corresponding to Microsoft.AspNetCore.All 2.0 and EF Core 2.1 is correspoinding to Microsoft.EntityFrameworkCore 2.1, you could refer Microsoft.EntityFrameworkCore 2.1 in Microsoft.AspNetCore.All 2.0.
Follow steps below to resolve your issue.
Update package Microsoft.EntityFrameworkCore to V2.2.3 and Microsoft.EntityFrameworkCore.Tools to V2.2.3
Change DbSet to DbQuery
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
public DbQuery<LOB> lobs { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
// Customize the ASP.NET Identity model and override the defaults if needed.
// For example, you can rename the ASP.NET Identity table names and more.
// Add your customizations after calling base.OnModelCreating(builder);
}
}
Change your razor code.
public async Task OnGetAsync()
{
lOBs = await _dbContext.Query<LOB>().FromSql(
"EXECUTE sp")
.AsNoTracking()
.ToListAsync();
LOBOptions = new SelectList(lOBs, "Desc", "Desc", "Desc1");
}
Change your view
<select class="form-control" required multiple id="selLOB"
asp-for="SelectedLOBs" asp-items="Model.LOBOptions">
</select>