Month: June 2013

ASP.NET MVC Model Validation using Data Annotations

In ASP.NET MVC, there are several ways to validate the model data prior to saving the data into the data store.
1. Validate the model data explicitly
2. Implement the IValidateableObject interface
3. Specify Data Annotations [Recommended]

Data Annotation is recommended because there are built-in Data Annotations in .NET Framework. You don’t have to implement your own validation logic, instead specifying the Validation Data Annotation that you need. The Data Annotations specified support both server-side & client-side validation. In case the built-in cannot fulfill your requirements, you can also implement your own Data Annotation.

Built-in Data Annotations

Namespace: System.ComponentModel.DataAnnotations

Validation Attribute Description
CompareAttribute Compares two properties
CustomValidationAttribute Specifies a custom validation method
DataTypeAttribute
Data type of the data field
MaxLengthAttribute
Max. length of array or string data allowed
MinLengthAttribute
Min. length of array or string data allowed
RangeAttribute
Numeric range constraints for the data field value
RegularExpressionAttribute
Data field value must match the specified regular expression
RequiredAttribute
Data field value is required
StringLengthAttribute
Min. and max. length of characters that are allowed in a data field

Namespace: System.Web.Security

Validation Attribute Description
MembershipPasswordAttribute Validates whether a password field meets the current password requirements for the membership provider

Code Sample

using System;
using System.ComponentModel.DataAnnotations;
using System.Web.Security;

namespace AspNetMvc.Models
{
    public class User
    {
        [DataType(DataType.Text)]
        [StringLength(30, MinimumLength = 6)]
        [Required()]
        public string UserName { get; set; }

        [DataType(DataType.Password)]
        [StringLength(255, MinimumLength = 8)]
        [Required()]
        [MembershipPassword()]
        public string Password { get; set; }

        [Compare("Password")]
        [DataType(DataType.Password)]
        [StringLength(255, MinimumLength = 8)]
        [Required()]
        [MembershipPassword()]
        public string ConfirmPassword { get; set; }

        [DataType(DataType.EmailAddress)]
        [StringLength(128)]
        [Required()]
        public string Email { get; set; }

        [DataType(DataType.PhoneNumber)]
        [RegularExpression("^[0-9]{8}$")]
        [StringLength(32)]
        public string Phone { get; set; }

        [DataType(DataType.Date)]
        public DateTime Birthday { get; set; }

        [DataType(DataType.MultilineText)]
        [StringLength(255)]
        public string Remarks { get; set; }
    }
}

Screenshots

ASP.NET MVC Validation (Required)
ASP.NET MVC Validation (Required)
ASP.NET MVC Validation (StringLength, DataType, RegularExpression)
ASP.NET MVC Validation (StringLength, DataType, RegularExpression)
ASP.NET MVC Validation (Compare)
ASP.NET MVC Validation (Compare)

Client-side Validation
1. Edit the ~\web.config file as the following

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <appSettings>
    <add key="webpages:Version" value="2.0.0.0" />
    <add key="webpages:Enabled" value="false" />
    <add key="PreserveLoginUrl" value="true" />
    <add key="ClientValidationEnabled" value="true" />
    <add key="UnobtrusiveJavaScriptEnabled" value="true" />
  </appSettings>
</configuration>

2. Check the ~\App_Start\BundleConfig.cs file to include the following line

bundles.Add(new ScriptBundle("~/bundles/jqueryval").Include(
			"~/Scripts/jquery.validate.unobtrusive*",
			"~/Scripts/jquery.validate*"));

References

Visual Studio Code Metrics

How to Generate Code Metrics Data

  • Click Analysis menu, then click Calculate Code Metrics for Solution

1. Maintainability Index
+ Measures ease of code maintenance. Higher values are better.
+ No individual method or property should have a maintainability index lower than 40

2. Cyclomatic Complexity
+ Measures number of branches. Lower values are better.
+ No individual method or property should have a cyclomatic complexity greater than 10

3. Depth of Inheritance
+ Measures length of object inheritance hierarchy. Lower values are better.
+ No individual type should have object inheritance hierarchy greater than 4

4. Class Coupling
+ Measures number of classes that are referenced. Lower values are better.
+ No individual type, method or property should have class coupling greater than 10

5. Lines of Code
+ Approximates the lines of executable code (IL code). Lower values are better.
+ Not a good measure by itself

References

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

Lesson Learned: GUID as a Primary Key

In RDBMS, it’s very common to use an int value as a Primary Key (PK). In SQL Server, we have Identity column. In Oracle, we have Sequence.

I’ve never thought of using GUID as a PK. Why we need to spend extra 12 bytes for a PK? (int is 4 bytes vs. GUID is 16 bytes)

In my recent project, the system consists of 2 kind of databases. 1) Server-side RDBMS (SQL Server), 2) Mobile-side RDBMS (Android SQLite). Initially, we designed to use int datatype as a PK for tables which need to be synchronized between the 2 database. However, this approach cannot uniquely identify the record across the 2 databases at all time.

We researched and found a solution to change the PK to GUID. GUID = Globally Unique Identifier. The GUID is so random that the probability of the same number being generated randomly twice is negligible.

This solution saved us 1) Extra roundtrip to query the newly generated Identity value from server RDBMS, 2) Extra roundtrip to synchronize the Identity value from server to the mobile.

How to: Using GUID in Database

Database Data Type Generation
SQL Server UNIQUEIDENTIFIER NEWID()
Oracle RAW(16) SYS_GUID()

References:

 

IIS Application Pool Managed Pipeline Modes

In IIS 7 or above, application pools can run in 2 managed pipeline modes. 1) Integrated, 2) Classic.

1. Integrated Mode
+ By default, IIS application pools run in this mode
+ IIS will use the integrated request processing pipeline to process all IIS requests, including ASP.NET requests

2. Classic Mode
+ IIS will process the requests same as if the application running in IIS 6.0
+ IIS will route ASP.NET requests to Aspnet_isapi.dll ISAPI extension

References

Missing Layout in ASP.NET MVC Area’s Views

In ASP.NET MVC, it’s quite common to expect that the site layout defined in ~\Views\Shared\_Layout.cshtml can be applied in every view in the site. However, when you create a area’s view in ASP.NET MVC, you may found that the site layout defined in ~\Views\Shared\_Layout.cshtml cannot be rendered in the area’s view.

There are 3 ways to resolve this problem:
1. Copy the ~\Views\Shared\_Layout.cshtml file to the ~\Views\Shared\ folder in each area
2. Copy the ~\Views\_ViewStart.cshtml file to the ~\Views\ folder in each area
3. (Recommended) Move the ~\Views\_ViewStart.cshtml file to the root folder ~\ and modify the ~\web.config file

Method 3 is recommended because it is the only “Do once solve all” solution. Don’t need to do any extra things when you create a new area. Don’t Repeat Yourself (DRY).

1. Copy and paste system.web.webPages.razor config section group and system.web.webPages.razor section in ~\Views\web.config to ~\web.config

<configSections>
	<sectionGroup name="system.web.webPages.razor" type="System.Web.WebPages.Razor.Configuration.RazorWebSectionGroup, System.Web.WebPages.Razor, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
		<section name="host" type="System.Web.WebPages.Razor.Configuration.HostSection, System.Web.WebPages.Razor, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" />
		<section name="pages" type="System.Web.WebPages.Razor.Configuration.RazorPagesSection, System.Web.WebPages.Razor, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" />
	</sectionGroup>
</configSections>
<system.web.webPages.razor>
	<host factoryType="System.Web.Mvc.MvcWebRazorHostFactory, System.Web.Mvc, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
	<pages pageBaseType="System.Web.Mvc.WebViewPage">
		<namespaces>
			<add namespace="System.Web.Mvc" />
			<add namespace="System.Web.Mvc.Ajax" />
			<add namespace="System.Web.Mvc.Html" />
			<add namespace="System.Web.Optimization" />
			<add namespace="System.Web.Routing" />
		</namespaces>
	</pages>
</system.web.webPages.razor>

2. Move the ~\Views\_ViewStart.cshtml file to the root folder ~\

3. Make sure the layout file defined in ~\_ViewStart.cshtml is point to ~\Views\Shared\_Layout.cshtml

@{
    Layout = "~/Views/Shared/_Layout.cshtml";
}

4. Completed!!! The layout defined in ~\Views\Shared\_Layout.cshtml can now be rendered in the area’s view.

References
+ Sharing a single _ViewStart Across Areas in ASP.NET MVC
+ Asp.Net MVC 3 Areas and _ViewStart.cshtml Scope

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

Updated Blog Post: ASP.NET MVC 5 Menu using Site Map Provider & Bootstrap 3 Navbar

1. Install Bootstrap for MVC 4 NuGet package. This package will install Twitter Bootstrap to the project and add bundling and minification to application start.

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

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

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

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

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

5. The Twitter Bootstrap package installed in step 1 wouldn’t add the style and script rendering to the layout file. You need to do it manually by modifying the _Layout.cshtml file as following.

<!DOCTYPE html>
<html>
<head>
  <meta charset=&amp;amp;quot;utf-8&amp;amp;quot; />
  <meta name=&amp;amp;quot;viewport&amp;amp;quot; content=&amp;amp;quot;width=device-width&amp;amp;quot; />
  <title>@ViewBag.Title</title>
  @Styles.Render(&amp;amp;quot;~/Content/css&amp;amp;quot;)
  @Styles.Render(&amp;amp;quot;~/Content/bootstrap&amp;amp;quot;)
  @Scripts.Render(&amp;amp;quot;~/bundles/modernizr&amp;amp;quot;)
</head>
<body>
  @RenderBody()

  @Scripts.Render(&amp;amp;quot;~/bundles/jquery&amp;amp;quot;)
  @Scripts.Render(&amp;amp;quot;~/bundles/bootstrap&amp;amp;quot;)
  @RenderSection(&amp;amp;quot;scripts&amp;amp;quot;, required: false)
</body>
</html>

6. 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)
{
  @:<div class=&amp;amp;quot;navbar&amp;amp;quot;><div class=&amp;amp;quot;navbar-inner&amp;amp;quot;><ul class=&amp;amp;quot;nav&amp;amp;quot;>
  foreach (SiteMapNodeModel node in nodeList)
  {
    string url = node.IsClickable ? node.Url : &amp;amp;quot;#&amp;amp;quot;;

    if (!node.Children.Any())
    {
      @:<li><a href=&amp;amp;quot;@url&amp;amp;quot;>@node.Title</a></li>
    }
    else
    {
      @:<li class=&amp;amp;quot;dropdown&amp;amp;quot;><a class=&amp;amp;quot;dropdown-toggle&amp;amp;quot; data-toggle=&amp;amp;quot;dropdown&amp;amp;quot; href=&amp;amp;quot;@url&amp;amp;quot;>@node.Title <b class=&amp;amp;quot;caret&amp;amp;quot;></b></a>@DropDownMenu(node.Children)</li>
    }

    if (node != nodeList.Last())
    {
      @:<li class=&amp;amp;quot;divider-vertical&amp;amp;quot;></li>
    }
  }

  @:</ul></div></div>
}

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

    string url = node.IsClickable ? node.Url : &amp;amp;quot;#&amp;amp;quot;;

    if (!node.Children.Any())
    {
      @:<li><a href=&amp;amp;quot;@url&amp;amp;quot;>@node.Title</a></li>
    }
    else
    {
      @:<li class=&amp;amp;quot;dropdown-submenu&amp;amp;quot;><a href=&amp;amp;quot;@url&amp;amp;quot;>@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).

7. Add HomeController, and then add Index View for testing.

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

......
<body>
    <div class=&amp;amp;quot;container&amp;amp;quot;>
        <div class=&amp;amp;quot;row&amp;amp;quot;>
            <div class=&amp;amp;quot;span12&amp;amp;quot;>
                <nav>
                    @Html.MvcSiteMap().Menu(&amp;amp;quot;BootstrapMenuHelperModel&amp;amp;quot;)
                </nav>
            </div>
        </div>
        <!-- //row -->
        <div class=&amp;amp;quot;row&amp;amp;quot;>
            <div class=&amp;amp;quot;span12&amp;amp;quot;>
                @RenderBody()
            </div>
        </div>
        <!-- //row -->
    </div>
    ......
</body>
......

9. Completed!!!
Drop Down Menu
DropDown Menu

Drop Down Menu w/ Separator
DropDown Menu w/ Separator

You can download the code sample here.

References