C# 创建一个;“环境语境”;(UserContext)用于使用静态工厂函数的ASP.NET应用程序<;T>;

C# 创建一个;“环境语境”;(UserContext)用于使用静态工厂函数的ASP.NET应用程序<;T>;,c#,asp.net,asp.net-mvc,dependency-injection,ambientcontext,C#,Asp.net,Asp.net Mvc,Dependency Injection,Ambientcontext,我发现几乎每个类(控制器、视图、HTML助手、服务等)都需要当前登录的用户数据。因此,我考虑创建一个“环境上下文”,而不是直接注入IUserService或用户 我的方法是这样的 public class Bootstrapper { public void Boot() { var container = new Container(); // the call to IUserService.GetUser is cached per Http

我发现几乎每个类(控制器、视图、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 User
{
   string Name { get; set; }
   bool IsSuperUser { get; set;}
   IEnumerable<AzManOperation> Operations { get; set}
}
视图中我们检查操作,仅当用户有权这样做时才显示编辑或删除按钮。视图从不使用DependencyResolver,而是使用ViewBag或ViewModel。我的想法是实现一个定制的ViewBasePage并提供一个ActiveUser属性,这样视图就可以轻松访问

HtmlHelpers中,我们根据发布者和操作(传入用户对象或使用DependencyResolver)呈现控件

服务类中我们也需要这些属性。例如,决定篮子是否有效(检查用户是否允许购买标准列表中不包含的物品)。因此,服务类依赖于
IUserService
和调用
GetUser()

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

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

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

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

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

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


在本例中,您讨论的是验证,这是一个贯穿各领域的问题。您应该使用decorators来添加此行为。

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

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

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

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

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

所以我的建议是第一个。这是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;
GenericEntity和GenericPrincipal可以替换为您想要的任何东西,只要它们实现(琐碎的)IIdentity和IPrincipal接口。您可以使用所需的任何额外属性创建这些类的自己实现

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

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

您会注意到IPrincipal有一个名为IsInRole的方法,因此您会说:

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

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

在使用环境上下文之前,我会查看所有的替代方案。看这本好书。这本书我已经拥有了。正如我所提到的,我几乎在应用程序中的任何地方都需要用户,因此我认为可以开始使用环境上下文,而不是使用构造函数注入或服务位置
    var identity = new GenericIdentity(user, "Forms");
    var principal = new GenericPrincipal(identity, null);
    Context.User = principal;
if (HttpContext.Current.User.IsInRole("SuperUser"))