Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Hiding Html.ActionLinks based on Role-based security

I use @RenderSection("Contextual", false) within my _Layout.cshtml to allow different views to render their particular content there. Some don't have any, others do.

Additionally, I use role-based security and an ActionFilter to control whether a particular user has access to particular controller actions and thus routes on my site.

What I'd like to do is provide a @RenderSection("Contextual", false) section on my _Layout.cshtml and then have the particular page provide whatever contextual stuff makes sense for that page and have the corresponding controller handle the vetting of whether a user can perform an action and maybe even see that the options exist but I'm not sure that I'm thinking about this correctly. Here's how things are currently:

Right now I've got a section in one of my Index.cshtml files like so:

@section Contextual {
  <div>@Html.ActionLink("Create New", "Create")</div>
  <div>@Html.ActionLink("Generate Report", "Report")</div>
  <div>@Html.ActionLink("Other Stuff", "Other")</div>
}

and then in my corresponding controller, I've got something like so:

[Authorize(Roles = "Editor")]
public ActionResult Create()
{
   // stuff
}

This will work as I want (non-Editors won't get to create new items) but the Create entry is there for all to see. I can do something like so:

@section Contextual {
  @if (User.IsInRole("Editor"))
  {
     <div>@Html.ActionLink("Create New", "Create")</div>
  }
  <div>@Html.ActionLink("Generate Report", "Report")</div>
  <div>@Html.ActionLink("Other Stuff", "Other")</div>
}

And that works well enough, hiding the Create link from the non-Editors, but I'm on the fence about whether it's good or not to handle it this way plus I can see that down the road I've got the situation where the rules change and then I've got two locations to keep in sync: the attribute on the controller action and the code in the view.

Is this a reasonable approach? Is there a better way to approach this?

like image 853
itsmatt Avatar asked May 05 '11 17:05

itsmatt


1 Answers

I like to use flags that are more explicit for the view model that are populated on the contorller.

For example:

 // on the controller
 viewModel.CanCrete = User.IsInRole("Editor");
 // ...snip...
 return View(viewModel);
}

So, you would need to add this flag to your view model or possibly in the base class of your view models. You could go the route of creating a Custom Action Filter to populate it across several controllers or do some handling in your controller base class.

I also like to define a handy extension method:

public static string If( this string s, bool condition )
{
  return condition ? s : String.Empty;
}

Depending on which APIs you're using, you may also need to extend MvcHtmlString.

Then in the view:

@section Contextual {
  <div>@Html.ActionLink("Create New", "Create").If(Model.CanCrete)</div>
  <div>@Html.ActionLink("Generate Report", "Report")</div>
  <div>@Html.ActionLink("Other Stuff", "Other")</div>
}

You can decide what you would like to do about the div, you may want to have another helper that wraps links in divs, or maybe you can use CSS to achieve whatever visual layout you're going for.

like image 65
TJB Avatar answered Nov 10 '22 07:11

TJB