views:

72

answers:

4

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?

+1  A: 

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

rockinthesixstring
Yes, you could substitute Roles for Groups. The question is if there is a better approach than repeating what you suggest throughout the code base.
ElectricDialect
Can you give us an idea of what behavior, and how much difference there is between the different roles?
rockinthesixstring
+1  A: 

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.

Dirk
A: 

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().

Jared
+1  A: 

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.

pb