tags:

views:

530

answers:

4

If I want the default url of my web app to display completely different UIs depending on the user, what is the best way to accomplish this? I don't really want to use the same controller for every type of user. To put it another way, if a user is logged in and goes to http://mysweetapp.com and is an admin user, they should get what they would see the same thing as if they had gone to http://mysweetapp.com/admin. If the user is logged in as a normal user, they should see the same thing as if they had gone to http://mysweetapp.com/normaluser

Should I just make a "redirect" controller as my default and have it send the client to the appropriate controller?

routes.MapRoute(
               "Default",
               "{controller}/{action}/{id}",
               new { controller = "Redirect", action = "Index", id = "0" });

I've also tried creating my own ControllerFactory, but I don't think I was clear on the concept and couldn't get it to work.

Thanks

+1  A: 

For simplicity, in your HomeController Index method (or whatever default controller you are using) you could put some code like this and then the links from the AdminIndex view or the Index view can send the users to appropriate areas when they start navigating round your site - that way you have one shared controller and the other controllers can be specific to the user type.

return user.IsAdministrator ? View("AdminIndex") : View("Index");

the user.IsAdministrator call is pseudocode of course - replace this with whatever method you are using to work out if the user is an admin user

Steve Willcock
That's a little too simple I think, the views need models prepared that are completely different, which is why I think different controllers is appropriate. But what you're suggesting might be what I do just switching out your View()s with RedirectToRoute()s
jayrdub
Yes, then a redirect might be a better option if the models are very different.
Steve Willcock
A: 

What you might want to consider is areas. This would allow you to have separate controllers for each area. Then permit access to those areas based on roles or whatever you wish. This will give you routes like '/admin/controller/action', '/users/controller/action', etc. The 'pattern' separates all your controllers by namespace, and handles the routing quite well. Separate master pages easily, etc.

It won't give you the (potentially confusing, IMO) '/' and '/admin/' looking the same to an admin user, but it will let you separate the content and controllers.

What you are describing would lead to potentially tons of methods for each controller, something that is generally frowned upon by the MVC/REST crowd. It's not horrible, but its not considered best practice either.

You can read about areas at this blog here. Google 'asp.net mvc areas' for more.

--------edit-----------

To expand a bit:

Without custom routes or some other shenanigans, actions are mapped to controllers by the url. So if you want to keep all admin actions and views different, but on the root url, along with normal user actions, this would lead to one big controller that has to handle all these actions, or some strange "if this role, this view; if that role, that view" sort of nonsense that would have to happen in each action. Kind of a mess to debug potentially.

Similarly, the default view engine finds the views based on the url as well.

This would mean that all of your views are going to sit in one big ugly directory full of all sorts of weird similarly named but differently behaving views.

In short, this would become a potentially horrific maintenance nightmare, depending on complexity of the application.

Chad Ruppert
Could you clarify why you say "What you are describing would lead to potentially tons of methods for each controller" and what an alternative would be?
jayrdub
Is the edit a bit more helpful?
Chad Ruppert
Got it, basically what you're saying is using the default controller/viewname behavior, there is no good way around keeping the url clean.
jayrdub
No. Areas are a great way of keeping them clean. But yeah, doing it the other way would likely not be clean at all.
Chad Ruppert
A: 

If you don't want to use the same controller set up individual controllers and views for each item first - mysweetapp.com/admin and mysweetapp.com/normaluser.

You can then redirect specific users to this page through a default controller based on their logged in role.

if (User.IsInRole("Admin")
            {
                return RedirectToAction("Index", "admin");
            }
            else if (User.IsInRole("Standard")
            {
                return RedirectToAction("Index", "normaluser");
            }
Adrian
This was my initial thought too, I was just thinking there might be a more standard way to do this using routes or something.
jayrdub
+1  A: 

The cleanest way in my opinion would be to create a custom route handler to be used by your default route. Then you can separate out which controller to be used if the controller name is your default controller name, in the example below, it is: Home. Then check if the user is an administrator or not and process the request with the controller you would like to use.

Here is the code:

public class CustomHttpHandler : IHttpHandler
{
    public RequestContext RequestContext { get; private set; }

    public CustomHttpHandler(RequestContext requestContext)
    {
        try
        {
            string controllerName = RequestContext.RouteData.GetRequiredString("controller");
            if (controllerName.Equals("home", StringComparison.CurrentCultureIgnoreCase))
            {
                bool isAdmin = RequestContext.HttpContext.User.IsInRole("Admin");
                controllerName = isAdmin ? "admin" : "normaluser";
            }

            IControllerFactory factory = ControllerBuilder.Current.GetControllerFactory();
            IController controller = factory.CreateController(RequestContext, controllerName);
            if (controller != null)
            {
                controller.Execute(RequestContext);
            }
        }
        finally
        {
            factory.ReleaseController(controller);
        }
    }
}

public class CustomRouteHandler : IRouteHandler
{
    public IHttpHandler GetHttpHandler(RequestContext requestContext)
    {
        return new CustomHttpHandler(requestContext);
    }
}

// Now use the CustomRouteHandler when you map your default route.
routes.MapRoute(
    "Default",
    "{controller}/{action}/{id}",
    new { controller = "Home", action = "Index", id = "" }
).RouteHandler = new CustomRouteHandler();

Hope this helps.

Dale Ragan
That looks like a nice solution, I'll give it a go, thanks.
jayrdub