In my application, I have two roles: Administrators and Users. Administrators can assign Users to allow them to perform specific functions to a specific object.
Since this goes beyond the scope of what the SimpleMembership Roles can provide, I have a static helper method that checks whether a user has access to a specific function:
public static class SecurityCheck
{
public static bool UserHasAccess(int objectId, string functionName)
{
// Decorates the security provider -- gets logged in User ID and calls to a repository to query the database
// ...
}
}
Which I can then use in my views to determine whether or not a specific function should be rendered for that user based on the object's ID:
@foreach (var item in Model.FooData)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.Name)
</td>
<td>
@Html.DisplayFor(modelItem => item.Notes)
</td>
<td>
@Html.ActionLink("View Data", "View", new { @id = item.Id })
@if (SecurityCheck.UserHasAccess(item.id, "Edit Data"))
{
@Html.ActionLink("Edit Data", "Edit", new {@id = item.Id})
}
@if (SecurityCheck.UserHasAccess(item.id, "Delete"))
{
@Html.ActionLink("Delete", "Delete", new {@id = item.Id})
}
</td>
</tr>
}
I have to believe there is a better way to do this, since each individual call to the static method involves a separate round-trip to the database, but I am stuck on where the best place would be to put the code. Some thoughts I have considered:
- Adding methods to my ViewModels to pass a list of functions to a repository, returning a list of the functions the user can perform for each object. Thinking about this further, I'm not even sure this is possible, as ugly as it would be.
- Keep the ViewModel dumb, and have my application layer service fetch the available functions. This would involve adding additional properties to my domain model objects, which I am not crazy about.
- Create a separate service that can be called from the controller that can populate the function list to the ViewModel. This would involve having multiple services injected into each controller -- also not crazy about this.
I'm leaning towards #2., but I still feel like I am overlooking what would be a more solid implementation. Has anyone dealt with something similar to this before?