1

我已经搜索并尝试了许多本地化方法,但都不是我想要的。基本上,我希望我的网址是这样的

  • www.myweb.com <== 默认语言(即英语)
  • www.myweb.com/promotion
  • www.myweb.com/th/promotion <== 当地语言(泰语)
  • www.myweb.com/cn/promotion <== 当地语言(中文)

然后我希望这些 url 映射到不同的视图结构,如下所示

/Views
    /_Localization
        /cn
            /Home
                /About.cshtml
                /Index.cshtml
            /Shared
                /_Layout.cshtml
                /Error.cshtml
        /th
            /Home
                /About.cshtml
            /Shared
    /Home
        /About.cshtml
        /Index.cshtml
    /Shared
        /_Layout.cshtml
        /_LogOnPartial.cshtml
        /Error.cshtml
    _ViewStart.cshtml
    Web.config

如您所见, Thai 没有它自己的Index.cshtml,_Layout.cshtmlError.cshtml. 所以,我希望它回退到使用默认值。但是中国人会自己用。

我试过MapRoute喜欢这个

routes.MapRoute(
    "DefaultLocal",
    "{lang}/{controller}/{action}/{id}",
    new { lang = "th", controller = "Home", action = "Index", id = UrlParameter.Optional }
);

routes.MapRoute(
    "Default",
    "{controller}/{action}/{id}",
    new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);

但我不知道如何指向不同的视图结构。在这个例子中,Brian Reiter,它Cookie不使用 url。

那么,我怎样才能做到这一点。请注意,我使用 RazorViewEngine。 感谢您的任何帮助和想法。

4

1 回答 1

3

由于需要大量代码,我只会说明如何完成它的想法。

您可以RazorViewEngine像这样子类化:

public class I18NRazorViewEngine : RazorViewEngine
{
    public I18NRazorViewEngine() : this(null)
    { }

    protected string[] I18NAreaViewLocationFormats;
    protected string[] I18NAreaMasterLocationFormats;
    protected string[] I18NAreaPartialViewLocationFormats;
    protected string[] I18NViewLocationFormats;
    protected string[] I18NMasterLocationFormats;
    protected string[] I18NPartialViewLocationFormats;

    public I18NRazorViewEngine(IViewPageActivator viewPageActivator)
        : base(viewPageActivator)
    {
        this.I18NAreaViewLocationFormats = new string[]
        {
            "~/Areas/{3}/{2}/Views/{1}/{0}.cshtml",
            "~/Areas/{3}/{2}/Views/{1}/{0}.vbhtml",
            "~/Areas/{3}/{2}/Views/Shared/{0}.cshtml",
            "~/Areas/{3}/{2}/Views/Shared/{0}.vbhtml"
        };

        this.I18NAreaMasterLocationFormats = new string[]
        {
            "~/Areas/{3}/{2}/Views/{1}/{0}.cshtml",
            "~/Areas/{3}/{2}/Views/{1}/{0}.vbhtml",
            "~/Areas/{3}/{2}/Views/Shared/{0}.cshtml",
            "~/Areas/{3}/{2}/Views/Shared/{0}.vbhtml"
        };

        this.I18NAreaPartialViewLocationFormats = new string[]
        {
            "~/Areas/{3}/{2}/Views/{1}/{0}.cshtml",
            "~/Areas/{3}/{2}/Views/{1}/{0}.vbhtml",
            "~/Areas/{3}/{2}/Views/Shared/{0}.cshtml",
            "~/Areas/{3}/{2}/Views/Shared/{0}.vbhtml"
        };

        this.I18NViewLocationFormats = new string[]
        {
            "~/Views/{2}/{1}/{0}.cshtml",
            "~/Views/{2}/{1}/{0}.vbhtml",
            "~/Views/{2}/Shared/{0}.cshtml",
            "~/Views/{2}/Shared/{0}.vbhtml"
        };

        this.I18NMasterLocationFormats = new string[]
        {
            "~/Views/{2}/{1}/{0}.cshtml",
            "~/Views/{2}/{1}/{0}.vbhtml",
            "~/Views/{2}/Shared/{0}.cshtml",
            "~/Views/{2}/Shared/{0}.vbhtml"
        };

        this.I18NPartialViewLocationFormats = new string[]
        {
            "~/Views/{2}/{1}/{0}.cshtml",
            "~/Views/{2}/{1}/{0}.vbhtml",
            "~/Views/{2}/Shared/{0}.cshtml",
            "~/Views/{2}/Shared/{0}.vbhtml"
        };
    }

    public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache)
    {
        var langValue = controllerContext.Controller.ValueProvider.GetValue("lang");
        if (langValue == null || String.IsNullOrEmpty(langValue.AttemptedValue))
            return base.FindView(controllerContext, viewName, masterName, useCache);

        //Code here
    }

    public override ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName, bool useCache)
    {
        var langValue = controllerContext.Controller.ValueProvider.GetValue("lang");
        if (langValue == null || String.IsNullOrEmpty(langValue.AttemptedValue))
            return base.FindPartialView(controllerContext, partialViewName, useCache);

        //Code here
    }        
}

接下来你应该做的是查看VirtualPathProviderViewEngineFindView 和实现的内部FindPartialView。反映的代码是这样的:

public virtual ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName, bool useCache)
{
    if (controllerContext == null)
    {
        throw new ArgumentNullException("controllerContext");
    }
    if (string.IsNullOrEmpty(partialViewName))
    {
        throw new ArgumentException(MvcResources.Common_NullOrEmpty, "partialViewName");
    }
    string requiredString = controllerContext.RouteData.GetRequiredString("controller");
    string[] searchedLocations;
    string path = this.GetPath(controllerContext, this.PartialViewLocationFormats, this.AreaPartialViewLocationFormats, "PartialViewLocationFormats", partialViewName, requiredString, "Partial", useCache, out searchedLocations);
    if (string.IsNullOrEmpty(path))
    {
        return new ViewEngineResult(searchedLocations);
    }
    return new ViewEngineResult(this.CreatePartialView(controllerContext, path), this);
}

public virtual ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache)
{
    if (controllerContext == null)
    {
        throw new ArgumentNullException("controllerContext");
    }
    if (string.IsNullOrEmpty(viewName))
    {
        throw new ArgumentException(MvcResources.Common_NullOrEmpty, "viewName");
    }
    string requiredString = controllerContext.RouteData.GetRequiredString("controller");
    string[] first;
    string path = this.GetPath(controllerContext, this.ViewLocationFormats, this.AreaViewLocationFormats, "ViewLocationFormats", viewName, requiredString, "View", useCache, out first);
    string[] second;
    string path2 = this.GetPath(controllerContext, this.MasterLocationFormats, this.AreaMasterLocationFormats, "MasterLocationFormats", masterName, requiredString, "Master", useCache, out second);
    if (string.IsNullOrEmpty(path) || (string.IsNullOrEmpty(path2) && !string.IsNullOrEmpty(masterName)))
    {
        return new ViewEngineResult(first.Union(second));
    }
    return new ViewEngineResult(this.CreateView(controllerContext, path, path2), this);
}

两种方法都依赖于私有GetPath方法:

private string GetPath(ControllerContext controllerContext, string[] locations, string[] areaLocations, string locationsPropertyName, string name, string controllerName, string cacheKeyPrefix, bool useCache, out string[] searchedLocations)
{
    searchedLocations = VirtualPathProviderViewEngine._emptyLocations;
    if (string.IsNullOrEmpty(name))
    {
        return string.Empty;
    }
    string areaName = AreaHelpers.GetAreaName(controllerContext.RouteData);
    List<VirtualPathProviderViewEngine.ViewLocation> viewLocations = VirtualPathProviderViewEngine.GetViewLocations(locations, (!string.IsNullOrEmpty(areaName)) ? areaLocations : null);
    if (viewLocations.Count == 0)
    {
        throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, MvcResources.Common_PropertyCannotBeNullOrEmpty, new object[]
        {
            locationsPropertyName
        }));
    }
    bool flag = VirtualPathProviderViewEngine.IsSpecificPath(name);
    string text = this.CreateCacheKey(cacheKeyPrefix, name, flag ? string.Empty : controllerName, areaName);
    if (useCache)
    {
        return this.ViewLocationCache.GetViewLocation(controllerContext.HttpContext, text);
    }
    if (!flag)
    {
        return this.GetPathFromGeneralName(controllerContext, viewLocations, name, controllerName, areaName, text, ref searchedLocations);
    }
    return this.GetPathFromSpecificName(controllerContext, name, text, ref searchedLocations);
}

你应该做的是重新实现它。大多数代码您可以重用,但您应该创建自己的方法而不是VirtualPathProviderViewEngine.GetViewLocations. 这里它的反映代码:

private static List<VirtualPathProviderViewEngine.ViewLocation> GetViewLocations(string[] viewLocationFormats, string[] areaViewLocationFormats)
{
    List<VirtualPathProviderViewEngine.ViewLocation> list = new List<VirtualPathProviderViewEngine.ViewLocation>();
    if (areaViewLocationFormats != null)
    {
        for (int i = 0; i < areaViewLocationFormats.Length; i++)
        {
            string virtualPathFormatString = areaViewLocationFormats[i];
            list.Add(new VirtualPathProviderViewEngine.AreaAwareViewLocation(virtualPathFormatString));
        }
    }
    if (viewLocationFormats != null)
    {
        for (int j = 0; j < viewLocationFormats.Length; j++)
        {
            string virtualPathFormatString2 = viewLocationFormats[j];
            list.Add(new VirtualPathProviderViewEngine.ViewLocation(virtualPathFormatString2));
        }
    }
    return list;
}

您还可以重用大部分代码,但您应该使用自己的类,而不是 VirtualPathProviderViewEngine.ViewLocation 和 VirtualPathProviderViewEngine.AreaAwareViewLocation。他们可能是这样的:

class ViewLocation
{
    protected string _virtualPathFormatString;
    public ViewLocation(string virtualPathFormatString)
    {
        this._virtualPathFormatString = virtualPathFormatString;
    }
    public virtual string Format(string viewName, string controllerName, string areaName, string lang)
    {
        return string.Format(CultureInfo.InvariantCulture, this._virtualPathFormatString, new object[]
        {
            viewName,
            controllerName,
                    lang
        });
    }
}

和:

class AreaAwareViewLocation : VirtualPathProviderViewEngine.ViewLocation
{
    public AreaAwareViewLocation(string virtualPathFormatString) : base(virtualPathFormatString)
    {
    }
    public override string Format(string viewName, string controllerName, string areaName, string lang)
    {
        return string.Format(CultureInfo.InvariantCulture, this._virtualPathFormatString, new object[]
        {
            viewName,
            controllerName,
            areaName,
                    lang
        });
    }
}

然后当您调用Format方法时,您应该将langValue.AttemptedValue(它来自第一个代码块的范围FindViewFindPartialView)传递给 lang 参数。通常它会被调用VirtualPathProviderViewEngine.GetPathFromGeneralName

主要建议是使用 ILSpy 或其他反汇编程序来探索 System.Web.Mvc 的代码(甚至更好 - 下载其源代码)。目标是重新实现FindViewFindPartialView. 提供的其余代码是为了说明它是如何在 mvc 框架中完成的。

寻找在我们的新视图引擎中声明的数组而不是那些已经存在并被默认类使用的没有 I18N 前缀的数组也很重要

尽管答案是间接的,但希望它会有所帮助。如果您遇到任何困难,您可以提出其他问题。

PS 不要忘记在 global.asax.cs 中注册您的视图引擎,之后它将被开发。

protected virtual void Application_Start()
{
   ViewEngines.Engines.Clear();
   ViewEngines.Engines.Add(new I18NRazorViewEngine());
}
于 2012-06-05T07:44:59.610 回答