Hiding links that shouldn’t be shown in ASP.NET MVC
Problem
Any web application has pages that should be seen by some users and not by others. At the very least, there are administrative pages and public pages. In the past, I’ve always used ad-hoc code when generating pages to determine which links (or tabs, labels etc.) should show up for which user. In ASP.NET this might look something like this:<% if (userCanEditDocuments) { %> <%= Html.ActionLink ("Documents", "Edit", "Edit my documents") %> <% } %>This works, but it entails far too much duplication. Duplication on the front end entering these conditionals. Duplication on the back end calculating the relevant user permissions for each page.
Solution
If you use authorization filters to determine the access to each action, the information you need to is available to the view already. By accessing these attributes, it’s possible to replace the above view with something like this:<%= Html.LinkIfAccessible ("Documents", "Edit", "Edit my documents) %>Less code on the front end, and best of all no code in the action to write extra view data. The method to determine if a link is accessible is a little involved:
public bool IsAccessible(string controller, string action) { IController controllerImpl = null; IControllerFactory controllerFactory = null; try{ var httpContext = new HttpContextWrapper(HttpContext.Current); var requestContext = new RequestContext(httpContext, new RouteData()); controllerFactory = ControllerBuilder.Current.GetControllerFactory(); controllerImpl = controllerFactory.CreateController(requestContext, controller); var controllerContext = new ControllerContext(requestContext, (ControllerBase)controllerImpl); var controllerDescriptor = new ReflectedControllerDescriptor(controllerImpl.GetType()); var actionDescriptor = controllerDescriptor.FindAction(controllerContext, action); var filters = actionDescriptor.GetFilters(); var authorizationContext = new AuthorizationContext(controllerContext, actionDescriptor); foreach (var filter in filters.AuthorizationFilters) filter.OnAuthorization(authorizationContext); var result = authorizationContext.Result; return !(result is UnauthorizedAccessResult); } finally { if (controllerFactory != null && controllerImpl != null) controllerFactory.ReleaseController(controllerImpl); } }Although this method is kind of scary, it’s really just making use of what’s already in ASP.NET MVC.
Caveats
Using this does place a couple of restrictions on the authorization filters used by your actions.- Your authorization filters should not have any side-effects (this is a good idea anyway), as this method does actually run the filter methods
- The view result from your filters needs to be set to a recognisable type (in this case UnauthorizedAccessResult) in the event of the user not being authorized.