Подтвердить что ты не робот

Где я должен присоединить пользовательскую оболочку сеанса пользовательского контекста в ASP.NET MVC3?

Я прочитал много сообщений о данных Session-scoped в MVC, но до сих пор неясно, где именно подходящее место для включения в решение специальной оболочки сеанса.

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

Ни один из следующих подходов, похоже, не подходит для того, что я хочу сделать.

Вариант 1. Прямой доступ к коллекции сеансов

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

public class ControllerBase : Controller {
   public ControllerBase() : this(new UserRepository()) {}
   public ControllerBase(IUserRepository userRepository) {
      _userRepository = userRepository;
   }
   protected IUserRepository _userRepository = null;
   protected const string _userSessionKey = "ControllerBase_UserSessionKey";
   protected User {
      get { 
         var user = HttpContext.Current.Session[_userSessionKey] as User;
         if (user == null) {
            var principal = this.HttpContext.User;
            if (principal != null) {
               user = _userRepository.LoadByName(principal.Identity.Name);
               HttpContext.Current.Session[_userSessionKey] = user;
            }
         }
         return user;
      }
   }
}

Вариант 2. Внедрение сеанса в конструктор класса сообщение в форуме

Этот параметр кажется довольно хорошим, но я все еще не уверен, как его присоединить к контроллеру и представлению. Я мог бы обновить его в контроллере, но не следует ли его вводить как зависимость?

public class UserContext {
   public UserContext() 
       : this(new HttpSessionStateWrapper(HttpContext.Current.Session), 
              new UserRepository()) { } 

   public UserContext(HttpSessionStateBase sessionWrapper, IUserRepository userRepository) { 
      Session = sessionWrapper;
      UserRepository = userRepository; 
   } 

   private HttpSessionStateBase Session { get; set; }
   private IUserRepository UserRepository{ get; set; }

   public User Current { 
      get {
         //see same code as option one
      }
   }
}

Вариант 3: используйте класс StatefulStorage Brad Wilson

В своем presentation Брэд Уилсон показывает свой класс StatefulStorage. Это умный и полезный набор классов, который включает в себя интерфейсы и использует инъекцию конструктора. Тем не менее, это, по-видимому, ведет меня по тому же пути, что и к варианту 2. Он использует интерфейсы, но я не мог использовать Container для его ввода, потому что он полагается на статический factory. Даже если бы я мог вставить его, как он передается в представление. Должен ли каждый ViewModel иметь базовый класс с настраиваемым свойством User?

Вариант 4: Используйте что-то похожее на Hanselman IPrincipal ModelBinder

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

public ActionResult Edit(int id, 
   [ModelBinder(typeof(IPrincipalModelBinder))] IPrincipal user)
{ ... }

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

4b9b3361

Ответ 1

Мой подход к сеансу:

Покрытие сеанса с интерфейсом:

public interface ISessionWrapper
{
    int SomeInteger { get; set; }
}

Внедрить интерфейс, используя HttpContext.Current.Session:

public class HttpContextSessionWrapper : ISessionWrapper
{
    private T GetFromSession<T>(string key)
    {
        return (T) HttpContext.Current.Session[key];
    }

    private void SetInSession(string key, object value)
    {
        HttpContext.Current.Session[key] = value;
    }

    public int SomeInteger
    {
        get { return GetFromSession<int>("SomeInteger"); }
        set { SetInSession("SomeInteger", value); }
    }
}

Ввод в контроллер:

public class BaseController : Controller
{
    public ISessionWrapper SessionWrapper { get; set; }

    public BaseController(ISessionWrapper sessionWrapper)
    {
        SessionWrapper = sessionWrapper;
    }
}

Ненделевая зависимость:

Bind<ISessionWrapper>().To<HttpContextSessionWrapper>()

Вы можете передать часто используемую информацию с помощью ViewData, если хотите использовать ее на главной странице и использовать модель представления в определенных представлениях.

Ответ 2

Я настоятельно рекомендую передать все, что вам нужно, в представлении вниз через контроллер. Таким образом, решение о том, какие данные должен отображать вид, остается с контроллером. Чтобы сделать это как можно проще, создание абстрактного класса ViewModelWithUserBase, обладающего настраиваемым свойством User, действительно не плохая идея. Опция заключается в создании интерфейса IViewModelWithUser и повторной реализации свойства User каждый раз (или в сочетании с базовым классом, но у вас будет возможность повторно реализовать вместо наследования базового класса, если это делает вещи проще в некоторых случаях).

Что касается заполнения этого свойства, его можно, вероятно, легко сделать с помощью фильтра действий. Используя метод OnActionExecuted, вы можете проверить, прошел ли модель, переданная в представление, свой базовый класс (или интерфейс), а затем, если необходимо, заполнить свойство правильным IPrincipal. Это имеет то преимущество, что, поскольку фильтры эффектов не выполняются в модульных тестах, вы можете использовать зависимый от HttpContext.Current.Session код от вашего варианта 1 в своем фильтре действий и все еще иметь тестируемый интерфейс на контроллере.