Categories
ASP.NET ASP.NET MVC Microsoft

Create an authorized action link extension for ASP.NET MVC 3

In this article I will explain how to create an action link extension that is authorizations aware, so that we can hide or disable an action link based on its authorizations.

When you create a website with multiple permissions, it would be nice to display action links depending on those permissions. For example if your website contains a menu, its better to display links according to the permissions. You don’t want your users to click on a link, and then display an unauthorized access message.

 

With ASP.NET MVC, we can easily create an action link that is authorizations aware. It will be above the standard action link.

Important: note that the code below is only for ASP.NET MVC 3. It’s because there has been some changes with filters since ASP.NET MVC 2.

 

 Creation

First we will create an extension that will allow us to know if an action is authorized:

using System.Web.Mvc;

namespace MvcApplication.AuthorizedActionLink.Extensions
{
	public static class ActionExtensions
	{
		public static bool ActionAuthorized(this HtmlHelper htmlHelper, string actionName, string controllerName)
		{
			ControllerBase controllerBase = string.IsNullOrEmpty(controllerName) ? htmlHelper.ViewContext.Controller : htmlHelper.GetControllerByName(controllerName);
			ControllerContext controllerContext = new ControllerContext(htmlHelper.ViewContext.RequestContext, controllerBase);
			ControllerDescriptor controllerDescriptor = new ReflectedControllerDescriptor(controllerContext.Controller.GetType());
			ActionDescriptor actionDescriptor = controllerDescriptor.FindAction(controllerContext, actionName);

			if (actionDescriptor == null)
				return false;

			FilterInfo filters = new FilterInfo(FilterProviders.Providers.GetFilters(controllerContext, actionDescriptor));

			AuthorizationContext authorizationContext = new AuthorizationContext(controllerContext, actionDescriptor);
			foreach (IAuthorizationFilter authorizationFilter in filters.AuthorizationFilters)
			{
				authorizationFilter.OnAuthorization(authorizationContext);
				if (authorizationContext.Result != null)
					return false;
			}
			return true;
		}
	}
}

As you can see we are retrieving action authorization filters. For each filter we test if the authorization context Result property is null. If this property is not null, it means that the user is not authorized to go on the action.

 

We also use an extension named GetControllerByName, in case the controller name is not specified:

using System;
using System.Globalization;
using System.Web.Mvc;

namespace MvcApplication.AuthorizedActionLink
{
	internal static class Helpers
	{
		public static ControllerBase GetControllerByName(this HtmlHelper htmlHelper, string controllerName)
		{
			IControllerFactory factory = ControllerBuilder.Current.GetControllerFactory();
			IController controller = factory.CreateController(htmlHelper.ViewContext.RequestContext, controllerName);
			if (controller == null)
			{
				throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, "The IControllerFactory '{0}' did not return a controller for the name '{1}'.", factory.GetType(), controllerName));
			}
			return (ControllerBase)controller;
		}
	}
}

 

Now that we are able to know if an action is authorized, we can create a new link extension named ActionLinkAuthorized so it sticks to the standard ActionLink:

using System.Collections.Generic;
using System.Web.Mvc;
using System.Web.Mvc.Html;
using System.Web.Routing;
using MvcApplication.AuthorizedActionLink.Extensions;

namespace MvcApplication.AuthorizedActionLink.Html
{
	public static class LinkExtensions
	{
		public static MvcHtmlString ActionLinkAuthorized(this HtmlHelper htmlHelper, string linkText, string actionName, bool showActionLinkAsDisabled = false)
		{
			return htmlHelper.ActionLinkAuthorized(linkText, actionName, null, new RouteValueDictionary(), new RouteValueDictionary(), showActionLinkAsDisabled);
		}

		public static MvcHtmlString ActionLinkAuthorized(this HtmlHelper htmlHelper, string linkText, string actionName, object routeValues, bool showActionLinkAsDisabled = false)
		{
			return htmlHelper.ActionLinkAuthorized(linkText, actionName, null, new RouteValueDictionary(routeValues), new RouteValueDictionary(), showActionLinkAsDisabled);
		}

		public static MvcHtmlString ActionLinkAuthorized(this HtmlHelper htmlHelper, string linkText, string actionName, string controllerName, bool showActionLinkAsDisabled = false)
		{
			return htmlHelper.ActionLinkAuthorized(linkText, actionName, controllerName, new RouteValueDictionary(), new RouteValueDictionary(), showActionLinkAsDisabled);
		}

		public static MvcHtmlString ActionLinkAuthorized(this HtmlHelper htmlHelper, string linkText, string actionName, RouteValueDictionary routeValues, bool showActionLinkAsDisabled = false)
		{
			return htmlHelper.ActionLinkAuthorized(linkText, actionName, null, routeValues, new RouteValueDictionary(), showActionLinkAsDisabled);
		}

		public static MvcHtmlString ActionLinkAuthorized(this HtmlHelper htmlHelper, string linkText, string actionName, object routeValues, object htmlAttributes, bool showActionLinkAsDisabled = false)
		{
			return htmlHelper.ActionLinkAuthorized(linkText, actionName, null, new RouteValueDictionary(routeValues), new RouteValueDictionary(htmlAttributes), showActionLinkAsDisabled);
		}

		public static MvcHtmlString ActionLinkAuthorized(this HtmlHelper htmlHelper, string linkText, string actionName, RouteValueDictionary routeValues, IDictionary<string, object> htmlAttributes, bool showActionLinkAsDisabled = false)
		{
			return htmlHelper.ActionLinkAuthorized(linkText, actionName, null, routeValues, htmlAttributes, showActionLinkAsDisabled);
		}

		public static MvcHtmlString ActionLinkAuthorized(this HtmlHelper htmlHelper, string linkText, string actionName, string controllerName, object routeValues, object htmlAttributes, bool showActionLinkAsDisabled = false)
		{
			return htmlHelper.ActionLinkAuthorized(linkText, actionName, controllerName, new RouteValueDictionary(routeValues), new RouteValueDictionary(htmlAttributes), showActionLinkAsDisabled);
		}

		public static MvcHtmlString ActionLinkAuthorized(this HtmlHelper htmlHelper, string linkText, string actionName, string controllerName, RouteValueDictionary routeValues, IDictionary<string, object> htmlAttributes, bool showActionLinkAsDisabled)
		{
			if (htmlHelper.ActionAuthorized(actionName, controllerName))
			{
				return htmlHelper.ActionLink(linkText, actionName, controllerName, routeValues, htmlAttributes);
			}
			else
			{
				if (showActionLinkAsDisabled)
				{
					TagBuilder tagBuilder = new TagBuilder("span");
					tagBuilder.InnerHtml = linkText;
					return MvcHtmlString.Create(tagBuilder.ToString());
				}
				else
				{
					return MvcHtmlString.Empty;
				}
			}
		}
	}
}

 

Example of use

Let’s say we want to authorize only users with role Administrator on the HomeController action named ThePrivilegeZone. ThePrivilegeZone action will be decorated with an authorize attribute. We add a link to this action in the site menu with the authorized action link extension specifying that we want to show the  action link as disabled if the user is not authorized:

<%@ Master Language="C#" Inherits="System.Web.Mvc.ViewMasterPage" %>
<%@ Import Namespace="MvcApplication.AuthorizedActionLink.Html" %>

<!DOCTYPE html>
<html>
<head runat="server">
    <title><asp:ContentPlaceHolder ID="TitleContent" runat="server" /></title>
    <link href="../../Content/Site.css" rel="stylesheet" type="text/css" />
    <script src="<%: Url.Content("~/Scripts/jquery-1.4.4.min.js") %>" type="text/javascript"></script>
</head>

<body>
    <div class="page">

        <div id="header">
            <div id="title">
                <h1>My MVC Application</h1>
            </div>
              
            <div id="logindisplay">
                <% Html.RenderPartial("LogOnUserControl"); %>
            </div> 
            
            <div id="menucontainer">
            
                <ul id="menu">              
                    <li><%: Html.ActionLink("Home", "Index", "Home")%></li>
                    <li><%: Html.ActionLink("About", "About", "Home")%></li>
                    <li><%: Html.ActionLinkAuthorized("The Privilege Zone", "ThePrivilegeZone", "Home", true)%></li>
                </ul>
            
            </div>
        </div>

        <div id="main">
            <asp:ContentPlaceHolder ID="MainContent" runat="server" />

            <div id="footer">
            </div>
        </div>
    </div>
</body>
</html>

 

The result looks like below when the user is not authorized and the action link disabled:

It would be better to add some styling 🙂

 

To go further

To be honest I didn’t invent anything here. I have just take a deep look into the ASP.NET MVC 3 source code available on the Microsoft website in order to understand how authorizations works, and I have mixed it with the standard ActionLink. My code is based on three classes: ControllerActionInvoker, MvcHandler, LinkExtensions.

 

Summary

We have seen how to create and use an authorized action link.

You can download the example solution here:

Download full sources

(Note that the project uses ASP.NET MVC 3)

 

Please feel free to comment or contact me if you have any question about this article.

Categories
ASP.NET ASP.NET MVC Microsoft

Create a custom AuthorizeAttribute that accepts parameters of type enum

In this article I will explain how to simply create an AuthorizeAttribute that accepts parameters of type enum in order to avoid roles hard coding.

Have you ever tried to use an [Authorize] attribute and assign roles for example with an Enum value in one of your ASP.NET MVC projects?

If so, you will get the following error message when compiling:

An attribute argument must be a constant expression, typeof expression or array creation expression of an attribute parameter type

 

It is because you need to use static values and it makes impossible to use an Enum to set properties of an Attribute. It means that you can not set the property Roles of an [AuthorizeAttribute] with an Enum value.

That is frustrating, because I personally don’t like having to hard code roles in an application. It makes the application dirty and more complicated to maintain.

 

Hopefully, ASP.NET MVC allows us to customize the [AuthorizeAttribute] easily without having to override the standard security process.

 

 Creation

For our needs we will create the following Enum to declare roles:

namespace MvcApplication.HowTo.Enums
{
    public enum Role
    {
        Administrator = 1,
        UserWithPrivileges = 2,
        User = 3,
    }
}

 

Now we are going to create a custom [AuthorizeAttribute] that accepts Enum as parameters in the constructor. It will inherit from the standard System.Web.Mvc.AuthorizeAttribute:

using System;
using System.Linq;
using System.Web.Mvc;

namespace MvcApplication.HowTo.Attributes
{
    [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true, AllowMultiple = true)]
    public class AuthorizeEnumAttribute : AuthorizeAttribute
    {
        public AuthorizeEnumAttribute(params object[] roles)
        {
            if (roles.Any(r => r.GetType().BaseType != typeof(Enum)))
                throw new ArgumentException("roles");

            this.Roles = string.Join(",", roles.Select(r => Enum.GetName(r.GetType(), r)));
        }
    }
}

As you can see we have to pay attention to several things here:

  • The constructor accepts parameters of type object, that is the little trick. If you use parameters of type Enum, you will get the same error message as above. We can do that because an Enum is an object.
  • To ensure that we are passing parameters of type Enum, we check the type of every roles. If one role is not of type Enum, the constructor will throw an ArgumentException.
  • Then we set the standard Roles property with the name of our roles with the string.Join method.

 

Example of use

Let’s say we want to authorize only users with roles Administrator or UserWithPrivileges on the HomeController action named ThePrivilegeZone. ThePrivilegeZone action will be decorated with our custom authorize attribute like bellow:

using System.Web.Mvc;
using MvcApplication.HowTo.Attributes;
using MvcApplication.HowTo.Enums;

namespace MvcApplication.HowTo.Controllers
{
    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            ViewBag.Message = "Welcome to ASP.NET MVC!";

            return View();
        }

        public ActionResult About()
        {
            return View();
        }

        [AuthorizeEnum(Role.Administrator, Role.UserWithPrivileges)]
        public ActionResult ThePrivilegeZone()
        {
            return View();
        }
    }
}

Our code is clean like that, isn’t it?

 

To go further

If you take a look with a tool like Reflector, it is interesting to understand how the action ThePrivilegeZone is decorated when compiled. Here is a screenshot:

We understand that once compiled, Role Enum values are used and not names. Here 1 (Administrator) and 2 (UserWithPrivileges).

Note that if you use an Enum for your roles without setting values like we did here, Enum values will be 0, 1, 2, etc. to the number of your roles less 1.

 

Summary

We have seen how to create and use a custom AuthorizeAttribute that accepts parameters of type enum. Here we are only setting roles, but depending on your needs, you can do the same with users. I personally use this custom attribute in my framework so that I am able to reuse it in all my ASP.NET MVC projects.

You can download the example solution here:

Download the Authorize Enum Solution

(Note that the project uses ASP.NET MVC 3)

 

Please feel free to comment or contact me if you have any question about this article.