23

Similar questions have been asked before but not quite the same (unless I missed it)

I want to pass IUserInfo class instance through my Service, Domain , Domain Events, Domain Event Handlers...

Whats is the best way to do it.

Should I

  • Inject it using IoC by registering it against instance of Httpcontext.Current.session["CurrentUser"];

  • Add the data to Current Thread.

  • Any other way

I am stuck at Domain Event Handlers where I want to use the data for auditing as well as sending emails.

I want to be able to use the CurrentUser information from almost anywhere in my application.

With threading as threads are pooled I am skeptical if the reuse of threads will reset the data. If not please shopw me how to use threading to pass IUser instance.

Regards,

Mar

TheMar
  • 1,934
  • 2
  • 30
  • 51
  • I just wanted to point out that there is a little overlap with my question that I've asked more recently about allowing Repositories to be aware of user context. I've been trying to resolve this issue along with the issue of using the user context to access other user context sensitive values. http://stackoverflow.com/questions/5374176/can-ddd-repositories-be-aware-of-user-context – jpierson Apr 22 '11 at 02:53

2 Answers2

16

My approach might not be ideal, but I find it working quite well. Thing what I did - I decided not to use dependency injection pass current user everywhere directly because that was getting too cumbersome and switched to static context. Problem with contexts - they are a bit difficult to manage.

This one is defined in my domain:

public static class UserContext{
  private static Func<User> _getCurrentUser;
  private static bool _initialized;
  public static User Current{
    get{
      if(!_initialized) 
        throw new Exception("Can i haz getCurrentUser delegate?");
      var user=_getCurrentUser();
      return user??User.Anonymous;
    }
  }
  public static void Initialize(Func<User> getCurrentUser){
    _getCurrentUser=getCurrentUser;
    _initialized=true;
  }
}

Note that delegate is static - for whole app only one at a time. And I'm not 100% sure about it's life cycle, possible memory leaks or whatnot.

Client application is responsible to initialize context. My web application does that on every request:

public class UserContextTask:BootstrapperTask{
 private readonly IUserSession _userSession;
 public UserContextTask(IUserSession userSession){
   Guard.AgainstNull(userSession);
   _userSession=userSession;
 }
 public override TaskContinuation Execute(){
   UserContext.Initialize(()=>_userSession.GetCurrentUser());
   return TaskContinuation.Continue;
 }
}

Using mvcextensions library to stream-line bootstrapping tasks. You can just subscribe for according events in global.asax for that.

In client side (web app), I implement application service named IUserSession:

public User GetCurrentUser(){
  if(HttpContext.Current.User==null) return null;
  var identity=HttpContext.Current.User.Identity;
  if(!identity.IsAuthenticated) return null;
  var user=_repository.ByUserName(identity.Name);
  if(user==null) throw new Exception("User not found. It should be. Looks bad.");
  return user;
}

There is some more lame code necessary in order to use forms auth with roles w/o membership provider and role provider. But that's not the point of this question.

At domain level - I'm explicitly describing permissions that users might have like this one:

public class AcceptApplications:IUserRights{
  public bool IsSatisfiedBy(User u){
    return u.IsInAnyRole(Role.JTS,Role.Secretary);
  }
  public void CheckRightsFor(User u){
    if(!IsSatisfiedBy(u)) throw new ApplicationException
      ("User is not authorized to accept applications.");
  }
}

Cool thing is - those permissions can be made more sophisticated. E.g.:

public class FillQualityAssessment:IUserRights{
  private readonly Application _application;
  public FillQualityAssessment(Application application){
    Guard.AgainstNull(application,
      "User rights check failed. Application not specified.");
    _application=application;
  }
  public bool IsSatisfiedBy(User u){
    return u.IsInRole(Role.Assessor)&&_application.Assessors.Contains(u);
  }
  public void CheckRightsFor(User u){
    if(!IsSatisfiedBy(u))
      throw new ApplicationException
        ("User is not authorized to fill quality assessment.");
    }
  }

Permissions can be checked vica versa too - User has these fellas:

public virtual bool HasRightsTo<T>(T authorizationSpec) where T:IUserRights{
  return authorizationSpec.IsSatisfiedBy(this);
}
public virtual void CheckRightsFor<T>(T authorizationSpec) where T:IUserRights{
  authorizationSpec.CheckRightsFor(this);
}

Here's my aggregate root base class:

public class Root:Entity,IRoot{
  public virtual void Authorize(IUserRights rights){
    UserContext.Current.CheckRightsFor(rights);
  }
}

And here's how I check permissions:

public class Application{
  public virtual void Accept(){
    Authorize(new AcceptApplications());
    OpeningStatus=OpeningStatus.Accepted;
  }
}

I hope that helps...

Arnis Lapsa
  • 45,880
  • 29
  • 115
  • 195
  • 1
    @Arnis- Thanks for the detailed example.Looking at the code I am kind of inclined towards the IoC way for readability concern and not have to thinks of maintaining the static class. I am still going to give it a try and see in action. – TheMar Oct 19 '10 at 16:56
  • I think this could be simplified a bit but still would suffer from dependence on HttpContext which means it's not very testable. By using IoC you can use HttpContext for the website but pass in whatever you want for testing. IMO this is a huge plus. – hackerhasid Oct 19 '10 at 16:58
  • @statichippo there is inversion of control in my example. passing delegate to `Initialize` function is IoC. Context itself does not know anything how it is going to get current user. UserSession implementation sits in web application, domain is free from technical concerns. It is testable - I just need to initialize it with what I need. Only bad thing is - it's static and I can't see from top if anything will use UserContext. – Arnis Lapsa Oct 19 '10 at 18:24
  • @statichippo but that was the whole idea - to avoid passing current user everywhere directly, paying with implicit dependency on UserContext just like everywhere (cause You don't know what sits underneath). – Arnis Lapsa Oct 19 '10 at 18:32
  • 2
    @Arnis- I really like your exceptions messages! – jeremy-george Jun 15 '11 at 16:00
  • @Arnis - I like your approach, I am looking into codecampserver, they use a similar approach, I think I am going to use something similar - Implement an interface with IUserSession (held inside my domain), use the IUserSession throughout my domain and in the client (asp.net mvc) inject the domain from the container into my IKernal... I hope that works - what do you think? :p – Haroon Jun 16 '11 at 07:04
  • @Haroon in general - that's what I'm doing. static user context in domain model that is fed from outside. btw, my approach is influenced from CodeCampServer too. – Arnis Lapsa Jun 16 '11 at 09:11
  • @Arnis, thanks for your help - I finally got this working with ninject, my implementation is a lot more simpler than yours, but it works :) I am comtemplating if I should add roles to my IUserSession, but I am doing that @ action level anyway, I think for Get Requests it would be best to validate roles on action level and post with your method, what do you do? – Haroon Jun 23 '11 at 10:09
  • @Haroon yes... for GET`s I'm usually decorating action methods. – Arnis Lapsa Jun 23 '11 at 14:55
  • This is almost exactly what I did in my last application, and it worked well. I was able to share a business layer between an MVC app and WinForms app no problem. I think I'm going to just use normal IoC container like StructureMap for the next version though. :) – Samantha Branham Jul 25 '13 at 15:49
5

I've done this kind of thing before using IoC. The benefit of this is that it's very testable -- you can stub out your user info for testing -- and reasonably readable and easy to follow.

hackerhasid
  • 11,699
  • 10
  • 42
  • 60
  • Thank you. I will wait a day more to see if anyone else have got other ideas. For the IoC way were you storing it in Httpcontext.Current.session["CurrentUser"] first? – TheMar Oct 19 '10 at 04:13
  • 1
    I wouldn't store it in the Session but rather in the HttpContext.Items. Session is persisted but HttpContext.Items exists only for the lifetime of the request so it's perfect for things like this. – hackerhasid Oct 19 '10 at 16:56
  • 6
    I've probably misunderstood something, but how can you access HttpContext from all the layers, when, for example, domain mustn't know anything about HttpContext at all? – vorou Mar 28 '13 at 13:54