Definir cultura em um aplicativo ASP.Net MVC


85

Qual é o melhor lugar para definir a cultura / cultura da interface do usuário em um aplicativo ASP.net MVC

Atualmente, tenho uma classe CultureController que se parece com isto:

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

e um hiperlink para cada idioma na página inicial com um link como este:

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

que funciona bem, mas estou pensando que existe uma maneira mais apropriada de fazer isso.

Estou lendo a Cultura usando o seguinte ActionFilter http://www.iansuttle.com/blog/post/ASPNET-MVC-Action-Filter-for-Localized-Sites.aspx . Eu sou um pouco um novato MVC, então não tenho certeza se estou definindo isso no lugar correto. Eu não quero fazer isso no nível do web.config, tem que ser baseado na escolha do usuário. Eu também não quero verificar seus cabeçalhos http para obter a cultura de suas configurações de navegador.

Editar:

Só para deixar claro - não estou tentando decidir se devo usar a sessão ou não. Estou feliz com essa parte. O que estou tentando descobrir é se é melhor fazer isso em um controlador de cultura que tem um método de ação para cada cultura a ser definida, ou se há um lugar melhor no pipeline MVC para fazer isso?


Usar o estado da sessão para selecionar a cultura do usuário não é uma boa escolha. A melhor maneira é incluir a cultura como parte da URL , o que torna fácil "trocar" a página atual por outra cultura.
NightOwl888

Respostas:


114

Estou usando este método de localização e adicionei um parâmetro de rota que define a cultura e o idioma sempre que um usuário visita example.com/xx-xx/

Exemplo:

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

Eu tenho um filtro que faz a configuração real de cultura / idioma:

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

    }
}

Para ativar o atributo Internacionalização, basta adicioná-lo à sua classe:

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

Agora, sempre que um visitante vai para http://example.com/de-DE/Home/Index, o site alemão é exibido.

Espero que essas respostas apontem na direção certa.

Eu também fiz um pequeno projeto de exemplo MVC 5 que você pode encontrar aqui

Basta ir para http: // {yourhost}: {port} / en-us / home / index para ver a data atual em inglês (EUA) ou alterá-la para http: // {yourhost}: {port} / de -de / home / index para alemão etc.


15
Também gosto de colocar o idioma no URL, porque ele se tornou rastreável por mecanismos de pesquisa em diferentes idiomas e permite ao usuário salvar ou enviar um URL com um idioma específico.
Eduardo Molteni

50
Adicionar o idioma ao url não viola o REST. De fato, adere a ele, tornando o recurso da web independente de um estado de sessão oculto.
Jace Rhea

4
O recurso da web não depende de um estado oculto, da maneira como é processado. Se você deseja acessar o recurso como um serviço da web, você precisará escolher um idioma para fazê-lo.
Dave Van den Eynde

4
Tive alguns problemas com este tipo de solução. As mensagens de erro de validação não estavam sendo traduzidas. Para resolver o problema, defini a cultura na função Application_AcquireRequestState do arquivo global.asax.cs.
ADH

4
Colocar isso em um filtro NÃO é uma boa ideia. A vinculação do modelo usa CurrentCulture, mas o ActionFilter ocorre após a vinculação do modelo. É melhor fazer isso em Global.asax, Application_PreRequestHandlerExecute.
Stefan

38

Eu sei que esta é uma questão antiga, mas se você realmente gostaria de ter isso funcionando com o seu ModelBinder (em relação a DefaultModelBinder.ResourceClassKey = "MyResource";, bem como os recursos indicados nas anotações de dados das classes de viewmodel), o controlador ou mesmo um ActionFilteré tarde demais para definir a cultura .

A cultura pode ser definida Application_AcquireRequestState, por exemplo:

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

EDITAR

Na verdade, existe uma maneira melhor de usar um gerenciador de rotas personalizado que define a cultura de acordo com a url, perfeitamente descrito por Alex Adamyan em seu blog .

Tudo o que há a fazer é substituir o GetHttpHandlermétodo e definir a cultura lá.

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

Infelizmente, RouteData etc. não estão disponíveis no método "Application_AcquireRequestState", mas estão em Controller.CreateActionInvoker (). Portanto, sugiro "sobrescrever protegido IActionInvoker CreateActionInvoker ()" e definir CultureInfo ali.
Skorunka František

Eu li esse blog. Há algum problema se eu prosseguir com o cookie? Uma vez que não tenho permissão para alterá-lo. Por favor, me informe. há algum problema com essa abordagem?
kbvishnu

@VeeKeyBee Se o seu site for público, todos os idiomas não serão indexados corretamente ao usar cookies. Para sites protegidos, provavelmente você está bem.
marapet

não é público. Você pode dar uma dica sobre a palavra "indexado"?
kbvishnu

1
Você deve fazer sua própria pergunta e ler sobre SEO, isso não tem mais nada a ver com a pergunta original. webmasters.stackexchange.com/questions/3786/…
marapet

25

Eu faria isso no evento Initialize do controlador assim ...

    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
a string de cultura não pode ser uma const, pois o usuário precisa ser capaz de especificar a cultura que gostaria de usar no site.
NerdFury

2
Eu entendo isso, mas a questão era onde seria melhor definir a cultura e não como fazê-lo.
Jace Rhea

Em vez de const, você pode usar algo como: var newCulture = new CultureInfo (RouteData.Values ​​["lang"]. ToString ());
Nordes de

AuthorizeCore é chamado antes de OnActionExecuting, portanto, você não terá nenhum detalhe de cultura em seu método substituído AuthorizeCore. Usar o método de inicialização do controlador pode funcionar melhor, especialmente se você estiver implementando um AuthorizeAttribute customizado, já que o método Initialize é chamado antes de AuthorizeCore (você terá detalhes da cultura em AuthorizeCore).
Nathan R

7

Por se tratar de uma configuração que é armazenada por usuário, a sessão é um local adequado para armazenar as informações.

Eu mudaria seu controlador para usar a string de cultura como parâmetro, em vez de ter um método de ação diferente para cada cultura potencial. Adicionar um link à página é fácil e você não deve precisar escrever o mesmo código repetidamente sempre que uma nova cultura for necessária.

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>

obrigado pela resposta, não estou tentando decidir se devo usar a sessão ou não. Estou feliz com essa parte. O que estou tentando descobrir é se é melhor fazer isso em um controlador de cultura que tem um método de ação para cada cultura a ser definida ou se há um lugar melhor no pipeline MVC para fazer isso
ChrisCa

Eu forneci uma resposta editada que se encaixa melhor à pergunta.
NerdFury

Sim, isso é certamente mais limpo, mas o que realmente quero saber é se isso deve ser feito em um controlador. Ou se há um lugar melhor no pipeline MVC para definir a cultura. Ou se for melhor em ActionFilters, Handlers, Modules etc
ChrisCa

Um manipulador e um módulo não fazem sentido porque o usuário não teve a chance de fazer uma seleção. Você precisa de uma maneira para o usuário fazer uma seleção e, em seguida, processar a seleção de usuários, que será feita em um controlador.
NerdFury

acordado, os manipuladores e os módulos são muito cedo para permitir a interação do usuário. No entanto, sou muito novo no MVC, então não tenho certeza se este é o melhor lugar no pipeline para configurá-lo. Se eu não ouvir de outra forma depois de um tempo, aceitarei sua resposta. ps essa sintaxe que você usou para passar um parâmetro para um método Action não parece funcionar. Não tem um controlador definido, então apenas usa o padrão (que não é o correto neste caso). E não parece haver outra sobrecarga adequada
ChrisCa

6

Qual é o melhor lugar é a sua pergunta. O melhor lugar é dentro do método Controller.Initialize . O MSDN escreve que ele é chamado após o construtor e antes do método de ação. Ao contrário de substituir OnActionExecuting, colocar seu código no método Initialize permite que você se beneficie de ter todas as anotações e atributos de dados personalizados em suas classes e propriedades a serem localizados.

Por exemplo, minha lógica de localização vem de uma classe que é injetada em meu controlador personalizado. Tenho acesso a este objeto porque Initialize é chamado após o construtor. Eu posso fazer a atribuição de cultura do Thread e não ter todas as mensagens de erro exibidas corretamente.

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

Mesmo que sua lógica não esteja dentro de uma classe como o exemplo que forneci, você tem acesso ao RequestContext que permite que você tenha a URL e HttpContext e o RouteData, que você pode fazer basicamente qualquer análise possível.


Isso funciona para meu HTML5 Telerik ReportLocalization !. Obrigado @Patrick Desjardins
CoderRoller

4

Se estiver usando Subdomínios, por exemplo, como "pt.mydomain.com" para definir o português, por exemplo, usar Application_AcquireRequestState não funcionará, porque não é chamado em solicitações de cache subsequentes.

Para resolver isso, sugiro uma implementação como esta:

  1. Adicione o parâmetro VaryByCustom ao OutPutCache assim:

    [OutputCache(Duration = 10000, VaryByCustom = "lang")]
    public ActionResult Contact()
    {
        return View("Contact");
    }
    
  2. Em global.asax.cs, obtenha a cultura do host usando uma chamada de função:

    protected void Application_AcquireRequestState(object sender, EventArgs e)
    {
        System.Threading.Thread.CurrentThread.CurrentUICulture = GetCultureFromHost();
    }
    
  3. Adicione a função GetCultureFromHost a 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. E, finalmente, substitua GetVaryByCustomString (...) para usar também esta função:

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

A função Application_AcquireRequestState é chamada em chamadas não armazenadas em cache, o que permite que o conteúdo seja gerado e armazenado em cache. GetVaryByCustomString é chamado em chamadas em cache para verificar se o conteúdo está disponível no cache e, neste caso, examinamos o valor do domínio do host de entrada, novamente, em vez de confiar apenas nas informações da cultura atual, que poderiam ter mudado para a nova solicitação (porque estamos usando subdomínios).


4

1: Crie um atributo personalizado e um método de substituição como este:

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: Em App_Start, encontre FilterConfig.cs, adicione este atributo. (funciona para TODO o aplicativo)

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

É isso aí !

Se você deseja definir a cultura para cada controlador / ação em vez de todo o aplicativo, você pode usar este atributo assim:

[Culture]
public class StudentsController : Controller
{
}

Ou:

[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
Explique por que essa é considerada a melhor maneira.
Max Leske,
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.