Установить культуру в приложении ASP.Net MVC

85

Как лучше всего установить культуру / культуру пользовательского интерфейса в приложении ASP.net MVC

В настоящее время у меня есть класс CultureController, который выглядит так:

public class CultureController : Controller
{
    public ActionResult SetSpanishCulture()
    {
        HttpContext.Session["culture"] = "es-ES";
        return RedirectToAction("Index", "Home");
    }

    public ActionResult SetFrenchCulture()
    {
        HttpContext.Session["culture"] = "fr-FR";
        return RedirectToAction("Index", "Home");
    }
}

и гиперссылку для каждого языка на домашней странице со ссылкой, например, этой:

<li><%= Html.ActionLink("French", "SetFrenchCulture", "Culture")%></li>
<li><%= Html.ActionLink("Spanish", "SetSpanishCulture", "Culture")%></li>

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

Я читаю Культуру, используя следующий ActionFilter http://www.iansuttle.com/blog/post/ASPNET-MVC-Action-Filter-for-Localized-Sites.aspx . Я немного новичок в MVC, поэтому не уверен, что устанавливаю это в правильном месте. Я не хочу делать это на уровне web.config, это должно быть основано на выборе пользователя. Я также не хочу проверять их http-заголовки, чтобы узнать культуру из настроек браузера.

Редактировать:

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

ChrisCa
источник
Использование состояния сеанса для выбора культуры пользователя - не лучший выбор. Лучший способ - включить язык и региональные параметры как часть URL-адреса , что упростит «замену» текущей страницы другой культурой.
NightOwl888, 01

Ответы:

114

Я использую этот метод локализации и добавляю параметр маршрута, который устанавливает культуру и язык всякий раз, когда пользователь посещает example.com/xx-xx/.

Пример:

routes.MapRoute("DefaultLocalized",
            "{language}-{culture}/{controller}/{action}/{id}",
            new
            {
                controller = "Home",
                action = "Index",
                id = "",
                language = "nl",
                culture = "NL"
            });

У меня есть фильтр, который выполняет фактическую настройку культуры / языка:

using System.Globalization;
using System.Threading;
using System.Web.Mvc;

public class InternationalizationAttribute : ActionFilterAttribute {

    public override void OnActionExecuting(ActionExecutingContext filterContext) {

        string language = (string)filterContext.RouteData.Values["language"] ?? "nl";
        string culture = (string)filterContext.RouteData.Values["culture"] ?? "NL";

        Thread.CurrentThread.CurrentCulture = CultureInfo.GetCultureInfo(string.Format("{0}-{1}", language, culture));
        Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo(string.Format("{0}-{1}", language, culture));

    }
}

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

[Internationalization]
public class HomeController : Controller {
...

Теперь всякий раз, когда посетитель переходит на http://example.com/de-DE/Home/Index, отображается немецкий сайт.

Надеюсь, этот ответ укажет вам правильное направление.

Я также сделал небольшой пример проекта MVC 5, который вы можете найти здесь

Просто перейдите по адресу http: // {yourhost}: {port} / en-us / home / index, чтобы увидеть текущую дату на английском языке (США), или измените ее на http: // {yourhost}: {port} / de -de / home / index для немецкого языка и т. д.

Jao
источник
15
Мне также нравится указывать язык в URL-адресе, потому что он стал доступным для сканирования поисковыми системами на разных языках и позволяет пользователю сохранять или отправлять URL-адрес с определенным языком.
Эдуардо Молтени,
50
Добавление языка к URL-адресу не нарушает REST. Фактически, он соблюдает его, делая веб-ресурс независимым от скрытого состояния сеанса.
Джейс Реа,
4
Веб-ресурс не зависит от скрытого состояния, как он отображается. Если вы хотите получить доступ к ресурсу как к веб-службе, вам нужно будет выбрать язык, на котором это будет делать.
Дэйв Ван ден Эйнде,
4
У меня были некоторые проблемы с этим типом решения. Сообщения об ошибках проверки не переводились. Чтобы решить эту проблему, я установил культуру в функции Application_AcquireRequestState файла global.asax.cs.
ADH
4
Помещать это в фильтр - НЕ хорошая идея. Привязка модели использует CurrentCulture, но ActionFilter возникает после привязки модели. Лучше сделать это в Global.asax, Application_PreRequestHandlerExecute.
Стефан
38

Я знаю, что это старый вопрос, но если вы действительно хотите, чтобы это работало с вашим ModelBinder (в отношении DefaultModelBinder.ResourceClassKey = "MyResource";ресурсов, указанных в аннотациях данных классов модели представления), контроллер или даже ActionFilterслишком поздно установить культуру .

Культура может быть установлена Application_AcquireRequestState, например:

protected void Application_AcquireRequestState(object sender, EventArgs e)
    {
        // For example a cookie, but better extract it from the url
        string culture = HttpContext.Current.Request.Cookies["culture"].Value;

        Thread.CurrentThread.CurrentCulture = CultureInfo.GetCultureInfo(culture);
        Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo(culture);
    }

РЕДАКТИРОВАТЬ

На самом деле, есть лучший способ использовать собственный обработчик маршрутов, который устанавливает культуру в соответствии с URL-адресом, прекрасно описанный Алексом Адамяном в своем блоге .

Все, что нужно сделать, - это переопределить GetHttpHandlerметод и установить там культуру.

public class MultiCultureMvcRouteHandler : MvcRouteHandler
{
    protected override IHttpHandler GetHttpHandler(RequestContext requestContext)
    {
        // get culture from route data
        var culture = requestContext.RouteData.Values["culture"].ToString();
        var ci = new CultureInfo(culture);
        Thread.CurrentThread.CurrentUICulture = ci;
        Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(ci.Name);
        return base.GetHttpHandler(requestContext);
    }
}
марапет
источник
К сожалению, данные RouteData и т. Д. Недоступны в методе Application_AcquireRequestState, но они находятся в Controller.CreateActionInvoker (). Поэтому я предлагаю «защищенное переопределение IActionInvoker CreateActionInvoker ()» и прямо здесь установить CultureInfo.
Скорунка Франтишек
Я читал этот блог. Есть ли проблема, если я продолжу использовать cookie? Поскольку у меня нет разрешения на его изменение. Пожалуйста, сообщите мне. есть ли проблема с этим подходом?
kbvishnu
@VeeKeyBee Если ваш сайт является общедоступным, все языки не будут правильно проиндексированы при использовании файлов cookie, для защищенных сайтов с вами, вероятно, все в порядке.
marapet
не его не публично. Не могли бы вы намекнуть на слово "индексированный"?
kbvishnu
1
Вы должны задать свой вопрос и почитать о SEO, это больше не имеет ничего общего с исходным вопросом. webmasters.stackexchange.com/questions/3786/…
marapet
25

Я бы сделал это в событии Initialize контроллера вот так ...

    protected override void Initialize(System.Web.Routing.RequestContext requestContext)
    {
        base.Initialize(requestContext);

        const string culture = "en-US";
        CultureInfo ci = CultureInfo.GetCultureInfo(culture);

        Thread.CurrentThread.CurrentCulture = ci;
        Thread.CurrentThread.CurrentUICulture = ci;
    }
Джейс Реа
источник
1
строка культуры не может быть константой, так как пользователь должен иметь возможность указать культуру, которую он хотел бы использовать на сайте.
NerdFury
2
Я понимаю это, но вопрос заключался в том, где лучше всего установить культуру, а не как ее установить.
Jace Rhea,
Вместо константы вы можете использовать что-то вроде: var newCulture = new CultureInfo (RouteData.Values ​​["lang"]. ToString ());
Nordes
AuthorizeCore вызывается до OnActionExecuting, поэтому в вашем переопределенном методе AuthorizeCore не будет никаких сведений о культуре. Использование метода инициализации контроллера может работать лучше, особенно если вы реализуете настраиваемый атрибут AuthorizeAttribute, поскольку метод Initialize вызывается до AuthorizeCore (в AuthorizeCore у вас будут сведения о культуре).
Nathan R
7

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

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

public class CultureController : Controller    
{
        public ActionResult SetCulture(string culture)
        {
            HttpContext.Session["culture"] = culture
            return RedirectToAction("Index", "Home");
        }        
}

<li><%= Html.ActionLink("French", "SetCulture", new {controller = "Culture", culture = "fr-FR"})%></li>
<li><%= Html.ActionLink("Spanish", "SetCulture", new {controller = "Culture", culture = "es-ES"})%></li>
БотаникFury
источник
спасибо за ответ, я не пытаюсь решить, использовать сеанс или нет. Я доволен этим. Я пытаюсь понять, лучше ли это сделать в контроллере культуры, у которого есть метод действия для каждой культуры, который должен быть установлен, или есть лучшее место в конвейере MVC для этого
ChrisCa
Я дал отредактированный ответ, который лучше соответствует вопросу.
NerdFury
Да, это, безусловно, чище, но я действительно хочу знать, нужно ли это вообще делать в контроллере. Или, если в конвейере MVC есть лучшее место для установки Culture. Или, если лучше, в ActionFilters, Handler, Modules и т. Д.
ChrisCa
Обработчик и модуль не имеют смысла, потому что у пользователя не было возможности сделать выбор. Вам нужен способ, чтобы пользователь мог сделать выбор, а затем обработать выбор пользователей, что будет сделано в контроллере.
NerdFury
согласовано, обработчики и модули должны быть слишком ранними, чтобы обеспечить взаимодействие с пользователем. Однако я новичок в MVC, поэтому не уверен, что это лучшее место в конвейере для его установки. Если через некоторое время я не услышу иного, я приму ваш ответ. ps этот синтаксис, который вы использовали для передачи параметра методу действия, похоже, не работает. Для него не определен контроллер, поэтому используется только тот, который используется по умолчанию (который в данном случае не является правильным). И, похоже, нет другой подходящей перегрузки
ChrisCa
6

Какое лучшее место - ваш вопрос. Лучшее место - внутри метода Controller.Initialize . MSDN пишет, что он вызывается после конструктора и перед методом действия. В отличие от переопределения OnActionExecuting, размещение вашего кода в методе Initialize позволяет вам получить выгоду от локализации всех настраиваемых аннотаций и атрибутов данных в ваших классах и ваших свойствах.

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

 public BaseController(IRunningContext runningContext){/*...*/}

 protected override void Initialize(RequestContext requestContext)
 {
     base.Initialize(requestContext);
     var culture = runningContext.GetCulture();
     Thread.CurrentThread.CurrentUICulture = culture;
     Thread.CurrentThread.CurrentCulture = culture;
 }

Даже если ваша логика не находится внутри класса, подобного приведенному мной примеру, у вас есть доступ к RequestContext, который позволяет вам иметь URL-адрес, HttpContext и RouteData, которые вы можете выполнять практически любым возможным синтаксическим анализом .

Патрик Дежарден
источник
Это работает для моего HTML5 Telerik ReportLocalization !. Спасибо @Patrick Desjardins
CoderRoller 06
4

Если вы используете субдомены, например, pt.mydomain.com для установки португальского, например, использование Application_AcquireRequestState не будет работать, потому что оно не вызывается при последующих запросах кеша.

Чтобы решить эту проблему, я предлагаю такую ​​реализацию:

  1. Добавьте параметр VaryByCustom в OutPutCache следующим образом:

    [OutputCache(Duration = 10000, VaryByCustom = "lang")]
    public ActionResult Contact()
    {
        return View("Contact");
    }
    
  2. В global.asax.cs получите культуру от хоста с помощью вызова функции:

    protected void Application_AcquireRequestState(object sender, EventArgs e)
    {
        System.Threading.Thread.CurrentThread.CurrentUICulture = GetCultureFromHost();
    }
    
  3. Добавьте функцию GetCultureFromHost в global.asax.cs:

    private CultureInfo GetCultureFromHost()
    {
        CultureInfo ci = new CultureInfo("en-US"); // en-US
        string host = Request.Url.Host.ToLower();
        if (host.Equals("mydomain.com"))
        {
            ci = new CultureInfo("en-US");
        }
        else if (host.StartsWith("pt."))
        {
            ci = new CultureInfo("pt");
        }
        else if (host.StartsWith("de."))
        {
            ci = new CultureInfo("de");
        }
        else if (host.StartsWith("da."))
        {
            ci = new CultureInfo("da");
        }
    
        return ci;
    }
    
  4. И, наконец, переопределите GetVaryByCustomString (...), чтобы также использовать эту функцию:

    public override string GetVaryByCustomString(HttpContext context, string value)
    {
        if (value.ToLower() == "lang")
        {
            CultureInfo ci = GetCultureFromHost();
            return ci.Name;
        }
        return base.GetVaryByCustomString(context, value);
    }
    

Функция Application_AcquireRequestState вызывается для некэшированных вызовов, что позволяет создавать и кэшировать контент. GetVaryByCustomString вызывается при кэшированных вызовах, чтобы проверить, доступен ли контент в кеше, и в этом случае мы снова проверяем входящее значение домена хоста, вместо того, чтобы полагаться только на текущую информацию о культуре, которая могла измениться для нового запроса (потому что мы используем поддомены).

Энди
источник
4

1: Создайте настраиваемый атрибут и переопределите метод следующим образом:

public class CultureAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
    // Retreive culture from GET
    string currentCulture = filterContext.HttpContext.Request.QueryString["culture"];

    // Also, you can retreive culture from Cookie like this :
    //string currentCulture = filterContext.HttpContext.Request.Cookies["cookie"].Value;

    // Set culture
    Thread.CurrentThread.CurrentCulture = new CultureInfo(currentCulture);
    Thread.CurrentThread.CurrentUICulture = CultureInfo.CreateSpecificCulture(currentCulture);
    }
}

2: В App_Start найдите FilterConfig.cs, добавьте этот атрибут. (это работает для ВСЕГО приложения)

public class FilterConfig
{
    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
    // Add custom attribute here
    filters.Add(new CultureAttribute());
    }
}    

Это оно !

Если вы хотите определить культуру для каждого контроллера / действия вместо всего приложения, вы можете использовать этот атрибут следующим образом:

[Culture]
public class StudentsController : Controller
{
}

Или:

[Culture]
public ActionResult Index()
{
    return View();
}
Мэн Сюэ
источник
0
protected void Application_AcquireRequestState(object sender, EventArgs e)
        {
            if(Context.Session!= null)
            Thread.CurrentThread.CurrentCulture =
                    Thread.CurrentThread.CurrentUICulture = (Context.Session["culture"] ?? (Context.Session["culture"] = new CultureInfo("pt-BR"))) as CultureInfo;
        }
user2861593
источник
3
Пожалуйста, объясните, почему это лучший способ.
Макс Леске