Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Language-specific Default URL using MVC's Attribute Routing and RouteLocalization.mvc

I would like to be able to create a succinct language-specific default URL for my website so that when someone browses to:

somesite.com

They get redirected to a language-culture page such as:

  • somesite.com/en-US/
  • somesite.com/sp-MX/
  • somesite.com/fr-FR/

Specifically, I do not want /Home/Index appended to the URLs:

  • somesite.com/en-US/Home/Index
  • somesite.com/sp-MX/Home/Index
  • somesite.com/fr-FR/Home/Index

I am committed to making this site using RouteLocalization.mvc

  • Dresel/RouteLocalization
  • Translating Your ASP.NET MVC Routes

And I would like to use MVC Attribute Routing to the extent feasible.

I am having trouble figuring out how to cause the Start() method redirect to a language-culture specific URL without the addition of something like "index".

Samples of what I have attempted follow:

using RouteLocalization.Mvc;
using RouteLocalization.Mvc.Extensions;
using RouteLocalization.Mvc.Setup;

public class RouteConfig
{
    public static void RegisterRoutes(RouteCollection routes)
    {
        routes.Clear();

        routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

        routes.MapMvcAttributeRoutes(Localization.LocalizationDirectRouteProvider);

        const string en = "en-us";
        ISet<string> acceptedCultures = new HashSet<string>() { en, "de", "fr", "es", "it" };

        routes.Localization(configuration =>
        {
            configuration.DefaultCulture = en;
            configuration.AcceptedCultures = acceptedCultures;
            configuration.AttributeRouteProcessing = AttributeRouteProcessing.AddAsNeutralAndDefaultCultureRoute;
            configuration.AddCultureAsRoutePrefix = true;
            configuration.AddTranslationToSimiliarUrls = true;
        }).TranslateInitialAttributeRoutes().Translate(localization =>
        {
            localization.AddRoutesTranslation();
        });

        CultureSensitiveHttpModule.GetCultureFromHttpContextDelegate = Localization.DetectCultureFromBrowserUserLanguages(acceptedCultures, en);

        var defaultCulture = System.Threading.Thread.CurrentThread.CurrentUICulture.Name;

        routes.MapRoute(
            name: "DefaultLocalized",
            url: "{culture}/{controller}/{action}/{id}",
            constraints: new { culture = @"(\w{2})|(\w{2}-\w{2})" }, 
            defaults: new { culture = defaultCulture, controller = "Home", action = "Index", id = UrlParameter.Optional }
        );

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

And my home controller:

public class HomeController : Controller
{
    [HttpGet]
    [Route]
    public RedirectToRouteResult Start()
    {
        return RedirectToAction("Home", new { culture = Thread.CurrentThread.CurrentCulture.Name });
    }

    [Route("Index", Name = "Home.Index")]
    public ActionResult Index()
    {
        return View();
    }

    public ActionResult Contact()
    {
        return View();
    }

    public ActionResult About()
    {
        return View();
    }
}

My Global.asax file:

public class MvcApplication : System.Web.HttpApplication
{
    protected void Application_Start()
    {            
        FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
        RouteConfig.RegisterRoutes(RouteTable.Routes);
        AreaRegistration.RegisterAllAreas();
    }
}
like image 882
SqlDataboy Avatar asked Mar 13 '23 07:03

SqlDataboy


1 Answers

Redirecting is a separate concern than routing. Since your goal of redirecting any URL to its localized counterpart is a cross-cutting concern your best bet is to make a global filter.

public class RedirectToUserLanguageFilter : IActionFilter
{
    private readonly string defaultCulture;
    private readonly IEnumerable<string> supportedCultures;

    public RedirectToUserLanguageFilter(string defaultCulture, IEnumerable<string> supportedCultures)
    {
        if (string.IsNullOrEmpty(defaultCulture))
            throw new ArgumentNullException("defaultCulture");
        if (supportedCultures == null || !supportedCultures.Any())
            throw new ArgumentNullException("supportedCultures");

        this.defaultCulture = defaultCulture;
        this.supportedCultures = supportedCultures;
    }

    public void OnActionExecuting(ActionExecutingContext filterContext)
    {
        var routeValues = filterContext.RequestContext.RouteData.Values;

        // If there is no value for culture, redirect
        if (routeValues != null && !routeValues.ContainsKey("culture"))
        {
            string culture = this.defaultCulture;
            var userLanguages = filterContext.HttpContext.Request.UserLanguages;
            if (userLanguages.Length > 0)
            {
                foreach (string language in userLanguages.SelectMany(x => x.Split(';')))
                {
                    // Check whether language is supported before setting it.
                    if (supportedCultures.Contains(language))
                    {
                        culture = language;
                        break;
                    }
                }
            }

            // Add the culture to the route values
            routeValues.Add("culture", culture);

            filterContext.Result = new RedirectToRouteResult(routeValues);
        }
    }

    public void OnActionExecuted(ActionExecutedContext filterContext)
    {
        // Do nothing
    }
}

Usage

public class FilterConfig
{
    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        filters.Add(new RedirectToUserLanguageFilter("en", new string[] { "en", "de", "fr", "es", "it" }));
        filters.Add(new HandleErrorAttribute());
    }
}

Note also that your routing is misconfigured. The route setup is run one time per application startup, so setting the default culture to that of the current thread is meaningless. In fact, you should not be setting a default culture at all for your culture route because you want it to miss so your Default route will execute if there is no culture set.

routes.MapRoute(
    name: "DefaultLocalized",
    url: "{culture}/{controller}/{action}/{id}",
    constraints: new { culture = @"(\w{2})|(\w{2}-\w{2})" },
    defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);

routes.MapRoute(
    name: "Default",
    url: "{controller}/{action}/{id}",
    defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
like image 144
NightOwl888 Avatar answered Apr 27 '23 05:04

NightOwl888