首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >MVC5和使用DropDownList、Cookie、用户配置文件设置的文化/文化User

MVC5和使用DropDownList、Cookie、用户配置文件设置的文化/文化User
EN

Stack Overflow用户
提问于 2014-06-25 21:07:08
回答 1查看 2.2K关注 0票数 1

我在我的项目中部分实施了全球化/本土化。该项目需要一个用于资源字符串的数据库,我发现了一个很好的NuGet包,名为WestWind.Globalization,它完成了我所需要的功能。

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

代码语言:javascript
复制
@Html.Encode( Resources.lblResourceName )

代码语言:javascript
复制
object Value = this.GetLocalResourceObject("ResourceName");

代码语言:javascript
复制
object GlobalValue = this.GetGlobalResourceObject("Resources","ResourceKey");

甚至:

代码语言:javascript
复制
dbRes.T(resourceName, resourceSet, culture)

我不想手动指定区域性,所以选择了以下方法:

代码语言:javascript
复制
<p class="pageprompt">@AccountRequestAccount.pagePrompt</p>

对我来说,Westwind.Globalization很神奇。这解决了我的一个大问题,但我遇到了一个障碍,我不知道如何克服。也就是说,如何设置区域性/文化性use,以便包能够自动使用指定的语言资源。

我创建了一个包含下拉语言列表的PartialView。它包含在~/Views/Shared/文件夹中,并包含在_Layout.cshtml中。我对GET和POST控制器操作进行了编码,这些操作按预期工作,只是无法持久化区域性/CultureUI设置。我怀疑这是因为在选择语言之后立即重定向(下文解释)。

所以,我找到了一个所以问题,它的答案似乎是可行的。我把这个答案整合到我的项目中。有关守则是:

RouteConfig.cs:

代码语言:javascript
复制
 routes.MapRoute("DefaultLocalized",
 "{language}-{culture}/{controller}/{action}/{id}",
 new
 {
     controller = "Home",
     action = "Index",
     id = "",
     language = "en",
     culture = "US"
 });

~/帮助者/国际化属性。

代码语言:javascript
复制
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));
        }
    }
}

在我的控制员中:

代码语言:javascript
复制
[Authorize]
[Internationalization]
public class AccountController : Controller
{
    ...
}

到目前一切尚好。这是因为我能够访问http://example.com/en-mx/Account/Login/的URL,并看到页面被Westwind.Globalization本地化,以及我创建的资源字符串。

我遇到的问题是:

  1. 如果用户是匿名的,他们的语言偏好应该由cookie (如果存在)控制,否则默认为en-US。
  2. 如果对用户进行了身份验证,则其语言首选项应由其配置文件设置中的语言字段控制。(使用ASP.NET标识2.0的简单成员资格)。
  3. 在全局标题中有一个语言选择下拉列表。用户应该能够从下拉列表中选择他们的语言首选项,如果他们选择了,该设置将被写入cookie (对于匿名用户和身份验证用户),如果用户经过身份验证,则会更新用户配置文件中的语言设置。
  4. 不是世界末日,但最好不要将语言包括在URL中。有人可能会问,为什么我要安装@jao的解决方案呢?让我解释一下。

所有的代码都为下拉列表做好了准备,允许用户进行语言选择。上述#1、#2和#3的逻辑工作正常,但不会生效并触发were .全球化的DbResourceProvider来传递所选的语言资源字符串。

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

代码语言:javascript
复制
System.Threading.Thread.CurrentThread.CurrentCulture = 
    System.Globalization.CultureInfo.GetCultureInfo(SelectedLanguage);
System.Threading.Thread.CurrentThread.CurrentUICulture = 
    System.Globalization.CultureInfo.GetCultureInfo(SelectedLanguage);

通过我在这里的问题提供的答复,我了解到,如果在原始视图呈现之前进行重定向,这些设置将不会持续/生效。然而,重定向回到原来的视图似乎是明智的,因为语言正在被改变,需要重新呈现。我认为@jao的解决方案克服了重定向问题,但它强制由URL指定全球化/本地化?有点像-22.

我已经要求@jao回顾这个问题,并提供任何提示。我想我的问题最好概括如下:

如何使用用户的cookie/配置文件设置来一次性设置文化/文化URL,以便Westwind.Globalization能够读取全球化/本地化,而不依赖于在URL中传递的区域性?

EN

回答 1

Stack Overflow用户

发布于 2014-11-11 15:57:45

我将此答案作为使用异步控制器的ASP.NET MVC5进行本地化的另一种定制方式发布。也许您会在我的解决方案中发现一些问题,特别是在路由和设置cookie方面。

这是我为我的异构/自定义方法草草写下的一个简短的教程。所以我更喜欢这样,而不是WordPress。:)

很抱歉没有给出你的问题的精确和离散的答案。希望它能在其他方面帮助您,也能帮助其他人,他们希望做同样的设置。

在他的博客帖子中,Nadeem描述了在解决方案中创建一个单独的项目Resource以使用静态资源文件实现国际化的策略。在博客续集中,他详细介绍了如何通过数据库和XML驱动的方法扩展相同的项目以处理资源。对于前者,他使用了ADO.NET,与实体框架分离。

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

首先,让我们在项目根目录中添加一个参考资料文件夹,并提供所需的语言变体:~/Resources/Resources.resx (默认资源文件对应en-US区域性)、~/Resources/Resources.fi.resx~/Resources/Resources.nl.resx。将资源标记为公共资源,以便在视图中可用。

~/Views/Web.config中,在<namespace>元素:<add namespace="YourMainNamespace.Reousrces" />下添加资源命名空间。在“控制器”下,创建一个基本控制器类:

来了cookies

代码语言:javascript
复制
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中注册一个全局过滤器,以确保每个操作在执行之前都应该使用正确的区域性:

又来了饼干!

代码语言:javascript
复制
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(new SetCultureActionFilterAttribute());方法中添加MyApplication.Application_Start()方法。

~/App_Start/RoutesConfig.cs中,将默认路由更改为:

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

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

接下来,我们将为模型属性创建一个名为Translatable的自定义属性。

代码语言:javascript
复制
class TranslatableAttribute : Attribute
{ }

这就够了。但是,如果您希望能够指定作用域,可以使用这个类来实现它。

现在添加一个名为Resource的模型,其中包含三个属性和一个助手方法:

代码语言:javascript
复制
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
}

此助手方法将帮助您检索已翻译的内容。例如,考虑到,您可以说:

代码语言:javascript
复制
var name = Resource.GetPropertyValue<Product>(item.Id.ToString(), "Name");

注意,在任何时候,可翻译字段列中的数据都是不可靠的;它总是保存最后更新的值。在创建记录时,我们将镜像所有受支持的区域性的资源模型中所有可转换属性的值。

我们使用异步控制器,因此为了插入、修改和删除,我们将在我们的SaveChangesAsync()类中重写DbContext

代码语言:javascript
复制
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;
}

这将负责更新和删除。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/24418585

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档