Copied to clipboard

Flag this post as spam?

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


  • Andrew 23 posts 156 karma points
    Mar 07, 2023 @ 14:35
    Andrew
    0

    Currency Switching in Vendr

    Hi

    In our current Umbraco solution we set the region by hooking into the request pipeline, inspecting the url culture segment (www.thissite.com/en-gb/, /en-us/ etc) then setting the current culture and UI culture.

    private void SetRequestContextCulture(HttpContextBase httpContextBase)
        {
            var cultureCookie = httpContextBase.Request.Cookies[Cookies.CultureId];
            if (cultureCookie == null)
            {
                return;
            }
    
            var cultureInfo = new CultureInfo(cultureCookie.Value);
            System.Threading.Thread.CurrentThread.CurrentCulture = cultureInfo;
            System.Threading.Thread.CurrentThread.CurrentUICulture = cultureInfo;
        }
    

    As part of this region switch I want to also ensure that the default currency is set for Vendr, to ensure that customers are getting the correct currency for their region. I also need to clear the current cart, as products differ between cultures.

    I have written the following code to do this.

    public void SetCurrentCulture()
        {
            var currentRequestCulture = _cultureService.GetCultureSuffix();
            if (_sessionManager.GetDefaultCurrency(_storeId).CultureName
                .Equals(currentRequestCulture, StringComparison.CurrentCultureIgnoreCase))
            {
                return;
            }
    
            _sessionManager.ClearCurrentOrder(_storeId);
    
            var currencies = _currencyService.GetCurrencies(_storeId);
            var currentCurrency = currencies.FirstOrDefault(currency => currency.CultureName.Equals(
                _cultureService.GetCultureSuffix(),
                StringComparison.CurrentCultureIgnoreCase));
    
            _sessionManager.SetDefaultCurrency(_storeId, currentCurrency, true);
        }
    

    Unfortunately, the code used to get the storeId is causing a runtime error; Attempt to create a scoped instance without a current scope.

    _storeId = contextFactory
                .EnsureUmbracoContext()
                .UmbracoContext
                .Content
                .GetAtRoot(false)
                .OfType<PimRoot>()
                .FirstOrDefault()
                .GetStore()
                .Id;
    

    I presume this is because on app startup, we are attempting to inject the umbraco context before it has been created.

    Do you know of any way to easily set the currency on culture/language change? I can potentially trigger the currency logic when my basket is created, but I'm still unsure how I'd clear the basket on the culture switch.

    Any help would be appreciated.

    Thanks

    Andrew

  • Matt Brailsford 4124 posts 22215 karma points MVP 9x c-trib
    Mar 07, 2023 @ 14:39
    Matt Brailsford
    0

    Hey Andrew,

    Can you share the full stack trace so I can see what is actually erroring? Also, can you show more code around the _storeId such as how you are getting the contextFactory?

  • Andrew 23 posts 156 karma points
    Mar 07, 2023 @ 14:57
    Andrew
    0

    Hi Matt

    Thanks for the swift reply.

    I may have been wrong with the assumption that it's the umbraco context, I have narrowed it down to being the ISessionManager in the service one level above. Code is here:

        public class ShopRegionService : IShopRegionService
    {
        private readonly ICurrencyService _currencyService;
        private readonly ISessionManager _sessionManager;
    
        private readonly Guid _storeId;
    
        public ShopRegionService(ICurrencyService currencyService, IShopService shopService, ISessionManager sessionManager)
        {
            _currencyService = currencyService;
            _sessionManager = sessionManager;
            _storeId = shopService.StoreId;
        }
    
        public void SetCurrentCulture(string cultureId)
        {
            if (_sessionManager.GetDefaultCurrency(_storeId).CultureName
                .Equals(cultureId, StringComparison.CurrentCultureIgnoreCase))
            {
                return;
            }
    
            _sessionManager.ClearCurrentOrder(_storeId);
    
            var currencies = _currencyService.GetCurrencies(_storeId);
            var currentCurrency = currencies.FirstOrDefault(currency => currency.CultureName.Equals(
                cultureId,
                StringComparison.CurrentCultureIgnoreCase));
    
            _sessionManager.SetDefaultCurrency(_storeId, currentCurrency, true);
        }
    }
    

    Code for the _storeId call (I have currently commented this out and returning guid.empty to find that the ISessionManager was causing issues)

    public class VendrShopService : IShopService
    {
        private readonly Guid _storeId;
    
        public Guid StoreId => _storeId;
    
        public VendrShopService(IUmbracoContextFactory contextFactory)
        {
            _storeId = contextFactory
                .EnsureUmbracoContext()
                .UmbracoContext
                .Content
                .GetAtRoot(false)
                .OfType<PimRoot>()
                .FirstOrDefault()
                .GetStore()
                .Id;
        }
    
    }
    

    Stack trace is in comment below as I can't get it to format properly. It doesn't explicitly mention the class, but when I remove the ISessionManager injection call it works fine.

  • Andrew 23 posts 156 karma points
    Mar 07, 2023 @ 14:58
    Andrew
    0

    Boot failed: Umbraco cannot run. See Umbraco's log file for more details.

    -> Umbraco.Core.Exceptions.BootFailedException: Boot failed.

    -> System.InvalidOperationException: Unable to resolve type: Umbraco.Core.Composing.ComponentCollection, service name: at LightInject.ServiceContainer.CreateDelegate(Type serviceType, String serviceName, Boolean throwError) in C:\projects\lightinject\src\LightInject\LightInject.cs:line 4748 at LightInject.ServiceContainer.CreateDefaultDelegate(Type serviceType, Boolean throwError) in C:\projects\lightinject\src\LightInject\LightInject.cs:line 4705 at LightInject.ServiceContainer.GetInstance(Type serviceType) in C:\projects\lightinject\src\LightInject\LightInject.cs:line 3437 at Umbraco.Core.FactoryExtensions.GetInstance[T](IFactory factory) in D:\a\1\s\src\Umbraco.Core\FactoryExtensions.cs:line 23 at Umbraco.Core.Runtime.CoreRuntime.Boot(IRegister register, DisposableTimer timer) in D:\a\1\s\src\Umbraco.Core\Runtime\CoreRuntime.cs:line 218

    -> System.InvalidOperationException: Unable to resolve type: bd2.Umbraco.Web.Components.ApplicationEventsComponent, service name: at LightInject.ServiceContainer.CreateDelegate(Type serviceType, String serviceName, Boolean throwError) in C:\projects\lightinject\src\LightInject\LightInject.cs:line 4748 at LightInject.ServiceContainer.CreateDefaultDelegate(Type serviceType, Boolean throwError) in C:\projects\lightinject\src\LightInject\LightInject.cs:line 4705 at LightInject.ServiceContainer.GetInstance(Type serviceType) in C:\projects\lightinject\src\LightInject\LightInject.cs:line 3437 at Umbraco.Core.Composing.ComponentCollectionBuilder.CreateItem(IFactory factory, Type itemType) in D:\a\1\s\src\Umbraco.Core\Composing\ComponentCollectionBuilder.cs:line 33 at System.Linq.Enumerable.WhereSelectArrayIterator2.MoveNext() at System.Linq.Buffer1..ctor(IEnumerable1 source) at System.Linq.Enumerable.ToArray[TSource](IEnumerable1 source) at Umbraco.Core.Composing.ComponentCollectionBuilder.CreateItems(IFactory factory) in D:\a\1\s\src\Umbraco.Core\Composing\ComponentCollectionBuilder.cs:line 25 at Umbraco.Core.Composing.CollectionBuilderBase3.CreateCollection(IFactory factory) in D:\a\1\s\src\Umbraco.Core\Composing\CollectionBuilderBase.cs:line 120 at LightInject.PerContainerLifetime.GetInstance(Func1 createInstance, Scope scope) in C:\projects\lightinject\src\LightInject\LightInject.cs:line 6169 at LightInject.ServiceContainer.EmitLifetime(ServiceRegistration serviceRegistration, Action1 emitMethod, IEmitter emitter) in C:\projects\lightinject\src\LightInject\LightInject.cs:line 4656 at LightInject.ServiceContainer.<>c__DisplayClass153_0.<CreateEmitMethodWrapper>b__0(IEmitter ms) in C:\projects\lightinject\src\LightInject\LightInject.cs:line 3856 at LightInject.ServiceContainer.CreateDynamicMethodDelegate(Action1 serviceEmitter) in C:\projects\lightinject\src\LightInject\LightInject.cs:line 3777 at LightInject.ServiceContainer.CreateDelegate(Type serviceType, String serviceName, Boolean throwError) in C:\projects\lightinject\src\LightInject\LightInject.cs:line 4743

    -> System.InvalidOperationException: Attempt to create a scoped instance without a current scope. at LightInject.PerScopeLifetime.GetInstance(Func1 createInstance, Scope scope) in C:\projects\lightinject\src\LightInject\LightInject.cs:line 6257 at DynamicMethod(Object[] ) at LightInject.PerContainerLifetime.GetInstance(Func1 createInstance, Scope scope) in C:\projects\lightinject\src\LightInject\LightInject.cs:line 6169 at LightInject.ServiceContainer.EmitLifetime(ServiceRegistration serviceRegistration, Action1 emitMethod, IEmitter emitter) in C:\projects\lightinject\src\LightInject\LightInject.cs:line 4656 at LightInject.ServiceContainer.<>c__DisplayClass153_0.<CreateEmitMethodWrapper>b__0(IEmitter ms) in C:\projects\lightinject\src\LightInject\LightInject.cs:line 3856 at LightInject.ServiceContainer.CreateDynamicMethodDelegate(Action1 serviceEmitter) in C:\projects\lightinject\src\LightInject\LightInject.cs:line 3777 at LightInject.ServiceContainer.CreateDelegate(Type serviceType, String serviceName, Boolean throwError) in C:\projects\lightinject\src\LightInject\LightInject.cs:line 4743

  • Matt Brailsford 4124 posts 22215 karma points MVP 9x c-trib
    Mar 07, 2023 @ 15:26
    Matt Brailsford
    0

    Hmm, are you registering your dependencies in a component? Can you share your component code as it looks like it's the component collection builder failing 🤔

  • Matt Brailsford 4124 posts 22215 karma points MVP 9x c-trib
    Mar 07, 2023 @ 15:31
    Matt Brailsford
    0

    Ahh, actually, I likely know what the issue is.

    The problem is the session manager is registered as a scoped dependency, but my guess is your services are probably registered as singletons and you can't resolved a scoped dependency in a singleton.

    You could either try registering your services as a scoped dependency also, or, you'd have to create methods that can accept the current session manager, or you could try injecting the SessionManagerAccessor and try accessing the session manager from there (this will only work when within a web session though, if you run your service in a background task or something, it won't be able to resolve the session manager either).

  • Andrew 23 posts 156 karma points
    Mar 08, 2023 @ 11:42
    Andrew
    0

    So we're not registering any of the classes as singletons, everything is the default of transient. I've tried changing the default transient registration to scoped for the relevant classes in the chain but still getting the same error.

    However, injecting the SessionManagerAccessor has worked, and I'm able to access the properties of it.

    Unfortunately, the setting of the currency isn't saving. I'm using the following code, and when I put a breakpoint on "var newCurrency =" it still shows the default of "en-GB" rather than being set to "en-US" (I have stepped through this to ensure the correct currency is being passed to SetDefaultCurrency). No errors are being thrown.

    public class ShopRegionService : IShopRegionService
    {
        private readonly ICurrencyService _currencyService;
        private readonly SessionManagerAccessor  _sessionManagerAccessor;
    
        private readonly Guid _storeId;
    
        public ShopRegionService(ICurrencyService currencyService, IShopService shopService, SessionManagerAccessor  sessionManager)
        {
            _currencyService = currencyService;
            _sessionManagerAccessor = sessionManager;
            _storeId = shopService.StoreId;
        }
    
        public void SetCurrentCulture(string cultureId)
        {
            var sessionManager = _sessionManagerAccessor.Instance;
    
            var currentDefaultCurrency = sessionManager.GetDefaultCurrency(_storeId).CultureName;
    
            if (currentDefaultCurrency.Equals(cultureId, StringComparison.CurrentCultureIgnoreCase))
            {
                return;
            }
    
            sessionManager.ClearCurrentOrder(_storeId);
    
            var currencies = _currencyService.GetCurrencies(_storeId);
            var currentCurrency = currencies.FirstOrDefault(currency => currency.CultureName.Equals(
                cultureId,
                StringComparison.CurrentCultureIgnoreCase));
    
            sessionManager.SetDefaultCurrency(_storeId, currentCurrency, false);
    
            var newCurrency = sessionManager.GetDefaultCurrency(_storeId);
        }
    }
    

    Below is the currency I am trying to set

    enter image description here

    I have made sure that the currencies are set up correctly in the back office, with the relevant culture attached.

  • Andrew 23 posts 156 karma points
    Mar 08, 2023 @ 11:42
    Andrew
    0

    This is the currency directly after trying to set USD

    enter image description here

  • Matt Brailsford 4124 posts 22215 karma points MVP 9x c-trib
    Mar 08, 2023 @ 11:51
    Matt Brailsford
    0

    Hmmm, I'll have to double check if we are reading from the response cookie first as those values are stored in a cookie so it's whether it's reading from the request rather than the response at that point 🤔

  • Matt Brailsford 4124 posts 22215 karma points MVP 9x c-trib
    Mar 08, 2023 @ 11:54
    Matt Brailsford
    100

    Ahh, just checking the code for setting the default currency actually and it will check whether the default payment country is set and that the currency is allowed by that country so it may be that it's not being set because the default payment country isn't set / it's set to a country that doesn't allow that currency.

  • Andrew 23 posts 156 karma points
    Mar 08, 2023 @ 12:43
    Andrew
    0

    Thanks Matt, I'll investigate this now.

  • Andrew 23 posts 156 karma points
    Mar 08, 2023 @ 13:53
    Andrew
    0

    That was the issue! The payment country was only being set much later in the process near order finalisation so it was always the default of en-GB here.

    I've rearranged the method logic to go Culture -> Country -> Default Currency, rather than making the direct link from Culture -> Currency.

    This makes more sense generally, and allows me to set the default payment country and be sure that the currency being set will match.

    As always I greatly appreciate all the assistance, thanks.

    Full class below for anyone else who may encounter a similar issue.

    public class ShopRegionService : IShopRegionService
    {
        private readonly ICurrencyService _currencyService;
        private readonly ICountryService _countryService;
        private readonly SessionManagerAccessor _sessionManagerAccessor;
    
        private readonly Guid _storeId;
    
        public ShopRegionService(ICurrencyService currencyService,
            IShopService shopService,
            ICountryService countryService,
            SessionManagerAccessor sessionManager)
        {
            _currencyService = currencyService;
            _sessionManagerAccessor = sessionManager;
            _countryService = countryService;
            _storeId = shopService.StoreId;
        }
    
        public void SetCurrentCulture(string cultureId)
        {
            var cultureInfo = new CultureInfo(cultureId);
            SetCurrentCulture(cultureInfo);
        }
    
        public void SetCurrentCulture(CultureInfo cultureInfo)
        {
            var region = new RegionInfo(cultureInfo.LCID);
            SetCurrentCulture(region);
        }
    
        private void SetCurrentCulture(RegionInfo regionInfo)
        {
            var sessionManager = _sessionManagerAccessor.Instance;
    
            var countries = _countryService.GetCountries(_storeId);
            var cultureCountry = countries.FirstOrDefault(country =>
                country.Code.Equals(regionInfo.TwoLetterISORegionName, StringComparison.CurrentCultureIgnoreCase));
    
            if (cultureCountry?.DefaultCurrencyId == null || cultureCountry?.DefaultCurrencyId.HasValue == false)
            {
                //No currency for this country. Leave as the default currency
                return;
            }
    
            if (sessionManager.GetDefaultPaymentCountry(_storeId) == cultureCountry)
            {
                //Already on the correct country
                return;
            }
    
            //Only clear the order once we've checked the culture will be switched.
            sessionManager.ClearCurrentOrder(_storeId);
    
            var currency = _currencyService.GetCurrency(cultureCountry.DefaultCurrencyId.Value);
            sessionManager.SetDefaultPaymentCountry(_storeId, cultureCountry);
            sessionManager.SetDefaultCurrency(_storeId, currency);
        }
    }
    
  • Matt Brailsford 4124 posts 22215 karma points MVP 9x c-trib
    Mar 08, 2023 @ 14:17
    Matt Brailsford
    1

    Awesome! Glad we could get things up and working 👍

Please Sign in or register to post replies

Write your reply to:

Draft