MVC 4:自定义路线& Html.Action不同步

时间:2013-12-31 01:20:06

标签: c# asp.net-mvc asp.net-mvc-4 asp.net-mvc-routing mvcroutehandler

所以我有这个自定义路由,它根据URL中的区域性设置路由表,但是当我调用Url.Action(...)时,它不会生成本地化的URL。我有什么想法我做错了吗?文化正在改变页面,我能够确定用户选择了哪种语言,但Url.Action没有生成本地化的URL ..

这是自定义路由,它会更改路由表值(不确定这种标准方式):

public class CultureRoute : Route
{
    public CultureRoute(string url, object defaults, object contraints)
        : base(url, new MvcRouteHandler())
    {
        base.Defaults = CreateRouteValueDictionary(defaults);
        base.Constraints = CreateRouteValueDictionary(contraints);
    }
    public override RouteData GetRouteData(HttpContextBase httpContext)
    {
        var routeData = base.GetRouteData(httpContext);
        if (routeData != null)
        {
            var culture = routeData.Values["culture"].ToString();
            var cookie = httpContext.Request.Cookies["culture"];
            var areEqual = false;
            if (cookie == null || cookie.Value == "" || !(areEqual = string.Equals(culture, cookie.Value, StringComparison.OrdinalIgnoreCase)))
            {
                routeData.Values["culture"] = culture;
                httpContext.Response.Cookies.Add(new HttpCookie("culture", culture));                    
            }
            else if (!areEqual)
            {                    
                routeData.Values["culture"] = cookie.Value;
            }
            CultureHelper.SetCurrentCulture(culture);
        }            
        return routeData;
    }
    private static RouteValueDictionary CreateRouteValueDictionary(object values)
    {
        var dictionary = values as IDictionary<string, object>;
        if (dictionary != null)
        {
            return new RouteValueDictionary(dictionary);
        }
        else
        {
            return new RouteValueDictionary(values);
        }
    }  
}

和这个帮助器类来设置线程文化:

public class CultureHelper
{
    public static void SetCurrentCulture(string culture)
    {
        var info = CultureInfo.CreateSpecificCulture(culture);
        Thread.CurrentThread.CurrentCulture = info;
        Thread.CurrentThread.CurrentUICulture = info;  
    }
    public static string GetCurrentCulture(bool ignoreRouteData = false)
    {
        if (!ignoreRouteData)
        {
            var routeData = HttpContext.Current.Request.RequestContext.RouteData;
            object culture;
            if (routeData.Values.TryGetValue("culture", out culture))
            {
                return culture.ToString();
            }
        }
        var cookie = HttpContext.Current.Request.Cookies["culture"];
        if (cookie != null && cookie.Value != null)
        {
            return cookie.Value;
        }
        return GetThreadCulture();
    }
    public static string GetThreadCulture()
    {
        var culture = Thread.CurrentThread.CurrentCulture.Name;
        if (culture.IndexOf('-') > -1)
        {
            culture = culture.Substring(0, 2);
        }
        return culture;
    }
}

以及RouteConfig类,它从Global.asax调用并使用我的自定义路由类设置路由:

public class RouteConfig
{
    public static void RegisterRoutes(RouteCollection routes)
    {
        routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

        routes.Add("Partial", new CultureRoute(
            "{culture}/{cotroller}/partial/{view}",
            new { culture = "ka", controller = "home", action = "partial", view = "" },
            new { culture = "(ka|en)" }));

        routes.Add("Default", new CultureRoute(
            "{culture}/{controller}/{action}/{id}",
            new { culture = "ka", controller = "home", action = "index", id = UrlParameter.Optional },
            new { culture = "(ka|en)" }));
    }
}

但是没有这种扩展方法,我无法生成基于文化的路由,即Url.Action不会根据自定义路由类创建的路由表生成URL:

public static string Action2(this UrlHelper helper, string action)
{ 
    var culture = CultureHelper.GetThreadCulture();
    return helper.Action(action, new { culture = culture });
}

2 个答案:

答案 0 :(得分:1)

要在ActionLink中构建URL,您还需要覆盖名为GetVirtualPath的反向查找方法。以下是我如何做到这一点的示例(但我继承RouteBase而不是Route,因此您可能需要以不同的方式完成。

    public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
    {
        VirtualPathData result = null;

        if (requestContext.RouteData.IsAreaMatch(this.area))
        {
            var tenant = this.appContext.CurrentTenant;

            // Get all of the pages
            var pages = this.routeUrlPageListFactory.GetRouteUrlPageList(tenant.Id);
            IRouteUrlPageInfo page = null;

            if (this.TryFindMatch(pages, values, out page))
            {
                result = new VirtualPathData(this, page.VirtualPath);
            }
        }

        return result;
    }

    private bool TryFindMatch(IEnumerable<IRouteUrlPageInfo> pages, RouteValueDictionary values, out IRouteUrlPageInfo page)
    {
        page = null;
        Guid contentId = Guid.Empty;

        var action = Convert.ToString(values["action"]);
        var controller = Convert.ToString(values["controller"]);
        var localeId = (int?)values["localeId"];

        if (localeId == null)
        {
            return false;
        }

        if (Guid.TryParse(Convert.ToString(values["id"]), out contentId) && action == "Index")
        {
            page = pages
                .Where(x => x.ContentId.Equals(contentId) &&
                    x.ContentType.ToString().Equals(controller, StringComparison.InvariantCultureIgnoreCase))
                .Where(x => x.LocaleId.Equals(localeId))
                .FirstOrDefault();

            if (page != null)
            {
                return true;
            }
        }

        return false;
    }

我发现因为我的几条路线需要本地化,而且我在内部使用CultureInfo.LCID而不是文化字符串来识别文化,所以最好将文化解析代码放在Application_BeginRequest中Global.asax中的事件。但如果您仅在内部使用文化字符串,则可能没有必要。

顺便说一句 - 我不认为在你的情况下使用cookie是必要的,因为文化可以直接从URL派生。这似乎是不必要的开销,特别是当您考虑在每个请求(包括图像和javascript文件)上传输cookie时。更不用说这样做的安全隐患 - 你至少应该加密cookie中的值。 Here is an example显示了如何正确清理Cookie数据。

答案 1 :(得分:0)

实际上是导致错误行为的其他因素。我有一个扩展方法,它生成切换语言的操作,并修改了路由数据。

public static string CultureRoute(this UrlHelper helper, string culture = "ka") 
{
    var values = helper.RequestContext.RouteData.Values;
    string actionName = values["action"].ToString();
    if (values.ContainsKey("culture"))
    {
        values["culture"] = culture;
    }
    else
    {
        values.Add("culture", culture);
    }
    return helper.Action(actionName, HttpContext.Current.Request.QueryString.ToRouteValues());
} 

我把它改成了它并且有效:

public static string CultureRoute(this UrlHelper helper, string culture = "ka") 
{
    var values = helper.RequestContext.RouteData.Values;
    string actionName = values["action"].ToString();
    return helper.Action(actionName, new { culture = culture });
}

我不需要覆盖GetVirtualPath方法才能让它发挥作用..我认为大部分时间我们都可以使用Route类并且只是覆盖GetRouteData,但是喜欢听别人的想法...