ایجاد Menu اتوماتیک ازکنترلرها در MVC

دوشنبه 28 فروردین 1396

شما می توانیدمنوی سایت خود را در ASP.NET MVC به صورت اتوماتیک, و با استفاده از ویژگی هایی که روی کنترلرها اعمال میشود بسازید. در این روش دیگر نیاز به نوشتن کدهای HTML برای ایجاد منو ندارید و کنترلر برای شما یک منوی امن و زیبا می سازد.

ایجاد Menu اتوماتیک ازکنترلرها در MVC

مقدمه

این ایده زمانی آغاز شد که ما میخواستیم آیتم های منو را ایجاد کنیم اما نه با برنامه نویسی (hardcode) یا با استفاده از پایگاه داده ، چرا که می دانستیم در برنامه mvc هر ویو( یا صفحه ای که شما بخواهید) توسط متد های موجود در کنترلر(Contoroller)  قابل ارائه است. بنابراین ما استفاده از ویژگی ها (attributes ) و بازتاب آنها را برای این کار انتخاب کردیم .

مزایا

1-از هارد کد برای نام منو استفاده نشده است.

2-فقط با استفاده از ویژگی ها و متد هایی که روی کنترلر و یا متد های اکشن وجود دارد می توانید منو را حذف یا اضافه کنید.

3-منو محدود شده است ، به عنوان مثال : تولید منو با ایجاد حق دسترسی قابل کنترل است. در اینجا ما از یک ویژگی استفاده میکنیم اما شما در صورت نیاز میتوانید ویژگی های بیشتری را اضافه کنید(در این صورت باید در کد تغییراتی ایجاد کنید.)

4-سفارشی سازی منو قابل کنترل است.

5-ارتقا متدهای اکشن به عنوان یک منوی اصلی(در این صورت روی کنترلر ویژگی(attribute) گذاشته نمی شود.

6-منوی غیر متحرک ایجاد کنید. برای مثال : نام کنترلر به عنوان یک منوی سطح بالا(منوی اصلی) به کار رفته است ، اما شما نمی خواهید با زدن گزینه منوی اصلی که شامل زیر منو است یک  صفحه را باز کنید و فقط گزینه های زیر منو قابلیت کلیک داشته باشند.

7-اضافه کردن آیکون روی منو ( با استفاده از fontawsome)

8-توسعه پذیری – اگر نیاز به ایجاد ویژگی های بیشتری روی منو دارید میتوانید این ویژگی ها را ایجاد کنید.

مهم : منبع دانلود

این کد بدون هیچ مشکلی بر روی Visual studio 2015 اجرا می شود. اگر شما این کد رو با نسخه های قدیمی تر باز کنید ممکن است مشکلاتی  ایجاد شود و برای حلش به یک سری چیزها نیاز دارید.(شاید اگر فقط فایل های مهم را کپی کنید بهتر باشد.)

این کد بدون بسته Nuget packages است. بنابراین شما باید به صورت دستی نصب رو انجام بدید در غیر اینصورت کد اجرا نمی شود.

در اینجا میتوانید طریقه نصب NuGet Packages را ببینید.

https://www.codeproject.com/KB/Articles/1130643/nuget.png

استفاده از کد :

MenuItemAttribute  نقش کلیدی را در اینجا دارد.

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
 public class MenuItemAttribute : Attribute
 {
     public MenuItemAttribute()
     {
         IsClickable = true;
     }
     public bool IsClickable { get; set; }
     public string Title { get; set; }
     public string Action { get; set; }
     public string CssIcon { get; set; }
     public int Order { get; set; }
     public Type ParentController { get; set; }
 }

همه خاصیت ها اختیاری است و شما بر اساس نیاز خود میتوانید از آنها استفاده کنید. اگر از خاصیت های این کد استفاده کنید چه اتفاقی می افتد :

IsClickable- به صورت پیش فرض trueاست.یعنی وقتی شما روی آن کلیک میکنید اجرا میشود .اگر این خاصیت را روی false تنظیم کنید با کلیک روی آن اتفاقی نمی افتد مگر اینکه به زیر منو دسترسی داشته باشد(در صورت وجود).

Title- به صورت پیش فرض نام کنترلر را بدون پسوند Controller می گیرد.مثلا “Home” برای دسترسی به کلاس HomeController استفاده می شود،نام Action در صورتیکه از متد اکشن استفاده کرده باشیدکاربرد دارد.

Action –به صورت پیش فرض “Index” است و تنها زمانی موثر است که روی کنترلر اعمال شود، روی action method تاثیری ندارد و همیشه نام اکشن( action name)را میگیرد.

CssIcon به صورت پیش فرض آیکون ندارد . شما میتوانید از آیکن های font-awesome استفاده کنید.

Order به صورت پیش فرض صفراست یعنی منو کدهای سفارشی را از کنترلر میخواند.اگر مقداری برایش تنظیم کنید به صورت صعودی مرتب میشود.

ParentController اگر بخواهید میتوانید از یک کنترلر دیگر به عنوان زیر منوی کنترلر استفاده کنید.

یک نمونه از کنترلر: 

    [MenuItem (Title = "مدیر", Order = 500,Action = "Users")]
    public class AdminController : Controller
    {
        // GET: Admin
        public ActionResult Index()
        {
            return View();
        }

        [MenuItem(Title = "کاربران", Order = 500 ,CssIcon = "fa fa-users fa-lg fa-fw")]
        [AuthorizedRole("Admin")]
        public ActionResult Users()
        {
            return View();
        }
        [MenuItem(Title = "تنظیمات", Order = 500)]
        [AuthorizedRole("Super user")]
        public ActionResult Settings()
        {
            return View();
        }
    }

خوب ، چه اتفاقی می افتد ؟

در اولین خط نام اکشن "Users"   ثبت شده است این به چه معناست؟ یعنی با زدن دکمه مدیر(Admin) در منو وارد صفحه کاربران خواهد شد.

در اکشن “Users” ، یک آیکون font-awesome تنظیم شده است ،که تنها برای کاربرانی که نقش" admin" دارند نمایش داده میشود. AuthorizedRole یکی دیگر از ویژگی های سفارشی شده است که مسئول بررسی ورود و خروج در هر زمانی است. شما می توانید کد را بررسی کنید و منطق آن را در زمان واقعی تغییر دهید.

روی اکشن"settings" عنوان نمایشی روی منو به  " Site Settings " تغییر کرده است و به کاربرانی که نقش " Super user " را دارند، محدود شده است.

این فقط یک مثال بود . اما کنترل های دیگر موجود درکد را خودتان چک کنید.

چگونه این ویژگی ها تبدیل به منو میشوند:

MenuItemAttribute روی کنترلر ها و اکشن ها اعمال می شود و با کد نویسی به صورت  داینامیک انتخاب میشود و لیستی از کلاس های  Bootstrap Menu  را جهت زیبا سازی منو برمی گرداند.

کد زیر بخشی از کلاس ساخت منو است.

    public static List<Menu> CreateMenu()
        {
            var menus = new List<Menu>();

            var currentAssembly = Assembly.GetAssembly(typeof(MenuGenerator));
            var allControllers = currentAssembly.GetTypes().Where(t => t.IsSubclassOf(typeof(Controller))).ToList();
            var menuControllers = allControllers.Where(t => t.GetCustomAttribute<MenuItemAttribute>() != null ||
                                                             t.GetMethods().Any(m => m.GetCustomAttribute<MenuItemAttribute>() != null))
                                                             .ToList();
            var submenuControllers = new List<Menu>();
            menuControllers.ForEach(controller =>
            {
                var navigation = controller.GetCustomAttribute<MenuItemAttribute>();
                if (navigation == null) //navigation is set only against actions
                {
                    controller.GetMethods().ToList().ForEach(method =>
                    {
                        navigation = method.GetCustomAttribute<MenuItemAttribute>();
                        if (navigation == null) return;
                        if (!UserHasAccess(method.GetCustomAttribute<AuthorizedRoleAttribute>())) return;
                        Menu actionMenu = CreateAreaMenuItemFromAction(controller, method, navigation);
                        menus.Add(actionMenu);
                    });
                    return;
                }

                if (!UserHasAccess(controller.GetCustomAttribute<AuthorizedRoleAttribute>())) return;
                Menu menu = CreateAreaMenuItemFromController(controller, navigation);
                if (navigation.ParentController != null)
                {
                    if (navigation.ParentController.IsSubclassOf(typeof(Controller)))
                    {
                        menu.ParentControllerFullName = navigation.ParentController.FullName;
                        submenuControllers.Add(menu);
                    }
                }
                menus.Add(menu);
            });
            menus = menus.Except(submenuControllers).ToList();
            submenuControllers.ForEach(sm =>
            {
                var parentMenu = menus.FirstOrDefault(m => m.ControllerFullName == sm.ParentControllerFullName);
                parentMenu?.SubMenus.Add(new SubMenu() { Name = sm.Name, Url = sm.Url });
            });
            return menus.OrderBy(m => m.Order).ToList();
        }

این متد از MenuController  فراخوانی می شود که از ویوی Layout   صدا زده شده است.

 public class MenuController : Controller
    {
        // GET: Menu
        public PartialViewResult Index()
        {
            List<Menu> menus = MenuGenerator.CreateMenu();
            return PartialView("Partials/_menu", menus);
        }
    }

کد منو و زیر منو :

public class Menu
    {
        public Menu()
        {
            SubMenus = new List<SubMenu>();
        }
        public string Name { get; set; }
        public string CssIcon { get; set; }
        public string Url { get; set; }
        public List<SubMenu> SubMenus { get; set; }
        public string ParentControllerFullName { get; set; }
        public string ControllerFullName { get; set; }
        public int Order { get; set; }
    }
public class SubMenu
{
    public string Name { get; set; }
    public string Url { get; set; }
    public string CssIcon { get; set; }
    public int Order { get; set; }
}

ویوی منو

@using DynamicMvcMenu.Models
@model List<DynamicMvcMenu.Models.Menu>
<ul class="nav">
    @foreach (Menu menu in Model)
    {
        <li class="dropdown">
            @if (string.IsNullOrWhiteSpace(menu.Url))
            {

                <a href="#" class="dropdown-toggle" id="dropdownCommonMenu" data-toggle="dropdown">
                    <span class="icon">
                        <i class="@menu.CssIcon" aria-hidden="true"></i>
                    </span>
                    @menu.Name
                </a>
            }
            else
            {
                <a href="@Url.Content(menu.Url)">
                    <span class="icon">
                        <i class="@menu.CssIcon" aria-hidden="true"></i>
                    </span>
                    @menu.Name
                </a>
            }
            @if (menu.SubMenus.Any())
            {
                <a class="dropdown-toggle" data-toggle="dropdown" href="#">
                    <span class="caret"></span>
                </a>
                <ul class="dropdown-menu navmenu-nav" role="menu" aria-labelledby="dropdownCommonMenu">

                    @foreach (SubMenu subMenu in menu.SubMenus)
                    {
                        <li role="menuitem">
                            <a href="@Url.Content(subMenu.Url)">
                                <span class="icon">
                                    <i class="@subMenu.CssIcon" aria-hidden="true"></i>
                                </span>
                                @subMenu.Name
                            </a>
                        </li>
                    }
                </ul>
            }

        </li>

    }
</ul>

نتیجه:


 

تنظیمات اضافه :


 

1.    شما می توانید منوی دلخواه خود را(لینک یک HTML  استاتیک یا یک سایت خارجی (از طریق افزودن به لیست منو از Menucontroller ، به منوی داینامیک اضافه کنید. 

2.    این یک نوع منوی دوسطحی است شما میتوانید بسته به نیازتان منوی سه سطحی هم ایجاد کنید.


 

بومی سازی:

شما میتوانید یک ویژگی جدید LanguageKey روی کلاس MenuItemAttribute اضافه کنید و با تغییرات سازنده آن را طوری در نظر بگیرید که یک ویژگی اجباری شود.

public MenuItemAttribute(string LangKey)
{
    IsClickable = true;
    LanguageKey = LangKey;
            
}


خوب حالا کلاس های Menu  و SubMenu  نامشان را ازlangaugekey میگیرند که برای بازیابی متن بر اساس اولویت کاربران مورد استفاده قرار گیرد. شما می توانید از یک فایل منبع و یا دیتابیس (یا هر چیز دیگری) برای خواندن مقادیرKey استفاده کنید.

LanguageService


public class LanguageService
{
    public string GetText(string LangKey)
    {
       //var userlang = get user preferene from cookie or database
       //read from resourse/database or wherever you want
    }
}

و در تابع CreateMenu ، نام  ویژگی هایی از منو و زیر منو با فراخوانی متد GetText تنظیم خواهد شد.

menu.Name = LangaugeService.GetText(attribute.LanguageKey);

امیدوارم ساخت منوی داینامیک برای شما سودمند باشد.

آموزش asp.net mvc

فایل های ضمیمه

برنامه نویسان

نویسنده 3355 مقاله در برنامه نویسان

کاربرانی که از نویسنده این مقاله تشکر کرده اند

در صورتی که در رابطه با این مقاله سوالی دارید، در تاپیک های انجمن مطرح کنید