MVC5和DropDownList的,饼干,用户配置文件设置设置文化/ CultureUI [英] MVC5 and setting Culture/CultureUI with DropDownList, Cookie, User Profile Setting

查看:373
本文介绍了MVC5和DropDownList的,饼干,用户配置文件设置设置文化/ CultureUI的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我已经部分地在我的项目实施全球化/本地化。该项目需要一个数据库用于资源字符串,我发现一个优秀的NuGet包叫做WestWind.Globalization,做正是我需要的。

I have partially implemented Globalization/Localization in my project. The project requires a database to be used for resource strings and I found an excellent NuGet package called WestWind.Globalization that does exactly what I needed.

这个包的NuGet允许您使用几种不同的方法来显示资源字符串。它提供了一个选项,以生成一个包含所有资源字符串的强类型类,所以你可以用它喜欢的:

This NuGet package allows you to display resource strings using several different methods. It provides an option to generate a strongly typed class that contains all of your resource strings so you can use it like:

@Html.Encode( Resources.lblResourceName )

object Value = this.GetLocalResourceObject("ResourceName");

object GlobalValue = this.GetGlobalResourceObject("Resources","ResourceKey");

甚至

dbRes.T(resourceName, resourceSet, culture)

我不想手动指定的文化,所以我选择了这种方式:

I didn't want to specify the culture manually, so I opted for this method:

<p class="pageprompt">@AccountRequestAccount.pagePrompt</p>

对于我来说,Westwind.Globalization是不可思议的。它解决了一个巨大的问题我,但我遇到了一个障碍,我不知道如何来克服。这是,如何设置文化/ CultureUI这样包会自动使用指定的语言资源。

For me, Westwind.Globalization is magical. It resolved a huge issue for me, but I ran into a snag that I wasn't sure how to overcome. That was, how to set the Culture/CultureUI so that the package would automatically use a specified language resource.

我创建了一个包含一个语言下拉列表中PartialView。它包含在〜/查看/共享/文件夹和被包含在_Layout.cshtml。我codeD GET和POST控制器操作该工作如预期,但我无法坚持文化/ CultureUI设置。我怀疑它马上语言选择以下(下文解释)是由于重定向

I created a PartialView that contains a dropdown list of languages. It is contained in the ~/Views/Shared/ folder and gets included in _Layout.cshtml. I coded the GET and POST Controller Actions which work as intended, except that I was unable to persist the Culture/CultureUI settings. I suspect that it was due to a redirect immediately following language selection (explained below)

所以,我发现了一个 SO问题的有这似乎可行的答案。我综合了答案到我的项目。有关code是:

So, I found an SO question that had an answer that seemed viable. I integrated that answer into my project. The relevant code is:

RouteConfig.cs:

RouteConfig.cs:

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

〜/助手/ InternationalizationAttribute.cs

~/Helpers/InternationalizationAttribute.cs

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Threading;
using System.Web;
using System.Web.Mvc;

namespace GPS_Web_App.Helpers
{
    public class InternationalizationAttribute : ActionFilterAttribute
    {
        public override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            string language = 
                (string)filterContext.RouteData.Values["language"] ?? "en";

            string culture = 
                (string)filterContext.RouteData.Values["culture"] ?? "US";

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

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

在我的控制器:

[Authorize]
[Internationalization]
public class AccountController : Controller
{
    ...
}

到目前为止好。这工作,因为我能够去 http://example.com/en-mx/Account/Login/ 的网址,看到网页由西风被本地化.Globalization和我创建了资源字符串。

So far so good. This works in that I am able to go to a URL of http://example.com/en-mx/Account/Login/ and see the page being localized by Westwind.Globalization and the resource strings I've created.

我有这样的问题是:


  1. 如果用户是匿名他们的语言preference应该由浏览器来控制(如果存在),否则默认为EN-US。

  1. If the user is anonymous their language preference should be controlled by cookie (if it exists) otherwise default to en-US.

如果用户通过验证他们的语言preference应由语言字段在个人资料设置进行控制。 (使用ASP.NET 2.0的身份简单成员)。

If the user is authenticated their language preference should be controlled by the Language field in their profile settings. (Simple Membership using ASP.NET Identity 2.0).

有是一个全球性的标头中的语言选择下拉列表。用户应该能够从下拉列表中选择自己的语言preference如果他们这样做,设置被写入cookie的(匿名和身份验证的用户),如果用户通过验证他们的语言设置在用户配置文件被更新

There is a language selection dropdown in a global header. The user should be able to choose their language preference from the dropdown and if they do, the setting gets written to cookie (for both anonymous and authenticated users) and if the user is authenticated their Language setting in the user profile gets updated.

世界没有结束,但它是高度preferable该语言不包含在URL中。有人可能会问,那么为什么我安装@饶宗颐的解决方案?让我来解释这一点。

Not the end of the world, but it would be highly preferable that the language not be included in the URL. Some might ask, well why did I install @jao's solution? Let me explain that.

所有code的是地方的下拉菜单,允许用户进行语言选择。 #1,#2,#以上3的逻辑是正常工作,但不会生效,并引发Westwind.Globalization的DbResourceProvider通过所选择的语言资源字符串。

All of the code was in place for the dropdown to allow a user to make a language selection. The logic for #1, #2, and #3 above were working correctly, but wouldn't take effect and trigger Westwind.Globalization's DbResourceProvider to pass the selected language resource strings.

我通过调试发现了什么,我的设置中没有坚持:

What I discovered through debugging was that my settings were not persisting in:

System.Threading.Thread.CurrentThread.CurrentCulture = 
    System.Globalization.CultureInfo.GetCultureInfo(SelectedLanguage);
System.Threading.Thread.CurrentThread.CurrentUICulture = 
    System.Globalization.CultureInfo.GetCultureInfo(SelectedLanguage);

通过这里,我的问题作出答复让我学会了这些设置不会持续/如果一个重定向是之前的原始视图渲染制作生效。然而,一个重定向回原来的意见似乎明智的,因为语言的变更后,需要再次进行渲染。我觉得@饶宗颐的解决方案克服了重定向问题,但是它迫使全球化/本地化到URL指定?多少有些左右为难...

Through responses provided by my question here on SO I learned that those settings would not persist/take effect if a redirect was made prior to the original View rendering. Yet a redirect back to the original View seemed sensible since the language was being changed and needed to be rendered again. I think @jao's solution overcomes the redirect problem, but it forces Globalization/Localization to be specified by the URL? Somewhat of a catch-22...

我也问过@jao审查这个问题,并提供有关此任何提示。我想我的问题是最好的概括为这样的:

I have asked @jao to review this question and provide any hints on this. I think my question is best summed up as this:

如何使用用户的Cookie /配置文件设置,设置文化/ CultureUI一劳永逸,这样Westwind.Globalization可以阅读,而不是依赖于文化的URL被传递全球化/本地化?

推荐答案

我张贴这个答案与ASP.NET MVC5做本地化异步控制器的替代,自定义的方式。也许你会发现我的解决方案的一些陷阱特别是当它涉及到的路由和设置cookie。

I am posting this answer as an alternate, custom way of doing localization with ASP.NET MVC5 with asynchronous controller. Perhaps you may find some gotchas in my solution especially when it comes to routing and setting cookies.

这是那种一个简短的教程我潦草下来我的异类/自定义的方法。所以我美元的字preSS SO pferred p $。 :)

This is sort of a short tutorial I scribbled down for my heterogeneous/custom approach. So I preferred SO over WordPress. :)

对不起,不给precise和离散回答你的问题。希望它会帮助你在一些其他的方式,而其他人也;谁正在寻找做同样设置的。

Sorry for not giving the precise and discrete answer to your problem. Hopefully it will help you in some other way, and other folks as well; who are looking to do the same sort of setup.

在他的博客帖子,纳迪姆Afana描述创建一个独立的策略项目的资源在解决方案中使用静态资源文件实现国际化。在博客续集,他详细介绍关于延长同一项目来处理通过数据库和XML驱动的方法的资源。对于前者,他用ADO.NET,从实体框架脱钩。

In his blog post, Nadeem Afana described a strategy of creating a separate project Resource in the solution to implement internationalization using static resource files. In the blog sequel, he detailed on extending the same project to handle resources via Databases and XML-driven approaches. For the former one, he used ADO.NET, decoupled from Entity Framework.

我们需要对MVC项目中实现静态和动态的资源,尊重MVC公约的概念。

We needed to implement both static and dynamic resources within the MVC project, respecting the concepts of MVC conventions.

首先让我们在项目的根添加一个资源文件夹,用所需的语言变体:〜/资源/ Resources.resx (默认的资源文件对应的en-US文化),〜/资源/ Resources.fi.resx 〜/资源/ Resources.nl.resx 。标记资源公开,这样使它们在视图中使用。

First lets add a Resources folder in project root, with the desired language variants: ~/Resources/Resources.resx (the default resource file corresponds to en-US culture), ~/Resources/Resources.fi.resx and ~/Resources/Resources.nl.resx. Mark the resources as public, so to make them available in Views.

〜/查看/ Web.config中,下添加的资源空间&LT;空间&GT; 元素:&LT;添加命名空间=YourMainNamespace.Reousrces/&GT; 。在控制器,创建一个基控制器类:

In ~/Views/Web.config, add the resources namespace under <namespace> element: <add namespace="YourMainNamespace.Reousrces" />. Under controllers, create a base controller class:

这里来的饼干

namespace YourNamespace.Controllers
{
    // Don't forget to inherit other controllers with this
    public class BaseController : Controller
    {
        protected override IAsyncResult BeginExecuteCore(AsyncCallback callback, object state)
        {
            string cultureName = null;

            // Attempt to read the culture cookie from Request
            HttpCookie cultureCookie = Request.Cookies["_culture"];
            if (cultureCookie != null)
                cultureName = cultureCookie.Value;
            else
                cultureName = Request.UserLanguages != null && Request.UserLanguages.Length > 0 ?
                        Request.UserLanguages[0] :  // obtain it from HTTP header AcceptLanguages
                        null;
            // Validate culture name
            cultureName = CultureHelper.GetImplementedCulture(cultureName); // This is safe

            // Modify current thread's cultures            
            Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo(cultureName);
            Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture;

            return base.BeginExecuteCore(callback, state);
        }
    }
}

接下来,在〜/ Global.asax.cs中注册一个全球性的过滤器,以确保每一个动作应在执行前使用正确的文化:

Next, register a global filter to in ~/Global.asax.cs to ensure that every action should use the correct culture before executing:

这里又来了饼干!

public class SetCultureActionFilterAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
       base.OnActionExecuting(filterContext);

        var response = filterContext.RequestContext.HttpContext.Response;
        var culture = filterContext.RouteData.Values["culture"].ToString();

        // Validate input
        culture = CultureHelper.GetImplementedCulture(culture);

        Thread.CurrentThread.CurrentUICulture = new CultureInfo(culture);

        // Save culture in a cookie
        HttpCookie cookie = filterContext.RequestContext.HttpContext.Request.Cookies["_culture"];
        if (cookie != null)
            cookie.Value = culture;   // update cookie value
        else
        {
            cookie = new HttpCookie("_culture");
            cookie.Value = culture;
            cookie.Expires = DateTime.Now.AddYears(1);
        }
        response.Cookies.Add(cookie);
    }
}

和增加 GlobalFilters.Filters.Add(新SetCultureActionFilterAttribute()); MyApplication.Application_Start()方法

〜/ App_Start / RoutesConfig.cs ,更改为默认路由:

routes.MapRoute(
    name: "Default",
    url: "{culture}/{controller}/{action}/{id}",
    defaults: new { culture = "en-US", controller = "Home", action = "Index", id = UrlParameter.Optional }
);

在这一点上,我们将能够在视图中使用的资源。例如; @ Resources.Headline

接下来,我们将创建一个名为可翻译的模型属性。

Next, we will create a custom attribute called Translatable for model properties.

class TranslatableAttribute : Attribute
{ }

这是不够的。但是,如果你希望能够指定的范围,你可以使用这个类来实现它。

This is enough. But if you want to be able to specify scope, you can use this class to implement it.

现在添加一个名为资源具有三个属性和一个辅助方法模型:

Now add a model called Resource with three properties and a helper method:

public class Resource
{
    [Key, Column(Order = 0)]
    public string Culture { get; set; }

    [Key, Column(Order = 1)]
    public string Name { get; set; }

    public string Value { get; set; }

    #region Helpers
    // Probably using reflection not the best approach.
    public static string GetPropertyValue<T>(string id, string propertyName) where T : class
    {
        return GetPropertyValue<T>(id, propertyName, Thread.CurrentThread.CurrentUICulture.Name);
    }
    public static string GetPropertyValue<T>(string id, string propertyName, string culture) where T : class
    {
        Type entityType = typeof(T);
        string[] segments = propertyName.Split('.');

        if (segments.Length > 1)
        {
            entityType = Type.GetType("YourNameSpace.Models." + segments[0]);
            propertyName = segments[1];
        }

        if (entityType == null)
            return "?<invalid type>";

        var propertyInfo = entityType.GetProperty(propertyName);
        var translateableAttribute = propertyInfo.GetCustomAttributes(typeof(TranslatableAttribute), true)
                                    .FirstOrDefault();
        /*var requiredAttribute = propertyInfo.GetCustomAttributes(typeof(RequiredAttribute), true)
                                .FirstOrDefault();*/

        if (translateableAttribute == null)
            return "?<this field has no translatable attribute>";

        var dbCtx = new YourNamespaceDbContext();
        var className = entityType.Name;
        Resource resource = dbCtx.Resources.Where(r =>
                            (r.Culture == culture) &&
                            r.Name == className + id + propertyName).FirstOrDefault();

        if (resource != null)
            return resource.Value;

        //return requiredAttribute == null ? string.Empty : "?<translation not found>";
        return string.Empty;
    }
    #endregion
}

这个helper方法会帮助你找回翻译的内容。例如,在视图中,你可以说:

This helper method will help you retrieve the translated content. For instance in view, you can say:

var name = Resource.GetPropertyValue<Product>(item.Id.ToString(), "Name");

请注意的是,在任何时候,在可平移字段列中的数据是不可靠的;它会永远保存最近更新的价值。在创建记录,我们将在镜像资源模型所有可翻译属性的值对所有支持的文化。

Note that, at any point, the data in the translatable field column is unreliable; it will always hold the last updated value. On creating the record, we will mirror all the translatable properties' values in Resource model for all supported cultures.

我们使用的是异步控制器,所以对于插入,修改和删除,我们将覆盖 SaveChangesAsync()我们的DbContext 类:

We are using asynchronous controllers, so for insertion, modification and deletion we will be overriding SaveChangesAsync() in our DbContext class:

public override Task<int> SaveChangesAsync()
{
    ObjectContext ctx = ((IObjectContextAdapter)this).ObjectContext;

    List<ObjectStateEntry> objectDeletedStateEntryList =
        ctx.ObjectStateManager.GetObjectStateEntries(EntityState.Deleted)
        .ToList();

    List<ObjectStateEntry> objectCreateOrModifiedStateEntryList =
        ctx.ObjectStateManager.GetObjectStateEntries(EntityState.Added
                                                    | EntityState.Modified)
        .ToList();

    // First handle the delition case,
    // before making changes to entry state
    bool changed = UpdateResources(objectDeletedStateEntryList);

    // Now save the changes
    int result = base.SaveChangesAsync().Result;

    // Finally handle the remaining cases
    changed |= UpdateResources(objectCreateOrModifiedStateEntryList);

    if (changed)
        return base.SaveChangesAsync();

    return Task.FromResult<int>(result);
}

private bool UpdateResources(List<ObjectStateEntry> objectStateEntryList)
{
    bool changed = false;

    foreach (ObjectStateEntry entry in objectStateEntryList)
    {
        var typeName = entry.EntitySet.ElementType.Name;

        if (entry.IsRelationship || typeName == "Resource")
            return false;

        var type = Type.GetType("YourNamespace.Models." + typeName);

        if (type == null) // When seeds run (db created for the first-time), sometimes types might not be create
            return false;

        if (entry.State == EntityState.Deleted)
        {
            changed |= DeleteResources(type, typeName, entry);
            continue;
        }

        foreach (var propertyInfo in type.GetProperties())
        {
            var attribute = propertyInfo.GetCustomAttributes(typeof(TranslatableAttribute), true).FirstOrDefault();

            if (attribute == null)
                continue;

            CurrentValueRecord current = entry.CurrentValues;
            object idField = current.GetValue(current.GetOrdinal("Id"));

            if (idField == null)
                continue;

            var id = idField.ToString();
            var propertyName = propertyInfo.Name;
            string newValue = current.GetValue(current.GetOrdinal(propertyName)).ToString();
            var name = typeName + id + propertyName;

            Resource existingResource = this.Resources.Find(Thread.CurrentThread.CurrentUICulture.Name, name);

            if (existingResource == null)
            {
                foreach (var culture in CultureHelper.Cultures)
                {
                    this.Resources.Add(new Resource
                    {
                        Culture = culture,
                        Name = name,
                        Value = newValue
                    });

                    changed |= true;
                }
            }
            else
            {
                existingResource.Value = newValue;
                changed |= true;
            }
        }
    }

    return changed;
}

private bool DeleteResources(Type type, string typeName, ObjectStateEntry entry)
{
    bool changed = false;
    var firstKey = entry.EntityKey.EntityKeyValues.Where(k => k.Key.Equals("Id", StringComparison.InvariantCultureIgnoreCase)).FirstOrDefault();

    if (firstKey == null)
        return false;

    var id = firstKey.Value.ToString();

    foreach (var propertyInfo in type.GetProperties())
    {
        var name = typeName + id + propertyInfo.Name;

        foreach (var culture in CultureHelper.Cultures)
        {
            Resource existingResource = this.Resources.Find(culture, name);

            if (existingResource == null)
                continue;

            this.Resources.Remove(existingResource);
            changed |= true;
        }
    }

    return changed;
}

这会照顾更新和删除。

这篇关于MVC5和DropDownList的,饼干,用户配置文件设置设置文化/ CultureUI的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆