ClaimType.GivenName doesn't return my first name - asp.net-core

I am developing .net core 2.2 application that authenticates from Azure AD. I would like to get the user's first name in the _LoginPartial.cshtml in RAZOR web app. I am able to get the user's surname and email but not the first name. Is there away to get this?
This is what i have in my login partial view:
Claim nameClaim = User.Claims.FirstOrDefault<Claim>(claim => string.Compare(claim.Type, "name", StringComparison.Ordinal) == 0);
string userName = (nameClaim != null) && !string.IsNullOrEmpty(nameClaim.Value) ? nameClaim.Value : ((User != null) && (User.Identity != null) ? User.Identity.Name : string.Empty);
Also i tried this:
#User.FindFirst(System.Security.Claims.ClaimTypes.GivenName).Value
The given name returns email same as name and email properties!!
What would be the ideal way to get the first name by extending the identity model in asp.net?

For Identity, there is no FirstName in the built-in IdentityUser, you need to implement your own user like:
public class ApplicationUser:IdentityUser
{
public string FirstName { get; set; }
}
Then, implement UserClaimsPrincipalFactory<ApplicationUser>
public class CustomClaimsIdentityFactory : UserClaimsPrincipalFactory<ApplicationUser>
{
public CustomClaimsIdentityFactory(UserManager<ApplicationUser> userManager
, IOptions<IdentityOptions> optionsAccessor)
: base(userManager, optionsAccessor)
{
}
public async override Task<ClaimsPrincipal> CreateAsync(ApplicationUser user)
{
var principal = await base.CreateAsync(user);
//custom claims
((ClaimsIdentity)principal.Identity).AddClaims(new[] {
new Claim("FirstName", user.FirstName)
});
return principal;
}
}
Then, you could check the FirstName by #User.Claims.FirstOrDefault(c => c.Type == "FirstName")?.Value like
#using Microsoft.AspNetCore.Identity
#using TestIdentity.Data
#inject SignInManager<ApplicationUser> SignInManager
#inject UserManager<ApplicationUser> UserManager
<ul class="navbar-nav">
#if (SignInManager.IsSignedIn(User))
{
<li class="nav-item">
<a class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Manage/Index" title="Manage">Hello #User.Claims.FirstOrDefault(c => c.Type == "FirstName")?.Value!</a>
</li>
<li class="nav-item">
<form class="form-inline" asp-area="Identity" asp-page="/Account/Logout" asp-route-returnUrl="#Url.Action("Index", "Home", new { area = "" })">
<button type="submit" class="nav-link btn btn-link text-dark">Logout</button>
</form>
</li>
}
else
{
<li class="nav-item">
<a class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Register">Register</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Login">Login</a>
</li>
}
</ul>

Related

ASP.Net Core MVC Passing model to controller empty why?

I have a razor view page which is showing data from my database. I have a submit button which calls the action with the model but in the controller the model is empty. Can some please explain what I am doing wrong? Here is my razor page code:
<div class="row align-items-start mt-4">
<div class="col-12">
<a class="btn btn-primary float-right" asp-controller="CustomerDetails" asp-action="UpdateCustomer" asp-route-customermodel="#Model.customer"><span class="fas fa-plus-circle"></span> Submit</a>
</div>
</div>
Here is my controller code: -
public IActionResult UpdateCustomer(Customer customermodel)
{
if (ModelState.IsValid)
{
customerRepository.update(customermodel);
} else
{
string error = ModelState.Values.ToString();
}
return View("../Home/Index");
}
I found the line of code below which seems to work but again I have to add 50 property names rather than just the model. Seems like this is not the correct approach or I am doing it wrong.
public ActionResult Create([Bind(Include = "CourseID,Title,Credits,DepartmentID")]Course course)
Thanks for any advice,
If you use asp-route-customermodel="#Model.customer",you will get:
<a class="btn btn-primary float-right" href="/CustomerDetails/UpdateCustomer?customermodel=ClientSideDemo3.Models.Customer"><span class="fas fa-plus-circle"></span> Submit</a>
asp-route-xxxcannot bind model,here is a demo:
Model:
public class Customer
{
public int Id { get; set; }
public string Name { get; set; }
}
View:
<a class="btn btn-primary float-right" asp-controller="CustomerDetails" asp-action="UpdateCustomer" asp-route-Id="#Model.customer.Id" asp-route-Name="#Model.customer.Name"><span class="fas fa-plus-circle"></span> Submit</a>
result:

ASP.NET Core does not log me in

EDIT: So After many failed attempts to figure out why this is not working I decided to create a project and choose to have user login implemented from there so it will configure everything for me.
after that I just scraped the automated login/register code and replaced it with my own. everything works amazingly well. I am pretty sure in my original version I missed some needed parameter
I am trying to make a simple login function. but I cant seem to get it to work and it drives me crazy ..
so I do my shared folder under _layout.cshtml I have added this
#inject SignInManager<IdentityUser> signInManager;
and then just a little later inside my navbar I added this elements
<ul class="navbar-nav ml-auto">
#if (signInManager.IsSignedIn(User)) {
<li class="nav-item">
<form method="post" asp-controller="User" asp-action="Logout">
<button type="submit" class="nav-link btn btn-link py-0" style="width:auto">
Logout #User.Identity.Name
</button>
</form>
</li>
}
else {
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="User" asp-action="Register">Register</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="User" asp-action="Login">Login</a>
</li>
}
</ul>
and in my controller I added this I called this controller UserController.cs
If I enter wrong login info . like wrong password or username it will tell me I that. so the connection with the database is no issue. but when I am send in my "Home" Index I am not logged it. as if it does not create a session I am not sure
[HttpGet]
public IActionResult Login()
{
return View();
}
[HttpPost]
public async Task<IActionResult> Login(Login model)
{
if (ModelState.IsValid)
{
var result = await signInManager.PasswordSignInAsync(model.UserName,model.Password,
model.RememberMe,false);
if (result.Succeeded)
{
//creates a temponary sign in session cookie. that cookie is going to be lost after the browser is closed
return RedirectToAction("index", "home");
}
//if succeeded = false then ew llop through the error list
ModelState.AddModelError(string.Empty,"Invalid Login");
}
return View(model);
}
Here is the whole working demo like below:
View(Login.cshtml):
#using Microsoft.AspNetCore.Identity
#model LoginViewModel
#inject SignInManager<IdentityUser> SignInManager
#{
ViewData["Title"] = "Log in";
}
<h1>#ViewData["Title"].</h1>
<div class="row">
<div class="col-md-8">
<section>
<form asp-controller="Account" asp-action="Login" asp-route-returnurl="#ViewData["ReturnUrl"]" method="post" class="form-horizontal" role="form">
<h4>Use a local account to log in.</h4>
<hr />
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label asp-for="Email" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="Email" class="form-control" />
<span asp-validation-for="Email" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<label asp-for="Password" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="Password" class="form-control" />
<span asp-validation-for="Password" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<div class="checkbox">
<label asp-for="RememberMe">
<input asp-for="RememberMe" />
#Html.DisplayNameFor(m => m.RememberMe)
</label>
</div>
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<button type="submit" class="btn btn-default">Log in</button>
</div>
</div>
<p>
<a asp-action="Register" asp-route-returnurl="#ViewData["ReturnUrl"]">Register as a new user?</a>
</p>
<p>
<a asp-action="ForgotPassword">Forgot your password?</a>
</p>
</form>
</section>
</div>
<div class="col-md-4">
<section>
<h4>Use another service to log in.</h4>
<hr />
#{
var schemes = await SignInManager.GetExternalAuthenticationSchemesAsync();
var loginProviders = schemes.ToList();
if (loginProviders.Count == 0)
{
<div>
<p>
There are no external authentication services configured. See this article
for details on setting up this ASP.NET application to support logging in via external services.
</p>
</div>
}
else
{
<form asp-controller="Account" asp-action="ExternalLogin" asp-route-returnurl="#ViewData["ReturnUrl"]" method="post" class="form-horizontal" role="form">
<div>
<p>
#foreach (var provider in loginProviders)
{
<button type="submit" class="btn btn-default" name="provider" value="#provider.Name" title="Log in using your #provider.Name account">#provider.Name</button>
}
</p>
</div>
</form>
}
}
</section>
</div>
</div>
#section Scripts {
#{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); }
}
Controller:
[Authorize]
public class AccountController : Controller
{
private readonly UserManager<IdentityUser> _userManager;
private readonly SignInManager<IdentityUser> _signInManager;
public AccountController(
UserManager<IdentityUser> userManager,
SignInManager<IdentityUser> signInManager)
{
_userManager = userManager;
_signInManager = signInManager;
}
// GET: /Account/Login
[HttpGet]
[AllowAnonymous]
public IActionResult Login()
{
return View();
}
// POST: /Account/Login
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Login(LoginViewModel model)
{
if (ModelState.IsValid)
{
var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, lockoutOnFailure: false);
if (result.Succeeded)
{
return RedirectToAction("Index", "Home");
}
else
{
ModelState.AddModelError(string.Empty, "Invalid login attempt.");
return View(model);
}
}
return View(model);
}
}
DbContext:
public class ApplicationDbContext : IdentityDbContext
{
public ApplicationDbContext(DbContextOptions options) : base(options) { }
}
Startup.cs
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentityCore<IdentityUser>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddSignInManager()
.AddDefaultTokenProviders();
services.AddAuthentication(o =>
{
o.DefaultScheme = IdentityConstants.ApplicationScheme;
o.DefaultSignInScheme = IdentityConstants.ExternalScheme;
})
.AddIdentityCookies(o => { });
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}
}
Result:

How to display First Name and Last Name instead of email _LoginPartial

am building a web application using ASP.NET CORE 3.0(razor pages), I extended my IdentityUser by adding first name and last name field, and I want display the both fields in place of email in _loginPartial here is my Class
public partial class AppUser : IdentityUser
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Gender { get; set; }
}
Here is where am calling the username
<li>
<a class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Manage/Index" title="Manage">Hello #User.Identity.Name!</a>
<ul>
<li>
<form class="form-inline" asp-area="Identity" asp-page="/Account/Logout" asp-route-returnUrl="#Url.Page("/", new { area = "" })" method="post">
<button type="submit" class="nav-link btn btn-link text-dark">Logout</button>
</form>
</li>
</ul>
</li>
I am not sure of how to get FirstName and LastName in place of User.Identity.Name.
please I need help.
The simplest way to achieve that is to add it to ViewData like this:
In your Account controller on the Manage index view
// read the application user from db
var user = dbContext.Users...
ViewData["UserFirstName"] = user.FirstName;
ViewData["LastName"] = user.LastName;
And then in your view just read those values from ViewData
<div>
Hello #ViewData["UserFirstName"] #ViewData["UserLastName"]
</div>
Another approach is to add a model to your view; here are some good docs that you can read:
https://learn.microsoft.com/en-us/aspnet/core/mvc/views/overview?view=aspnetcore-3.1

How do you display a users full name in the login partial view in ASP.NET Core

I'm new to ASP.NET Core, most of my existing experience is with Java/PHP, and a little ASP.NET MVC about 10 years ago.
I'm using a Mac so I'm trying to build a project using Visual Studio Code and ASP.NET Core.
I've created a web application using 'yo'. I've changed the default database connection string to use an MSSQL database I have on a server. I've ran the dotnet ef database update command to create the necessary tables in the database.
I wanted to add firstname and lastname to the user, so I've created the columns in the AspNetUser table, and edited the ApplicationUser class to reflect this;
namespace pabulicms.Models
{
// Add profile data for application users by adding properties to the ApplicationUser class
public class ApplicationUser : IdentityUser
{
public string Firstname { get; set; }
public string Lastname { get; set; }
}
}
I've gone ahead and amended the view model for the registration form to include the firstname and lastname, and I've updated the Signup method so that the firstname and lastname is saved to the database.
By default the _LoginPartial.cshtml view displays the users username(email address), I'd like to change this to display the users full name, but I'm unsure as to how I do this.
This is what the _LoginPartial looks like at the moment;
#using Microsoft.AspNetCore.Identity
#using pabulicms.Models
#inject SignInManager<ApplicationUser> SignInManager
#inject UserManager<ApplicationUser> UserManager
#if (SignInManager.IsSignedIn(User))
{
<form asp-area="" asp-controller="Account" asp-action="Logout" method="post" id="logoutForm" class="navbar-right">
<ul class="nav navbar-nav navbar-right">
<li>
<a asp-area="" asp-controller="Manage" asp-action="Index" title="Manage">Hello #UserManager.GetUserName(User)!</a>
</li>
<li>
<button type="submit" class="btn btn-link navbar-btn navbar-link">Log out</button>
</li>
</ul>
</form>
}
else
{
<ul class="nav navbar-nav navbar-right">
<li><a asp-area="" asp-controller="Account" asp-action="Register">Register</a></li>
<li><a asp-area="" asp-controller="Account" asp-action="Login">Log in</a></li>
</ul>
}
It's obviously this line I need to change;
Hello #UserManager.GetUserName(User)!
However changing it to #UserManager.GetFirstname(User)! doesn't work, as it tells me that the method GetFirstname doesn't exist;
I searched a lot and finally found this solution:
Change this:
#UserManager.GetUserName(User)
To this: #UserManager.GetUserAsync(User).Result.LastName
refer to: Link
ASP.NET Core Identity library uses claims-based approach to Authorization. It means that a logged in user (the one you can access via User object in your views) has some list of claims (name-value pairs) associated with it.
By default, that list contains two claims: for ID and username.
However, it's easy to add to that list any other claim you need (first/last name, the name of the company, current user's balance, etc.).
You will just need to create your own implementation of IUserClaimsPrincipalFactory interface and register it in DI to override the default one.
Here is the article with a step-by-step description how to do it.
You can skip the first part ("zero" part to be more exact) "Preparations" if you already have the additional properties (like FirstName/LastName) in your ApplicationUser class.
In the end I used a view component.
First I created a view component myprojectname/ViewComponents/AccountStatusViewComponent.cs
namespace myprojectname.ViewComponents
{
public class AccountStatusViewComponent : ViewComponent
{
private readonly SignInManager<ApplicationUser> _signInManager;
private readonly UserManager<ApplicationUser> _userManager;
public AccountStatusViewComponent(SignInManager<ApplicationUser> signInManager, UserManager<ApplicationUser> userManager)
{
_signInManager = signInManager;
_userManager = userManager;
}
public async Task<IViewComponentResult> InvokeAsync()
{
AccountStatusModel model = new AccountStatusModel();
model.User = _userManager.GetUserAsync(Request.HttpContext.User).Result;
return View(model);
}
}
}
Next I created the view for the view component myprojectname/Views/Shared/Components/AccountStatus/Default.cshtml
#model AccountStatusModel
#using Microsoft.AspNetCore.Identity
#using myprojectname.Models.ViewComponentModels;
#using myprojectname.Models
#inject SignInManager<ApplicationUser> SignInManager
#inject UserManager<ApplicationUser> UserManager
#if (SignInManager.IsSignedIn(User))
{
<form asp-area="" asp-controller="Account" asp-action="Logout" method="post" id="logoutForm" class="navbar-right">
<ul class="nav navbar-nav navbar-right">
<li>
<a asp-area="" asp-controller="Manage" asp-action="Index" title="Manage">Hello #Model.User.Firstname #Model.User.Lastname</a>
</li>
<li>
<button type="submit" class="btn btn-link navbar-btn navbar-link">Log out</button>
</li>
</ul>
</form>
}
else
{
<ul class="nav navbar-nav navbar-right">
<li><a asp-area="" asp-controller="Account" asp-action="Register">Register</a></li>
<li><a asp-area="" asp-controller="Account" asp-action="Login">Log in</a></li>
</ul>
}
I created a model to hold the data I wanted passing the view in myprojectname/Models/ViewComponentModels/AccountStatusModel.cs
using System;
namespace pabulicms.Models.ViewComponentModels
{
public class AccountStatusModel
{
public ApplicationUser User { get; set; }
}
}
Finally, in the example .NET website I edited the file Views/Shared/_Layout.cshtml and replaced this line;
#await Html.PartialAsync("_LoginPartial")
With;
#await Component.InvokeAsync("AccountStatus")
my simple way :
top of view
#{
#inject UserManager<ApplicationUser> UserManager;
var DisplayName= UserManager.Users.FirstOrDefault(m=>m.UserName==User.Identity.Name).FullName;
}
Hello #DisplayName
ofcourse may it's not be a best way with best performance but worked for me.

Dynamic menu from database

I have a Login page where on login it takes user to Home Page where dynamic menu is loaded.The problem is that when a user clicks on one of the menulink the loaded menu is not visible
This is because I have written the code inside Index action of theHome controller.
So my question is where should I write the logic for dynamic menu so that it is accessible on clicking the menulink.
_Layout.cshtml file where menu is loaded
#model SMS.Models.ViewModel.DashboardVM
#if (Model != null && Model.MenuParentList.Count > 0)
{
<!-- Sidebar Menu -->
<ul class="sidebar-menu">
<li class="header">MAIN NAVIGATION</li>
<li class="active">
<a href="#">
<i class="fa fa-dashboard"></i> <span>Dashboard</span>
</a>
</li>
#foreach (var parentItem in Model.MenuParentList)
{
<li class="treeview">
<a href="#">
<i class="fa fa-th"></i>
<span>#parentItem.MenuParentName</span>
<i class="fa fa-angle-left pull-right"></i>
</a>
<ul class="treeview-menu">
#Html.Partial("_MenuParent", Model.MenuList.Where(x => x.ParentID == parentItem.MenuParentID))
</ul>
</li>
}
</ul>
}
Logic for dynamic menu goes here
public ActionResult Index()
{
var _dashboardVM = new DashboardVM
{
User = _employee.Users.FirstOrDefault(),
MenuParentList=_db.Menus
.Where(x => _parentList.Contains(x.Id))
.Select(x => new SMS.Models.ViewModel.DashboardVM.MenuParent
{
MenuParentID = x.Id,
MenuParentName = x.MenuName
})
.OrderBy(x=>x.MenuParentID)
.ToList(),
MenuList=_employee.Designation.Role.MenuRoles
.Select(x=>x.Menu)
.ToList()
};
}
Create a separate [ChildActionOnly] method that generates your menu and call it from the layout page so its available in all pages
[ChildActionOnly]
public ActionResult Menu()
{
var model = new DashboardVM
{
....
}
return PartialView("_Menu", model);
}
and create a _Menu.cshtml partial view to generate the html
#model DashboardVM
....
and then in your layout, remove #model SMS.Models.ViewModel.DashboardVM (a layout should not have a model unless that model is a base class for all models used by the layout) and then include
#Html.Action("Menu", yourControllerName)
which will call the Menu method and insert the partial view it returns into the layout.