Вот два подхода:
Первый
Этот подход имеет больше смысла, если эти другие уровни действительно должны знать о пользователе. Например, они могут проверить разрешения или принять какое-то решение, основываясь на том, кто пользователь.
Вы можете создать абстракцию или интерфейс для описания того, что вы хотите, чтобы эти классы имели доступ, что-то вроде этого:
public interface IUserContext
{
SomeClassContainingUserData GetCurrentUser();
}
Я бы определил этот класс пользовательских данных в соответствии с потребностями класса потребителя вместо того, чтобы просто использовать какой-то существующий класс Customer
, чтобы не допустить его тесной связи с вашим веб-приложением.
Теперь вы можете добавить этот класс в другие классы:
public class MyBusinessLogicClass
{
private readonly IUserContext _userContext;
public MyBusinessLogicClass(IUserContext userContext)
{
_userContext = userContext;
}
public void SomeOtherMethod(Whatever whatever)
{
var user = _userContext.GetCurrentUser();
// do whatever you need to with that user
}
}
Это позволяет тестировать другие ваши классы, потому что легко внедрить макет интерфейса, который возвращает то, что вы хотите, чтобы вы могли убедиться, что ваш класс ведет себя корректно для разных типов пользователей.
Если ваши пользовательские данные поступают с HttpContext
, то ваша реализация во время выполнения может выглядеть примерно так:
public class HttpUserContext
{
private readonly IHttpContextAccessor _contextAccessor;
public HttpUserContext(IHttpContextAccessor contextAccessor)
{
_contextAccessor = contextAccessor;
}
public SomeClassContainingUserData GetCurrentUser()
{
var httpContext = _contextAccessor.HttpContext;
// get the user data from the context and return it.
}
}
Это грубый набросок. Одним из соображений является обзор. Если все ваши объекты ограничены для каждого запроса, то реализация IUserContext
может сгенерировать пользовательские данные один раз и сохранить их в переменной-члене, а не обращаться к ним снова и снова.
Недостатком является необходимость внедрять это везде, но это неизбежно, если этим классам нужна эта информация.
Второй
Что если эти внутренние классы вообще не нуждаются в информации пользователя? Что если вы просто хотите регистрировать, какие пользователи отправляли запросы, которые обрабатывались этими классами? Что если вы хотите, чтобы отдельный объект проверял разрешения?
В этом случае опцией будет перехватчик или оболочка. В простейшем виде это может выглядеть так:
public class SomeBusinessClassSecurityInterceptor : ISomeBusinessClass
{
private readonly ISomeBusinessClass _inner;
private readonly IUserContext _userContext;
public SomeBusinessClassSecurityInterceptor(
ISomeBusinessClass inner, IUserContext userContext)
{
_inner = inner;
_userContext = userContext;
}
public void UpdateSomeData(Foo data)
{
if(!CanUpdate())
throw new YouCantUpdateThisException();
_inner.UpdateSomeData(data);
}
private bool CanUpdate()
{
var user = _userContext.GetCurrentUser();
// make some decision based on the user
}
}
Если поиск разрешений для пользователя более сложен, вы можете захотеть получить IUserPermissions
и ввести его вместо IUserContext
. Затем введите IUserContext
в реализацию IUserPermissions
. Во время выполнения он извлекает текущего пользователя, а затем делает свое дело, чтобы определить, какие разрешения имеет пользователь.
Если у вас много классов и методов, то обслуживание отдельных классов-оберток может стать утомительным. Другой вариант - использовать перехватчик, что, вероятно, означает использование другого контейнера для ввода зависимостей, такого как Windsor или Autofac . Они особенно хороши для регистрации.
Используя в качестве примера Autofac, это означало бы написание такого класса:
public class LoggingInterceptor : IInterceptor
{
private readonly IUserContext _userContext;
private readonly ILogger _logger;
public CallLogger(IUserContext userContext, ILogger logger)
{
_userContext = userContext;
_logger = logger;
}
public void Intercept(IInvocation invocation)
{
var user = _userContext.GetCurrentUser();
_logger.Log(
// some stuff about the invocation, like method name and maybe parameters)
// and who the user was.
// Or if you were checking permissions you could throw an exception here.
invocation.Proceed();
}
}
Тогда вы скажете контейнеру, что все вызовы «реальной» реализации данного класса проходят через этот перехватчик (очень хорошо описано в их документации.)
Если вы проверяли разрешения, вы можете ввести IUserPermissions
. Перехватчик может проверить атрибут во внутреннем методе, который указывает, какие разрешения необходимы, и сравнить его с разрешениями текущего пользователя.
Обычно вы можете написать один перехватчик и использовать его с множеством других классов, потому что ему не нужно ничего знать о внутреннем целевом классе. Но если вам нужно, вы также можете написать более узкие перехватчики, используемые только для определенных классов.
Приятно то, что он дает вам большую гибкость, но не затрагивает реальный интерфейс или реализацию вашей бизнес-логики или классов данных. Они могут сосредоточиться на своих отдельных обязанностях, в то время как другие классы настроены на запись запросов, сделанных к ним, или проверку прав пользователей.