ตั้งค่าวัฒนธรรมในแอป ASP.Net MVC


85

สถานที่ที่ดีที่สุดในการตั้งค่าวัฒนธรรม / UI วัฒนธรรมในแอป 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 noob เล็กน้อยดังนั้นฉันจึงไม่มั่นใจว่าฉันตั้งค่านี้ในตำแหน่งที่ถูกต้อง ฉันไม่ต้องการทำในระดับ 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 เพียงเพิ่มในชั้นเรียนของคุณ:

[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 / ดัชนีสำหรับ ฯลฯ เยอรมัน


15
ฉันชอบใส่ภาษาใน URL ด้วยเนื่องจากเครื่องมือค้นหาในภาษาต่างๆสามารถรวบรวมข้อมูลได้และอนุญาตให้ผู้ใช้บันทึกหรือส่ง URL ด้วยภาษาเฉพาะ
Eduardo Molteni

50
การเพิ่มภาษาใน url ไม่ได้ละเมิด REST โดยการทำให้ทรัพยากรบนเว็บไม่ขึ้นอยู่กับสถานะเซสชันที่ซ่อนอยู่
Jace Rhea

4
ทรัพยากรบนเว็บไม่ได้ขึ้นอยู่กับสถานะที่ซ่อนอยู่วิธีการแสดงผลคือ หากคุณต้องการเข้าถึงทรัพยากรในรูปแบบบริการเว็บคุณจะต้องเลือกภาษาที่จะใช้
Dave Van den Eynde

4
ฉันมีปัญหาบางอย่างกับโซลูชันประเภทนี้ ไม่ได้แปลข้อความแสดงข้อผิดพลาดการตรวจสอบความถูกต้อง เพื่อแก้ปัญหาฉันตั้งค่าวัฒนธรรมในฟังก์ชัน Application_AcquireRequestState ของไฟล์ global.asax.cs
ADH

4
การใส่สิ่งนี้ในตัวกรองไม่ใช่ความคิดที่ดี การผูกโมเดลใช้ประโยชน์จาก CurrentCulture แต่ ActionFilter เกิดขึ้นหลังจากการรวมโมเดล จะดีกว่าที่จะทำใน Global.asax, Application_PreRequestHandlerExecute
Stefan

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

แก้ไข

ที่จริงมีวิธีที่ดีกว่าการใช้routehandler กำหนดเองซึ่งกำหนดวัฒนธรรมตาม URL ที่อธิบายได้อย่างสมบูรณ์แบบโดยอเล็กซ์ Adamyan ในบล็อกของเขา

สิ่งที่ต้องทำคือการลบล้าง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);
    }
}

Unfortunatelly RouteData ฯลฯ ไม่มีในเมธอด "Application_AcquireRequestState" แต่อยู่ใน Controller.CreateActionInvoker () ดังนั้นฉันแนะนำให้ "มีการป้องกันแทนที่ IActionInvoker CreateActionInvoker ()" และตั้งค่า CultureInfo ที่นั่น
Skorunka František

ฉันอ่านบล็อกนั้น จะมีปัญหาอะไรหรือไม่ถ้าฉันใช้คุกกี้ เนื่องจากฉันไม่ได้รับอนุญาตให้เปลี่ยนแปลง กรุณาแจ้งให้ฉันทราบ มีปัญหากับแนวทางนี้หรือไม่?
kbvishnu

@VeeKeyBee หากไซต์ของคุณเป็นแบบสาธารณะภาษาทั้งหมดจะไม่ได้รับการจัดทำดัชนีอย่างถูกต้องเมื่อใช้คุกกี้สำหรับไซต์ที่ได้รับการป้องกันคุณคงไม่เป็นไร
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
สตริงวัฒนธรรมไม่สามารถเป็น 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>

ขอบคุณสำหรับคำตอบฉันไม่ได้พยายามที่จะตัดสินใจว่าจะใช้เซสชันหรือไม่ ฉันมีความสุขกับบิตนั้น สิ่งที่ฉันพยายามหาคือถ้าจะทำสิ่งนี้ให้ดีที่สุดในตัวควบคุมวัฒนธรรมที่มีวิธีการดำเนินการสำหรับแต่ละวัฒนธรรมที่จะตั้งค่าหรือมีสถานที่ที่ดีกว่าในท่อ MVC ที่จะทำเช่นนี้
ChrisCa

ฉันได้ให้คำตอบที่แก้ไขแล้วซึ่งเหมาะกับคำถามมากขึ้น
NerdFury

ใช่มันจะสะอาดกว่าอย่างแน่นอน แต่สิ่งที่ฉันอยากรู้จริงๆคือควรทำในคอนโทรลเลอร์หรือไม่ หรือถ้ามีที่ที่ดีกว่าในท่อ MVC เพื่อตั้งค่าวัฒนธรรม หรือถ้าจะดีกว่าใน ActionFilters, Handlers, Modules และอื่น ๆ
ChrisCa

ตัวจัดการและโมดูลไม่สมเหตุสมผลเนื่องจากผู้ใช้ไม่มีโอกาสเลือก คุณต้องการวิธีให้ผู้ใช้ทำการเลือกจากนั้นประมวลผลการเลือกผู้ใช้ซึ่งจะดำเนินการในคอนโทรลเลอร์
NerdFury

ตกลงตัวจัดการและโมดูลต่างๆจะต้องดำเนินการก่อนเพื่อให้ผู้ใช้โต้ตอบได้ อย่างไรก็ตามฉันค่อนข้างใหม่กับ MVC ดังนั้นฉันจึงไม่แน่ใจว่านี่เป็นสถานที่ที่ดีที่สุดในการตั้งค่าหรือไม่ ถ้าฉันไม่ได้ยินอีกสักครู่ฉันจะยอมรับคำตอบของคุณ ps ไวยากรณ์ที่คุณใช้ในการส่งผ่านพารามิเตอร์ไปยังเมธอด Action ดูเหมือนจะไม่ได้ผล ไม่มีตัวควบคุมที่กำหนดไว้ดังนั้นให้ใช้ค่าเริ่มต้น (ซึ่งไม่ใช่ตัวควบคุมที่ถูกต้องในกรณีนี้) และดูเหมือนว่าจะไม่มีการโอเวอร์โหลดอีก
ChrisCa

6

คำถามของคุณคือสถานที่ใดที่ดีที่สุด สถานที่ที่ดีที่สุดอยู่ในตัวควบคุมวิธีการเริ่มต้น MSDN เขียนว่าถูกเรียกตามตัวสร้างและก่อนวิธีการดำเนินการ ในทางตรงกันข้ามกับการแทนที่ OnActionExecuting การวางรหัสของคุณในวิธีการเริ่มต้นช่วยให้คุณได้รับประโยชน์จากการมีคำอธิบายประกอบและแอตทริบิวต์ข้อมูลที่กำหนดเองทั้งหมดในคลาสของคุณและบนคุณสมบัติของคุณที่จะแปลเป็นภาษาท้องถิ่น

ตัวอย่างเช่นตรรกะการแปลของฉันมาจากคลาสที่ถูกฉีดเข้าไปในคอนโทรลเลอร์ที่กำหนดเองของฉัน ฉันสามารถเข้าถึงวัตถุนี้ได้เนื่องจาก 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

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
โปรดอธิบายว่าเหตุใดจึงควรเป็นวิธีที่ดีที่สุด
Max Leske
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.