1

I have be configuring localisation for a website that is already published in ASP.NET MVC 5.

The guide I was following to integrate localisation into my site is from codeproject and I found it quite good: https://www.codeproject.com/Articles/1095786/Route-friendly-localization-of-ASP-NET-MVC

Now to explain the issue, whenever I load the homepage index http://www.example.com/ with no route path it will redirect to the correct language http://www.example.com/en which is the desired result.

My website urls are in the format of http://www.example.com/controller/action. Whenever I visit http://www.example.com/controller/ it will now redirect me back to http://www.example.com/en rather than http://www.example.com/en/controller/index. And when I try typing http://www.example.com/controller/action it throws a 404 error rather than http://www.example.com/en/controller/action.

I think the issue is in the GetLocalistedUrl function in the LocalizationHelper.cs file.

    public static string GetLocalisedUrl(Uri initialUri, IList<string> controllersNames, IList<string> userLangs)
    {
        var res = string.Empty;

        var supportedLocales = GetSupportedLocales();

        var origUrl = initialUri;

        // Dicide requested url to parts
        var cleanedSegments = origUrl.Segments.Select(X => X.Replace("/", "")).ToList();

        // Check is already supported locale defined in route
        // cleanedSegments[0] is empty string, so lang parameter will be in [1] url segment
        var isLocaleDefined = cleanedSegments.Count > 1 && supportedLocales.Contains(cleanedSegments[1]);

        // does request need to be changed
        var isRequestPathToHandle =
            // Url has controller's name part
            (cleanedSegments.Count > 1 && cleanedSegments.Intersect(controllersNames).Count() > 0) ||
            // This condition is for default (initial) route
            (cleanedSegments.Count == 1) ||
            // initial route with lang parameter that is not supported -> need to change it
            (cleanedSegments.Count == 2 && !supportedLocales.Contains(cleanedSegments[1]));     

        if (!isLocaleDefined && isRequestPathToHandle)
        {
            var langVal = "";
            // Get user preferred language from Accept-Language header
            if (userLangs != null && userLangs.Count > 0)
            {
                // For our locale name approach we'll take only first part of lang-locale definition
                var splitted = userLangs[0].Split(new char[] { '-' });
                langVal = splitted[0];
            }

            // If we don't support requested language - then redirect to requested page with default language
            if (!supportedLocales.Contains(langVal))
                langVal = supportedLocales[0];

            var normalisedPathAndQuery = origUrl.PathAndQuery;
            if ((cleanedSegments.Count > 2 &&
                !controllersNames.Contains(cleanedSegments[1]) &&
                controllersNames.Contains(cleanedSegments[2])) ||
                (cleanedSegments.Count == 2) && (!controllersNames.Contains(cleanedSegments[1])))
            {
                // Second segment contains lang parameter, third segment contains controller name
                cleanedSegments.RemoveAt(1);

                // Remove wrong locale name from initial Uri
                normalisedPathAndQuery = string.Join("/", cleanedSegments) + origUrl.Query;
            }

            // Finally, create new uri with language loocale
            res = string.Format("{0}://{1}:{2}/{3}{4}", origUrl.Scheme, origUrl.Host, origUrl.Port, langVal.ToLower(), normalisedPathAndQuery);
        }

        return res;
    }

The function is called in the IHttpModule extension that was created.

public class LangQueryAppenderModule : IHttpModule
{
    /// <summary>
    /// List of supported locales
    /// </summary>
    private readonly IList<string> _supportedLocales;

    /// <summary>
    /// We need to have controllers list to correctly handle situations
    /// when target method name is missed
    /// </summary>
    private readonly IList<string> _controllersNamesList;

    public LangQueryAppenderModule()
    {
        // Get list of supported locales 
        _supportedLocales = LocalizationHelper.GetSupportedLocales();

        // Get controllers list of current project by reflection
        var asmPath = HttpContext.Current.Server.MapPath("~/bin/Central.dll");
        Assembly asm = Assembly.LoadFile(asmPath);

        var controllerTypes = asm.GetTypes()
            .Where(type => typeof(Controller).IsAssignableFrom(type));
        _controllersNamesList = new List<string>();

        foreach (var controllerType in controllerTypes)
        {
            var fullName = controllerType.Name;

            // We need only name part of Controller class that is used in route
            _controllersNamesList.Add(fullName.Substring(0, fullName.Length - "Controller".Length));
        }
    }

    // In the Init function, register for HttpApplication 
    // events by adding your handlers.
    public void Init(HttpApplication application)
    {
        application.BeginRequest += (new EventHandler(this.Application_BeginRequest));
    }

    private void Application_BeginRequest(Object source, EventArgs e)
    {
        try
        {
            HttpApplication app = (HttpApplication)source;
            HttpContext ctx = app.Context;

            // We will redirect to url with defined locale only in case for HTTP GET verb
            // cause we assume that all requests with other verbs will be called from site directly
            // where all the urls created with URLHelper, so it complies with routing rules and will contain "lang" parameter
            if (string.Equals(ctx.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase))
            {
                var localisedUri = LocalizationHelper.GetLocalisedUrl(ctx.Request.Url, _controllersNamesList, ctx.Request.UserLanguages);
              if (!string.IsNullOrEmpty(localisedUri))
                    // Perform redirect action to changed url if it exists
                    ctx.Response.Redirect(localisedUri);
            }
        }
        catch (Exception)
        {
            // Some logging logic could be here
        }
    }

    public void Dispose() { }

}
marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Corona
  • 375
  • 5
  • 21
  • While this approach might work, it is definitely not the MVC way. You should use routing (not HTTP modules) to localize your URLs because it works for both incoming routes and generating URLs for your views. See [ASP.NET MVC 5 culture in route and url](http://stackoverflow.com/a/32839796/181087). If you want to go beyond that and translate your URLs into different languages, have a look at [RouteLocalization](https://github.com/Dresel/RouteLocalization). – NightOwl888 Apr 30 '17 at 21:19

1 Answers1

0

I fixed the problem, because some of my hyperlinks had caps I had to then use string.ToLower() on any controller name comparisons to ensure it found a matching controller.

  public static string GetLocalisedUrl(Uri initialUri, IList<string> controllersNames, IList<string> userLangs)
    {
        var res = string.Empty;

        var supportedLocales = GetSupportedLocales();

        var origUrl = initialUri;

        // Dicide requested url to parts
        var cleanedSegments = origUrl.Segments.Select(X => X.Replace("/", "")).ToList();

        // Check is already supported locale defined in route
        // cleanedSegments[0] is empty string, so lang parameter will be in [1] url segment
        var isLocaleDefined = cleanedSegments.Count > 1 && supportedLocales.Contains(cleanedSegments[1]);

        cleanedSegments = cleanedSegments.ConvertAll(d => d.ToLower());

        // does request need to be changed
        var isRequestPathToHandle =
            // Url has controller's name part
            (cleanedSegments.Count > 1 && cleanedSegments.Intersect(controllersNames).Count() > 0) ||
            // This condition is for default (initial) route
            (cleanedSegments.Count == 1) ||
            // initial route with lang parameter that is not supported -> need to change it
            (cleanedSegments.Count == 2 && !supportedLocales.Contains(cleanedSegments[1]));     

        if (!isLocaleDefined && isRequestPathToHandle)
        {
            var langVal = "";
            // Get user preferred language from Accept-Language header
            if (userLangs != null && userLangs.Count > 0)
            {
                // For our locale name approach we'll take only first part of lang-locale definition
                var splitted = userLangs[0].Split(new char[] { '-' });
                langVal = splitted[0];
            }

            // If we don't support requested language - then redirect to requested page with default language
            if (!supportedLocales.Contains(langVal))
                langVal = supportedLocales[0];

            var normalisedPathAndQuery = origUrl.PathAndQuery;
            if ((cleanedSegments.Count > 2 &&
                !controllersNames.Contains(cleanedSegments[1].ToLower()) &&
                controllersNames.Contains(cleanedSegments[2].ToLower())) ||
                (cleanedSegments.Count == 2) && (!controllersNames.Contains(cleanedSegments[1])))
            {
                // Second segment contains lang parameter, third segment contains controller name
                cleanedSegments.RemoveAt(1);

                // Remove wrong locale name from initial Uri
                normalisedPathAndQuery = string.Join("/", cleanedSegments) + origUrl.Query;
            }

            // Finally, create new uri with language loocale
            res = string.Format("{0}://{1}:{2}/{3}{4}", origUrl.Scheme, origUrl.Host, origUrl.Port, langVal.ToLower(), normalisedPathAndQuery.ToLower());
        }

        return res;
    }
marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Corona
  • 375
  • 5
  • 21