Встановіть культуру в програмі 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 для цього?


Використання стану сеансу для вибору культури користувача - невдалий вибір. Найкращий спосіб - включити культуру як частину URL-адреси , що полегшує "обмін" поточної сторінки на іншу культуру.
NightOwl888

Відповіді:


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 для німецької мови тощо.


15
Мені також подобається розміщувати посилання в URL-адресі, оскільки воно стало доступним для сканування пошуковими системами на різних мовах і дозволило користувачеві зберегти або надіслати URL-адресу з певною мовою.
Едуардо Молтені,

50
Додавання мови до URL-адреси не порушує REST. Фактично він дотримується його, роблячи веб-ресурс не залежним від стану прихованого сеансу.
Jace Rhea

4
Веб-ресурс не залежить від прихованого стану, а спосіб його відображення. Якщо ви хочете отримати доступ до ресурсу як веб-сервіс, вам потрібно буде вибрати мову, якою це потрібно робити.
Дейв Ван ден Ейнде,

4
У мене були проблеми з рішенням такого типу. Повідомлення про помилку перевірки не перекладалися. Для вирішення проблеми я встановив культуру у функції Application_AcquireRequestState у файлі global.asax.cs.
ADH

4
Помістити це у фільтр - НЕ гарна ідея. Прив'язка моделі використовує CurrentCulture, але ActionFilter відбувається після прив'язки моделі. Краще це робити в Global.asax, Application_PreRequestHandlerExecute.
Стефан

38

Я знаю, що це старе запитання, але якщо ви справді хотіли б, щоб це працювало з вашим ModelBinder (стосовно DefaultModelBinder.ResourceClassKey = "MyResource";, а також ресурсів, зазначених в анотаціях даних класів viewmodel), контролер або навіть a 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, для захищених сайтів ви, мабуть, добре.
марапет

не його не публічний. Чи можете ви дати підказку щодо слова "індексовано"?
kbvishnu

1
Вам слід задати власне запитання та прочитати про SEO, це вже не має нічого спільного з початковим запитанням. webmasters.stackexchange.com/questions/3786/…
marapet

25

Я б зробив це у випадку ініціалізації контролера, як це ...

    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
рядок культури не може бути const, оскільки користувач повинен мати можливість вказати культуру, яку він хотів би використовувати на сайті.
NerdFury

2
Я це розумію, але питання полягало в тому, де найкраще встановлювати культуру, а не як її встановлювати.
Jace Rhea

Замість const можна використовувати щось на зразок: 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>

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

Я дав відредаговану відповідь, яка краще відповідає питанню.
NerdFury

Так, це, безумовно, чистіше, але те, що я справді хочу знати, це те, чи взагалі це слід робити в контролері. Або якщо в трубопроводі MVC є краще місце для встановлення культури. Або якщо це краще в ActionFilters, Handlers, Modules тощо
ChrisCa

Обробник та модуль не мають сенсу, оскільки користувач не мав можливості зробити вибір. Вам потрібен спосіб для користувача зробити вибір, а потім обробити вибір користувачів, який буде зроблено в контролері.
NerdFury

погоджуються, обробники та модулі починають рано, щоб дозволити взаємодію з користувачем. Однак я абсолютно новачок у MVC, тому не впевнений, чи найкраще це місце в конвеєрі для його встановлення. Якщо через деякий час я не почую інакше, я прийму вашу відповідь. ps той синтаксис, який ви використовували для передачі параметра методу Action, здається, не працює. У ньому не визначено контролер, тому він просто використовує контролер за замовчуванням (що в цьому випадку не є правильним). І, здається, не підходить інше перевантаження
ChrisCa,

6

Яке найкраще місце - це ваше запитання. Найкраще місце знаходиться всередині методу Controller.Initialize . MSDN пише, що його викликають після конструктора та перед методом дії. На відміну від перевизначення OnActionExecuting, розміщення коду в методі Initialize дозволяє вам отримати перевагу від того, що всі власні анотації та атрибути даних у ваших класах та у ваших властивостях повинні бути локалізовані.

Наприклад, моя логіка локалізації походить від класу, який вводиться в мій спеціальний контролер. Я маю доступ до цього об’єкта, оскільки Initialize викликається після конструктора. Я можу призначити культуру Thread і не відображати кожне повідомлення про помилку правильно.

 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

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;
        }

3
Поясніть, будь ласка, чому це має бути найкращим способом.
Макс Леске,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.