Pada ASP.Net MVC kita bisa menyembunyikan menu/node tertentu pada sitemap beradasarkan user role. Tambahkan roles pada existing sitemapnode kita seperti di bawah ini. Jika kita multiple role gunakan “,” sebagai pemisah.
<mvcSiteMapNode title="Vehicle Index" key="VehicleIndex" controller="Vehicle" action="Index" visibility="false" roles="Admin, Accountant"/>
kemudian buatlah Customer provider untuk mengoverride visibility dari sitemapnode tersebut.
public class SiteMapVisibilityCustomProvider : MvcSiteMapProvider.SiteMapNodeVisibilityProviderBase
{
private bool IsInRole(ISiteMapNode node)
{
if (HttpContext.Current == null)
return true;
if (HttpContext.Current.User == null)
return true;
if(node.Roles.Count == 0)
return true;
var IsInRole = false;
foreach(var role in node.Roles)
{
IsInRole = HttpContext.Current.User.IsInRole(role);
if (IsInRole)
break;
}
return IsInRole;
}
public override bool IsVisible(ISiteMapNode node, IDictionary<string, object> sourceMetadata)
{
if (node == null)
{
return true;
}
// Is a visibility attribute specified?
object visibilityValue = null;
node.Attributes.TryGetValue("visibility", out visibilityValue);
if (visibilityValue != null)
{
bool nodeVisible;
string visibility = visibilityValue.ToString();
if (bool.TryParse(visibility, out nodeVisible))
{
return nodeVisible;
}
}
else
{
//check user role
return IsInRole(node);
}
return true;
}
}
Pada web config set default visibility provider ke class tersebut, value = {className}, {assemblyName}
<add key="MvcSiteMapProvider_DefaultSiteMapNodeVisibiltyProvider" value="Astral.MVC.Helpers.SiteMapVisibilityCustomProvider, Astral.MVC"/>
Sekarang, bagaimana agar mencegah user mengakses secara langsung alamat url dari page tersebut? kita buat custom authorization attribute yang akan melewatkan key dari sitemapnode dan medirect url ke halaman tertentu jika role user tidak memiliki akses.
[AttributeUsageAttribute(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
public class SiteMapAuthorizeAttribute : AuthorizeAttribute
{
public string Key { get; set; }
public override void OnAuthorization(AuthorizationContext filterContext)
{
base.OnAuthorization(filterContext);
ISiteMapNode siteMapNode = null;
var siteMap = MvcSiteMapProvider.SiteMaps.Current;
if (siteMap == null)
return;
siteMapNode = siteMap.FindSiteMapNodeFromKey(Key);
if (siteMapNode == null)
return;
if (siteMapNode.Roles.Count == 0)
return;
var IsInRole = false;
foreach (var role in siteMapNode.Roles)
{
IsInRole = filterContext.HttpContext.User.IsInRole(role);
if (IsInRole)
break;
}
if (!IsInRole)
{
HandleUnauthorizedRequest(filterContext);
}
}
//Called when access is denied
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
//User isn't logged in
if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
{
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary(new { controller = "Account", action = "Login" })
);
}
//User is logged in but has no access
else
{
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary(new { controller = "Home", action = "Index" })
);
}
}
Cara penggunaan attribute ini di controller action seperti ini.
[SiteMapAuthorize(Key ="VehicleIndex")]
public ActionResult Index()
{
}
Atau dengan cara lebih praktis, dimanage pada BaseController seperti ini
private void CheckAuthorization(ActionExecutingContext filterContext, string currentPageUrl)
{
ISiteMapNode siteMapNode = null;
var siteMap = MvcSiteMapProvider.SiteMaps.Current;
if (siteMap == null)
return;
siteMapNode = siteMap.FindSiteMapNode(currentPageUrl);
if (siteMapNode == null)
return;
if (siteMapNode.Roles.Count == 0)
return;
var IsInRole = false;
foreach (var role in siteMapNode.Roles)
{
IsInRole = filterContext.HttpContext.User.IsInRole(role);
if (IsInRole)
break;
}
if (!IsInRole)
{
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary(new { controller = "User", action = "Unauthorized" })
);
}
}
private string GetPageUrl(ActionExecutingContext filterContext)
{
string controllerName = filterContext.Controller.GetType().Name;
controllerName = controllerName.Substring(0, controllerName.Length - 10);
string actionName = filterContext.ActionDescriptor.ActionName;
return string.Format("/{0}/{1}", controllerName, actionName);
}
override OnActionExecuting pada BaseController seperti ini.
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
var currentPageUrl = GetPageUrl(filterContext);
CheckAuthorization(filterContext, currentPageUrl);
}