Создание "Окружающего контекста" (UserContext) для приложения ASP.NET с использованием статического factory Func <T>

Я выяснил, что мне нужны текущие зарегистрированные данные пользователя почти в каждом классе (контроллеры, просмотр, 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}
}

В Контроллерах нам нужно проверить, является ли Пользователь суперусером, чтобы предоставить SuperUser дополнительную функциональность.

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, так что Views имеет легкий доступ.

В HtmlHelpers мы обрабатываем элементы управления в зависимости от IsSuperUser и Operations (передача в объекте User или с помощью DependencyResolver).

В Сервисные классы нам нужны эти свойства. Например, чтобы решить, действительна ли корзина или нет (проверьте, разрешено ли пользователю покупать статьи, которые не входят в стандартный список). Таким образом, класс службы зависит от IUserService и вызывает GetUser().

В Action Filters, чтобы заставить пользователя изменить свой пароль (только если это не SuperUser, а User.ForcePasswordChange - true). Здесь мы используем DependencyResolver.

Мое желание состоит в том, чтобы получить более простой способ получить объект User, вместо использования DependencyResolver.Current.GetService(). GetUser() или используя такие вещи, как ViewBag.ActiveUser = User. Объект User - это объект, который почти везде необходим для проверки разрешений и т.п.

Ответ 1

В "Представлениях" мы проверяем "Операции" только кнопку редактирования или удаления, если пользователь имеет на это право.

Вид не должен делать эту проверку. Контроллер должен вернуть модель представления в представление, содержащее логические свойства, указывающие, должны ли эти кнопки быть видимыми. Возврат bool с помощью IsSuperUser уже переместился во многозначное представление. Представление не должно знать, что он должен показать определенную кнопку для суперпользователя: это до контроллера. В представлении должно быть указано, что показывать.

Если почти все представления имеют этот код, есть способы извлечь повторяющиеся части из ваших представлений, например, с частичными представлениями. Если вы обнаружите, что повторяете эти свойства во многих моделях просмотра, возможно, вам следует определить модель представления огибающей (общая модель представления, которая обертывает конкретную модель как T). Контроллер может создать свою модель представления, в то время как вы создаете сервис или сквозную проблему, которая обертывает его в конверте.

В классах обслуживания нам тоже нужны эти свойства. Например, чтобы решить, действительна ли корзина или нет

В этом случае вы говорите о проверке, которая является сквозной проблемой. Вы должны использовать декораторы, чтобы добавить это поведение вместо этого.

Ответ 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 уже разрешило, и у меня была бы аневризма, если бы я видел типы, которые вы предлагаете в производственном приложении.

Ответ 3

Я думаю, что самым простым и удобным решением является создание статического класса CurrentUserProvider, который имеет только один метод Get (HttpContextBase), который возвращает текущего пользователя, за сценой вы можете использовать DependencyResolver для получения службы, которая фактически возвращает пользователя. Затем, где вам нужен CurrentUser, вы можете вызвать CurrentUserProvider.Get(контекст) и выполнить любую настраиваемую логику, которую вы должны выполнить.

Другим решением, которое вы пытаетесь сделать, является инъекция службы в конструктор базового контроллера, который в порядке, если у вас есть несколько контроллеров, это станет проблемой, если у вас есть довольно много контроллеров, а не все контроллеры требует этого обслуживания. Написание тестов для этого контроллера было бы такой болью в области шеи, потому что вы должны создавать заглушки/макеты для этой службы для всех проверок вашего контроллера. Возможно, вы можете использовать вложение свойств вместо конструктора для его адресации.

Вы можете использовать ту же инъекцию свойств для фильтров.

Теперь остальные два - это вид и помощник. Для представления вы можете создать специальный базовый класс, который наследует от WebViewPage/ViewPage, и использовать IViewActivator для инъекции службы, и то же самое относится к помощникам, создавать помощники, которые наследуются от системных помощников, и использовать их в ваших базовых контроллерах и представлениях.

Я думаю, что второй подход немного громоздкий, и он не добавляет такого значения, чтобы делать все эти вещи.

Итак, мое предложение - перейти к первому.