Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How should I access my ApplicationUser properties from ASP.NET Core Views?

I'm working on an ASP.Net vNext / MVC6 project. I'm getting to grips with ASP.Net Identity.

The ApplicationUser class is apparently where I'm supposed to add any additional user properties, and this works with Entity Framework and my additional properties get stored in the database as expected.

However, the problem comes when I want to access the details of the currently logged in user from within my views. Specifically, I have a _loginPartial.cshtml in which I want to retrieve and display the user's Gravatar icon, for which I need the email address.

The Razor View base class has a User property, which is a ClaimsPrincipal. How do I go from this User property back to my ApplicationUser, to retrieve my custom properties?

Note that I'm not asking how to find the information; I know how to lookup an ApplicationUser from the User.GetUserId() value. This is more a question about how to approach this problem sensibly. Specifically, I don't want to:

  • Perform any sort of database lookup from within my views (separation of concerns)
  • Have to add logic to every controller to retrieve the current user's details (DRY principle)
  • Have to add a User property to every ViewModel.

This seems like a 'cross-cutting concern' that ought to have a centralized standard solution, but I feel like I'm missing a piece of the jigsaw puzzle. What is the best way to get at those custom user properties from within views?

Note: It seems that the MVC team has side-stepped this issue within the project templates by ensuring that the UserName property is always set to the user's email address, neatly avoiding the need for them to perform this lookup to get the user's email address! That seems like a bit of a cheat to me and in my solution the user's login name may or may not be their email address, so I can't rely on that trick (and I suspect there will be other properties I need to access later).

like image 500
Tim Long Avatar asked Mar 03 '16 19:03

Tim Long


2 Answers

Update to original answer: (This violates the op's first requirement, see my original answer if you have the same requirement) You can do it without modifying the claims and adding the extension file (in my original solution) by referencing FullName in the Razor View as:

@UserManager.GetUserAsync(User).Result.FullName

Original Answer:

This is pretty much just a shorter example of this stackoverflow question and following this tutorial.

Assuming you already have the property set up in the "ApplicationUser.cs" as well as the applicable ViewModels and Views for registration.

Example using "FullName" as extra property:

Modify the "AccountController.cs" Register Method to:

    public async Task<IActionResult> Register(RegisterViewModel model, string returnUrl = null)
        {
            ViewData["ReturnUrl"] = returnUrl;
            if (ModelState.IsValid)
            {
                var user = new ApplicationUser {
                    UserName = model.Email,
                    Email = model.Email,
                    FullName = model.FullName //<-ADDED PROPERTY HERE!!!
                };
                var result = await _userManager.CreateAsync(user, model.Password);
                if (result.Succeeded)
                {
                    //ADD CLAIM HERE!!!!
                    await _userManager.AddClaimAsync(user, new Claim("FullName", user.FullName)); 

                    await _signInManager.SignInAsync(user, isPersistent: false);
                    _logger.LogInformation(3, "User created a new account with password.");
                    return RedirectToLocal(returnUrl);
                }
                AddErrors(result);
            }

            return View(model);
        }

And then I added a new file "Extensions/ClaimsPrincipalExtension.cs"

using System.Linq;
using System.Security.Claims;
namespace MyProject.Extensions
    {
        public static class ClaimsPrincipalExtension
        {
            public static string GetFullName(this ClaimsPrincipal principal)
            {
                var fullName = principal.Claims.FirstOrDefault(c => c.Type == "FullName");
                return fullName?.Value;
            }   
        }
    }

and then in you views where you need to access the property add:

@using MyProject.Extensions

and call it when needed by:

@User.GetFullName()

The one problem with this is that I had to delete my current test user and then re-register in order see the "FullName" even though the database had the FullName property in it.

like image 67
willjohnathan Avatar answered Nov 17 '22 09:11

willjohnathan


I think you should to use Claims property of User for that purpose. I found good post about: http://benfoster.io/blog/customising-claims-transformation-in-aspnet-core-identity

User class

public class ApplicationUser : IdentityUser
{
    public string MyProperty { get; set; }
}

Let's put MyProperty into Claims of Authenticated User. For this purpose we are overriding UserClaimsPrincipalFactory

public class MyUserClaimsPrincipalFactory : UserClaimsPrincipalFactory<ApplicationUser, IdentityRole>
{
    public MyUserClaimsPrincipalFactory (
        UserManager<ApplicationUser> userManager,
        RoleManager<IdentityRole> roleManager,
        IOptions<IdentityOptions> optionsAccessor) : base(userManager, roleManager, optionsAccessor)
    {
    }

    public async override Task<ClaimsPrincipal> CreateAsync(ApplicationUser user)
    {
        var principal = await base.CreateAsync(user);

        //Putting our Property to Claims
        //I'm using ClaimType.Email, but you may use any other or your own
        ((ClaimsIdentity)principal.Identity).AddClaims(new[] {
        new Claim(ClaimTypes.Email, user.MyProperty)
    });

        return principal;
    }
}

Registering our UserClaimsPrincipalFactory in Startup.cs

public void ConfigureServices(IServiceCollection services)
{
    //...
    services.AddScoped<IUserClaimsPrincipalFactory<ApplicationUser>, MyUserClaimsPrincipalFactory>();
    //...
}

Now we may access our propery like this

User.Claims.FirstOrDefault(v => v.Type == ClaimTypes.Email).Value;

We may create an extension

namespace MyProject.MyExtensions
{
    public static class MyUserPrincipalExtension
    {
        public static string MyProperty(this ClaimsPrincipal user)
        {
            if (user.Identity.IsAuthenticated)
            {
                return user.Claims.FirstOrDefault(v => v.Type == ClaimTypes.Email).Value;
            }

            return "";
        }
    }
}

We should add @Using to View (I add it to global _ViewImport.cshtml)

@using MyProject.MyExtensions

And finally we may use this property in any View as method calling

@User.MyProperty()

In this case you haven't additional queries to database for getting user information.

like image 18
Alfer Avatar answered Nov 17 '22 10:11

Alfer