ASP.NET MVCはじめました~権限による多階層ドロップダウンメニューの表示切り替え
概要
前回の続きというか、前回忘れていた権限によるメニューの表示切り替えです。
環境
- Visual Studio Community 2015
- ASP.NET MVC5
- BootStrap 3.3.7
- jQuery 3.1.1
権限項目の追加
右端のRoleId項目を追加しました。権限の種類(サンプルではユーザ、チョットできるユーザ、管理者の3種類)ごとに値を設定しておきます。あとはログインユーザごとの権限でメニューの項目を取得してくればOK。
■/Models/MenuItem.cs
public enum Role { User, PowerUser, Administrator, } public class Navigation { public Navigation() : this(role: Role.User) { } public Navigation(Role role) { this.RoleId = (int)role; } public int RoleId { get; } public IEnumerable<NavigationLink> Menu { get { var menu = new List<NavigationLink>(); using (var context = new AppDbContext()) { var data = context.MenuItems .Where(n => n.RoleId <= RoleId) .OrderBy(n => n.Order) .ToList(); var mainMenu = data.Where(n => n.ParentId == null); foreach (var item in mainMenu) { NavigationLink linkItem = MappingLinkItems(item); menu.Add(linkItem); } SetChildMenu(menu, data); } return menu; } } #region helper method... }
実行結果
User権限の場合
RoleIdが"0"以下の項目のみなので、メインメニューの3つだけ。
Power User権限の場合
RoleIdが"1"以下の項目のみで、3階層分の一部だけ。
Administrator権限の場合
RoleIdが"2"以下の項目のみ、つまり全部表示。
ASP.NET MVCはじめました~データベースで管理する BootStrap 多階層ドロップダウンメニュー
概要
ログインユーザの権限などで表示項目が変わるような動的なメニューが欲しかったので作ってみました。2階層くらい表現できれば十分かとも思ったのですが、多階層ドロップダウンメニューにチャレンジしてみます。
環境
- Visual Studio Community 2015
- ASP.NET MVC5
- BootStrap 3.3.7
- jQuery 3.1.1
多階層ドロップダウンメニューの作成
まずはBootStrapで多階層のドロップダウンメニューを実装してみました。CSSはこちらから拝借。CSSとViewを書くだけで実装できました。この時点ではリンクリストは直書き。
■/Views/Shared/_Layout.cshtml
<div class="navbar-collapse collapse"> <ul class="nav navbar-nav"> <li>@Html.ActionLink("Home", "Index", "Home")</li> <li>@Html.ActionLink("About", "About", "Home")</li> <li>@Html.ActionLink("Contact", "Contact", "Home")</li> <li class="dropdown"> <a class="dropdown-toggle" data-toggle="dropdown" href="#" role="button"> サンプル<span class="caret"></span> </a> <ul class="dropdown-menu" role="menu"> <li>@Html.ActionLink("Upload", "Upload", "Home")</li> <li>@Html.ActionLink("Autocomplete", "Create", "Home")</li> <li><a href="#">リンクのリスト1</a></li> <li><a href="#">リンクのリスト2</a></li> <li><a href="#">リンクのリスト3</a></li> <li class="dropdown-submenu"> <a tableindex="-1" href="#">More options</a> <ul class="dropdown-menu"> <li><a href="http://google.com">Google</a></li> <li><a href="http://yahoo.com">Yahoo</a></li> </ul> </li> </ul> </li> </ul> </div>
3階層までだとこんな感じに。固定でよければ簡単に実装できますね!
データベースよりメニューデータを取得
動的なメニューを実現するにはメンテナンス的にもデータベースでデータを持ってた方がいいんじゃないのってことで、ParentIdによる階層関係のデータを持ったテーブルを用意してみました。これで何階層でも行ける↑↑↑あとはビューヘルパーのActionLinkメソッドを使いたいのでリンクテキストやらアクションメソッドと並び順で構成。
これらのデータを一旦全部取得して、再帰関数で階層構造データになるように再構成。LINQで一発でできそうな気がすると思ったのですが、無念…。いつかちゃんと調べる。
■/Models/MenuItem.cs
public class MenuItem { [Key] public int Id { get; set; } public int? ParentId { get; set; } [Required] public string LinkText { get; set; } public string ActionName { get; set; } public string ControllerName { get; set; } public int Order { get; set; } } public class NavigationLink : MenuItem { public IEnumerable<NavigationLink> ChildMenu { get; set; } } public class Navigation { public IEnumerable<NavigationLink> Menu { get { var menu = new List<NavigationLink>(); using (var context = new AppDbContext()) { var data = context.MenuItems .OrderBy(n => n.Order) .ToList(); var mainMenu = data.Where(n => n.ParentId == null); foreach (var item in mainMenu) { NavigationLink linkItem = MappingLinkItems(item); menu.Add(linkItem); } SetChildMenu(menu, data); } return menu; } } #region helper method... }
カスタムビューヘルパーで描画
TagBuilderを使ったカスタムビューヘルパーを作ってみたのですが、思ってたより複雑になったのでサブメニュー部分だけ実装したらいまいちな感じになってしまいました。本当はこれもヘルパー一発で描画したかった。
■/Helper/NavigationMenuHelper.cs
public static class NavigationMenuHelper { public static IHtmlString NavigationSubmenuListFor(this HtmlHelper helper, IEnumerable<NavigationLink> submenu) { if (submenu == null) { throw new ArgumentNullException("submenu", "submenu is null."); } var sb = new StringBuilder(); foreach (var item in submenu) { if (item.ChildMenu == null) { var li = new TagBuilder("li"); li.InnerHtml = helper.ActionLink(item.LinkText, item.ActionName, item.ControllerName).ToString(); sb.Append(li.ToString(TagRenderMode.Normal)); } else { sb.Append(CreateSubMenu(helper, item.LinkText, item.ChildMenu)); } } return MvcHtmlString.Create(sb.ToString()); } private static string CreateSubMenu(HtmlHelper helper, string linkText, IEnumerable<NavigationLink> subMenu) { var a = new TagBuilder("a"); var attributes = new Dictionary<string, string> { ["tableindex"] = "-1", ["href"] = "#", }; a.MergeAttributes(attributes); a.InnerHtml = linkText; var top_li = new TagBuilder("li"); top_li.MergeAttribute("class", "dropdown-submenu"); top_li.InnerHtml = a.ToString(); var ul = new TagBuilder("ul"); ul.MergeAttribute("class", "dropdown-menu"); foreach (var item in subMenu) { if (item.ChildMenu == null) { var li = new TagBuilder("li"); li.InnerHtml = helper.ActionLink(item.LinkText, item.ActionName, item.ControllerName).ToString(); ul.InnerHtml += li.ToString(); } else { ul.InnerHtml += CreateSubMenu(helper, item.LinkText, item.ChildMenu); } } top_li.InnerHtml += ul.ToString(); return top_li.ToString(); } }
■/Views/Shared/_Layout.cshtml
<div class="navbar-collapse collapse"> <ul class="nav navbar-nav"> @foreach (var item in Session["menu"] as List<NavigationLink>) { if (item.ChildMenu == null) { <li>@Html.ActionLink(item.LinkText, item.ActionName, item.ControllerName)</li> } else { <li class="dropdown"> <a class="dropdown-toggle" data-toggle="dropdown" href="#" role="button" aria-expanded="false"> @item.LinkText <span class="caret"></span> </a> <ul class="dropdown-menu" role="menu"> @Html.NavigationSubmenuListFor(item.ChildMenu) </ul> </li> } } </ul> </div>
NLogを使ってみる
ファイルとコンソールに出力
コンソールアプリケーションでログ出力
ログレベルごとにログメッセージを書き込みます。
using NLog; using System; namespace NLogConsole { class Program { private static Logger logger = LogManager.GetCurrentClassLogger(); static void Main(string[] args) { logger.Trace("Sample trace message"); logger.Debug("Sample debug message"); logger.Info("Sample informational message"); logger.Warn("Sample warning message"); logger.Error("Sample error message"); logger.Fatal("Sample fatal error message"); Console.ReadKey(true); } } }
コンフィグ
NLog.configファイルにログ出力先としてファイルとコンソールを指定して、それぞれにログレベルを設定します。
<?xml version="1.0" encoding="utf-8" ?> <nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.nlog-project.org/schemas/NLog.xsd NLog.xsd" autoReload="true" throwExceptions="false" internalLogLevel="Off" internalLogFile="c:\temp\nlog-internal.log"> <targets> <target xsi:type="File" name="logfile" fileName="file.txt" layout="${date:format=yyyyMMddHHmmss} ${message}" /> <target xsi:type="ColoredConsole" name="console" layout="${longdate} ${uppercase:${level}} ${message}" /> </targets> <rules> <logger name="*" minlevel="Info" writeTo="logfile" /> <logger name="*" minlevel="Trace" writeTo="console" /> </rules> </nlog>
実行結果
ファイルは直下にInfoログ以上が出力されました。
layout="${date:format=yyyyMMddHHmmss} ${message}"
と指定したフォーマットで日付も出力されてます。
ColoredConsoleを指定したので、コンソールには色付きでTraceログ以上が出力されました。出力内容もコンソール用の内容layout="${longdate} ${uppercase:${level}} ${message}"
になってますね。
Gmailに出力
Gmailにも遅れるようなのでやってみます。
NLog.config
targetsにGmail用の設定を追記します。メールアドレスは適宜読み替えで。パスワードはGmailの二段階認証でアプリパスワードを発行しました。
このとき二段階認証にしてたので別のアプリパスワードを発行。
sh-yoshida.hatenablog.com
<target xsi:type="Mail" name="gmail" smtpServer="smtp.gmail.com" smtpPort="587" smtpAuthentication="Basic" smtpUserName="user@gmail.com" smtpPassword="password" enableSsl="true" from="user@gmail.com" to="user@gmail.com" cc="user@gmail.com" />
rulesにはログレベルにFatalを指定して追記しました。
<logger name="*" minlevel="Fatal" writeTo="gmail" />
実行結果
Fatalレベルのログだけメールで飛んできました。簡単♪
今回はこれくらいで。あとは実際に使うかもってなったらもう少し詳しく踏み込んで調べてみよう。