Guruprasad Rao Guruprasad Rao - 3 months ago 27
C# Question

Fetching timezone based on user logged in with httpcontext asp.net mvc

In the application am working on, We have an option for each user to select their own

timezone
and when displaying data for the particular user, we are fetching
timezone
opted by him and display accordingly. Now as per the answer mentioned here, which is really an awesome one, I went on implementing the mentioned options, i.e. conversion of date at
model
level, and I have done it as below:

NotificationViewModel.cs

public class NotificationViewModel
{

public string Text{ get; set; }
public DateTime Moment
{
get
{
return _Created;
}
set
{
_Created = Repository.GetUserTimeZoneDateTime(value);
}
}
private DateTime _Created { get; set; }
public string Icon { get; set; }
}


Repository.cs


GetUserTimeZoneDateTime
has 2 overloads

public static DateTime GetUserTimeZoneDateTime(DateTime dTime)
{
using (var context = new EntityContext())
{
var tZone = context.tbl_usrs.AsNoTracking().FirstOrDefault(x => x.uname == HttpContext.Current.User.Identity.Name).preferred_timezone;
var tZoneInfo = TimeZoneInfo.FindSystemTimeZoneById(tZone);
return TimeZoneInfo.ConvertTimeFromUtc(dTime, tZoneInfo);
}
}

public static DateTime GetUserTimeZoneDateTime(EntityContext context, DateTime dTime)
{
var tZone = context.tbl_usrs.AsNoTracking().FirstOrDefault(x => x.uname == HttpContext.Current.User.Identity.Name).preferred_timezone;
var tZoneInfo = TimeZoneInfo.FindSystemTimeZoneById(tZone);
return TimeZoneInfo.ConvertTimeFromUtc(dTime, tZoneInfo);
}


In the above case first overload will be called, but then, when called from
model
level,
HttpContext.Current
will be
null
and hence it fails.




In the 2nd approach, I tried, the
timezone
will be fetched from controller level.

NotificationViewModel.cs

public class NotificationViewModel
{
public string Text { get; set; }
public DateTime Moment { get; set; }
public string Icon { get; set; }
}


TestController.cs

using (var context = new EntityContext())
{
var localTime = Repository.GetUserTimeZoneDateTime(context, DateTime.UtcNow);
List<NotificationViewModel> model = new List<NotificationViewModel>();
int days = DateTime.UtcNow.DayOfWeek - DayOfWeek.Sunday;
DateTime weekStart = localTime.AddDays(-days);
DateTime weekEnd = weekStart.AddDays(6);
var p = context.tbl_prchs
.Where(x => x.c_date <= weekEnd && x.c_date >= weekStart)
.Select(x => new NotificationViewModel()
{
Icon = "fa fa-gbp",
Moment = Repository.GetUserTimeZoneDateTime(context,x.c_date),
Text = "Test notes",
}).ToList();
model.AddRange(p);
}


var localTime = Repository.GetUserTimeZoneDateTime(context, DateTime.UtcNow);
fetches proper
datetime
according to the preferred user
timezone
. But then
Moment= Repository.GetUserTimeZoneDateTime(context,x.c_date),
inside
linq expression
throws error as below


LINQ to Entities does not recognize the method 'System.DateTime
GetUserTimeZoneDateTime(Direct_Commercial_Van.Models.EntityDataModel.dcvEntities,
System.DateTime)' method, and this method cannot be translated into a
store expression.


which is expected. What else options I can try here to achieve this? or how in other ways I can handle timezone issue here?

Answer

In the first case, you would need to inject HttpContext (or User.Identity.Name) into the model (view a constructor), and from there, into the method via an additional parameter in the method (all very messy and not recommended).

In the second case, you would need to materialize the query first before your .Select()

var p = context.tbl_prchs
    .Where(...)
    .ToList() // materialize query in in-memory set
    .Select(x => new NotificationViewModel()
    {
    }).ToList();

However you code is very inefficient because you calling the database (via the GetUserTimeZoneDateTime() method) for each row your query is returning. You should change you code to get the TimeZoneInfo before calling your query.

// Get the time zone info
var tZone = context.tbl_usrs.AsNoTracking().......'
var tZoneInfo = TimeZoneInfo.FindSystemTimeZoneById(tZone);
// Generate the view model
var p = context.tbl_prchs
    .Where(...)
    .ToList() // materialize query in in-memory set
    .Select(x => new NotificationViewModel()
    {
        ....
        Moment = TimeZoneInfo.ConvertTimeFromUtc(x.c_date, tZoneInfo);
    }).ToList();

Alternatively, if you do not want to materialize the query first, you could inject the TimeZoneInfo into your view model and modify the Moment to a calculated property.

public class NotificationViewModel
{
    public string Text { get; set; }
    public DateTime CDate { get; set; }
    public string Icon { get; set; }
    public TimeZoneInfo TimeZoneInfo { get; set; }
    public DateTime Moment
    {
        get { return TimeZoneInfo.ConvertTimeFromUtc(CDate, TimeZoneInfo); }
    }
}

and then the query would be

....
var tZoneInfo = TimeZoneInfo.FindSystemTimeZoneById(tZone);
var p = context.tbl_prchs
    .Where(...)
    .Select(x => new NotificationViewModel()
    {
        ....
        CDate = x.c_date,
        TimeZoneInfo = tZoneInfo
    }).ToList();
Comments