Custom routing with language attribute in URL for MVC .net site
Asked Answered
H

2

5

I have a site which requires localization into a number of different languages. To achieve this I followed the tutorial here https://www.ryadel.com/en/setup-a-multi-language-website-using-asp-net-mvc/ In my route config I have :

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

Currently my language switcher works like this, for every available language I take the current URL and create a link with the appropriate language slug i.e. en-US or ru-RU etc

<div class="dropdown-menu" aria-labelledby="dropdownMenuLink">
        @foreach (CultureViewModel culture in Global.EnabledCultures)
        {
            <li><span class="Language dropdown-item" title="@culture.DisplayName">@Html.Raw(Url.LangSwitcher(culture.DisplayName, ViewContext.RouteData, culture.Name))</span></li>
        }
    </div>

Where Url.LangSwitcher is an Extension of UrlHelper

public static string LangSwitcher(this UrlHelper url, string name, RouteData routeData, string lang, bool isMainMenu = false)
{
    var action = (routeData.Values["action"] ?? "").ToString().ToLower();
    var controller = (routeData.Values["controller"] ?? "").ToString().ToLower();
    var requestContext = HttpContext.Current.Request.RequestContext;
    string link = "";

    // We need to create a unique URL for the current page for each of the enabled languages for this portal
    // If the lang variable is specified for current URL we need to substitute with the incoming lang variable
    //we need to duplicate the RouteData object and use the duplicate for URL creation as
    //changing the value of lang in RouteData object passed to this function changes the current culture of the request

        RouteData localValues = new RouteData();
        foreach (KeyValuePair<string, object> var in routeData.Values)
        {
            if (var.Key != "lang")
            {
                localValues.Values.Add(var.Key, var.Value);
            }
        }
    localValues.Values.Add("lang", lang);
    link = "<a href = \"" + new UrlHelper(requestContext).Action(action, controller, localValues.Values) + "\" >";


    string img = "<img src=\"/Content/images/Flags/" + lang + ".gif\" alt = \"" + lang + "\"> " + name;
    string closeTags = "</a>";

    return link + img + closeTags;
}

so it takes the current URL and switches out the language slug and outputs a link for the menu we are creating.

This all works fine on links that follow the standard {lang}/{controller}/{action}/{id} pattern

however, I want to be able to create custom links with attributes on the controllers like so:

[Route("brokers/this/is/a/custom/url")]
        public ActionResult CompareBrokers(int page = 1)
        {

so when I try to access this route from a view like so:

@Url.Action("CompareBrokers", "Brokers")

the URL that is generated is like this:

https://localhost:44342/brokers/this/is/a/custom/url?lang=en-US

where I want it to be like this

https://localhost:44342/en-US/brokers/this/is/a/custom/url

Any advice on how I can achieve what I want given my current set up?

UPDATE putting [Route("{lang}/brokers/this/is/a/custom/url")] as my attribute has some limited success, it will work as long as there is a lang variable in the current URL, so if I am on http://site.url/en-US then the links get created correctly, but if I am on http://site.url they do not.

Ive tried putting 2 attributes in my controller:

[Route("brokers/this/is/a/custom/url")]
[Route("{lang}/brokers/this/is/a/custom/url")]

but it just uses the first one

UPDATE 2

Following advice in the comments I used the attribute:

[Route("{lang=en-GB}/brokers/this/is/a/custom/url")]

and it works perfectly, my links get generated correctly, however I need to be able to accommodate the default localization without the lang var in the URL

Hays answered 2/5, 2018 at 8:14 Comment(6)
have you tried with the attribute [Route("{lang}/brokers/this/is/a/custom/url")] ?Selfconceit
Actually, yes it does, but only if I have the language part of the slug in the URL, I wonder how I can get it to work without the slug so that I dont have to supply if for default language (english) If I try to put 2 routes, one with and one without the lang slug, then it defaults to the first oneHays
You would use the attribute [Route("{lang=en-US}/brokers/this/is/a/custom/url")] But you would have to hard code the default langage in every custom routeSelfconceit
I cant do that because one of the stipulations is that the default localization does not have the lang var in the URLHays
Try with [Route("{lang}/brokers/this/is/a/custom/url", Order = 1)] [Route("brokers/this/is/a/custom/url", Order = 2)] That way the router first tries to match the route with {lang}, if the lang parameter isn't supplied, it fallsback on the route without langSelfconceit
That looks like it does it. seems to work exactly as I want it now. IF you put your comments into an answer Ill accept them as the answer and the bounty is yoursHays
S
6

If you put two attributes with the order paramter, the routing will first try to match the first route with the lang parameter, and if the lang parameter is not supplied it will fallback on the second route.

[Route("{lang}/brokers/this/is/a/custom/url", Order = 1)]
[Route("brokers/this/is/a/custom/url", Order =  2)]
public ActionResult CompareBrokers(int page = 1)
{
}

UPDATE : To use the 2nd route when lang is the default language you could remove lang from the routedata by adding in your actionFilter or controllerActivator :

if (filterContext.RouteData.Values["lang"]?.ToString() == _DefaultLanguage)
{
    filterContext.RouteData.Values.Remove("lang");
}
Selfconceit answered 4/5, 2018 at 11:44 Comment(2)
just one slight issue remains, how can I get it to select the top one when the language is non default, and the bottom one when it is the default language, so that it does not include the 'en-GB' var in the URLHays
One way to achieve that would be to remove lang from the routeData when the language is the default.Selfconceit
S
0

This is what worked for me one month later after i gave up and went to use normal routing / route config files, Please note that I'm using Areas and the area name here is "Merchant"

Normal link:

https://localhost:44364/En/Merchant/Financial/TransactionDetails/55

To open it as a Custom link like:

https://localhost:44364/En/Merchant/Financial/Transactions/Transaction/Details/55

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Web.Mvc;
    
    namespace GHLogistics.Areas.Merchant.Controllers
    {
        [RouteArea("{lang}")]
        [RoutePrefix("Merchant")]
        public class FinancialController : MerchantBaseController
        {
            [Route("Financial/Transactions/Transaction/Details/{Id}")]
            public ActionResult TransactionDetails(long Id)
            {
                return Content("TransactionDetails" + " " + Id.ToString());

                // or
                //pass relative path of view file, pass model
               //return View("~/Areas/Merchant/Views/Financial/TransactionDetails.cshtml",transactions);
            }
        }
    }

MerchantAreaRegistration.cs file

context.MapRoute(
"Merchant_default",
url: "{lang}/Merchant/{controller}/{action}/{id}",
constraints: new { lang = @"(\w{2})|(\w{2}-\w{2})" },
defaults: new { lang = "en", controller = "Home", action = "Index", id = UrlParameter.Optional },
namespaces: new[] { "GHLogistics.Areas.Merchant.Controllers" }
);

RouteConfig.cs file

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

            routes.MapRoute(
                name: "Default",
                url: "{lang}/{controller}/{action}/{id}",
                constraints: new { lang = @"(\w{2})|(\w{2}-\w{2})" },
                defaults: new { lang = "en", controller = "Home", action = "Index", id = UrlParameter.Optional },
                namespaces: new[] { "GHLogistics.Controllers" }
            );
        }
    }
}
Selfrighteous answered 7/10, 2019 at 12:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.