使用静态工厂Func <t> </t>为ASP.NET应用程序创建“Ambient Context”(UserContext)

时间:2013-06-09 15:56:40

标签: c# asp.net asp.net-mvc dependency-injection ambientcontext

我发现几乎每个类(控制器,视图,HTML帮助程序,服务等)都需要当前登录的用户数据。所以我想创建一个“Ambient Context”而不是直接注入IUserService或User。

我的方法看起来像那样。

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属性,以便View可以轻松访问。

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

服务类中,我们也需要这些属性。例如,判断篮子是否有效(检查用户是否被允许购买不在标准列表中的文章)。因此,服务类取决于IUserService并调用GetUser()

操作过滤器中强制用户更改其密码(仅当它不是超级用户并且User.ForcePasswordChange为true时)。这里我们使用DependencyResolver。

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

3 个答案:

答案 0 :(得分:8)

  
    

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

  

视图不应该执行此检查。 Controller应该将视图模型返回到包含布尔属性的视图,该属性指出这些按钮是否应该可见。返回带有IsSuperUser的bool已经移动到视图中。视图不应该知道它应该为超级用户显示某个按钮:这取决于控制器。该视图应仅告知要显示的内容。

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

  
    

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

  

在这种情况下,您谈论的是验证,这是一个跨领域的问题。您应该使用装饰器来添加此行为。

答案 1 :(得分: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已经解决过的问题,如果我看到你在生产应用程序中提出的类型,我会有动脉瘤。

答案 2 :(得分:0)

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

你要做的另一个解决方案是在基本控制器构造函数中注入服务,如果你有一些控制器就可以了,如果你有很多控制器而不是所有的控制器都会成为一个问题需要这项服务。为这些控制器编写测试将是如此痛苦,因为您必须为所有控制器测试创建该服务的存根/模拟。也许你可以使用属性注入而不是构造函数来解决它。

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

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

我认为第二种方法有点麻烦,并没有为所有这些自定义事物增加太多价值。

所以我的建议是第一个。