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.
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.
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?
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.
-> 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
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 🤔
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).
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
I have made sure that the currencies are set up correctly in the back office, with the relevant culture attached.
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 🤔
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.
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);
}
}
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.
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.
Unfortunately, the code used to get the storeId is causing a runtime error; Attempt to create a scoped instance without a current scope.
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
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 thecontextFactory
?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:
Code for the _storeId call (I have currently commented this out and returning guid.empty to find that the ISessionManager was causing issues)
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.
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.WhereSelectArrayIterator
2.MoveNext() at System.Linq.Buffer
1..ctor(IEnumerable1 source) at System.Linq.Enumerable.ToArray[TSource](IEnumerable
1 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(Func
1 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(Action
1 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(Func
1 createInstance, Scope scope) in C:\projects\lightinject\src\LightInject\LightInject.cs:line 6257 at DynamicMethod(Object[] ) at LightInject.PerContainerLifetime.GetInstance(Func
1 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(Action
1 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 4743Hmm, 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 🤔
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).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.
Below is the currency I am trying to set
I have made sure that the currencies are set up correctly in the back office, with the relevant culture attached.
This is the currency directly after trying to set USD
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 🤔
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.
Thanks Matt, I'll investigate this now.
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.
Awesome! Glad we could get things up and working 👍
is working on a reply...