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