-
Notifications
You must be signed in to change notification settings - Fork 2.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix the User TimeZoneService #16942
Fix the User TimeZoneService #16942
Conversation
src/OrchardCore.Modules/OrchardCore.Users/TimeZone/Services/IUserTimeZoneService.cs
Outdated
Show resolved
Hide resolved
src/OrchardCore.Modules/OrchardCore.Users/TimeZone/Services/IUserTimeZoneService.cs
Show resolved
Hide resolved
FYI I just wrote a comment here to allow @KeithRaven to chime again on his PR after there are a few comments later I suggest closing this and letting Keith finish his PR unless he doesn't have time. I hope you understand what I mean @MikeAlhayek |
@hishamco, I am looking at that PR now. That PR does not fix the problem this PR fixes. |
I never mind creating another PR, at least we need to know the status of the previous PR, and why it's closed. Could the author responds to the feedback |
Sorry I was going to sort out #15796 however it was mentioned that another issue needed resolving first #15796 (comment). This PR looks to be covering the same thing and more though, so makes sense to keep it in a single change set, so happy to leave the other closed. |
@deanmarcussen I believe this will fix your issue. However, we @sebastienros and I have two different opinions on the implementation. We need your opinion to determine the final implementation before we merge. @sebastienros suggest that we remove both Personally, I think it is okay to keep Either approaches are valid for me with no strong opinion. The question here, would you keep or remove both If we decide to remove public class UserTimeZoneSelector : UserEventHandlerBase, ITimeZoneSelector
{
private const string CacheKey = "UserTimeZone/";
private const string EmptyTimeZone = "NoTimeZoneFound";
private static readonly DistributedCacheEntryOptions _slidingExpiration = new()
{
SlidingExpiration = TimeSpan.FromHours(1),
};
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly IClock _clock;
private readonly IDistributedCache _distributedCache;
private readonly UserManager<IUser> _userManager;
public UserTimeZoneSelector(
IHttpContextAccessor httpContextAccessor,
IClock clock,
IDistributedCache distributedCache,
UserManager<IUser> userManager)
{
_httpContextAccessor = httpContextAccessor;
_clock = clock;
_distributedCache = distributedCache;
_userManager = userManager;
}
public Task<TimeZoneSelectorResult> GetTimeZoneAsync()
{
var result = !_httpContextAccessor.HttpContext.User.Identity.IsAuthenticated
? null
: new TimeZoneSelectorResult
{
Priority = 100,
TimeZoneId = async () =>
{
var timeZone = await GetAsync();
return timeZone?.TimeZoneId;
},
};
return Task.FromResult(result);
}
public override Task DeletedAsync(UserDeleteContext context)
=> ForgetCacheAsync(context.User.UserName);
public override Task UpdatedAsync(UserUpdateContext context)
=> ForgetCacheAsync(context.User.UserName);
public override Task DisabledAsync(UserContext context)
=> ForgetCacheAsync(context.User.UserName);
private async ValueTask<ITimeZone> GetAsync()
{
var currentTimeZoneId = await GetTimeZoneIdAsync();
if (string.IsNullOrEmpty(currentTimeZoneId))
{
return null;
}
return _clock.GetTimeZone(currentTimeZoneId);
}
private async ValueTask<string> GetTimeZoneIdAsync()
{
var principal = _httpContextAccessor.HttpContext.User;
var userName = principal.Identity.Name;
var key = GetCacheKey(userName);
var timeZoneId = await _distributedCache.GetStringAsync(key);
// The timeZone is not cached yet, resolve it and store the value.
if (string.IsNullOrEmpty(timeZoneId))
{
// At this point, we know the timeZoneId is not cached for the given userName.
// Retrieve the user and cache the timeZoneId.
var user = await _userManager.GetUserAsync(principal);
if (user is User u)
{
timeZoneId = u.As<UserTimeZone>()?.TimeZoneId;
}
// We store a placeholder string to indicate that there is no specific value for this user.
// This approach ensures compatibility with distributed cache implementations that may not support null values.
// Caching this placeholder helps avoid redundant queries for this user on each request when no time zone is set.
if (string.IsNullOrEmpty(timeZoneId))
{
timeZoneId = EmptyTimeZone;
}
await _distributedCache.SetStringAsync(key, timeZoneId, _slidingExpiration);
}
// If TimeZoneId matches the placeholder value, we return null instead of the placeholder itself.
if (timeZoneId == EmptyTimeZone)
{
return null;
}
return timeZoneId;
}
private Task ForgetCacheAsync(string userName)
{
var key = GetCacheKey(userName);
return _distributedCache.RemoveAsync(key);
}
private static string GetCacheKey(string userName)
=> CacheKey + userName;
} |
I don't have a massively strong opinion either way. We used the service because it was there, and largely because we needed to be able to differentiate between user time, site time, and utc, particularly being able to provide those values to javascript. Because we work in a very time distributed world, we need to present to users 2-3 date times to users, to save sanity. So realistically the timezone selector option doesn't work as ideally for us, as its results don't specify exactly what that timezone refers to. I can register it manually, and run it manually. Mostly I've opened tickets about this service, to improve them for the community, and if the service goes away, then we will just adopt it internally, and manage it outselves. I personally think it's a useful service, and concept (anyone implementing it themselves will probably forget to cache it, and drop their performance down, without knowing why) |
@sebastienros it sounds to me that @deanmarcussen is also leaning toward keeping the service. Unless you have a strong opinion on removing the service and move it into the time zone selector, I suggest we leave it. |
Cool then @MikeAlhayek you can merge. I wondered if we should keep |
Fix #16932