Copied to clipboard

Flag this post as spam?

This post will be reported to the moderators as potential spam to be looked at


  • Bo Jacobsen 438 posts 1818 karma points
    Jul 21, 2017 @ 19:49
    Bo Jacobsen
    0

    async calls make empty dictionary item

    Hi everyone.

    I need some help here.


    using umbraco 7.6.3

    Explanation: When i use async actions like Ajax.BeginForm, the threads cultureinfo is not set and therefor the library.dictionaryItem returns empty (i think).. First time the page load, the translation is there, but when i submit the form. then it returns with brackets [Username] and [Password] ("as it should if the returned DictionaryItem is Null or empty")

    Question: Is there a simple way to set the cultureinfo on async calls? Or is there a workaround? or do i just have to set the cultureinfo on all my models?

    enter image description here enter image description here


    My Model, with my custom DisplayNameAttribute, that uses a library.DictionaryItem.

    public class LoginModel
    {
        [Display("Username")] // Custom DisplayNameAttribute
        public string Username { get; set; }
    
        [Display("Password")] // Custom DisplayNameAttribute
        public string Password { get; set; }
    }
    

    Custom DisplayNameAttribute

    public class Display : DisplayNameAttribute
    {
        private string DictionaryItemKey { get; set; }
    
        public Display(string dictionaryItemKey) : base()
        {
            DictionaryItemKey = dictionaryItemKey;
        }
    
        public override string DisplayName
        {
            get
            {
                // Custom service wish return library.GetDictionaryItem(key)
                return TranslationService.GetDictionaryItem(DictionaryItemKey);
            }
        }
    }
    

    Custom service

    public static class TranslationService
    {
        public static string GetDictionaryItem(string key)
        {
            var dictionaryItem = umbraco.library.GetDictionaryItem(key);
            if (!string.IsNullOrWhiteSpace(dictionaryItem))
            {
                return dictionaryItem;
            }
            else
            {
                return string.Format("[{0}]", key);
            }
        }
    }
    

    SurfaceController

    public class LoginSurfaceController : Umbraco.Web.Mvc.SurfaceController
    {
        [ChildActionOnly]
        [AllowAnonymous]
        public ActionResult GetLogin()
        {
            return PartialView("~/Views/Partials/Models/login.cshtml", ModelHelper.NewLoginModel);
        }
    
        [HttpPost]
        [AllowAnonymous]
        [ValidateAntiForgeryToken]
        // Could also be public async Task<ActionResult> PostLogin(LoginModel model) with an await method.
        public ActionResult PostLogin(LoginModel model)
        {
            ModelState.AddModelError("", "Just to return an error");
            return PartialView("~/Views/Partials/Models/login.cshtml", model);
        }
    }
    

    home.cshtml

    @inherits Umbraco.Web.Mvc.UmbracoTemplatePage
    @{
        Layout = "layout.cshtml";
    
    }
    <div id="parentDiv">
        @{ Html.RenderAction("GetLogin", "LoginSurface"); }
    </div>
    

    PartialView login.cshtml

    @model ServiceApplication.Models.LoginModel
    
    @using (Ajax.BeginForm("PostLogin", "LoginSurface", new AjaxOptions
    {
        InsertionMode = InsertionMode.Replace,
        UpdateTargetId = "parentDiv",
        AllowCache = true,
    }))
    {
        @Html.AntiForgeryToken();
        @Html.ValidationSummary(true)
        <div>
            @Html.LabelFor(m => m.Username)
            @Html.TextBoxFor(m => m.Username)
            @Html.ValidationMessageFor(m => m.Username)
        </div>
        <div>
            @Html.LabelFor(m => m.Password)
            @Html.PasswordFor(m => m.Password)
            @Html.ValidationMessageFor(m => m.Password)
        </div>
        <div>
            <button type="submit" class="btn">Login</button>
        </div>
    }
    
  • Alex Skrypnyk 5908 posts 22603 karma points MVP 4x admin c-trib
    Jul 21, 2017 @ 22:21
    Alex Skrypnyk
    1

    Hi Bo

    I think you have to set culture with this lines of code:

    System.Threading.Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo(cultureCode);
    System.Threading.Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo(cultureCode);
    

    Check out this thread also - https://our.umbraco.org/forum/developers/api-questions/53735-GetDictionaryValue-returns-empty-string#comment-186402

    Thanks,

    Alex

  • Bo Jacobsen 438 posts 1818 karma points
    Jul 22, 2017 @ 19:05
    Bo Jacobsen
    0

    Hi Alex.

    Thanks for your answer.

    I was playing around with a RenderControllerFactory and overrideing of the IController, and your linked post got me closer to a solution. Setting the thread cultureInfo is not the problem, but getting the users last used cultureInfo is. (not the default set by the browser).

    So i am looking for a way to get the current document/context cultureinfo.


    Here is a working scenario using sessions. It's working locally, when i try switching between pages with different language set to them and post the form.

    CultureinfoControllerFactory

    using System.Globalization;
    using System.Web.Mvc;
    using System.Web.Routing;
    using Umbraco.Web.Mvc;
    
    namespace My.WebApplication.Namespace
    {
        public class CultureinfoControllerFactory : RenderControllerFactory
        {
            public override IController CreateController(RequestContext requestContext, string controllerName)
            {
                // Custom service to retrieve the current thread cultureinfo (if any)
                var cultureinfo = ThreadService.CultureInfo;
    
                // Custom service to get the current session that contains the cultureinfo (if set)
                var session = SessionService.GetSession(Constants.Session.CultureInfo);
    
                if (session != null && requestContext.HttpContext.Request.IsAjaxRequest())
                {
                    // Custom service to Set the thread cultureinfo.
                    ThreadService.CultureInfo = session as CultureInfo;
                    ThreadService.UiCultureInfo = session as CultureInfo;
                }
                else if (cultureinfo != null && cultureinfo != session)
                {
                    // Custom service to create or override a session.
                    SessionService.SetSession(Constants.Session.CultureInfo, cultureinfo);
                }
    
                // Returning the base method, since we do not manipulate with it.
                return base.CreateController(requestContext, controllerName);
            }
        }
    }
    

    ApplicationEventHandler

    using System.Web.Mvc;
    using Umbraco.Core;
    
    namespace My.WebApplication.Namespace
    {
        public class FactoryApplicationEvent : ApplicationEventHandler
        {
            protected override void ApplicationStarting(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext)
            {
                ControllerBuilder.Current.SetControllerFactory(typeof(CultureinfoControllerFactory));
            }
        }
    }
    
  • Bo Jacobsen 438 posts 1818 karma points
    Jul 23, 2017 @ 13:29
    Bo Jacobsen
    101

    I made a solution for ajax calls.

    The best solution i could come up with, was to add a meta tag with the cultureinfo set in Umbraco. That way we can intercept all ajax calls and add a HttpRequest header to it. Then create a ControllerFactory to get all Http Request sent to any SurfaceControllers, and look for the custom made header and set the thread cultureinfo if the custom header is received.


    Step 1: Adding a meta html tag to my layout.cshtml in the head section.

     <meta name="cultureinfo" content="@Model.Content.Site().GetCulture()" />
    

    Step 2: Adding a global ajax interceptor.

    <script>
        $(document).ajaxSend(function (event, jqxhr, settings) {
            var metaContent = $('meta[name="cultureinfo"]').attr("content");
            jqxhr.setRequestHeader('set-cultureinfo', metaContent);
        });
    </script>
    

    Step 3: Creating a controller factory.

    using System.Globalization;
    using System.Web.Mvc;
    using System.Web.Routing;
    using Umbraco.Web.Mvc;
    
    namespace My.WebApplication.Namespace
    {
        public class CultureinfoControllerFactory : RenderControllerFactory
        {
            public override IController CreateController(RequestContext requestContext, string controllerName)
            {
                // Constants.HttpHeader.SetCultureInfo = "set-cultureinfo";
    
                // Makeing sure nothing is null, and that the header we need exists.
                if (requestContext != null
                    && requestContext.HttpContext != null
                    && requestContext.HttpContext.Request != null
                    && requestContext.HttpContext.Request.Headers != null
                    && requestContext.HttpContext.Request.Headers[Constants.HttpHeader.SetCultureInfo] != null
                    && !string.IsNullOrEmpty(requestContext.HttpContext.Request.Headers[Constants.HttpHeader.SetCultureInfo]))
                {
                    // Createing a cultureInfo from the set-cultureinfo header value.
                    var cultureInfo = CultureInfo.CreateSpecificCulture(requestContext.HttpContext.Request.Headers[Constants.HttpHeader.SetCultureInfo].ToString());
    
                    System.Threading.Thread.CurrentThread.CurrentCulture = cultureInfo;
                    System.Threading.Thread.CurrentThread.CurrentUICulture = cultureInfo;
                }
    
                // Returning the base method, since we do not manipulate with the create method.
                return base.CreateController(requestContext, controllerName);
            }
        }
    }
    

    Step 4: Adding the controller factory to the builder.

    using System.Web.Mvc;
    using Umbraco.Core;
    
    namespace My.WebApplication.Namespace
    {
        public class FactoryApplicationEvent : ApplicationEventHandler
        {
            protected override void ApplicationStarting(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext)
            {
                ControllerBuilder.Current.SetControllerFactory(typeof(CultureinfoControllerFactory));
            }
        }
    }
    
  • Bendik Engebretsen 105 posts 198 karma points
    Dec 04, 2017 @ 15:38
    Bendik Engebretsen
    0

    Fantastic, Bo!

    Finally, a general solution to the ajax/culture problem:-) This should be in the Umbraco core. For everyone implementing ajax forms in Umbraco sites, the issue is incredibly frustrating. The problem isn't Umbraco's fault, but it would help a lot of developers if this fix was in there.

  • Bo Jacobsen 438 posts 1818 karma points
    Dec 06, 2017 @ 12:50
    Bo Jacobsen
    0

    It would be great if we somehow could find a solution, so we only had to add the global ajax interceptor.

  • This forum is in read-only mode while we transition to the new forum.

    You can continue this topic on the new forum by tapping the "Continue discussion" link below.

Please Sign in or register to post replies