С#: Итак, если статический класс - плохая практика хранения глобальной информации о состоянии, какая хорошая альтернатива предлагает такое же удобство?
Я заметил, что статические классы получают много плохого репутации на SO в отношении использования для хранения глобальной информации. (И глобальные переменные, которых презирают вообще), я просто хотел бы знать, какая хорошая альтернатива для моего примера ниже...
Я разрабатываю приложение WPF, и многие представления данных, извлеченных из моего db, фильтруются на основе идентификатора текущего зарегистрированного пользователя. Точно так же некоторые пункты в моем приложении должны быть доступны только пользователям, которые считаются "админами".
В настоящее время я храню loggedInUserId и isAdmin bool в статическом классе.
Различные части моего приложения нуждаются в этой информации, и мне интересно, почему она не идеальна в этом случае и каковы альтернативы. Кажется очень уверенным вставать и работать.
Единственное, что я могу представить в качестве альтернативы, - это использовать контейнер IoC для вставки экземпляра Singleton в классы, которым нужна эта глобальная информация, тогда классы могли бы поговорить с этим через свой интерфейс. Однако, является ли это излишним/приводит меня к анализу паралича?
Заранее благодарим за понимание.
Обновление
Таким образом, я склоняюсь к инъекции зависимостей через IoC, поскольку он будет лучше тестировать, поскольку я могу поменять место в службе, которая предоставляет "глобальную" информацию с макетом, если это необходимо. Я полагаю, что остается то, должен ли инжектируемый объект быть одиночным или статическим.: -)
Поверьте, ответьте на вопрос "Ответ", ожидая увидеть, есть ли еще какие-либо обсуждения. Я не думаю, что там правильный путь. Мне просто интересно посмотреть какую-то дискуссию, которая просветит меня, поскольку, кажется, существует много "плохих" "плохих" заявлений по некоторым подобным вопросам без каких-либо конструктивных альтернатив.
Обновление # 2
Поэтому я выбрал Роберта, увидев, что это отличная альтернатива (я полагаю, что альтернатива - это странное слово, возможно, "Единственный истинный путь", поскольку он встроен в рамки). Это не заставляет меня создавать статический класс /singleton (хотя это статический поток).
Единственное, что все еще вызывает у меня любопытство, это то, как это можно было бы решить, если бы "глобальные" данные, которые я должен был хранить, не имели ничего общего с аутентификацией пользователя.
Ответы
Ответ 1
Забудьте о синглтонах и статических данных. Эта схема доступа не даст вам возможности в какое-то время.
Создайте свой собственный IPrincipal и замените Thread.CurrentPrincipal на него в тот момент, когда логин подходит. Обычно вы сохраняете ссылку на текущий IIdentity.
В вашей программе, где пользователь входит в систему, например, вы подтвердили свои учетные данные, присоедините свой пользовательский принцип к потоку.
IIdentity currentIdentity = System.Threading.Thread.CurrentPrincipal.Identity;
System.Threading.Thread.CurrentPrincipal
= new MyAppUser(1234,false,currentIdentity);
в ASP.Net вы также должны установить HttpContext.Current.User
в то же время
public class MyAppUser : IPrincipal
{
private IIdentity _identity;
private UserId { get; private set; }
private IsAdmin { get; private set; } // perhaps use IsInRole
MyAppUser(userId, isAdmin, iIdentity)
{
if( iIdentity == null )
throw new ArgumentNullException("iIdentity");
UserId = userId;
IsAdmin = isAdmin;
_identity = iIdentity;
}
#region IPrincipal Members
public System.Security.Principal.IIdentity Identity
{
get { return _identity; }
}
// typically this stores a list of roles,
// but this conforms with the OP question
public bool IsInRole(string role)
{
if( "Admin".Equals(role) )
return IsAdmin;
throw new ArgumentException("Role " + role + " is not supported");
}
#endregion
}
Это предпочтительный способ сделать это, и это в рамках по какой-то причине. Таким образом, вы можете получить доступ к пользователю стандартным способом.
Мы также делаем такие вещи, как добавление свойств, если пользователь анонимен (неизвестен), чтобы поддерживать сценарий смешанных анонимных/зарегистрированных сценариев аутентификации.
Дополнительно:
- вы все равно можете использовать DI (Injection Dependancy), введя службу членства, которая извлекает/проверяет учетные данные.
- вы можете использовать шаблон репозитория, чтобы также получить доступ к текущему MyAppUser (хотя, возможно, это просто делает приведение в MyAppUser для вас, это может быть полезно)
Ответ 2
На SO есть много других ответов, которые объясняют, почему статика (включая Синглтон) для вас плоха, поэтому я не буду вдаваться в подробности (хотя я полностью открещиваю эти чувства).
Как правило, DI - это путь. Затем вы можете ввести службу, которая может сообщить вам все, что вам нужно знать о среде.
Однако, поскольку вы имеете дело с информацией о пользователе, Thread.CurrentPrincipal может быть жизнеспособной альтернативой (хотя это Thread Static).
Для удобства вы можете обернуть строго типизированный класс пользователя вокруг него.
Ответ 3
Я бы попробовал другой подход. Статический класс данных приведет вас к беде - это из опыта. У вас может быть объект User (см. Комментарий @Robert Paulson для отличного способа сделать это) и передать это каждому объекту по мере его создания - он может работать для вас, но вы получите много кода шаблона, который просто повторяется повсюду.
Вы можете хранить все свои объекты в базе данных/зашифрованном файле с необходимыми разрешениями, а затем динамически загружать их все на основе разрешений пользователей. С простой формой администратора в базе данных она довольно проста в обслуживании (файл немного сложнее).
Вы можете создать объект RequiresAdminPermissionAttribute для применения ко всем вашим чувствительным объектам и проверить его во время выполнения против вашего объекта User для условной загрузки объектов.
В то время как маршрут, на котором вы сейчас находитесь, имеет свои достоинства, я думаю, что есть несколько лучших вариантов.