1

I'm working on adding globalization/localization to a fairly large scale application. Everything is complete except for the views themselves. My approach so far is to add a global action that every view can call to get localized strings. The problem I am having is that I can't seem to create such a global action.

So far, I added an action to my regular controller... and I can hit it and everything works. However, when I move this method into the BaseController the action is not hit anymore and I get the error:

No route in the route table matches the supplied values.

So, BaseController method isn't working for me.

Next, I tried creating a new global controller outside of the "Areas" that any view should be able to call. This doesn't work either... I can't hit the action.

Here's what the structure looks like:

enter image description here

As you can see there are a number of areas which all must have access to the controller I created in the blue highlighted Controllers folder.

Here is my call from the view:

 @Html.Action("GetLocalizedString", new { key = "Avatar" })

And here is my action:

[GET("getlocalizedstring")]
public ActionResult GetLocalizedString(string key)
{
    return Content(ResourceController.GetResourceManger(Identity)[key]);
}

Again, this works on the controller which rendered the view, but I can't call it from a controller outside of the area or the basecontroller. I even tried adding area = string.empty to get rid of the area property, but still no luck.

EDIT 1 (Adding Route Config):

private void RegisterMVCRoutes()
{
    try
    {
        Application.Lock();

        RouteTable.Routes.Clear();

        System.Web.Http.GlobalConfiguration.Configure(c => { c.EnableCors(); c.MapHttpAttributeRoutes(); });

        RouteTable.Routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
        RouteTable.Routes.IgnoreRoute("{*allaxd}", new { allaxd = @".*\.axd(/.*)?" });

        // bring back MiniProfilers
        if (MiniProfiler.Settings.ProfilerProvider != null)
            StackExchange.Profiling.UI.MiniProfilerHandler.RegisterRoutes();

        RouteTable.Routes.MapAttributeRoutes(config =>
        {
            config.AddRoutesFromAssembly(System.Reflection.Assembly.GetExecutingAssembly());
            config.UseLowercaseRoutes = true;
        });

        AreaRegistration.RegisterAllAreas();

        RegisterGlobalFilters(GlobalFilters.Filters);

        int aspNetRoutingTableEntries = Components.PageRouteEngine.GetInstance().Initialize(RouteTable.Routes);

        // Page Routes for Design Mode
        if (Environment == Constants.SystemEnvironment.Staging || Environment == Constants.SystemEnvironment.RD)
        {
            RouteTable.Routes.Add(new CMSPageRoute("{*designUrlForAddColumn}", new { area = "CMS", controller = "Designer", action = "AddColumn" }, new { designUrlForAddColumn = @".*\.design/AddColumn(/.*)?" }, "CMS"));
            RouteTable.Routes.Add(new CMSPageRoute("{*designUrlForAddRowAbove}", new { area = "CMS", controller = "Designer", action = "AddRowAbove" }, new { designUrlForAddRowAbove = @".*\.design/AddRowAbove(/.*)?" }, "CMS"));
            RouteTable.Routes.Add(new CMSPageRoute("{*designUrlForAddRowAtBottom}", new { area = "CMS", controller = "Designer", action = "AddRowAtBottom" }, new { designUrlForAddRowAtBottom = @".*\.design/AddRowAtBottom(/.*)?" }, "CMS"));
            RouteTable.Routes.Add(new CMSPageRoute("{*designUrlForDeleteRow}", new { area = "CMS", controller = "Designer", action = "DeleteRow" }, new { designUrlForDeleteRow = @".*\.design/DeleteRow(/.*)?" }, "CMS"));
            RouteTable.Routes.Add(new CMSPageRoute("{*designUrlForUpdateUseFullWidthForRow}", new { area = "CMS", controller = "Designer", action = "UpdateUseFullWidthForRow" }, new { designUrlForUpdateUseFullWidthForRow = @".*\.design/UpdateUseFullWidthForRow(/.*)?" }, "CMS"));
            RouteTable.Routes.Add(new CMSPageRoute("{*designUrlForSaveMovedWidget}", new { area = "CMS", controller = "Designer", action = "SaveMovedWidget" }, new { designUrlForSaveMovedWidget = @".*\.design/SaveMovedWidget(/.*)?" }, "CMS"));
            RouteTable.Routes.Add(new CMSPageRoute("{*designUrlForAddNewWidget}", new { area = "CMS", controller = "Designer", action = "AddNewWidget" }, new { designUrlForAddNewWidget = @".*\.design/AddNewWidget(/.*)?" }, "CMS"));
            RouteTable.Routes.Add(new CMSPageRoute("{*designUrlForDeleteWidget}", new { area = "CMS", controller = "Designer", action = "DeleteWidget" }, new { designUrlForDeleteWidget = @".*\.design/DeleteWidget(/.*)?" }, "CMS"));
            RouteTable.Routes.Add(new CMSPageRoute("{*designUrlForMergeColumn}", new { area = "CMS", controller = "Designer", action = "MergeColumn" }, new { designUrlForMergeColumn = @".*\.design/MergeColumn(/.*)?" }, "CMS"));
            RouteTable.Routes.Add(new CMSPageRoute("{*designUrl}", new { area = "CMS", controller = "Designer", action = "Index" }, new { designUrl = @".*\.design(/.*)?" }, "CMS"));
        }

        // Page Routes... Page Index will also be a 404 catch all for routes not found.
        RouteTable.Routes.Add(new CMSPageRoute("{*url}", new { area = "CMS", controller = "Page", action = "Index" }, "CMS"));
    }
    finally
    {
        Application.UnLock();
    }
}

I think most of our route configuration is application specific only to us, but maybe it will help. Thanks again.

BrianLegg
  • 1,658
  • 3
  • 21
  • 36
  • What does your route config look like? – jao Feb 17 '17 at 19:12
  • Side note: why regular auto-generated resource string access classes not working for you? – Alexei Levenkov Feb 17 '17 at 19:24
  • @jao - Our route config is pretty large, but nothing out of the ordinary really. I will update and post momentarily. – BrianLegg Feb 17 '17 at 19:31
  • @Alexei - We are using a DB driven approach so we don't have any resource Resx files if that is what you are referring to. – BrianLegg Feb 17 '17 at 19:32
  • It is hard to come up with an answer other than the fact that you are asking this question is a clear indication that the application has made many wrong turns. There are only a handful of places where it might make sense to put localization code. The controller is definitely not one of them. There is [no good reason to have a base controller](http://stackoverflow.com/a/6119341/181087) - and you found yet another reason not to do it. – NightOwl888 Feb 17 '17 at 20:32
  • In the past I have had some success localizing views by using a ModelMetadataProvider [similar to this approach](https://github.com/eric-b/Localization). – NightOwl888 Feb 17 '17 at 20:39
  • @NightOwl - Thanks for the comment, I'll look into the metadata provider. I'm not new to MVC but this is my first attempt at globalization. I accept that I may be making some mistakes. – BrianLegg Feb 17 '17 at 20:41
  • 1
    @BrianLegg - No problem. The fact that you want to move the localization to a single place is a good thing. But that one place should be a *service* not a controller action method. That service can then be injected into the various parts of the application where it is needed (including the ModelMetadataProvider). It helps a lot to be using Dependency Injection to get this done quickly and easily, but it might be a bit much to ask to change an existing application to use it if it is not. – NightOwl888 Feb 17 '17 at 20:54

1 Answers1

0

I ended up using an approach similar to the one suggested by NightOwl888. Instead of calling the controller via @Html.Action() I wrote an extension method and call it via @Html.Translate(). This pulls the "translating" code into a single central location and does not require me to modify any of the controllers. Because we store the users language default on the web identity of the user I had to get this from the HtmlHelper object instead of relying on my base controller. Here is the code I ended up using - hopefully it will help someone else.

    public static class ExtensionMethods
    {
        public static IHtmlString Translate(this HtmlHelper helper, string text)
        {
            if (string.IsNullOrWhiteSpace(text))
                return new HtmlString(string.Empty);

            var identity = (WebIdentity)helper.ViewContext.HttpContext.User.Identity;

            return new HtmlString(ResourceController.GetResourceManger(identity.Learner.SystemLanguageId)[text]);
        }
    }
BrianLegg
  • 1,658
  • 3
  • 21
  • 36