locked
[Identity] Use current user's attributes in CSHTML RRS feed

  • Question

  • User-1370514677 posted

    Hi everyone,

    I've been searching the web for a way to use Identity current user's attributes with Razor syntax for hours and couldn't really find an answer.

    With a foreach loop I would use a

    @foreach(var user in ViewData["user_list"])
    {
        @user.Attribute
    }

    But this won't work as I want to access the current user's attributes

            @if(User.Identity.IsAuthenticated)
            {
                <a asp-controller="User" asp-action="Signout">
                    <a>
                        <img class="avatar_img" src="data:image/png;base64,@Convert.ToBase64String(@User.Identity.Avatar)"/>
                        @User.Identity.UserName
                    </a>
                <a asp-controller="User" asp-action="Signout">Se déconnecter</a>
            }
            else
            {
                <a asp-controller="User" asp-action="Signin">Se connecter</a>
            }

    Any Idea ?

    I also thought about directly sending those attributes from the Controller but it's the same issue (current user's attributes are not accesible from what I've tried)

    Thanks in advance for your help

    Saturday, January 16, 2021 10:25 PM

Answers

  • User475983607 posted

    valenciano8

    So your point is to return the avatar directly from the Controller.

    No. Not really.

    I followed your original design and stored the images as a byte array which is the same concept as a file on disk.   

    valenciano8

    But I already have a GetCurrentUserAvatar method in my UserController, I've changed it so that it returns a Base64String : 

            [HttpGet]
            [Authorize]
            public async Task<string> GetCurrentUserAvatar()
            {
                var current_user = await _usermanager.GetUserAsync(User);
    
                return System.Convert.ToBase64String(current_user.Avatar);
            }

    (see [Avatar picture] How to store avatar picture and link it to Identity | The ASP.NET Forums)

    I then modified my HTML code :

    <img id="user_avatar" src="/User/GetCurrentUserAvatar">

    If I use /User/GetCurrentUserAvatar manually I get a full string.

    If I just look at what the browser is rendering, it is just a blank image :(

    Base64 encoded images are passed to the View like any model.  The View contains the markup to embed the image.  Your last posting is an approach that you fabricated.  It is not an approach you'll found in any reference documentation because it is does not work.  Also, base64 encoding increases the image size by 1/3. 

    Is there some reason why you did not follow my example?  

    • Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
    Tuesday, January 19, 2021 7:20 PM

All replies

  • User1686398519 posted

    Hi valenciano8, 

    But this won't work as I want to access the current user's attributes

    Do you mean that "@User.Identity.UserName" can't get the name of the currently logged in user?If you want to get other information about the current logged in user in ASP.NET Core, you can refer to this link.

    Best Regards,

    YihuiSun

    Monday, January 18, 2021 10:07 AM
  • User-1370514677 posted

    Hi @YihuiSun,

    Thank you for your answer ;)

    Do you mean that "@User.Identity.UserName" can't get the name of the currently logged in user?

    I meant I want to access the Avatar attribute, take a look at my model here :

    using System.ComponentModel.DataAnnotations;
    using Microsoft.AspNetCore.Identity;
    
    namespace Blog.Models
    {
        public class User : IdentityUser
        {
            // Identity Fields
    
            // Customized fields
    
            [Required]
            [Display(Name = "Name : ")]
            public string Name { get; set; }
    
            [Required]
            [Display(Name = "Surname : ")]
            public string Surname { get; set; }
    
            public byte[] Avatar { get; set; }
        }
    }

    I can't just use @User.Avatar inside a .cshtml file.

    What I've tried to do is to create a "GetCurrentUserAvatar()" method that returns the byte array from UserController into HomeController where I return the Index() view.

    UserController.cs

            [HttpGet]
            [Authorize]
            public async Task<byte[]> GetCurrentUserAvatar()
            {
                var current_user = await _usermanager.GetUserAsync(User);
    
                return current_user.Avatar;
            }

    HomeController.cs

    using Microsoft.AspNetCore.Mvc;
    
    namespace Blog.Controllers
    {
        public class HomeController : Controller
        {
            [HttpGet]
            public IActionResult Index()
            {
    // Call of GetCurrentUserAvatar() should be here
    // But it is not a good MVC principle from what I've seen on the web return View(); } } }

    So how to proceed ? :(

    Monday, January 18, 2021 6:04 PM
  • User-1545767719 posted

                        <img class="avatar_img" src="data:image/png;base64,@Convert.ToBase64String(@User.Identity.Avatar)"/>
                        @User.Identity.UserName

    Assuming you are using the ASP.NET Core Identity for the authentication...

    User.Identity returns the ClaimsIdentity. If you want get the information from the ClaimsIdentity, therefore, such information must be registered in the ClaimsIdentity, In additoin you will have to define a extension method to obtain the information from the ClaimsIdentity.

    As a byte array cannot be registered in the ClaimsIdentity, you will have to convert the byte array to base64-encoded string and to register the encoded string. But keep in mind that it will probably make the authentication cookie very long and may result in exceeding the limit. 

    Tuesday, January 19, 2021 2:54 AM
  • User1686398519 posted

    Hi valenciano8, 

    Additional claims can be added to ASP.NET Core Identity by using the IUserClaimsPrincipalFactory<T> interface.

    You can customize a class called AdditionalUserClaimsPrincipalFactory and override GenerateClaimsAsync:

    • Add custom property by adding a new claim to the claims identity.

    AdditionalUserClaimsPrincipalFactory

        public class AdditionalUserClaimsPrincipalFactory : UserClaimsPrincipalFactory<User>
    	{
    		public AdditionalUserClaimsPrincipalFactory(
    			UserManager<User> userManager,
    			IOptions<IdentityOptions> optionsAccessor)
    			: base(userManager, optionsAccessor){ }
    		protected override async Task<ClaimsIdentity> GenerateClaimsAsync(User user)
    		{
    			var identity = await base.GenerateClaimsAsync(user);
    			identity.AddClaim(new Claim("Name", user.Name));
    			identity.AddClaim(new Claim("CustoSurname", user.Surname));
    			identity.AddClaim(new Claim("Avatar",Convert.ToBase64String(user.Avatar)));
    			return identity;
    		}
    	}

    View

    @using Microsoft.AspNetCore.Identity
    @inject SignInManager<User> SignInManager
    @inject UserManager<User> UserManager
    @if (SignInManager.IsSignedIn(User))
    {
        @User.Identity.Name
        <br />
        @(User.FindFirst("Name").Value)
        <br />
        @(User.FindFirst("CustoSurname").Value)
        <br />
        @(User.FindFirst("Avatar").Value)
    }
    else
    {
        <p> Not  Login</p>
    }

    Startup

            public void ConfigureServices(IServiceCollection services)
            {
                services.AddDbContext<ApplicationDbContext>(options =>
                    options.UseSqlServer(
                        Configuration.GetConnectionString("DefaultConnection")));
                services.AddDefaultIdentity<User>(options => options.SignIn.RequireConfirmedAccount = true)
                    .AddEntityFrameworkStores<ApplicationDbContext>();
                services.AddScoped<IUserClaimsPrincipalFactory<User>,AdditionalUserClaimsPrincipalFactory>();
                services.AddControllersWithViews();
                services.AddRazorPages();
            }

    Here is the result. 

    Best Regards,

    YihuiSun

    Tuesday, January 19, 2021 5:21 AM
  • User-1370514677 posted

    Hi @YihuiSun,

    Again, thanks a lot for your very detailed answer !

    I appreciate the help you're providing me with, yet I don't really understand how using Claims is going to help me get through my issue ?

    Because from what I understand, claims are mainly used for authentication : Claims-based authorization in ASP.NET Core | Microsoft Docs

    Isn't there an easier or more conventional way to simply access whatever attributes of current user ?

    Tuesday, January 19, 2021 12:01 PM
  • User475983607 posted

    I assume you are able to save the user's avatar as a byte[] in the database.  The next step is creating an action to return the image.

    I added a content type to the custom user fields so the user can upload different image types.

        public class ApplicationUser : IdentityUser
        {
            public byte[] Avatar { get; set; }
            public string AvatarContentType { get; set; }
        }

    The action gets the current user's avatar.  A default image is shown if the user have not uploaded an avatar.  The default image is in the wwwroot/images folder.  Find a default image you like and copy it to wwwroot/images with the name; no-image.png. 

        [Authorize]
        public class AvatarController : Controller
        {
            private readonly UserManager<ApplicationUser> _userManager;
            private readonly IWebHostEnvironment _webHostEnvironment;
            public AvatarController(UserManager<ApplicationUser> userManager,
                IWebHostEnvironment webHostEnvironment)
            {
                _userManager = userManager;
                _webHostEnvironment = webHostEnvironment;
            }
    
            // GET: AvatarController
            public async Task<ActionResult> IndexAsync()
            {
                var user = await _userManager.GetUserAsync(User);
    
                //Return a default image is the user does not an image on file.
                if(user.Avatar == null || user.Avatar.Length == 0)
                {
                    string path = System.IO.Path.Combine(_webHostEnvironment.WebRootPath, "images", "no-image.png");
                    System.IO.FileStream avatar = System.IO.File.OpenRead(path);
                    return File(avatar, "image/png");
                }
    
                return File(user.Avatar, user.AvatarContentType);
            }
        }

    HTML implementation

    <div>
        <div>Current Avatar</div>
        <img src="/Avatar" alt="Avatar" />               
    </div>

    Tuesday, January 19, 2021 5:12 PM
  • User-1370514677 posted

    Hi @mgebhard,

    Thank you for your answer.

    So your point is to return the avatar directly from the Controller.

    But I already have a GetCurrentUserAvatar method in my UserController, I've changed it so that it returns a Base64String : 

            [HttpGet]
            [Authorize]
            public async Task<string> GetCurrentUserAvatar()
            {
                var current_user = await _usermanager.GetUserAsync(User);
    
                return System.Convert.ToBase64String(current_user.Avatar);
            }

    (see [Avatar picture] How to store avatar picture and link it to Identity | The ASP.NET Forums)

    I then modified my HTML code :

    <img id="user_avatar" src="/User/GetCurrentUserAvatar">

    If I use /User/GetCurrentUserAvatar manually I get a full string.

    If I just look at what the browser is rendering, it is just a blank image :(

    Tuesday, January 19, 2021 6:42 PM
  • User475983607 posted

    valenciano8

    So your point is to return the avatar directly from the Controller.

    No. Not really.

    I followed your original design and stored the images as a byte array which is the same concept as a file on disk.   

    valenciano8

    But I already have a GetCurrentUserAvatar method in my UserController, I've changed it so that it returns a Base64String : 

            [HttpGet]
            [Authorize]
            public async Task<string> GetCurrentUserAvatar()
            {
                var current_user = await _usermanager.GetUserAsync(User);
    
                return System.Convert.ToBase64String(current_user.Avatar);
            }

    (see [Avatar picture] How to store avatar picture and link it to Identity | The ASP.NET Forums)

    I then modified my HTML code :

    <img id="user_avatar" src="/User/GetCurrentUserAvatar">

    If I use /User/GetCurrentUserAvatar manually I get a full string.

    If I just look at what the browser is rendering, it is just a blank image :(

    Base64 encoded images are passed to the View like any model.  The View contains the markup to embed the image.  Your last posting is an approach that you fabricated.  It is not an approach you'll found in any reference documentation because it is does not work.  Also, base64 encoding increases the image size by 1/3. 

    Is there some reason why you did not follow my example?  

    • Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
    Tuesday, January 19, 2021 7:20 PM
  • User-1370514677 posted

    Hi @mgebhard,

    You were right it works !

    I've simplified your code hereafter :

            [HttpGet]
            [Authorize]
            public async Task<IActionResult> GetCurrentUserAvatar()
            {
                var current_user = await _usermanager.GetUserAsync(User);
    
                return File(current_user.Avatar, "image/png");
            }

    So basically what I just had to do was to :

    1. Create a method inside my UserController to GetCurrentUserAvatar
    2. Access current user attributes by using _usermanager
    3. Make it return a File object composed of its source and its type
    4. Render it in HTML
    <img id="user_avatar" src="/User/GetCurrentUserAvatar"> // Yet I'm not sure that hard coding URLs is a good way to proceed

    Thanks !

    Tuesday, January 19, 2021 8:28 PM
  • User475983607 posted

    I've simplified your code hereafter :

    Um No.  You managed to brake the code.  Now there's no default image.  Every user must upload a png otherwise the request will result in a 404.

    // Yet I'm not sure that hard coding URLs is a good way to proceed

    Can you clarify what the issue is?  This one URL handles every authenticated application user.  I suppose you could turn it into a ViewComponent but I still do not understand the problem.

    Tuesday, January 19, 2021 8:56 PM
  • User-1545767719 posted

    Probably you do not want the web server to fetch the image from the database server every time a user requests a page. So you will consider to include a cache control in the action method so that the image will be stored in the Cache of browser for a certain period of time. It will work for the time being.

    However, some time later a user will change his avatar by uploading a new image. At that time the user does not want to see the old avatar on his page. But it is not good idea to expect a user to delete the cache. You will have to consider how to cope with it.

    Wednesday, January 20, 2021 3:25 AM