27

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).

TanvirArjel
  • 22,594
  • 10
  • 59
  • 91
Tim Long
  • 13,000
  • 18
  • 75
  • 141
  • The answer to this is a big one; that I don't have time to answer right now, but here's some ideas. 1. I believe anything that you added is accessible somewhere on the object. I've done it in the past, I just don't know where without tracking down old code. In regards to your bullets: 1. Without more code, can't say, but I don't think that happens by default. 2. You can add code in a single location that retrieves the user on every request; I'd have to find a tutorial. 3. You don't have to do that, but you might want to use a viewmodel instead of the Identity object. – drneel Mar 03 '16 at 20:11
  • 1
    I think I may be on to something: View Components. In MVC6 these allow partial views to have their own controller, into which the UserManager can be injected. More experimentation required, I will post back what I find unless someone comes up with a better answer first. – Tim Long Mar 03 '16 at 21:31
  • Couple of ways I can see this happen. you can either add claims to the principal with needed values or you can just create extension methods on your application user to get to those values in your view. – Muqeet Khan Mar 04 '16 at 02:42
  • Please update or add your own answer when you come up with a viable solution. Thank you. – Chad Mar 04 '16 at 15:16
  • I'm holding off on further development until Microsoft release RC2 of ASP.Net core. – Tim Long Apr 14 '16 at 22:16
  • Hey Tim, how have you done this? " I know how to lookup an ApplicationUser from the User.GetUserId() value" In asp.net core final there seems no extension method. – Pascal Aug 26 '16 at 20:21
  • @Pascal I haven't worked on this project for a while as I am waiting for the code and tools to stabilize, but on a different project I came up with what I think is an even better solution. I'll add it as an answer below... – Tim Long Aug 30 '16 at 17:27
  • I recently watched a video on Asp.net core security and the ClaimsPrinciple. They spoke about using some data stored in the cookie and then UseClaimsTransformation to look up from the database anything that is too much. In your instance, could you place the email address into the Claim cookie (it is secure)? – Edward Comeau Sep 08 '16 at 11:12
  • @EdwardComeau thanks for the suggestion. I prefer not to do that because it will be a duplication of data (the email address is already in the ApplicationUser base class provided by ASP.Net Identity). Probably nothing good will come of having duplicate copies of data. – Tim Long Sep 08 '16 at 23:42
  • @TimLong, no prob. I'm also trying to understand the many different approaches whilst working on similar code, I actually "favorited" this question and like how clearly you outlined the problem. Back to claims, whilst yes it is duplication isn't that the point? It's a convenient duplication so that multiple sites in an SSO environment don't have to go and look up that data themselves? My understanding of claims isn't very deep so I may have the wrong end of the stick... – Edward Comeau Sep 09 '16 at 09:19

6 Answers6

23

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.

Community
  • 1
  • 1
willjohnathan
  • 280
  • 2
  • 8
18

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.

Alfer
  • 509
  • 3
  • 13
  • This has a big problem! if you have a one to one relation with user tables and if you need to get some property from second table, you cannot! for example user.operator.Name is allways null with this way! – Hatef. Sep 26 '17 at 18:47
2

OK, here's how I eventually did it. I used a new feature in MVC6 called View Components. These work a bit like partial views, but they have a "mini controller" associated with them. The View Component is a lightweight controller that doesn't participate in model binding, but it can have something passed to it in constructor parameters, possibly using dependency injection and then it can construct a View Model and pass that to a partial view. So, for example, you can inject a UserManager instance into the View Component, use that to retrieve the ApplicationUser object for the current user and pass that to the partial view.

Here's what it looks like in code. First, the View Component, which lives in /ViewComponents directory:

public class UserProfileViewComponent : ViewComponent
    {
    readonly UserManager<ApplicationUser> userManager;

    public UserProfileViewComponent(UserManager<ApplicationUser> userManager)
        {
        Contract.Requires(userManager != null);
        this.userManager = userManager;
        }

    public IViewComponentResult Invoke([CanBeNull] ClaimsPrincipal user)
        {
        return InvokeAsync(user).WaitForResult();
        }

    public async Task<IViewComponentResult> InvokeAsync([CanBeNull] ClaimsPrincipal user)
        {
        if (user == null || !user.IsSignedIn())
            return View(anonymousUser);
        var userId = user.GetUserId();
        if (string.IsNullOrWhiteSpace(userId))
            return View(anonymousUser);
        try
            {
            var appUser = await userManager.FindByIdAsync(userId);
            return View(appUser ?? anonymousUser);
            }
        catch (Exception) {
        return View(anonymousUser);
        }
        }

    static readonly ApplicationUser anonymousUser = new ApplicationUser
        {
        Email = string.Empty,
        Id = "anonymous",
        PhoneNumber = "n/a"
        };
    }

Note that the userManager constructor parameter is injected by the MVC framework; this is configured by default in Startup.cs in a new project so there's no configuration to be done.

The view component is invoked, unsurprisingly, by calling the Invoke method or the asynchronous version of it. The method retrieves an ApplicationUser if possible, otherwise it uses an anonymous user with some safe defaultspreconfigured. It uses this user to its partiel view s the view model. The view lives in /Views/Shared/Components/UserProfile/Default.cshtml and starts like this:

@model ApplicationUser

<div class="dropdown profile-element">
    <span>
        @Html.GravatarImage(Model.Email, size:80)
    </span>
    <a data-toggle="dropdown" class="dropdown-toggle" href="#">
        <span class="clear">
            <span class="block m-t-xs">
                <strong class="font-bold">@Model.UserName</strong>
            </span> <span class="text-muted text-xs block">@Model.PhoneNumber <b class="caret"></b></span>
        </span>
    </a>

</div>

And finally, I invoke this from within my _Navigation.cshtml partial view like so:

@await Component.InvokeAsync("UserProfile", User)

This meets all of my original requirements, because:

  1. I'm performing the database lookup in a controller (a View Component is a type of controller) and not in a View. Further, the data may well already be in memory as the framework will have already authenticated the request. I haven't looked into whether another database round trip actually happens, and I probably won't bother but if anyone knows, please chime in!
  2. The logic is in one well-defined place; the DRY principle is respected.
  3. I don't have to modify any other View Model.

Result! I hope someone will find this useful...

Tim Long
  • 13,000
  • 18
  • 75
  • 141
  • I've moved away from thsi solution now. Instead, I define an interface ICurrentUser and a concrete class AspNetIdentityCurrentUser. All of the query logic is contained in the concrete class. Then I simply register my ICurrentUser interface as a service in application startup and then I can just inject it wherever it is needed. The devil is in the details of course... – Tim Long Jul 10 '16 at 23:11
2

I have the same problem and the same concerns, however I have chosen a different solution instead creating an extension method to ClaimsPrincipal and let the extension method retrieve the custom user property.

Here is my extension method :

public static class PrincipalExtensions
{
    public static string ProfilePictureUrl(this ClaimsPrincipal user, UserManager<ApplicationUser> userManager)
    {
        if (user.Identity.IsAuthenticated)
        {
            var appUser = userManager.FindByIdAsync(user.GetUserId()).Result;

            return appUser.ProfilePictureUrl;
        }

        return "";
    }
}

Next in my view (also the LoginPartial view), I inject the UserManager and then transfer that UserManager to the extension method :

@inject Microsoft.AspNet.Identity.UserManager<ApplicationUser> userManager;
<img src="@User.ProfilePictureUrl(userManager)">

This solution I believe also meets your 3 requirements of separation of concerns, DRY and no change to any ViewModel. However while this solution is simple and can be used in standard views not only ViewComponents, I am still not happy. Now in my view I can write : @User.ProfilePictureUrl(userManager), but I think it would not be too much to ask that I should be able to write only : @User.ProfilePictureUrl().

If only I could make the UserManager (or IServiceProvider) available in my extension method without function inject it, it would solve the problem, but I know of no way to do that.

Rasmus Rummel
  • 519
  • 3
  • 5
1

As I have been asked about it, I'm posting my eventual solution, albeit in a different (MVC5/EF6) project.

First, I defined an interface:

public interface ICurrentUser
    {
    /// <summary>
    ///     Gets the display name of the user.
    /// </summary>
    /// <value>The display name.</value>
    string DisplayName { get; }

    /// <summary>
    ///     Gets the login name of the user. This is typically what the user would enter in the login screen, but may be
    ///     something different.
    /// </summary>
    /// <value>The name of the login.</value>
    string LoginName { get; }

    /// <summary>
    ///     Gets the unique identifier of the user. Typically this is used as the Row ID in whatever store is used to persist
    ///     the user's details.
    /// </summary>
    /// <value>The unique identifier.</value>
    string UniqueId { get; }

    /// <summary>
    ///     Gets a value indicating whether the user has been authenticated.
    /// </summary>
    /// <value><c>true</c> if this instance is authenticated; otherwise, <c>false</c>.</value>
    bool IsAuthenticated { get; }

Then, I implement that in a concrete class:

/// <summary>
///     Encapsulates the concept of a 'current user' based on ASP.Net Identity.
/// </summary>
/// <seealso cref="MS.Gamification.DataAccess.ICurrentUser" />
public class AspNetIdentityCurrentUser : ICurrentUser
    {
    private readonly IIdentity identity;
    private readonly UserManager<ApplicationUser, string> manager;
    private ApplicationUser user;

    /// <summary>
    ///     Initializes a new instance of the <see cref="AspNetIdentityCurrentUser" /> class.
    /// </summary>
    /// <param name="manager">The ASP.Net Identity User Manager.</param>
    /// <param name="identity">The identity as reported by the HTTP Context.</param>
    public AspNetIdentityCurrentUser(ApplicationUserManager manager, IIdentity identity)
        {
        this.manager = manager;
        this.identity = identity;
        }

    /// <summary>
    ///     Gets the display name of the user. This implementation returns the login name.
    /// </summary>
    /// <value>The display name.</value>
    public string DisplayName => identity.Name;

    /// <summary>
    ///     Gets the login name of the user.
    ///     something different.
    /// </summary>
    /// <value>The name of the login.</value>
    public string LoginName => identity.Name;

    /// <summary>
    ///     Gets the unique identifier of the user, which can be used to look the user up in a database.
    ///     the user's details.
    /// </summary>
    /// <value>The unique identifier.</value>
    public string UniqueId
        {
        get
            {
            if (user == null)
                user = GetApplicationUser();
            return user.Id;
            }
        }

    /// <summary>
    ///     Gets a value indicating whether the user has been authenticated.
    /// </summary>
    /// <value><c>true</c> if the user is authenticated; otherwise, <c>false</c>.</value>
    public bool IsAuthenticated => identity.IsAuthenticated;

    private ApplicationUser GetApplicationUser()
        {
        return manager.FindByName(LoginName);
        }
    }

Finally, I make the following configuration in my DI kernel (I'm using Ninject):

        kernel.Bind<ApplicationUserManager>().ToSelf()
            .InRequestScope();
        kernel.Bind<ApplicationSignInManager>().ToSelf().InRequestScope();
        kernel.Bind<IAuthenticationManager>()
            .ToMethod(m => HttpContext.Current.GetOwinContext().Authentication)
            .InRequestScope();
        kernel.Bind<IIdentity>().ToMethod(p => HttpContext.Current.User.Identity).InRequestScope();
        kernel.Bind<ICurrentUser>().To<AspNetIdentityCurrentUser>();

Then whenever I want to access the current user, I simply inject it into my controller by adding a constructor parameter of type ICurrentUser.

I like this solution as it nicely encapsulates the concern and avoids my controllers having a direct dependency on EF.

Tim Long
  • 13,000
  • 18
  • 75
  • 141
0

You need to do a search (using e.g. Entity Framework) with the name of the current user:

HttpContext.Current.User.Identity.Name
Rob Bos
  • 811
  • 1
  • 8
  • 19
  • Assuming I'm going to be displaying the same information in every view (via a partial view within _layout.cshtml) it seems crazy to do the database lookup explicitly each time. 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 sensibly rather than having data access code in a view. Is there no way to standardise this for every view? – Tim Long Mar 03 '16 at 20:15
  • We usually save this information once into a session object and reuse that object. – Rob Bos Mar 03 '16 at 21:41
  • Makes sense - thanks. I'll put that trick in my back pocket in case I need it. I prefer not to use session state though. I had that drummed into me very early on when I was learning MVC and it's something I've stuck to so far... FYI, I answered my own question with the solution I eventually used. – Tim Long Mar 05 '16 at 13:49