Rookian Rookian - 2 months ago 20
ASP.NET (C#) Question

Creating an "Ambient Context" (UserContext) for an ASP.NET application using a static factory Func<T>

I have found out that I need the current logged in user data in nearly every class (controllers, view, HTML helpers, services and so on). So I thought about to create an "Ambient Context" instead of injecting an IUserService or the User directly.

My approach looks something like that.

public class Bootstrapper
{
public void Boot()
{
var container = new Container();
// the call to IUserService.GetUser is cached per Http request
// by using a dynamic proxy caching mechanism, that also handles cases where we want to
// invalidate a cache within an Http request
UserContext.ConfigureUser = container.GetInstance<IUserService>().GetUser;
}
}

public interface IUserService
{
User GetUser();
}

public class User
{
string Name { get; set; }
}

public class UserContext : AbstractFactoryBase<User>
{
public static Func<User> ConfigureUser = NotConfigured;

public static User ActiveUser { get { return ConfigureUser(); } }
}

public class AbstractFactoryBase<T>
{
protected static T NotConfigured()
{
throw new Exception(String.Format("{0} is not configured", typeof(T).Name));
}
}


Example usage:

public class Controller
{
public ActionResult Index()
{
var activeUser = UserContext.ActiveUser;
return View();
}
}


Is my approach correct or do I missing something? Do you have better solutions in mind?

UPDATE:

More Detail of the User class:

public class User
{
string Name { get; set; }
bool IsSuperUser { get; set;}
IEnumerable<AzManOperation> Operations { get; set}
}


In Controllers we need to check if an User is a SuperUser to only provide the SuperUser some extra functionality.

public class BaseController : Controller
{
private readonly IUserService _userService;

BaseControler(IUserService userService)
{
_userService = userService
}

public User ActiveUser
{
get { return _userService.GetUser(); }
}
}


In Views we check Operations to only show an edit or delete button if the user has the right to do so. A view never uses the DependencyResolver, but ViewBag or ViewModel. My idea here is to implementing a custom ViewBasePage and providing an ActiveUser property, so that Views have an easy accesss.

In HtmlHelpers we render controls depending on IsSuperUser and Operations (passing in the User object or using DependencyResolver).

In Service Classes we need those properties too. For instance to decide if a basket is valid or not (check if the User is allowed to buy articles that are not in a standard list). So the Service class depends on
IUserService
and calling
GetUser()
.

In Action Filters to force the user to change his password (only if it is not a SuperUser and User.ForcePasswordChange is true). Here we use the DependencyResolver.

My wish is to have a more easily way to get the User object, instead of using DependencyResolver.Current.GetService().GetUser() or using things like
ViewBag.ActiveUser = User
.
The User object is an object that is almost everywhere needed to check permissions or the like.

Answer

In Views we check Operations to only show an edit or delete button if the user has the right to do so.

The view should not do this check. The Controller should return a view model to the view that contains boolean properties that state whether those buttons should be visible. Returning a bool with IsSuperUser already moves to much knownledge into the view. The view shouldn't know that it should show a certain button for a super user: that's up to the controller. The view should only be told what to display.

If almost all views have this code, there are ways to extract repetitive parts out of your views, for instance with partial views. If you're finding yourself repeating those properties over many view models, perhaps you should define an envelope view model (a generic view model that wraps the specific model as T). A controller can create its view model, while you create a service or cross-cutting concern that wraps it in your envelope.

In Service Classes we need those properties too. For instance to decide if a basket is valid or not

In this case you are talking about validation, which is a cross-cutting concern. You should use decorators to add this behavior instead.

Comments