11

我发现几乎每个类(控制器、视图、HTML 助手、服务等)都需要当前登录的用户数据。所以我考虑创建一个“环境上下文”而不是直接注入 IUserService 或用户。

我的方法看起来像这样。

public class Bootstrapper
{
    public void Boot()
    {
        var container = new Container();
        // the call to IUserService.GetUser is cached per Http request
        // by using a dynamic proxy caching mechanism, that also handles cases where we want to 
        // invalidate a cache within an Http request
        UserContext.ConfigureUser = container.GetInstance<IUserService>().GetUser;
    }
}

public interface IUserService
{
    User GetUser();
}

public class User
{
    string Name { get; set; }
}

public class UserContext : AbstractFactoryBase<User>
{
    public static Func<User> ConfigureUser = NotConfigured;

    public static User ActiveUser { get { return ConfigureUser(); } }
}

public class AbstractFactoryBase<T>
{
    protected static T NotConfigured()
    {
        throw new Exception(String.Format("{0} is not configured", typeof(T).Name));
    }
}

示例用法:

public class Controller
{
     public ActionResult Index()
     {
         var activeUser = UserContext.ActiveUser;
         return View();
     }
}

我的方法是正确的还是我遗漏了什么?您有更好的解决方案吗?

更新:

用户类的更多细节:

public class User
{
   string Name { get; set; }
   bool IsSuperUser { get; set;}
   IEnumerable<AzManOperation> Operations { get; set}
}

控制器中,我们需要检查用户是否是超级用户,以便只为超级用户提供一些额外的功能。

public class BaseController : Controller
{
    private readonly IUserService _userService;

    BaseControler(IUserService userService)
    {
        _userService = userService
    }

    public User ActiveUser
    {
        get { return _userService.GetUser(); }
    }
}

视图中,我们检查操作以仅在用户有权这样做时显示编辑或删除按钮。视图从不使用 DependencyResolver,而是使用 ViewBag 或 ViewModel。我的想法是实现一个自定义 ViewBasePage 并提供一个 ActiveUser 属性,以便视图可以轻松访问。

HtmlHelpers中,我们根据 IsSuperUser 和 Operations 渲染控件(传入 User 对象或使用 DependencyResolver)。

服务类中,我们也需要这些属性。例如,决定一个购物篮是否有效(检查是否允许用户购买不在标准列表中的物品)。所以 Service 类依赖IUserService并调用GetUser().

Action Filters中强制用户更改他的密码(仅当它不是 SuperUser 并且 User.ForcePasswordChange 为 true 时)。这里我们使用 DependencyResolver。

我希望有一种更简单的方法来获取 User 对象,而不是使用 DependencyResolver.Current.GetService().GetUser() 或使用ViewBag.ActiveUser = User. 用户对象是一个几乎无处不在需要检查权限等的对象。

4

3 回答 3

8

在视图中,我们检查操作以仅在用户有权这样做时显示编辑或删除按钮。

视图不应执行此检查。控制器应该将视图模型返回到包含布尔属性的视图,这些属性说明这些按钮是否应该可见。返回一个IsSuperUser已经移动到视图中的布尔值。视图不应该知道它应该为超级用户显示某个按钮:这取决于控制器。视图应该只被告知要显示什么。

如果几乎所有视图都有此代码,则有办法从视图中提取重复部分,例如部分视图。如果您发现自己在许多视图模型上重复这些属性,也许您应该定义一个信封视图模型(将特定模型包装为的通用视图模型T)。控制器可以创建其视图模型,而您可以创建将其包装在信封中的服务或横切关注点。

在服务类中,我们也需要这些属性。例如决定一个篮子是否有效

在这种情况下,您谈论的是验证,这是一个横切关注点。您应该使用装饰器来添加此行为。

于 2013-06-10T21:02:56.283 回答
2

这是MVC,对吧?

你在重新发明轮子。

将此方法添加到您的 Global.asax.cs:

protected void Application_AuthenticateRequest(Object sender, EventArgs e)
{
    var authCookie = Request.Cookies[FormsAuthentication.FormsCookieName];
    if (authCookie != null)
    {
        var ticket = FormsAuthentication.Decrypt(authCookie.Value);
        var user = ticket.Name;
        var identity = new GenericIdentity(user, "Forms");
        var principal = new GenericPrincipal(identity, null);
        Context.User = principal;
    }
}

此示例显示表单身份验证,如果您使用另一种机制,您可以将其剥离。关键是这三行:

    var identity = new GenericIdentity(user, "Forms");
    var principal = new GenericPrincipal(identity, null);
    Context.User = principal;

GenericIdentity 和 GenericPrincipal 可以替换为您想要的任何内容,只要它们实现(普通)IIdentity 和 IPrincipal 接口。您可以使用所需的任何额外属性创建自己的这些类的实现。

然后,您可以通过 HttpContext.Current.User(它是静态的)从您列出的所有内容(控制器、视图等)中访问经过身份验证的用户。

如果您创建了自己的 IPrincipal 实现,则可以将该引用转换为您的自定义类型。

你会注意到 IPrincipal 有一个名为 IsInRole 的方法,所以你会说:

if (HttpContext.Current.User.IsInRole("SuperUser"))

TL;DR - 你过度设计了 ASP.NET 已经解决的问题,如果我看到你在生产应用程序中提出的类型,我会得动脉瘤。

于 2013-06-19T17:49:23.697 回答
0

我认为最简单且可维护的解决方案是创建一个静态类 CurrentUserProvider,它只有一个返回当前用户的方法 Get(HttpContextBase),在幕后您可以使用 DependencyResolver 来获取实际返回用户的服务。然后在您需要 CurrentUser 的地方,您可以调用 CurrentUserProvider.Get(context) 并执行您需要执行的任何自定义逻辑。

您尝试做的另一个解决方案是在基本控制器构造函数中注入服务,如果您有少数控制器,这没问题,如果您有相当多的控制器并且并非所有控制器都需要该服务,这将成为一个问题. 为这些控制器编写测试会让人头疼,因为您必须为所有控制器测试的该服务创建存根/模拟。也许您可以使用属性注入而不是构造函数来解决它。

您也可以对过滤器使用相同的属性注入。

现在,剩下的两个是视图和助手。对于 View,您可以创建从 WebViewPage/ViewPage 继承的特殊基类,并使用 IViewActivator 注入服务,这同样适用于助手,创建从系统助手继承的助手并在基本控制器和视图中使用它们。

我认为第二种方法有点麻烦,而且它并没有为做所有这些定制的事情增加太多的价值。

所以我的建议是选择第一个。

于 2013-06-18T10:15:45.473 回答