همانطور که میدانید در ASP.NET MVC برای کنترل دسترسی به یک Controller یا Action از فیلتر Authorize  استفاده میشود.

[Authorize(Roles = "Admin")]
public class HomeController : Controller
{
    public ActionResult Index()
    {
        return View();
    }
}

در مثال بالا تنها کاربرانی به HomeController دسترسی دارند که دارای نقش Admin باشند. اما در پروژه ای که دارای بخشهای مختلف و کاربرانی با نقشهای مختلف است دیگر هارد کد کردن نقش در داخل کد دیگر جوابگو نیست. در این مقاله قصد دارم تا نحوه تعریف نقش و تعیین سطوح دسترسی برای نقش بصورت دینامیک و اعطای نقش به کاربران با استفاده از ASP.NET Identity 2 را بیان کنم. با این مقدمه کوتاه میرم به سراغ اصل مطلب.

یک پروژه وب از نوع MVC در Visual Studio ایجاد نماید. در پروژه تمام Controller و Action را مزین به خاصیت Description نمایید.

[Description("خانه")]
public class HomeController : Controller
{
    [Description("صفحه اصلی")]
    public ActionResult Index()
    {
        return View();
    }

    [Description("درباره")]
    public ActionResult About()
    {
        ViewBag.Message = "Your application description page.";

        return View();
    }

    [Description("ارتباط")]
    public ActionResult Contact()
    {
        ViewBag.Message = "Your contact page.";

        return View();
    }
}

هنگامی که میخواهیم تعیین کنیم که نقش به کدام Controller وAction دسترسی دارد، برای نمایش لیست Controllerها و Actionها به جای نشان دادن HomeController به کاربر نام خانه را نشان میدهیم. بعد از انجام این کار به کلاسی نیاز داریم که لیست تمامی Controller و Actionها را برگرداند.

/// <summary>
/// A Utility class for MVC controllers.
/// </summary>
public class ControllerHelper
{
    /// <summary>
    /// Gets the controllers name an description with their actions.
    /// </summary>
    /// <param name="filter">The filter.</param>
    /// <returns>Lazy&lt;IEnumerable&lt;ControllerDescription&gt;&gt;.</returns>
    public IEnumerable<ControllerDescription> GetControllersNameAnDescription(string filter = null)
    {
        var assembly = Assembly.GetCallingAssembly();
        var controllers = assembly.GetTypes()
            .Where(type => type.IsSubclassOf(typeof(Controller)))
            .ToList();

        if (!string.IsNullOrWhiteSpace(filter))
            controllers = controllers.Where(t => !t.Name.Contains(filter)).ToList();

        var controllerList = (
                from controller in controllers
                let attribute = (DescriptionAttribute)Attribute.GetCustomAttribute(controller, typeof(DescriptionAttribute))
                let ctrlDescription = attribute == null ? "" : attribute.Description
                select new ControllerDescription
                {
                    Name = controller.Name.Replace("Controller", ""),
                    Description = ctrlDescription,
                    Actions = GetActionList(controller)
                }
            ).ToList();

        return controllerList;
    }

    /// <summary>
    /// Gets the action list.
    /// </summary>
    /// <param name="controllerType">Type of the controller.</param>
    /// <returns>IEnumerable&lt;ActionDescription&gt;.</returns>
    private static IEnumerable<ActionDescription> GetActionList(Type controllerType)
    {
        var actions = new ReflectedControllerDescriptor(controllerType).GetCanonicalActions().ToList();

        var actionList = (from actionDescriptor in actions
                          let attribute = actionDescriptor.GetCustomAttributes(typeof(DescriptionAttribute), false).LastOrDefault() as DescriptionAttribute
                          let acnDescription = attribute == null ? "" : attribute.Description
                          where attribute != null
                          select new ActionDescription { Name = actionDescriptor.ActionName, Description = acnDescription }).ToList();

        return actionList;
    }
}

/// <summary>
///  Controller description class.
/// </summary>
public class ControllerDescription
{
    /// <summary>
    /// Gets or sets the name of controller.
    /// </summary>
    /// <value>The name.</value>
    public string Name { get; set; }

    /// <summary>
    /// Gets or sets the description of controller.
    /// </summary>
    /// <value>The description.</value>
    public string Description { get; set; }

    /// <summary>
    /// Gets or sets the actions.
    /// </summary>
    /// <value>The actions.</value>
    public IEnumerable<ActionDescription> Actions { get; set; }
}

/// <summary>
/// Action description class.
/// </summary>
public class ActionDescription
{
    /// <summary>
    /// Gets or sets the name of action.
    /// </summary>
    /// <value>The name.</value>
    public string Name { get; set; }

    /// <summary>
    /// Gets or sets the description of action.
    /// </summary>
    /// <value>The description.</value>
    public string Description { get; set; }
}

کلاس بالا با استفاده از Reflection لیست تمامی Controllerها به همراه Actionهای متناظر با آن را برمیگرداند. اگر نسخه Visual Studio شما 2013 بهمراه Update 4 باشد، پروژه وبی که ساخته اید نسخه MVC آن 5 و از ASP.NET Identity 2 در AccountController برای احراز هویت با استفاده از میان افزار Owin استفاده شده است. حالا به سراغ ایجاد نقش میرویم. درپوشه مدل فایل IdentityModels.cs را باز کنید و کلاسی با نام ApplicationRole به آن اضافه نماید. بدنه کلاس به شکل زیر است:

public class ApplicationRole : IdentityRole
{
    public virtual ICollection<RoleAccess> RoleAccesses { get; set; }
}

کلاس دیگری را با نام RoleAccess اضافه کنید. بدنه کلاس به شکل زیر است:

public class RoleAccess
{
    public int Id { get; set; }

    public string Controller { get; set; }

    public string Action { get; set; }
    
    public string RoleId { get; set;} 
    
    public virtual ApplicationRole Role { get; set;} 
}

رابطه کلاس ApplicationRole با RoleAccess یک به چند است و اینکه یک Role به کدام Controller و Action دسترسی دارد، با استفاده از RoleAccess مشخص میگردد. کلاس ApplicationDbContext را که در همین فایل IdentityModels.cs قرار دارد، باز کنید و آنرا همانند زیر تغییر دهید:

public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
    public ApplicationDbContext()
        : base("DefaultConnection", throwIfV1Schema: false)
    {
    }

    public static ApplicationDbContext Create()
    {
        return new ApplicationDbContext();
    }

    public DbSet<RoleAccess> RoleAccesses { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<RoleAccess>().Property(ra => ra.Action)
            .IsUnicode(false).HasMaxLength(70).IsRequired();
        modelBuilder.Entity<RoleAccess>().Property(ra => ra.Controller)
            .IsUnicode(false).HasMaxLength(70).IsRequired();
        modelBuilder.Entity<RoleAccess>().HasRequired(ra => ra.Role)
            .WithMany(r => r.RoleAccesses)
            .HasForeignKey(ra => ra.RoleId);

        base.OnModelCreating(modelBuilder);
    }
}

پس از انجام تغییرات پوشه App_Start را باز کنید سپس IdentityConfig.cs را باز کنید و کلاس ApplicationRoleManager را به آن اضافه کنید:

public class ApplicationRoleManager : RoleManager<ApplicationRole>
{
    public ApplicationRoleManager(IRoleStore<ApplicationRole, string> store)
        : base(store)
    {
    }

    public static ApplicationRoleManager Create(IdentityFactoryOptions<ApplicationRoleManager> options, IOwinContext context)
    {
        return new ApplicationRoleManager(new RoleStore<ApplicationRole>(context.Get<ApplicationDbContext>()));
    }
}

از کلاس ApplicationRoleManager همانند کلاسهای ApplicationUserManager و ApplicationSignInManager برای مدیریت نقشها استفاده میکنیم. حالا فایل Startup.Auth.cs را باز کنید ودر داخل کلاس Startup به متد ConfigureAuth کد زیر اضافه نمایید:

public partial class Startup
{
    public void ConfigureAuth(IAppBuilder app)
    {
        // Configure the db context, user manager and signin manager to use a single instance per request
        app.CreatePerOwinContext(ApplicationDbContext.Create);
        app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
        app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
        
        //Add this line
        app.CreatePerOwinContext<ApplicationRoleManager>(ApplicationRoleManager.Create);

از این خط کد برا تزریق وابستگی کلاس ApplicationRoleManager توسط Owin استفاده مکنیم. حالا نوبت به ایجاد کنترلر نقش است. به پوشه Controllers کنترلر جدیدی از نوع MVC 5 Controller - Empty به نام RoleController اضافه نمایید. بدنه کنترلر به شکل زیر است:

<code>
public class RoleController : Controller
{
    private ApplicationRoleManager _roleManager;

    public RoleController()
    {
    }

    public RoleController(ApplicationRoleManager roleManager)
    {
        RoleManager = roleManager;
    }

    public ApplicationRoleManager RoleManager
    {
        get { return _roleManager ?? HttpContext.GetOwinContext().Get<ApplicationRoleManager>(); }
        private set { _roleManager = value; }
    }
}

به پوشه Models باز گردید و فایل AccountViewModels.cs را باز کنید و کلاس CreateRoleViewModel را به آن اضافه کنید:

public class CreateRoleViewModel
{
    [Required]
    [StringLength(256, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)]
    public string Name { get; set; }

    public IEnumerable<ControllerDescription> SelectedControllers { get; set; }

    public IEnumerable<ControllerDescription> Controllers { get; set; }
}

حالا به RoleController باز میگردیم و اکشن Create را به آن اضافه میکنیم:

private static Lazy<IEnumerable<ControllerDescription>> _controllerList = 
                                 new Lazy<IEnumerable<ControllerDescription>>();
public ActionResult Create()
{
    var createRoleViewModel = new CreateRoleViewModel
    {
        Controllers = GetControllers()
    };
    return View(createRoleViewModel);
}

private static IEnumerable<ControllerDescription> GetControllers()
{
    if (_controllerList.IsValueCreated)
        return _controllerList.Value;

    _controllerList = new Lazy<IEnumerable<ControllerDescription>>(() => new ControllerHelper().GetControllersNameAnDescription());
    return _controllerList.Value;
}

چون هنگام گرفتن لیست Controller و Actionها از Reflection استفاده مکنیم و برای کم کردن هزینه Reflection لیست  Controller و Actionها در یک متغیر static ذخیره کرده ایم که هر بار برای گرفتن این لیست از Reflection استفاده نکنیم و با استفاده از خاصیت Lazy بودن چک میکنیم که آیا متغیر در اولین درخواست مقدار دهی شده است یا نه.

به سراغ ایجاد View میرویم. برای نمایش ساختار درخت مانند Controller و Action از یک کتابخانه JavaScript به نام jquery-bonsai استفاده میکنیم. بدنه Create View به شکل زیر است:

@model WebApplication2.Models.CreateRoleViewModel
@{
    ViewBag.Title = "Create Role";
    int i = 0, j = 0;
}
@section header {
    @Styles.Render("~/Content/jquery.bonsai.css")
}

<h2>Create Role</h2>

@using (Html.BeginForm("Create", "Role", FormMethod.Post, new {@class = "form-horizontal", role = "form"}))
{
    @Html.AntiForgeryToken()
    <div class="form-group">
        @Html.LabelFor(m => m.Name, new {@class = "col-md-2 control-label"})
        <div class="col-md-10">
            @Html.TextBoxFor(m => m.Name, new {@class = "form-control"})
            @Html.ValidationMessageFor(m => m.Name, null, new {@class = "danger"})
        </div>
    </div>

    <div class="form-group">
        <label class="col-md-2 control-label">Access List</label>
        <div class="col-md-10">
            <ol id="tree">
                @foreach (var controller in Model.Controllers)
                {
                    string name;
                    {
                        name = string.IsNullOrWhiteSpace(controller.Description) ? controller.Name : controller.Description;
                    }
                    <li class="controller" data-value="@controller.Name">
                        @name
                        @if (controller.Actions.Any())
                        {
                            <ul>
                                @foreach (var action in controller.Actions)
                                {
                                    {
                                        name = string.IsNullOrWhiteSpace(action.Description) ? action.Name : action.Description;
                                    }
                                    <li data-value="@action.Name">@name</li>
                                    j++;
                                }
                            </ul>
                        }
                        @{ j = 0; }
                    </li>
                    i++;
                }
            </ol>

        </div>
    </div>

    <div class="form-group">
        <div class="col-md-offset-2 col-md-10">
            <input type="submit" class="btn btn-default" value="Create"/>
        </div>
    </div>
}
@section scripts{
    @Scripts.Render("~/Scripts/jquery.bonsai.js")
    <script>
        $(function() {
            $('#tree').bonsai({
                expandAll: false,
                checkboxes: true,
                createInputs: 'checkbox'
            });

            $('form').submit(function() {
                var i = 0, j = 0;
                $('.controller > input[type="checkbox"]:checked, .controller > input[type="checkbox"]:indeterminate').each(function() {
                    var ctrl = $(this);
                    if ($(ctrl).prop('indeterminate')) {
                        $(ctrl).prop("checked", true);
                    }
                    var cName = 'SelectedControllers[' + i + ']';
                    $(ctrl).prop('name', cName + '.Name');
                    $('ul > li > input[type="checkbox"]:checked', $(ctrl).parent()).each(function() {
                        var acn = $(this);
                        var aName = cName + '.Actions[' + j + '].Name';
                        $(acn).prop('name', aName);
                        j++;
                    });
                    j = 0;
                    i++;
                });

                return true;
            });
        });
    </script>
}

create role

هنگام پست شدن Form به سمت سرور، Controller و Actionهای انتخاب شده نام آنها تغییر خواهد کرد تا هنگام Bind شدن فرم به مدل، اطلاعات مربوط به SelectedControllers به درستی Bind شود. بدنه POST اکشن Create به شکل زیر است:

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(CreateRoleViewModel viewModel)
{
    if (!ModelState.IsValid)
    {
        viewModel.Controllers = GetControllers();
        return View(viewModel);
    }

    var role = new ApplicationRole
    {
        Name = viewModel.Name,
        RoleAccesses = new List<RoleAccess>()
    };

    foreach (var controller in viewModel.SelectedControllers)
    {
        foreach (var action in controller.Actions)
        {
            role.RoleAccesses.Add(new RoleAccess { Controller = controller.Name, Action = action.Name });
        }
    }

    RoleManager.Create(role);
    return RedirectToAction("Index");
}

پس از ایجاد اولین نقش چند نقش دیگر نیز ایجاد نمایید سپس به سراغ اعطای نقش به کاربر میرویم. اما قبل از اینکار یک کاربر ایجاد نمایید. بعد از ایجاد کاربر فایل AccountViewModels.cs را باز نمایید و کلاس UserRoleViewModel را اضافه نمایید. بدنه این کلاس به شکل زیر است:

public class UserRoleViewModel
{
    public string UserId { get; set; }

    public string UserName { get; set; }

    public IEnumerable<string> Roles { get; set; }
}

Controller جدیدی با نام AccessController برای اعطای دسترسی به کاربر اضافه نمایید. سپس اکشن Index را که لیست کاربرها به همراه نقش شان را برمی گرداند اضافه می نماییم:

[Description("دسترسی")]
public class AccessController : Controller
{
    private ApplicationDbContext _dbContext;

    // GET: Access
    [Description("لیست دسترسی ها")]
    public async Task<ActionResult> Index()
    {
        _dbContext = HttpContext.GetOwinContext().Get<ApplicationDbContext>();
        var users = await (from u in _dbContext.Users
            select new
            {
                UserId = u.Id,
                u.UserName,
                Roles = _dbContext.Roles.Where(r => u.Roles.Any(ur => ur.RoleId == r.Id)).Select(r => r.Name)
            }).Select(ra => new UserRoleViewModel
            {
                UserId = ra.UserId,
                UserName = ra.UserName,
                Roles = ra.Roles
            }).ToListAsync();

        return View(users);
    }
}

بدنه View Index به شکل زیر است:

@model IEnumerable<WebApplication2.Models.UserRoleViewModel>
@{
    ViewBag.Title = "Access List";
}

<h2>Access List</h2>
<table class="table table-bordered">
    <thead>
        <tr>
            <th>
                @Html.DisplayNameFor(m => m.UserName)
            </th>
            <th>
                @Html.DisplayNameFor(m => m.Roles)
            </th>
            <th> </th>
        </tr>
    </thead>
    <tbody>
        @foreach (var user in Model)
            {
            <tr>
                <td>@Html.DisplayFor(m => user.UserName)</td>
                <td>
                    @foreach (var role in user.Roles)
                    {
                        <span>@Html.DisplayFor(m => role)  </span>
                    }
                </td>
                <td>
                    @Html.ActionLink("Edit", "Edit", new { id = user.UserId })
                </td>
            </tr>
        }
    </tbody>
</table>

پروژه را اجرا نمایید و تا View در مرورگر مشاهده نمایید.

access list

فایل AccountViewModels.cs را باز نمایید و کلاس EditUserRoleViewModel را اضافه نمایید. بدنه این کلاس به شکل زیر است:

public class EditUserRoleViewModel
{
    [Required]
    public string UserId { get; set; }

    public string UserName { get; set; }

    [Required]
    public IEnumerable<string> SelectedRoles { get; set; }

    public IEnumerable<IdentityRole> Roles { get; set; }
}

حالا نوبت به اضافه کردن Action ویرایش دسترسی است. یک Action جدید به نام Edit به AccessController اضافه نمایید. در این Action کاربری  که قصد ویرایش نقشهای آن را داریم بر اساس Id از دیتابیش واکشی مکنیم. بدنه Action به شکل زیر است:

// GET: Access/Edit
[Description("ویرایش دسترسی")]
public async Task<ActionResult> Edit(string id)
{
    _dbContext = HttpContext.GetOwinContext().Get<ApplicationDbContext>();
    var user = await (from u in _dbContext.Users
                        select new
                        {
                            UserId = u.Id,
                            u.UserName,
                            Roles = _dbContext.Roles.Where(r => u.Roles.Any(ur => ur.RoleId == r.Id)).Select(r => r.Name)
                        }).SingleOrDefaultAsync(u => u.UserId == id);
    if (user == null)
        return HttpNotFound();

    var roles = await _dbContext.Roles.ToListAsync();

    var viewModel = new EditUserAccessViewModel
    {
        UserId = user.UserId,
        UserName = user.UserName,
        SelectedRoles = user.Roles,
        Roles = roles
    };

    return View(viewModel);
}

سپس View Edit را اضافه نمایید. بدنه View به شکل زیر است:

@model WebApplication2.Models.EditUserRoleViewModel
@{
    ViewBag.Title = "ویرایش دسترسی کاربر<";
}

<h2>ویرایش دسترسی کاربر</h2>
@using (Html.BeginForm("Edit", "Access", FormMethod.Post, new {@class = "form-horizontal", role = "form"}))
{
    @Html.AntiForgeryToken()
    @Html.HiddenFor(m => m.UserId)
    @Html.HiddenFor(m => m.UserName)
    @Html.ValidationSummary()
    <div class="form-group">
        @Html.LabelFor(m => m.UserName, new {@class = "col-md-2 control-label"})
        <div class="col-md-10">
            @Html.DisplayFor(m => m.UserName)
        </div>
    </div>

    <div class="form-group">
        <label class="col-md-2 control-label">Roles</label>
        <div class="col-md-10">
            @foreach (var role in Model.Roles)
            {
                <label><input type="checkbox" name="SelectedRoles[]" value="@role.Id" @if (Model.SelectedRoles.Contains(role.Name)) { <text> checked="checked" </text> }/> @role.Name  </label>
            }
        </div>
    </div>

    <div class="form-group">
        <div class="col-md-offset-2 col-md-10">
            <input type="submit" class="btn btn-default" value="ویرایش"/>
        </div>
    </div>
}

edit access

حالا متد POST اکشن Edit را اضافه منماییم:

// POST: Access/Edit
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Edit(EditUserRoleViewModel viewModel)
{
    _dbContext = HttpContext.GetOwinContext().Get<ApplicationDbContext>();
    if (!ModelState.IsValid)
    {
        viewModel.Roles = await _dbContext.Roles.ToListAsync();
        return View(viewModel);
    }

    var user = _dbContext.Users.Find(viewModel.UserId);
    user.Roles.Clear();
    foreach (var roleId in viewModel.SelectedRoles)
    {
        user.Roles.Add(new IdentityUserRole { RoleId = roleId });
    }
    await _dbContext.SaveChangesAsync();

    return RedirectToAction("Index");
}

پس از اعطای دسترسی به کاربر حالا نوبت به چک کردن درخواستهای کاربربرای دسترسی به Controller و Actionها میباشد. پوشه ای به نام Filters به پروژه اضافه کنید سپس کلاسی به نام CustomAuthorizeAttribute به این پوشه اضافه کنید. برای چک کردن دسترسیها از این به بعد به جای مزین کردن Controller و Actionها با فیلتر [Authorize] از [CustomAuthorize] استفاده میکنیم. کلاس از CustomAuthorizeAttribute از AuthorizeAttribute ارثبری خواهد کرد.

public class CustomAuthorizeAttribute : AuthorizeAttribute
{
}

متد OnAuthorization را override میکنیم تا Controller و Actionی که درخواست دسترسی به آن رسیده است را بدست بیاوریم:

private string _requestControllerName;
private string _requestedActionName;

public override void OnAuthorization(AuthorizationContext filterContext)
{
    _requestControllerName = filterContext.ActionDescriptor.ControllerDescriptor.ControllerName;
    _requestedActionName = filterContext.ActionDescriptor.ActionName;

    base.OnAuthorization(filterContext);
}

سپس متد AuthorizeCore را override میکنیم تا بررسی کنیم که آیا کاربر جاری میتواند به Controller و Action مورد درخواست دسترسی داشته باشد یا نه:

protected override bool AuthorizeCore(HttpContextBase httpContext)
{
    if (httpContext == null)
        throw new ArgumentNullException("httpContext");

    var user = httpContext.User;
    if (!user.Identity.IsAuthenticated)
        return false;

    var dbContext = httpContext.GetOwinContext().Get<ApplicationDbContext>();
    var roleAccess = from ra in dbContext.RoleAccesses
                        let userId = dbContext.Users.FirstOrDefault(u => u.UserName == user.Identity.Name).Id
                        let roleIds = dbContext.Roles.Where(r => r.Users.Any(u => u.UserId == userId)).Select(r => r.Id)
                        where roleIds.Contains(ra.RoleId)
                        select ra;

    if (roleAccess.Any(ra =>
        ra.Controller.Equals(_requestControllerName, StringComparison.InvariantCultureIgnoreCase) &&
        ra.Action.Equals(_requestedActionName, StringComparison.InvariantCultureIgnoreCase)))
        return true;

    return false;
}

سپس متد HandleUnauthorizedRequest را override میکنیم تا هنگامی که کاربر دسترسی نداشت HTTP 403 Forbidden را برگردانیم:

protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
    if (filterContext.HttpContext.Request.IsAuthenticated)
    {
        filterContext.Result = new HttpStatusCodeResult(403);
        return;
    }

    base.HandleUnauthorizedRequest(filterContext);
}

حالا برای هر Controller یا Action  که خواستید دسترسی کاربر را بررسی نمایید آن Controller یا Action را مزین به فیلتر [CustomAuthorize] نمایید. بر روی یکی از Controllerها و یا یکی از Action‌ها که کاربر دارای هیچ نقشی برای دسترسی آن نیست  فیلتر [CustomAuthorize] را بگذارید سپس پروژه را اجرا کنید. هنگام مشاهده با پیغام HTTP Error 403.0 - Forbidden روبرو خواهید شد.

http error 403

حالا نوبت به سمت کلاینت میرسد. هنگام ایجاد لینکها در Viewها باید بررسی کنیم که کدام لینکها با توجه به دسترسی های کاربر میتواند برای آن ایجاد گردد. Extension جدیدی برای کلاس HtmlHelper مینویسیم که از آن به جای ActionLink برای ایجاد لینکها استفاده میکنیم. پوشه جدیدی با نام Extensions به پروژه اضافه نماید سپس کلاسی با نام HtmlExtensions به پوشه اضافه کنید. به کلاس HtmlExtensions متدی به نام SecureActionLink اضافه میکنیم که برای ایجاد لینکها به جای ActionLink از آن آستفاده مکنیم.

public static class HtmlExtensions
{
    public static MvcHtmlString SecureActionLink(this HtmlHelper htmlHelper, string linkText, string actionName)
    {
        return SecureActionLink(htmlHelper, linkText, actionName, null, new RouteValueDictionary(), new RouteValueDictionary());
    }

    public static MvcHtmlString SecureActionLink(this HtmlHelper htmlHelper, string linkText, string actionName, object routeValues)
    {
        return SecureActionLink(htmlHelper, linkText, actionName, null, routeValues, (IDictionary<string, object>)new RouteValueDictionary());
    }

    public static MvcHtmlString SecureActionLink(this HtmlHelper htmlHelper, string linkText, string actionName, object routeValues, object htmlAttributes)
    {
        return SecureActionLink(htmlHelper, linkText, actionName, null, routeValues, htmlAttributes);
    }

    public static MvcHtmlString SecureActionLink(this HtmlHelper htmlHelper, string linkText, string actionName, RouteValueDictionary routeValues)
    {
        return SecureActionLink(htmlHelper, linkText, actionName, null, routeValues, new RouteValueDictionary());
    }

    public static MvcHtmlString SecureActionLink(this HtmlHelper htmlHelper, string linkText, string actionName, RouteValueDictionary routeValues, IDictionary<string, object> htmlAttributes)
    {
        return SecureActionLink(htmlHelper, linkText, actionName, null, routeValues, htmlAttributes);
    }

    public static MvcHtmlString SecureActionLink(this HtmlHelper htmlHelper, string linkText, string actionName, string controllerName)
    {
        return SecureActionLink(htmlHelper, linkText, actionName, controllerName, new RouteValueDictionary(), new RouteValueDictionary());
    }

    public static MvcHtmlString SecureActionLink(this HtmlHelper htmlHelper, string linkText, string actionName, string controllerName, object routeValues, object htmlAttributes)
    {
        //var url = htmlHelper.ActionLink(linkText, actionName, controllerName, routeValues, htmlAttributes);

        return CanAccess(htmlHelper, actionName, controllerName)
            ? htmlHelper.ActionLink(linkText, actionName, controllerName, routeValues, htmlAttributes)
            : new MvcHtmlString(string.Empty);
    }

    public static MvcHtmlString SecureActionLink(this HtmlHelper htmlHelper, string linkText, string actionName, string controllerName, RouteValueDictionary routeValues, IDictionary<string, object> htmlAttributes)
    {
        return CanAccess(htmlHelper, actionName, controllerName)
            ? htmlHelper.ActionLink(linkText, actionName, controllerName, routeValues, htmlAttributes)
            : new MvcHtmlString(string.Empty);
    }

    public static MvcHtmlString SecureActionLink(this HtmlHelper htmlHelper, string linkText, string actionName, string controllerName, string protocol, string hostName, string fragment, object routeValues, object htmlAttributes)
    {
        return CanAccess(htmlHelper, actionName, controllerName)
            ? htmlHelper.ActionLink(linkText, actionName, controllerName, protocol, hostName, fragment, routeValues, htmlAttributes)
            : new MvcHtmlString(string.Empty);
    }

    private static bool CanAccess(HtmlHelper htmlHelper, string actionName, string controllerName)
    {
        var httpContext = htmlHelper.ViewContext.HttpContext;
        var dbContext = httpContext.GetOwinContext().Get<ApplicationDbContext>();
        var user = httpContext.User;
        var roleAccess = from ra in dbContext.RoleAccesses
                            let userId = dbContext.Users.FirstOrDefault(u => u.UserName == user.Identity.Name).Id
                            let roleIds = dbContext.Roles.Where(r => r.Users.Any(u => u.UserId == userId)).Select(r => r.Id)
                            where roleIds.Contains(ra.RoleId)
                            select ra;

        if (string.IsNullOrWhiteSpace(controllerName))
            controllerName = htmlHelper.ViewContext.Controller.ToString().Split('.').Last().Replace("Controller", "");

        if (roleAccess.Any(ra =>
            ra.Controller.Equals(controllerName, StringComparison.InvariantCultureIgnoreCase) &&
            ra.Action.Equals(actionName, StringComparison.InvariantCultureIgnoreCase)))
            return true;

        return false;
    }
}

متد SecureActionLink همانند ActionLink دارای سربارهای متعددی است که از آن برای ایجاد لینک استفاده میشوند. هنگام ایجاد لینک با استفاده از متد CanAccess بررسی میشود که آیا کاربر با توجه به نقشهای آن به Controller و Actionی که لینک برای آن ایجاد میشود، دسترسی دارد یا نه. در صورت عدم دسترسی لینک ایجاد نمیگردد.

این یک نمونه ساده از پیاده سازی مدیریت دینامیک سطوح دسترسی کاربران بود. سورس کامل این پروژه را میتوانید از Github دریافت کنید.