Handle Deactivated JavaScript in ASP.NET MVC Web Application

Preface

Depending on what your ASP.NET MVC Web application is supposed to do, it might be necessary that the client’s browser has JavaScript support enabled.

If this is not the case, maybe you want to make sure the user will be informed and cannot use the application.

And because there is no JavaScript available, this has to be done in a non-script way.

Use The noscript-Tag & Redirect

To handle all this without any script is quite simple. HTML offers the noscript tag.

Inside the noscript area, one can add whatever HTML code is required.

Since I want the user to be redirected to another page, I will use meta http-equiv="refresh" content="0;…" to redirect the user to the page explaining that JavaScript is required to use my application.

Add It To The Layout Page

To make sure the user will be redirected independent which page should be opened, I add the noscsript area to the _Layout.cshtml page.

And because I do use a meta tag, the code is placed into the head area to avoid warnings that meta elements cannot be nested inside the body element.

In case you use different layout pages in your application, you should put the redirection code into a partial view and add this to all of your layout pages. If there is the need to change the redirection, there will be only one place that needs to be changed.

NoJScript Controller And View

I do have a ASP.NET MVC application, so want to redirect to an appropriate controller when JavaScript is disabled, not to a HTML page.

Accordingly, I add the NoJScript controller with an Index view to my project and redirect the user to /NoJScript. The view is used to inform the user that JavaScript support is required and can contain all non-script elements that are needed.

It is important that this view does not use the layout page itself. Otherwise there will be an endless redirection loop, because when the view is opened, the browser detects there is no JavaScript enabled, and again redirects to this view, and so forth.

Access Restrictions

In case you restrict access to your application by setting the AuthorizeAttribute either to your controller or by adding

filters.Add(new System.Web.Mvc.AuthorizeAttribute());

to the App_Start/FilterConfig.cs, remember to set the AllowAnonymousAttribute to the Index method of the NoJScript controller. Otherwise, the browser is hooked in an endless loop, switching between the login page and the JavaScript required page.

Putting It Together

Here are all changes listed.

In the _Layout.cshtml page, this section is added to the head element:

<head>
…
<noscript>
  <meta http-equiv="refresh" content="0;url=/NoJScript" />
</noscript>
…
<head>

The NoJScript controller is really simple:

public class NoJScriptController : Controller
{
  //
  // GET: /NoJScript/
  [AllowAnonymous]
  public ActionResult Index()
  {
    return View();
  }
}

And the view should contain a little bit more information than this sample:

@{
  ViewBag.Title = "JavaScript required";
  Layout = null; // <= Important!
}

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>@ViewBag.Title - My MVC App</title>

  @Styles.Render("~/Content/css")
  @Scripts.Render("~/bundles/modernizr")

</head>
<body>
 To use this application JavaScript is required.
</body>

Using Membership and Authorization in ASP.NET MVC 4

With the introduction of .NET 4.5 and MVC 4, some changes to Membership and Authorization came along.

To keep this posting as short as possible, I will just list the findings and refer to some postings I read.

1. Web Site Administration Tool is gone, and there is no replacement for it (weblogs.asp.net/jgalloway/archive/2012/08/29/simplemembership-membership-providers-universal-providers-and-the-new-asp-net-4-5-web-forms-and-asp-net-mvc-4-templates.aspx).

2. Use WebMatrix.WebData.SimpleMembershipProvider instead of System.Web.Providers.DefaultMembershipProvider in web.config (type="WebMatrix.WebData.SimpleMembershipProvider, WebMatrix.WebData, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35")

3. Use WebMatrix.WebData.SimpleRoleProvider instead of System.Web.Providers.DefaultRoleProvider in web.config (type="WebMatrix.WebData.SimpleRoleProvider, WebMatrix.WebData, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35")

4. There is no (WebMatrix.WebData.)SimpleProfileProvider.

5. To create the required tables yourself, here is a sample script from http://notebookheavy.com/2012/08/22/mvc-4-authentication/.

CREATE TABLE [dbo].[UserProfile] (
[UserId] INT IDENTITY (1, 1) NOT NULL,
[UserName] NVARCHAR (MAX) NULL,
PRIMARY KEY CLUSTERED ([UserId] ASC)
);
 
CREATE TABLE [dbo].[webpages_Membership] (
[UserId] INT NOT NULL,
[CreateDate] DATETIME NULL,
[ConfirmationToken] NVARCHAR (128) NULL,
[IsConfirmed] BIT DEFAULT ((0)) NULL,
[LastPasswordFailureDate] DATETIME NULL,
[PasswordFailuresSinceLastSuccess] INT DEFAULT ((0)) NOT NULL,
[Password] NVARCHAR (128) NOT NULL,
[PasswordChangedDate] DATETIME NULL,
[PasswordSalt] NVARCHAR (128) NOT NULL,
[PasswordVerificationToken] NVARCHAR (128) NULL,
[PasswordVerificationTokenExpirationDate] DATETIME NULL,
PRIMARY KEY CLUSTERED ([UserId] ASC)
);
 
 
CREATE TABLE [dbo].[webpages_OAuthMembership] (
[Provider] NVARCHAR (30) NOT NULL,
[ProviderUserId] NVARCHAR (100) NOT NULL,
[UserId] INT NOT NULL,
PRIMARY KEY CLUSTERED ([Provider] ASC, [ProviderUserId] ASC)
);
 
CREATE TABLE [dbo].[webpages_Roles] (
[RoleId] INT IDENTITY (1, 1) NOT NULL,
[RoleName] NVARCHAR (256) NOT NULL,
PRIMARY KEY CLUSTERED ([RoleId] ASC),
UNIQUE NONCLUSTERED ([RoleName] ASC)
);
 
CREATE TABLE [dbo].[webpages_UsersInRoles] (
[UserId] INT NOT NULL,
[RoleId] INT NOT NULL,
PRIMARY KEY CLUSTERED ([UserId] ASC, [RoleId] ASC),
CONSTRAINT [fk_UserId] FOREIGN KEY ([UserId]) REFERENCES [dbo].[UserProfile] ([UserId]),
CONSTRAINT [fk_RoleId] FOREIGN KEY ([RoleId]) REFERENCES [dbo].[webpages_Roles] ([RoleId])
);

6. One additional posting about “Using SimpleMembership With ASP.NET WebPages“.

ASP.NET MVC 4, VS 2012 and the Usage of jQuery UI

It took really some time to get the ASP.NET MVC jQuery sample on ASP.NET running.

At the time of writing, the sample was based on MVC 3, while I was working with MVC 4. Well, not a big deal, just change the name of some .css and .js files. But that doesn’t made it work. Starting the app, there was always an error indicating the datepicker function was unkown. And of course, the datepicker was not shown.

The solution was, as almost always, quite simple. For whatever reason, the _Layout.cshtml file generated by VS 2012 when creating an ASP.NET MVC 4 project, contains the following line at the bottom:

@Scripts.Render("~/bundles/jquery")

Removing this line made the jQuery UI parts work. Of course one should not forget to include the jQuery script and .css files into the header section of the layout file or move that line up there.

ASP.NET MVC 4 jQuery Validation Globalization

Going thru the ASP.NET MVC 4 intro on www.asp.net/mvc/tutorials/mvc-4/getting-started-with-aspnet-mvc4/adding-a-model, I thought that I couldn’t believe what I saw.

In this intro, a MVC Web application is built, having a page to enter movie data, including a date and a price. The price is a decimal value.

Now on my machine, the culture is en-US, while the UI culture is de-DE. Saving a price of 9.78 (where the point is the decimal separator), the app writes 978 into the database. And trying to save a date in German format (dd.MM.yyyy) leads to an “invalid date format” error on the client. On the other hand, the date entered in en-US format (MM/dd/yyyy), was displayed in German format after reading.

The kind of problem is not new to me. Working with UI apps (and not just in that case), one always has to consider the culture settings. What causes my surprise was the fact that still there seems to be no built-in support to handle this issue. Even ASP.NET MVC 4 is not able to handle this itself, making sure the client side validator validates the values using the correct culture settings 🙁

Well, I looked around a little bit, and found a solution. It doesn’t make me happy, but it works.

First, I extended the system.web section of the web.config to enable ASP.NET to set the UI culture and culture for a Web page automatically, based on the values that are sent by a browser:

<globalization enableClientBasedCulture="true" 
  culture="auto:en-US" 
  uiCulture="auto:en"/>

And then I extended the view with this code:

<script src="~/Scripts/globalize.js" type="text/javascript">
</script>
<script src="~/Scripts/globalize.culture.de-DE.js" type="text/javascript">
</script>
<script src="~/Scripts/globalize.culture.en-US.js" type="text/javascript">
</script>

<script>
  $.validator.methods.number = function (value, element) {
    return this.optional(element) ||
        !isNaN(Globalize.parseFloat(value));
  }

  $.validator.methods.date = function (value, element) {
    return this.optional(element) ||
        Globalize.parseDate(value);
  }

  $(document).ready(function () {
    Globalize.culture('@System.Threading.Thread.CurrentThread.CurrentCulture');
  });
</script>

<script>
  jQuery.extend(jQuery.validator.methods, {
    range: function (value, element, param) {
      //Use the Globalization plugin to parse the value        
      var val = Globalize.parseFloat(value);
      return this.optional(element) || (
          val >= param[0] && val <= param[1]);
    }
  });
</script>

Update: Please have a look at the comments below. Dan shared his solution for the situation when the user is in a browser that has native datepicker/number support and an unknown culture (i.e. you don’t want to account for and load ALL globalize.js culture files. Thanks, Dan!

Since this code is needed by several views, I put it into a partial view and included it into the Scripts section using

@Html.Partial("_PartialViewName");

In this context I think it’s worth it to mention that the link to the jQuery Globalization plugin https://github.com/nje/jquery-glob, which is mentioned in the Web several times, works, but the new location, where it should reside now (https://github.com/jquery/jquery-global, is not valid. I found the required files on https://github.com/jquery/globalize and copied the required files from the lib directory.