【问题标题】:ASP.NET MVC Custom user fields on every page每个页面上的 ASP.NET MVC 自定义用户字段
【发布时间】:2015-08-26 21:58:55
【问题描述】:

背景: 我正在构建越来越多的 Web 应用程序,其中设计师/模板制作者决定添加“个人资料图片”和其他一些与用户相关的数据,当然只有在有人登录时。

作为大多数 ASP.NET MVC 开发人员,我使用视图模型为剃刀布局提供我需要显示的信息,这些信息来自存储库等。

通过使用显示用户名很容易

HttpContext.Current.User.Identity.Name

如果我想在这些页面上显示保存在我的后备数据存储中的信息怎么办? ApplicationUser 类中的自定义字段,例如业务单位名称或个人资料图片 CDN url。

(为了简单起见,假设我将身份框架与包含我的 ApplicationUsers 的实体框架(SQL 数据库)一起使用)

问题

你如何解决这个问题:

  1. 不污染视图模型/控制器树(例如,构建 BaseViewModel 或 BaseController 填充/提供此信息?
  2. 不必为这些详细信息的每个页面请求往返数据库?
  3. 如果用户未登录则不查询数据库?
  4. 当您无法使用 SESSION 数据时(因为我的应用程序经常在多个 Azure 实例上扩展 - read why this isn't possible here- 我对 SQL 缓存或 Redis 缓存不感兴趣。

我曾考虑过使用新的视图模型的局部 - 但这仍然会在每次页面加载时往返 SQL 数据库。会话数据现在是安全的,但是当在 azure 中扩展时,这也不是一种方法。知道什么是我最好的选择吗?

TLDR;

如果用户已登录(匿名访问 = 允许),我想在我的应用程序的每个页面上显示用户个人资料信息 (ApplicationUser)。如何在不查询每个页面请求的数据库的情况下显示此信息?如果没有 Session 类,我该如何做到这一点?在不构建基类的情况下如何做到这一点?

【问题讨论】:

  • 我知道你肯定能做到。结帐:stackoverflow.com/questions/8369055/…
  • 您可以使用自定义 IPrincipal 并在用户登录时向您的 FormsAuthenticationTicket 添加其他值,因此可以在每个请求中从 cookie 中读取它们,而不是调用数据库(尽管添加图像cookie 可能不合适)。我不使用身份,但我相信它允许您使用声明来做到这一点。
  • 是否每个页面都相同,如果是,您可以在_Layout 中设置外观。在_Layout 中定义一个Div,它应该包含您的视图,并使用Ajax 加载PartialView 内容。这样您只需加载一次配置文件详细信息
  • 这些只解释了如何向用户对象添加新字段——这根本不是问题(我有这个工作)——我想要的只是在每个用户会话中获取该信息一次,而不是让一直查询数据库。 PartialView 解决方案不会成功,因为我们没有使用前端 mvvm 框架 - 每次加载页面时,都会重新创建/重新加载它。

标签: asp.net asp.net-mvc razor asp.net-identity-2


【解决方案1】:

使用 Identity 的最佳方式是使用声明来存储有关用户的自定义数据。山姆的回答与我在这里所说的非常接近。我会详细说明。

ApplicationUser 类上,您有GenerateUserIdentityAsync 方法,用于创建用户的ClaimsIdentity

public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser, string> manager)
{
    // Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
    var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);

   // Add custom user claims here
   userIdentity.AddClaims(new[]
   {
       new Claim("MyApp:FirstName",this.FirstName), //presuming FirstName is part of ApplicationUser class
       new Claim("MyApp:LastName",this.LastName),
   });

   return userIdentity;
}

这会在最终在身份验证 cookie 中序列化和加密的用户身份上添加键值对 - 记住这一点很重要。

用户登录后,您可以通过HttpContext.Current.User.Identity 使用此身份-该对象实际上是ClaimsIdentity,其声明来自cookie。因此,无论您在登录时间方面提出什么要求,您都可以使用,而无需深入了解您的数据库。

为了从声明中获取数据,我通常在 IPrincipal 上使用扩展方法

public static String GetFirstName(this IPrincipal principal)
{
    var claimsPrincipal = principal as ClaimsPrincipal;
    if (claimsPrincipal == null)
    {
        throw new DomainException("User is not authenticated");
    }

    var personNameClaim = claimsPrincipal.Claims.FirstOrDefault(c => c.Type == "MyApp:FirstName");
    if (personNameClaim != null)
    {
        return personNameClaim.Value;
    }

    return String.Empty;
}

这样您就可以从 Razor 视图中访问您的索赔数据:User.GetFirstName()

而且这个操作真的很快,因为它不需要从你的 DI 容器中解析任何对象,也不需要查询你的数据库。

唯一的障碍是当存储中的值实际更新时,auth cookie 中声明中的值在用户注销和登录之前不会刷新。但是您可以通过IAuehtenticationManager.Signout() 强制执行此操作,并立即使用更新后的声明值重新登录。

【讨论】:

  • 我最终使用了这种方法,感谢您提供更新值的提示(忘记了):)
【解决方案2】:

您可以将您的额外信息存储为声明。在您的登录方法中,将您的数据填充到生成的身份中。例如,如果您使用 Identity 的默认配置,您可以在 ApplicationUser.GenerateUserIdentityAsync() 方法中添加您的声明:

public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser, string> manager)
{
    // Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
    var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);

   // Add custom user claims here
   userIdentity.AddClaims(new[]
   {
       new Claim("MyValueName1","value1"),
       new Claim("MyValueName2","value2"),
       new Claim("MyValueName2","value3"),
       // and so on
   });

   return userIdentity;
}

在您的整个应用程序中,您可以通过阅读当前用户声明来访问这些信息。实际上HttpContext.Current.User.Identity.Name 使用了相同的方法。

public ActionResult MyAction()
{
     // you have access the authenticated user's claims 
     // simply by casting User.Identity to ClaimsIdentity
     var claims = ((ClaimsIdentity)User.Identity).Claims;
     // or 
     var claims2 = ((ClaimsIdentity)HttpContext.Current.User.Identity).Claims;
} 

【讨论】:

  • 我没想到 - 这是实现它的好方法 - 我会检查一下!
【解决方案3】:

我认为“如何做”有点主观,因为可能有很多可能的方法来解决这个问题,但我通过使用与 HttpContext 相同的模式解决了这个确切的问题。我创建了一个名为 ApplicationContext 的类,它具有一个使用 DI 返回一个实例的静态实例属性。 (如果由于某种原因您不使用 DI,您也可以更改该属性以生成单例。)

public interface IApplicationContext
{
    //Interface
    string GetUsername();
}

public class ApplicationContext : IApplicationContext
{
    public static IApplicationContext Current
    {
        get
        {
            return DependencyResolver.Current.GetService<IApplicationContext>();
        }
    }


    //appropriate functions to get required data
    public string GetUsername() {
        if (HttpContext.Current.User.Identity.IsAuthenticated)
        {
            return HttpContext.Current.User.Identity.Name;
        }
        return null;
    }
}

然后您只需直接在视图中引用“当前”属性。

@ApplicationContext.Current.GetUsername()

这将解决除#2 之外的所有要求。数据库调用可能不会增加足够大的开销来保证完全避免,但如果您需要它,那么您唯一的选择是在第一次查询用户数据时实现某种形式的用户数据缓存。

【讨论】:

  • 这实际上是我现在正在这样做的方式,因为它似乎是最合乎逻辑的方式。我只是想减少查询量,因为该系统可能必须支持数百个并发用户,每几秒 100 个查询确实会产生影响。
  • 这是一个非常糟糕的解决方案——这里有很多反模式:从视图中通过使用服务定位器的静态访问器到达服务。这违背了使用 MVC 的目的。
【解决方案4】:

只需使用缓存实现 ChildAction 并因登录用户而异

【讨论】:

  • 他的第四个要求是没有缓存。不过,使用子操作存储在 cookie 中可能仍然有效。
  • 缓存并不意味着您需要自动依赖 SESSION 数据,对吧?
  • 我看到它的方式是利用服务器缓存和由用户变化,并且可能来自 db 的令牌表明配置文件信息已更改。在最简单的情况下不需要使用 cookie,而且可能也不是最有效的策略。
  • 很抱歉回复晚了,但在他的第四个验收标准中,他明确表示他需要能够处理跨多个站点的扩展。所以他不能使用服务器缓存,因为不能保证用户返回到同一个服务器。他还说他对 SQL 缓存或 Redis(x-site 缓存解决方案)不感兴趣。我说 cookie 可以工作,因为您可以向每个请求提交的 cookie 发送一组声明。然后 ChildAction 可以使用 cookie 中的声明来构建响应。
猜你喜欢
  • 2012-08-24
  • 1970-01-01
  • 2014-06-27
  • 1970-01-01
  • 2018-12-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-11-15
相关资源
最近更新 更多