0

I have a question about how to proceed with the following situation:

I have a DDD project where I wanted somehow in the infrastructure layer to save the user data and be able to keep it throughout the session so I can use this data within the infrastructure itself, to make validations of which company this user is.

I created in UnitOfWork that is in the Repository layer that uses my context and maps to the other layers (APP,SERVICE) a User object.

public class UnitOfWork : IUnitOfWork
    {

        private readonly CRMVarejoContexto _db;

        public User _user { get; set;}
public UnitOfWork(CRMVarejoContexto db)
        {
            _db = db;
        }

        public UnitOfWork()
        {
            _db = new CRMVarejoContexto();
 
        }

And in the App layer I created a method passing a User Guid, reaching the UnitOfWork (Repository) and using the following query:

public void LoadLoggedUser(Guid UserId)
       {
           _user = _db.CatchLoggedUser(UserId);
       }

Which in the Context performs a direct query and returns the user:

public User CatchLoggedUser(Guid UserId)
        {
            var rawQuery = Database.SqlQuery<User>("SELECT * FROM dbo.IDENTITYUSER WHERE UserId = '" + UserId + "';");
            var task = rawQuery.FirstOrDefault();

            return task;

        }

My idea was to set this object throughout the session so I could always instantiate this property in UnitOfWork and pull the user's data. What's the best way to do this?

  • 1
    In general it's better to keep the repo a singleton so you can cache expensive resources that are centered around the data source, e.g. if there are domain tables. If this is an ASP.NET app, a common place to put the user information is in [`HttpContext.User`](https://learn.microsoft.com/en-us/dotnet/api/system.web.httpcontext.user?view=netframework-4.8). If it doesn't have a property that you need (e.g. company ID), you can create a [custom principal](https://stackoverflow.com/questions/1064271/asp-net-mvc-set-custom-iidentity-or-iprincipal). – John Wu Jan 26 '22 at 18:26
  • 1
    Well, using session is fine. You BETTER then turn on and use sql server for session management. While this setting is transparent to your code, and does cost a tiny bit of performance? It is the only practical way to ensure solid sessions that persist (even code errors can trigger app-pool restarts). But, be careful in your designs. Say I click on a grid row (to buy a house) and now display house. Of the user has two tabs open, and selects a different house from grid? Now two tabs are open, different houses display, but session information is for ONE house. I now buy house, but wrong one! – Albert D. Kallal Jan 26 '22 at 19:12
  • In other words, since session is global to user, then your designs have to be careful, or at the very least consider what is to occur if the user has two browsers open - they will also share the same session. So, for data PK id values etc, you have to be VERY careful. In fact I often pass a class in session to the target page (with all the info I need - including often database PK values, but on first page load I "transfer" that class to view state. That way, the applcation will work if the user decides to have multiple tabs open, or even launches the browser again. So caution required here – Albert D. Kallal Jan 26 '22 at 19:16

1 Answers1

0

As John mentioned in the comments, it's quite convenient to store this information in HttpContext.User, since it is accessible from all controllers. We have achieved this in our existing application by adding custom claims in the claims transformation stage, which happens before the controller actions are run.

public class SessionClaimsExpander : IClaimsTransformation
{
    private readonly ISessionRepository _sessions;

    public SessionClaimsExpander(ISessionRepository sessions)
    {
        _sessions = sessions;
    }

    public async Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
    {
        var userIdClaim = principal.FindFirst(ClaimTypes.NameIdentifier);
        if (userIdClaim == null || !int.TryParse(userIdClaim.Value, out var userId))
            return principal;
        
        var session = await _sessions.FindAsync(userId);
        if (session == null)
            return principal;

        var sessionIdentity = new ClaimsIdentity(new Claim[]
        {
            new Claim("app:session_id", session.Id.ToString()),
            new Claim("app:session_expiry", session.ExpiryDate.ToString("o")),
            new Claim("app:foo", session.Foo)
        });

        principal.AddIdentity(sessionIdentity);
        return principal;
    }
}

In Startup.cs, register the transformation:

public void ConfigureServices(IServiceCollection services)
{
    // ...
    services.AddScoped<IClaimsTransformation, UserClaimsExpander>();
}
Andrew Williamson
  • 8,299
  • 3
  • 34
  • 62