Настройка культуры для конкретного пользователя в веб-приложении ServiceStack + MVC

Мне нужно установить культуру, специфичную для пользователя, для каждого веб-запроса, отправляемого в мое веб-приложение, написанное с использованием ServiceStack 3 и MVC 4.

Культура каждого пользователя хранится в его профиле в базе данных, которую я извлекаю в свою собственную реализацию IAuthSession с помощью пользовательского поставщика аутентификации, полученного из CredentialsAuthProvider. Поэтому я не забочусь о заголовке браузера AcceptLanguage и вместо этого хочу установить культуру текущего потока в свойство Culture сеанса аутентификации сразу после того, как ServiceStack разрешит его из кеша. Это должно произойти как для ServiceStack служб, так и для MVC контроллеров (производных от ServiceStackController).

Каков наилучший способ выполнить вышеизложенное?

ОБНОВЛЕНИЕ 1

Я нашел способ сделать это, хотя я не уверен, что это оптимальное решение.

В моем базовом классе обслуживания, из которого происходят все службы, я переопределил свойство SessionAs<> следующим образом:

protected override TUserSession SessionAs<TUserSession>()
{
    var genericUserSession = base.SessionAs<TUserSession>();

    var userAuthSession = genericUserSession as UserAuthSession;
    if (userAuthSession != null && !String.IsNullOrWhiteSpace(userAuthSession.LanguageCode))
        System.Threading.Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo(userAuthSession.LanguageCode);

    return genericUserSession;
}

где UserAuthSession — это моя собственная реализация ServiceStack IAuthSession. Его свойство LanguageCode устанавливается во время входа в систему на выбранный пользователем код культуры ISO, хранящийся в профиле пользователя в базе данных.

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

public override IAuthSession AuthSession
{
    get
    {
        var userAuthSession = base.AuthSession as UserAuthSession;
        if (userAuthSession != null && !String.IsNullOrWhiteSpace(userAuthSession.LanguageCode))
            System.Threading.Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo(userAuthSession.LanguageCode);
        return userAuthSession;
    }
}

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

Если кто-то может придумать лучший подход, пожалуйста, дайте мне знать.

ОБНОВЛЕНИЕ 2

По предложению Скотта я создал собственный AuthenticateAndSetCultureAttribute:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
public class AuthenticateAndSetCultureAttribute : AuthenticateAttribute
{
    public AuthenticateAndSetCultureAttribute() : base() { }
    public AuthenticateAndSetCultureAttribute(ApplyTo applyTo) : base(applyTo) { }
    public AuthenticateAndSetCultureAttribute(string provider) : base(provider) { }
    public AuthenticateAndSetCultureAttribute(ApplyTo applyTo, string provider) : base(applyTo, provider) { }

    public override void Execute(IHttpRequest req, IHttpResponse res, object requestDto)
    {
        base.Execute(req, res, requestDto);

        var session = req.GetSession() as UserAuthSession;
        if (session != null && session.IsAuthenticated && !String.IsNullOrWhiteSpace(session.LanguageCode))
            System.Threading.Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo(session.LanguageCode);
    }
}

Поскольку я меняю культуру только тогда, когда пользователь аутентифицирован, имеет смысл (во всяком случае, на мой взгляд) сделать это в том же месте, где мы проверяем аутентификацию.

Затем я украсил все свои службы SS и контроллеры MVC этим атрибутом вместо оригинального [Authenticate].

Теперь, когда вызывается служба SS, выполняется метод атрибута Execute, и культура устанавливается правильно. Однако Execute никогда не выполняется, когда вызывается действие контроллера MVC, что действительно озадачивает, потому что как тогда MVC+SS знает, что нужно перенаправлять неаутентифицированные запросы на страницу входа.

Есть мысли, кто-нибудь?


person Caspian Canuck    schedule 10.06.2014    source источник


Ответы (2)


Я бы сделал это, используя RequestFilter, а не переопределяя SessionAs<T>. В вашем методе AppHost Configure:

public override void Configure(Container container)
{
    RequestFilters.Add((httpReq, httpResp, requestDto) => {
        var session = httpReq.GetSession() as UserAuthSession;
        if(session == null || !session.IsAuthenticated || String.IsNullOrWhiteSpace(session.LanguageCode))
            return;

        System.Threading.Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo(session.LanguageCode);
    });
}
person Scott    schedule 10.06.2014
comment
Спасибо за предложение! Я сделал еще один шаг и создал собственный атрибут аутентификации (см. мое второе обновление выше). Он отлично работает для служб SS, но проблема с контроллерами MVC все еще существует. - person Caspian Canuck; 12.06.2014

В итоге я создал собственный фильтр действий MVC, который устанавливает культуру потока запросов на основе настроек аутентифицированного пользователя:

public class SetUserCultureAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        base.OnActionExecuting(filterContext);

        var baseController = filterContext.Controller as BaseController;
        if (baseController == null) return;

        var userAuthSession = baseController.UserAuthSession;
        if (userAuthSession != null && userAuthSession.IsAuthenticated && !String.IsNullOrWhiteSpace(userAuthSession.LanguageCode))
            System.Threading.Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo(userAuthSession.LanguageCode);
    }
}

Затем я украсил свой класс BaseController этим атрибутом и защитил свои контроллеры/действия с помощью обычного атрибута Authorize ServiceStack.

Ранее созданный AuthenticateAndSetCultureAttribute, который я изначально планировал использовать как для контроллеров, так и для сервисов, теперь используется только для сервисов SS.

Культура правильно устанавливается как на стороне MVC, так и на стороне SS, так что я счастлив!

person Caspian Canuck    schedule 11.06.2014