views:

173

answers:

3

Hi all,

I have a User entity, and in various views, I want to create links to a user home page basically. This fuctionality should be avaialbe in different controllers, so I can easily redirect to the user's home page. Each user in my site has a role ; for example reader, writer, editor, manager and admin. Ideally, I want to try to achieve something like this:

In a controller, for example

public ActionResult SomeThingHere() {
    return View(User.GetHomePage());
//OR 
    return RedirectToROute(User.GetHomePage());
}

in a View, I also want to use the same functionality, for example:

<%= Html.ActionLink("Link to home", user.GetHomePage() %>

Is it possible to achieve such a design in MVC? If so , how should I go about it?

I currently use a method like this, but it is only in one controller at the moment. Now I need to use the same code somewhere else and I am trying to figure out how I could refractor this and avoid repeating myself?

....
private ActionResult GetHomePage(User user){
    if (user.IsInRole(Role.Admin))
        return RedirectToAction("Index", "Home", new { area = "Admin" });

    if (user.IsInRole(Role.Editor))
        // Managers also go to editor home page
        return RedirectToAction("Index", "Home", new {area = "Editor"});

    if (user.IsInRole(Role.Reader))
        // Writer and reader share the same home page
        return RedirectToAction("Index", "Home", new { area = "Reader" });

    return RedirectToAction("Index", "Home");
}
...

Thanks for your help

A: 

I would suggest a custom extension to the HtmlHelper class. Top of my head (liable to have syntax errors), something like this

public static class RoleLinksExtension
{
    public static string RoleBasedHomePageLink(this HtmlHelper helper, string text)
    {
        if (user.IsInRole(Role.Admin))
             return helper.ActionLink(text, "Index", "Home", new { area = "Admin" });

        // other role options here

        return string.Empty; // or throw exception
    }
}

Then it's just

<%= Html.RoleBasedHomePageLink("Link to home") %>

in your markup.

You don't really want to have a link to somewhere that simply redirects somewhere else, if you can avoid it.

Edit: No idea why I didn't think of this earlier, but if you do need to redirect (perhaps if you need some functionality before going to the home page), you could extend IPrinciple instead

public static class AreaHomePageExtensions
{
    public static string GetArea(this IPrinciple user)
    {
        if (user.IsInRole(Role.Admin))
            return "Admin";
        // Other options here
    }
}

Then you can do

return RedirectToAction("Index", "Home", new { area = User.GetArea() });

whenever you like.

pdr
Yes, That is a good idea, but the logic would have to appear again if I wanted to use the same logic on a controller.
Dai Bok
Why would you need the logic in a controller? You can use it in as many views as you like, used in as many controllers as you like, you can even put it in a master page. Each time it takes you straight to the Home/Index action.
pdr
To redirect to specific view, depending on your role. I guest from your edit, you realised this now. ;-) At the moment, I have some hybrid, controller extention method, building a url string, but extending the IPrinciple interface would be a better idea. I like that. The problem is, I guess I would have to use it like this: return RedirectToAction(User.GetActionMethod(), User.GetController(), new { area = User.GetArea() });
Dai Bok
A third option would be a new 'ActionResult' class, so you could just say 'return new RedirectByRoleResult(User);' but that would seem overkill to me. I like 'RedirectToAction(User.GetActionMethod(), User.GetController(), new { area = User.GetArea() })'. But if the Action does nothing other than redirect then I'd still incline towards my first solution
pdr
+1  A: 

How about something like this:

private string GetArea(User u)
{
   string area = string.empty;
   if (User.IsInRole(Admin)) area = "admin";
   else if (...)


   return area;
}
No Refunds No Returns
I can not see how this would help? Even if the area was the only difference, I would still land up with an ugly solution. Consider the case where a admin user may be directed to action="myMessages", controller="messages" then I would require GetController(User u) and GetAction(User u) ....
Dai Bok
Unless you're going to direct them to totally different sites on your first page, you're going to have to check every page request and link you build. I think you'll have better luck using master pages and choosing which master page to use based on the role of the current user. We do this and it works well. Just be sure to perform the same check on your actual page though to prevent someone from "knowing" the URL and bypassing your logic. The only way to be secure is to check at the point where you're about to do something that requires authorization.
No Refunds No Returns
No, we are using the same domain. We also do seperate checks on "secure" controllers/action methods. You have given me an idea. I could build the full URL somewhere once, that way I can use the resulting url as a parameter for the Redirect() in controllers. But is it a this agood idea, or hack?
Dai Bok
A: 

Hi, Well I finally came up with a design that seems to work. I have written an controller extension, with a GetHomePage Method. This extension can also be used in your views. Here is how I did It:

public static class UserHelperExtension    {
    public static string GetHomePage(this ControllerBase controller, User user) {
        return = "http://" + controller.ControllerContext
                            .HttpContext.Request
                            .ServerVariables["HTTP_HOST"] + "/"
                           + GetHomePage(user);
    }

    //need this for views
    public static string GetHomePage(string httphost, User user) {
        return  = "http://" + httphost + "/" + GetHomePage(user});
    }

    private static string GetHomePage(User user) {
        if (user.IsInRole(Role.Admin))
            return "/Admin/Home/Index";
        if (user.IsInRole(Role.Editor))
            return "/Editor/Home/Index";
        if (user.IsInRole(Role.Reader))
            return "/Reader/Home/Index";
        return "/Home/Index";
    }
}

The action method in the controller looks like this:

    using Extensions;
...
public ActionResult SomethingHere() {
    return Redirect(this.GetHomePage(user));
}
...

In the view I have this:

...
<%@ Import Namespace="Extensions"%>
<%=UserHelperExtension.GetHomePage(Request.ServerVariables["HTTP_HOST"], user)%>
...

The advantage is that I can easily use this "GetHomePage" method in various controllers, or views thoughout my application, and the logic is in one place. The disadvantage is that I would have preferred to have it more type safe. For example, in my orignal tests, I had access to RouteValues collection:

public void User_should_redirect_to_role_home(Role role,
    string area, string controller, string action) {
...
var result = (RedirectToRouteResult)userController.SomeThingHere();
    Assert.That(result.RouteValues["area"], 
        Is.EqualTo(area).IgnoreCase);
    Assert.That(result.RouteValues["controller"], 
        Is.EqualTo(controller).IgnoreCase);
    Assert.That(result.RouteValues["action"], 
        Is.EqualTo(action).IgnoreCase);
...

}

But now that I am using a string so it is not type safe, and checking the RedirectResult.Url.

...
var result = (RedirectResult) userController.SomethingHere();
Assert.That(result.Url.EndsWith("/" + area + "/" + controller + "/" + action), 
    Is.True);
...
Dai Bok