views:

376

answers:

3

We are building a site using asp.net mvc. We want to allow the user to easily register and create an account. There is though a very special piece of information, that will be registered in his profile, that we want to show to him *after registration is finished, and he is logging in for the first time.

The logic is whatever the URL that was hit, if the user is authenticated and does not have a valid profile, redirect him to the "Create Profile" page.

The whole ui will depend on those profile choices. What approach should we use within the MVC framework, to force this workflow on the visitor? The ideas I can come up with require tons of code duplication in controllers etc, so its clearly a bad idea.

We are using Membership for users, but profile is our own implementation (no profile provider) that will connect profile data to a userId.

+3  A: 

I think the easiest way to do this is either create a custom AuthorizeAttribute, extending the existing one or create a separate FilterAttribute. Either one of these would get applied to all of your controllers and ensure that an authenticated user has a profile. In the case where no profile exists, the filter would redirect the user to the page where the profile is created. This would be done by setting the result property on the context to a RedirectResult to the profile creation action. Only if the profile exists and is complete would the filter allow the user to proceed to the desired action.

Alternatively, you could create a base controller that overrides OnActionExecuting and performs the same task. I prefer the attribute mechanism as it is more flexible, i.e., you could have some public actions that are available without the profile (including the profile setting action).

tvanfosson
I 'm thinking that its a big overhead to have to apply the same actionfilter over and over again, when you know you always want to do this.I will look into doing this in either with a base controller (then I would need to make all my controllers inherit from that one instead right?) or go in the begin request of the global.asax.
thanos panousis
You can apply the filter at the controller or action level. When applied at the controller level it's basically the same thing as doing it in the base controller. Once you have several different filters that don't always apply, you'll start to appreciate the flexibility of the filter mechanism. Only when it is truly a global action would I recommend the base controller mechanism. In your case it may apply.
tvanfosson
Thanks for your replies. It indeed looks like something very special that I need to enforce wherever my registered users go. I think its a price to pay for the extra usability of not burdening your users too much on registration.Using a custom base controller requires that all controllers that need to enforce this check would have to extend the custom ctrler?Wouldn't be more easy just to put logic in Global.asas, as xandy suggests?
thanos panousis
I guess I don't like that because it forces it to work that way all the time -- or you have to embed some switch logic to take care of special cases. Doing it in the controller at least allows you override the behavior on a controller basis if you need to. Since your controllers need to derive from something I don't see the requirement to derive from your base controller as particularly onerous. In my case, the actions only apply to user actions not admin actions and so I've gone the filter route.
tvanfosson
A: 

Try consider ActionFilter and FilterAttribute if not most pages need to do so, or else, you may actually put this redirection logic to global.asax (in those Begin Request or similar events)

xandy
+1  A: 

Answering my own question: In the end I created a custom actionFilter. In the beginning, I took the path of subclassing [authorize] into [AuthorizeCheckProfile]. But then I realized that the use case was wrong: I did not want the logged-in only parts of my site to redirect to the create-profile page, if no user profile existed. I wanted any page of my site to redirect to that page, if its a logged-in user with no profile. The only place I don't want to check that is in the actual profile-create. Here's the code:

public class AssertProfileAttribute : ActionFilterAttribute {
    public AssertProfileAttribute() {
    }

    public override void OnActionExecuting(ActionExecutingContext filterContext) {
        if (filterContext.HttpContext.Request.IsAuthenticated == false)
            return;
        //we have a user, but does he have a profile?
        if (filterContext.HttpContext.Session["UserProfile"] == null) { //not in the session
            IUnityContainer container = filterContext.HttpContext.Application["container"] as IUnityContainer;
            Profile hasProfile = container.Resolve<IProfileRepository>().GetUserProfile(Membership.GetUser());
            if (hasProfile == null) {
                //we have to redirect to the create profile 
                string returnURL = filterContext.HttpContext.Request.AppRelativeCurrentExecutionFilePath;
                filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary(new { controller = "Profile", action = "Create", returnTo = returnURL }));
            } else {
                //he has a profile but we haven't put it in session yet
                filterContext.HttpContext.Session["UserProfile"] = hasProfile;
            }
        }
    }
}

It has the side-effect that it will store the profile in a Session key. This way it can be easily be fetched so that further role checks can happen in every request with other custom filters. The implementation uses Unity and repositories for db access.

A big thanks to the community.

thanos panousis