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 605 posts 2403 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 6150 posts 24110 karma points MVP 8x 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 605 posts 2403 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 605 posts 2403 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 202 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 605 posts 2403 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.

Please Sign in or register to post replies

Write your reply to:

Draft