I am working on an ASP.NET WebForms project, and we need the ability to configure behavior throughout the application based on the current user's "group". This applies to almost all aspects of the application, including site navigation, showing/hiding certain user controls on pages, and executing custom business logic in some cases. However, the vast majority of the application behavior is shared among groups, so we've ruled out the idea of creating entirely separate apps.
Essentially, what I'm looking for is an architectural approach to implementing custom behavior in an ASP.NET WebForms application. Is there a better approach than sprinkling if/else statements throughout the code base in the view layer, the business layer, and the persistence layer?
Edit: Some examples:
If a user in in Group A, their
navigation will consist of all
navigation from Group B plus a few
additional links.
If a user is in Group A, a page will
show user controls c1, c2, and c3.
If the user is in Group B, they will
only see c1 and c3 on the same page.
If a user saves some data on a form
and they are in Group A, send a
notification email. If the user is
in Group B, send a text message
instead.
We can solve all of these specific problems, but are looking for a way to encapsulate this behavior as much as possible so it's not scattered across the code base.
Edit: There are some interesting answers related to dynamically loading user controls. Should the logic to determine which controls to load or which behavior to use based on the user's group be encapsulated in one (non-cohesive) class, e.g.:
GroupManager.GetNavigationControl(int groupId) // loads site nav control based on group
GroupManager.PerformNotification(int groupId) // sends text or email based on group
Or should this logic exist as close as possible to the location in code where it is used, and therefore be spread across the different layers of the code base?
Well there's not a ton of details to go on here, but I would suspect you might benefit from polymorphism (i.e. various interface implementations) to deal with the parts of the application that differ between user groups. An Inversion of Control container like Spring.NET can help you wire up/configure these various implementations together based on the current user role. You might also benefit from Spring's Aspect Oriented Programming API in which you can decorate methods in your business layer/data access layer so that authorization logic can be executed.
By "Groups" do you mean "Roles"? If you're talking about roles, you can set your behavior by doing something like this
If User.IsInRole("SomeRandomRole") Then
'Do some random behavioral crap
ElseIF User.IsInRole("TheCoolRole") Then
'Do some cool behavioral crap
Else
'Do generic crap
End If
Another option might be to use UserControls based on roles. So when you have a page load, it will load a usercontrol based on the role that requested it.
you could have an PlaceHolder sitting empty and call the LoadControl method from the codebehind.
Then all your user controls would match your roles
Role = Admin | UserControl = Admin.ascx
Role = User | UserControl = User.ascx
Without going into too much detail and going on about IoC and all the like, I think I'd keep it pretty simple and have a plain old factory class that you would use to return the appropriate instantiated UI elements [user controls] based on the current user making the request. In doing this, you will have all of your 'if' statements in one single location. To displense with the 'if' statements you could simply create a mapping config file or DB table that contains references to the user controls to use when a user belongs to a particular group.
Note: Both of these options will result in the creation of dynamic controls on the page which is not without its own complications but I have successfully been using dynamic controls in my apps without issue for a while now - it was just a matter of getting down and dirty with the page life-cycle more than I initially felt comfortable with.
You could also inherit from the Principal object to handle this however you would like.
Here's how I have done it in an application that has custom rules like this:
Created my own IPrincipal object that descended from my "Person" object to be able to inherit the ability to look up groups and roles and such:
public class Principal : MyNamespace.Person, IPrincipal {
}
Make the current context use my IPrincipal object:
protected void Application_AuthenticateRequest(Object Sender, EventArgs E) {
if (HttpContext.Current.User != null &&
HttpContext.Current.User.Identity.IsAuthenticated &&
HttpContext.Current.User.Identity is FormsIdentity) {
FormsIdentity id = (FormsIdentity)HttpContext.Current.User.Identity;
HttpContext.Current.User = new MyNamespace.Principal(id);
}
}
I then made static methods so that I didn't have to cast every time I wanted to get the current user like this:
public class CurrentUser {
/// <summary>
/// Is the current user authenticated
/// </summary>
static public bool IsAuthed {
get { return System.Web.HttpContext.Current.User.Identity.IsAuthenticated; }
}
/// <summary>
/// Returns the Principal object in case it is needed. Also used for other static properties of this class.
/// </summary>
static public MyNamespace.Principal User {
get {
return (MyNamespace.Principal)System.Web.HttpContext.Current.User;
}
}
}
Then you can call things like CurrentUser.User.IsInGroup().
Related
What would be the easiest way to get AD user object properties via a webform based on user input?
To elaborate a bit more this is what I would need:
User enters input to an input field (Employee number - we store it as an extension attribute in AD)
On button click the form returns additional user object properties of the account (such as sAMAccountname, Manager) and displays it on the page (preferably)
I also need to have these properties converted to variables the form can use to pass on to another page sending an e-mail with the retrieved information.
We're using asp for our webforms. So far we only needed to pass user input directly to the mail sender, but this one seems more tricky.
Appreciate any help, thanks!
If you're on .NET 3.5 and up, you should check out the System.DirectoryServices.AccountManagement (S.DS.AM) namespace. Read all about it here:
Managing Directory Security Principals in the .NET Framework 3.5
MSDN docs on System.DirectoryServices.AccountManagement
Basically, you can define a domain context and easily find users and/or groups in AD:
// set up domain context
using (PrincipalContext ctx = new PrincipalContext(ContextType.Domain))
{
// find a user
UserPrincipal user = UserPrincipal.FindByIdentity(ctx, "SomeUserName");
if(user != null)
{
// display the various properties of the "user" object in your web page
}
}
The new S.DS.AM makes it really easy to play around with users and groups in AD!
Say I have a page in my web application that lets a user update their contact information. Pretend in order to retrieve or save this information I have the following class:
public class User
{
DataAccesClass dataAccesClass = new DataAccesClass()
public string UserName {get;set;}
public string Address {get;set;}
public string EmailAddress {get;set;}
public User(){}
public static User GetUser(int userID)
{
User user = dataAccesClass.GetUser(userID); //
return user;
}
public void Save()
{
dataAccesClass.SaveUser(this);
}
}
Say that on my Page_Load event I create a new instance of my User class (wrapped in a !isPostBack). I then use it's public properties to populate text fields on said page in my web application. Now the question is... When the page is posted back, what is the correct way to rebuild this class to then save the updated information? Because the class was created on Page_Load !isPostBack event it is not available. What is the correct way to handle this? Should I store it in a Session? ViewState? Should I simply rebuild it every post back? The User class in this example is small so that might influence the correct way to do it but I'd like to be able to take the same approach for much larger and more complex classes. Also, would this class be considered an acceptable business object?
what is the correct way to rebuild this class to then save the updated information?
I would say the best practice would be do not rebuild the class on every postback. You should build the data on the first request, set values on controls, then let the viewstate on those controls persist the data.
If there is a potential for the data to need to be updated, tie re-generation of the object to an event indicating there is actual need to update.
Should I store it in a Session? ViewState? Should I simply rebuild it every post back?
Selecting whether to store the value in session or re-pull from the data layer should be based on the memory footprint of the object, the scalability requirements of the application, the costliness of the database operation, and the likelihood that the object will need to be accessed on any particular request. So I believe that is highly situational.
Also, would this class be considered an acceptable business object?
I don't have a lot of experience with BLL's but it looks like you're on the right track.
Unless profiling indicates otherwise, it's okay to just reconstruct the object with every request. You can also implement some kind of caching in your data access code. Your class is an acceptable business object.
Given that User object might have info you wouldn't want to expose through ViewState, it can be stored in Session.
This is the "standard" way of doing this in ASP.NET.
In the case of your example, reconstructing the object looks fine as it is small. But if you have a small object you inevitably store for a while, I would use session. If the object is large, I would directly use database or session with database connection.
Depending how complex you are thinking of getting a JavaScript framework called knockout.js might be a good fit. You could create a json object to bind to a jQuery template that would build the HTML depending on the object, it handles complex objects very well.
You can get the name of a page within HttpContext via Request.Path.
Is there a way to distinguish between different requests from the same page?
That is when two different instances of yourpage.aspx make a request, how can you distinguish between the two using HttpContext?
you probably want to do this in a base Page class, but here's what i would do
public partial class Default : System.Web.UI.Page
{
private Guid _instanceID;
public Guid InstanceID
{
get { return _instanceID; }
}
/// <summary>
/// Constructor
/// </summary>
public Default()
{
this._instanceID = Guid.NewGuid();
}
}
then using the HttpContext somewhere else in your code...
if (HttpContext.Current.CurrentHandler is Default)
{
((Default)HttpContext.Current.CurrentHandler).InstanceID;
}
Nothing built into ASP.NET will allow you to differentiate different "page instances" or requests from them.
However, you can easily add a Guid to your view state to uniquely identify each page. This mechanism works fine when you are in the Page class itself. If you need to identify requests before you reach the page handler, you need to use a different mechanism (since view state is not yet restored).
The Page.LoadComplete event is a reasonable place to check if a Guid is associated with the page, and if not, create one.
If you're using authentication, would it work for you to distinguish which user submitted the page?
You could use System.Web.Httpcontext.Current.User.Identity.Name.
just throwing this out there: NInject (and other DI containers) use a scoping mechanism based on the HttpContext.Current object itself, so depending on what you're trying to do, you could attempt to retrieve a state object from the DI container and go from there.
We are working on an ASP.NET application. It has 3- 4 forms displaying country list dropdown. Here we would like to avoid binding these dropdowns each time by getting data from database. Instead looking for a better practice of binding it one time, say on application load/some other.
Would you please let me know how we could go head on this? Any reference link or document would be great.
Many Thanks,
Regards,
Nani
Place the drop down in a user control and enable output caching on the user control.
This solution cause the rendered HTML to be cached so the databinding won't need to be called on every page request.
Another possibility would be to use some caching mechanism on your BL logic. For instance in your page/usercontrol you could have (don't take my syntax too strict ;) )
public partial class MyPaged: Page
{
public void PageLoad(..)
{
if(!IsPostBack)
{
dropDownCountries.DataSource = CountryBL.GetCountries();
dropDownCountries.DataBind();
}
...
}
}
and in your business logic class you do some kind of caching where you may have a singleton class that holds the countries and functions as your cache. A pseudocode method could be
public IList<Country> GetCountries
{
//if the cache is empty or it should be refreshed, fills the local
//list of countries, i.e. the cache, with fresh entries from the DB
//there could be some time condition, i.e. refresh every day or so
EnsureCacheValid();
return CachedCountries; //the cache of countries
}
This could be another option with the advantage that your presentation logic doesn't even know about the caching and if you would add a webservice access or so, you would also benefit from the caching. The only thing you have to pay attention at is if there is the possibility that the user can change the countries (which in your example I don't suppose).
In my Webforms 3.5 application, I have a GridView of users that displays the last time they logged in. This value is stored in the Users table in UTC. When an Administrator views the grid, I want them to view it the time zone the Administrator has selected in their preferences (also stored in the Users table).
I have this working properly for the GridView:
<ItemTemplate>
<%# TimeZoneInfo.ConvertTimeFromUtc(Eval("LastLoginDateTimeUTC"), TimeZoneInfo.FindSystemTimeZoneById(UserService.GetUser(HttpContext.Current.User.Identity.Name).DisplayTimeZone))%>
</ItemTemplate>
So in this case, every time a row is created, a user object is created.
What I would like to do is determine the best way to handle returning commonly used User specific data (such as user.DisplayTimeZone). I have several ideas in mind, but I wanted to get some input on how others are doing it. I was looking at using a User Specific Cache, but didn't want to implement until I had more ideas.
I would like to avoid the Session object, but it's not a requirement. I would prefer to use the HttpRuntime.Cache.
Also, once this is determined, where should the code for this go? In a BasePage class? In the MasterPage? In an MVP BasePresenter?
Thanks
~S
I've done the same using a user specific cache as you mentioned. I actually implemented it in a separate namespace though as static get properties. For example:
namespace MyWeb.Session
public static class CurrentUser {
public static int DisplayTimeZone {
get {
// Check cache first.
...
// Cache miss, load from database and store in cache.
...
}
}
}
}
That way every time I needed the value I simply call MyWeb.Session.CurrentUser.DisplayTimeZone.