Tag: ASP.NET

ASP.NET MVC 5 Menu using Site Map Provider & Bootstrap 3 Navbar

1. Install MvcSiteMapProvider MVC5 NuGet package. This package is a SiteMapProvider implementation for the ASP.NET MVC 5 framework.

2. In web.config, downsize the MvcSiteMapProvider configuration to the minimal. Keep it simple, stupid. (KISS)

<system.web>
  <siteMap defaultProvider="MvcSiteMapProvider">
    <providers>
      <clear />
      <add name="MvcSiteMapProvider" type="MvcSiteMapProvider.DefaultSiteMapProvider, MvcSiteMapProvider" siteMapFile="~/Mvc.Sitemap" />
    </providers>
  </siteMap>
</system.web>

3. Modify the ~\Mvc.sitemap file as the following.

<?xml version="1.0" encoding="utf-8" ?>
<mvcSiteMap xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xmlns="http://mvcsitemap.codeplex.com/schemas/MvcSiteMap-File-4.0"
            xsi:schemaLocation="http://mvcsitemap.codeplex.com/schemas/MvcSiteMap-File-4.0 MvcSiteMapSchema.xsd">
  <mvcSiteMapNode title="Home" controller="Home" action="Index">
    <mvcSiteMapNode title="About" controller="Home" action="About" />
    <mvcSiteMapNode title="Contact" controller="Home" action="Contact" />
    <mvcSiteMapNode title="Administration" clickable="false">
      <mvcSiteMapNode title="User Mgmt" controller="Home" action="UserMgmt" />
      <mvcSiteMapNode title="Role Mgmt" controller="Home" action="RoleMgmt" />
    </mvcSiteMapNode>
    <mvcSiteMapNode title="Profile" clickable="false">
      <mvcSiteMapNode title="Change Password" controller="Home" action="ChangePassword" />
      <mvcSiteMapNode title="Separator" clickable="false" />
      <mvcSiteMapNode title="Sign Off" controller="Home" action="SignOff" />
    </mvcSiteMapNode>
  </mvcSiteMapNode>
</mvcSiteMap>

4. To support submenu inside a dropdown menu, append the following CSS to ~/Content/Site.css

/* Bootstrap Dropdown Submenu */
.dropdown-submenu {
	position: relative;
}

.dropdown-submenu > .dropdown-menu {
	top: 0;
	left: 100%;
	margin-top: -6px;
	margin-left: -1px;
	-webkit-border-radius: 0 6px 6px 6px;
	-moz-border-radius: 0 6px 6px 6px;
	border-radius: 0 6px 6px 6px;
}

.dropdown-submenu:hover > .dropdown-menu {
	display: block;
}

.dropdown-submenu > a:after {
	display: block;
	content: " ";
	float: right;
	width: 0;
	height: 0;
	border-color: transparent;
	border-style: solid;
	border-width: 5px 0 5px 5px;
	border-left-color: #cccccc;
	margin-top: 5px;
	margin-right: -10px;
}

.dropdown-submenu:hover > a:after {
	border-left-color: #ffffff;
}

.dropdown-submenu.pull-left {
	float: none;
}

.dropdown-submenu.pull-left > .dropdown-menu {
	left: -100%;
	margin-left: 10px;
	-webkit-border-radius: 6px 0 6px 6px;
	-moz-border-radius: 6px 0 6px 6px;
	border-radius: 6px 0 6px 6px;
}

.navbar-nav .divider-vertical {
    height: 40px;
    margin: 0 9px;
    border-left: 1px solid #f2f2f2;
    border-right: 1px solid #ffffff;
}

5. The MvcSiteMapProvider renders a menu as a unordered list which doesn’t fit the Bootstrap’s Navbar component. We need to create a partial view to fill this gap. Add a partial view file BootstrapMenuHelperModel.cshtml under ~\Views\Shared\DisplayTemplates

@model MvcSiteMapProvider.Web.Html.Models.MenuHelperModel
@using System.Web.Mvc.Html
@using MvcSiteMapProvider.Web.Html.Models

@helper  TopMenu(List<SiteMapNodeModel> nodeList)
{
    <nav class="navbar navbar-default" role="navigation">
        <div class="container-fluid">
            <div class="collapse navbar-collapse">
                <ul class="nav navbar-nav">
                    @foreach (SiteMapNodeModel node in nodeList)
                    {
                        string url = node.IsClickable ? node.Url : "#";

                        if (!node.Children.Any())
                        {
                            <li><a href="@url">@node.Title</a></li>
                        }
                        else
                        {
                            <li class="dropdown"><a class="dropdown-toggle" data-toggle="dropdown">@node.Title <span class="caret"></span></a>@DropDownMenu(node.Children)</li>
                        }

                        if (node != nodeList.Last())
                        {
                            <li class="divider-vertical"></li>
                        }
                    }
                </ul>
            </div>
        </div>
    </nav>
}

@helper DropDownMenu(SiteMapNodeModelList nodeList)
{
    <ul class="dropdown-menu" role="menu">
        @foreach (SiteMapNodeModel node in nodeList)
        {
            if (node.Title == "Separator")
            {
                <li class="divider"></li>
                continue;
            }

            string url = node.IsClickable ? node.Url : "#";

            if (!node.Children.Any())
            {
                <li><a href="@url">@node.Title</a></li>
            }
            else
            {
                <li class="dropdown-submenu"><a href="@url">@node.Title</a>@DropDownMenu(node.Children)</li>
            }
        }
    </ul>
}

@TopMenu(Model.Nodes)

Notes: 2 value-added features worth mention in the above code snippet. a) Handle multi-level site map using Bootstrap’s Dropdowns component (This post shows 2 levels site map, I tested with 4 levels site map); b) Support adding Separator in the menu (Refer to above ~\Mvc.sitemap snippet in step 4).

6. Edit the _Layout.cshtml file again to render the MvcSiteMap’s menu using the Bootstrap menu partial view.

......
<body>
	<div class="container">
		<div class="row">
			<div class="span12">
				<nav>
					@Html.MvcSiteMap().Menu("BootstrapMenuHelperModel")
				</nav>
			</div>
		</div>
		<!-- //row -->
		<div class="row">
			<div class="span12 body-content">
				@RenderBody()
				<hr />
				<footer>
					<p>&copy; @DateTime.Now.Year - My ASP.NET Application</p>
				</footer>
			</div>
		</div>
		<!-- //row -->
	</div>
    ......
</body>
......

7. Completed!!!

DropDown Menu
DropDown Menu
DropDown Menu w/ Separator
DropDown Menu w/ Separator

You can download the code sample here.

References

Password Policy Check in ASP.NET Membership

There are 3 attributes in ASP.NET Membership Providers for configuring the password policy check.

1. minRequiredPasswordLength
Specifies the minimum number of characters that are required in a password. The default is 7.

2. minRequiredNonalphanumericCharacters
Specifies the minimum number of special characters that must be present in a valid password. The default is 1.

3. passwordStrengthRegularExpression
Specifies the regular expression that is used to evaluate a password. The default is an empty string (“”).

The following is a sample of ASP.NET Membership Provider Configuration.

<membership defaultProvider="SqlMembershipProvider" userIsOnlineTimeWindow="30">
  <providers>
    <clear />
    <add name="SqlMembershipProvider" type="System.Web.Security.SqlMembershipProvider" applicationName="MyApplication" 
      minRequiredPasswordLength="8" 
      minRequiredNonalphanumericCharacters="1" 
      passwordStrengthRegularExpression="^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=(.*\W)).{8,128}$" />
  </providers>
</membership>

The above configuration validates the password to meet the following criteria:
+ It is at least 8 characters and not exceed 128 characters
+ It contains at least 1 lower-case character
+ It contains at least 1 upper-case character
+ It contains at least 1 numeric character
+ It contains at least 1 special (non-alphanumeric) character

References

Set CacheControlMaxAge to High Number

It’s a good practice to set the CacheControlMaxAge attribute in web.config to a high number.

<system.webServer>
	<staticContent>
		<!-- Set expire headers to 30 days for static content-->
		<clientCache cacheControlMode="UseMaxAge" cacheControlMaxAge="30.00:00:00" />
	</staticContent>
</system.webServer>

Turn On HTTP Compression

It’s a good practice to enable HTTP compression for any uncompressed content. Add the following code snippet in web.config.

<system.webServer>
	<!-- GZip static file content. Overrides the server default which only compresses static files over 2700 bytes -->
	<httpCompression directory="%SystemDrive%\websites\_compressed" minFileSizeForComp="1024">
		<scheme name="gzip" dll="%Windir%\system32\inetsrv\gzip.dll" />
		<staticTypes>
			<add mimeType="text/*" enabled="true" />
			<add mimeType="message/*" enabled="true" />
			<add mimeType="application/javascript" enabled="true" />
			<add mimeType="application/json" enabled="true" />
			<add mimeType="*/*" enabled="false" />
		</staticTypes>
	</httpCompression>
</system.webServer>

Logging ASP.NET Unhandled Exception

1. Create Application_Error event handler in Global.asax.cs

protected void Application_Error(object sender, EventArgs e)
{
	HttpException httpEx = this.Server.GetLastError() as HttpException;

	if ((httpEx != null) && (httpEx.InnerException != null))
	{
		Exception ex = httpEx.InnerException;

		ILogger logger = LogManager.GetLogger();
		logger.Fatal(ex);
	}
}

References