views:

55

answers:

2

Hello,

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

+1  A: 

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.

statichippo
@statichippo- 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
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.
statichippo
+3  A: 

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 L.
@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
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.
statichippo
@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 L.
@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 L.