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 wa开发者_开发技巧y to approach this?
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.
I like @TJB's answer a lot, and think I'll actually do something similiar. But if you want to go a different route... you could create your own LinkExtensions that overload the standard LinkExtensions.
public static class MyLinkExtensions
{
public static MvcHtmlString ActionLink(this HtmlHelper htmlHelper, string linkText, string actionName, YourAccessStuff access)
{
if(access.Has(actionName))
{
ActionLink(htmlHelper, linkText, actionName);
}
else
{
// Maybe only show the link text as if it's disabled and not a link?
// Maybe do nothing?
}
}
}
Assume here that "YourAccessStuff" is actually implemented. This would centralize those access checks as opposed to sticking them on every ActionLink. The downside obviously is that you can still forget to put your security check on. Using some sort of dependency injection would also make this nicer.
精彩评论