Developing Windows Phone App with HTML5 (PhoneGap)

In this post I will show how to develop To-Do app for Windows Phone with Html5 using Cordova (PhoneGap).

1- Download and Install Cordova

Get the package for Cordova  projet from this links :

Apache Cordova website

or

cordova-wp8 repository on GitHub

When downloaded, Un-package the project and copy the zip file and execute “createTemplates.bat” to generate projects template for both wp7 and wp8.

Copy CordovaWP8.zip generated file under C:\Users\User\Documents\Visual Studio 2012\Templates\ProjectTemplates folder.

Here we go, you are now able to create a windows phone app from Cordova template.

2- Create an Empty Project

Open Visual Studio Create New Project and select Visual C#. In the list of installed Templates you will found a new Template for Cordova.

new project

Give the Solution a name (In my case To-Do) and hit Ok. After loading all necessary files Visual Studio will show you the new Main page for the project.

empty project

3- Create the Main page for the application

This is the result we want for the Main page :

home page

So, in solution explorer  under www folder Open Index.html and update html content like this.

<!DOCTYPE html>

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="format-detection" content="telephone=no" />
<meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width, height=device-height, target-densitydpi=device-dpi" />
<link rel="stylesheet" type="text/css" href="css/index.css" />
<title>Just Note</title>
</head>
<body>
<div id="dv-app-title">
<span id="sp-app-title"></span>
</div>
<div class="container">
<input placeholder=" Your Note Text..." id="txt-note" type="text" />
<button id="btn-add-note">+</button>
<div style="padding: 0" class="to-do-list">

<h2>To do list</h2>

<hr style="background-color: #166B94;height: 2px">

<ul id="ul-notes">
<li>
No Note...
</li>
<!--<li>
<input style="float: left" id="checkbox1" type="checkbox" checked>
<label  style="float: left" for="checkbox1">Drink some coffee</label>
<input type="button" class="btn-remove" value="-" />
</li>-->
</ul>

</div> <!-- end to-do-list -->

</div> <!-- end container -->

<script type="text/javascript" src="cordova-2.3.0.js"></script>
<script type="text/javascript" src="js/index.js"></script>
<script type="text/javascript" src="js/jquery.js"></script>
<script type="text/javascript" src="js/Core.js"></script>
<script type="text/javascript">
app.initialize();

</script>
</body>
</html>

And update the stylesheet file for the Main Page to look like :


* {
-webkit-touch-callout: none;                /* prevent callout to copy image, etc when tap to hold */
-webkit-text-size-adjust: none;             /* prevent webkit from resizing text to fit */
-webkit-tap-highlight-color: rgba(0,0,0,0); /* make transparent link selection, adjust last value opacity 0 to 1.0 */
-webkit-user-select: none;                  /* prevent copy paste, to allow, change 'none' to 'text' */
}

body {
height:100%;
margin:0px;
padding:0px;
text-transform:uppercase;
width:100%;
background: #e5e5e5;
	color: #272727;
	font: 16px/40px "Open Sans", sans-serif;

}

h1 {
font-size:24px;
font-weight:normal;
margin:0px;
overflow:visible;
padding:0px;
text-align:center;
}

.event {
border-radius:4px;
-webkit-border-radius:4px;
color:#FFFFFF;
font-size:12px;
margin:0px 30px;
padding:2px 0px;
}

.event.listening {
background-color:#333333;
display:block;
}

.event.received {
background-color:#4B946A;
display:none;
}

@keyframes fade {
from { opacity: 1.0; }
50% { opacity: 0.4; }
to { opacity: 1.0; }
}

@-webkit-keyframes fade {
from { opacity: 1.0; }
50% { opacity: 0.4; }
to { opacity: 1.0; }
}

.blink {
animation:fade 3000ms infinite;
-webkit-animation:fade 3000ms infinite;
}

@charset "utf-8";

h2 { margin: 0; }

hr {
	background: #5e5e5e;
	border: none;
	height: 1px;
	margin: 0;
	min-height: 1px;
}

ul {
	list-style: none;
	margin: 0;
	padding: 0;
}

.container {
	height: 100%;
	/*left: 50%;*/
	/*margin: -160px 0 0 -152px;*/
	position: absolute;
	top: 45px;
	width: 100%;
margin-left: 5px;

}

/* ---------- To-Do-List ---------- */

#txt-note {
height: 40px; width: 80%;border: none;outline: none;background: white;font-size: 16px
}
#dv-app-title {
background-image: url('../img/icon.png');
background-repeat: no-repeat;
background-position: 5%;
top: 0;
background-color: #166B94;
border: none;
height: 40px;
width: 100%;
}

#sp-app-title {
background-image: url('../img/icon.png');
margin-left: 15px
}

#btn-add-note {
background-color: #166B94;
border: none;
outline: none;
text-align: center;
color: white;
font-size: 26px;
font-weight: bold;
width: 12%;
height: 39px;
}

.btn-remove {
background-color: red;
border: none;
outline: none;
text-align: center;
color: white;
font-size: 26px;
font-weight: bold;
width: 30px;
height: 30px;
float: right;
margin-right: 12px
}

.to-do-list {
	background: -webkit-linear-gradient(top, #aaa, #fff 2%);
	background: -moz-linear-gradient(top, #aaa, #fff 2%);
	background: -o-linear-gradient(top, #aaa, #fff 2%);
	background: -ms-linear-gradient(top, #aaa, #fff 2%);
	background: linear-gradient(top, #aaa, #fff 2%);
	-webkit-background-size: 100% 40px;
	-moz-background-size: 100% 40px;
	background-size: 100% 40px;
	border-radius: 5px;
	-webkit-box-shadow: 0 1px   0 rgba(0,   0,   0,   0.200),
	0 3px   0 rgba(255, 255, 255, 1.0),
	0 4px   0 rgba(0,   0,   0,   0.15),
	0 6px   0 rgba(255, 255, 255, 1.0);
	font-size: 14px;
	 -ms-touch-action:pan-y;
	width: 95%;
height: 100%

}

.to-do-list h2 {
	color: #166B94;
	font-size: 28px;
	font-weight: bold;
text-align: center
}

.to-do-list input[type=checkbox] {
	cursor: pointer;
	position: relative;
	visibility: hidden;
display: none
}

.to-do-list input[type="checkbox"]:after {
	border: 1px solid #166B94;
	border-radius: 3px;
	color: #fff;
	content: "";
	display: block;
	height: 16px;
	line-height: 16px;
	position: absolute;
	text-align: center;
	visibility: visible;
	width: 16px;
}

.to-do-list input[type=checkbox]:checked:after {
	border: 1px solid #979797;
	color: #979797;
	content: "✓";
}

.to-do-list input[type=checkbox]:checked + label {
	color: #979797;
	font-weight: normal;
	text-decoration: line-through;
}

.to-do-list label {
	color: #166B94;
	font-weight: bold;
	margin-left: 12px;
}

4- Create the JavaScript Code for Items management(Add, delete, make done)

Create a File named Core.js under js folder and put in it :

/// <reference path="jquery.js" />
/// <reference path="../cordova-2.3.0.js" />

var noteController = {};
var storage = localStorage;
var notes=new Array();
var status=new Array();

noteController.initializeObjects = function () {
notes = JSON.parse(storage.getItem("notes"));
status = JSON.parse(storage.getItem("status"));

if (notes === undefined || notes==null) {
notes = new Array();
storage.setItem("notes", JSON.stringify(notes));

}
if (status === undefined || status == null) {
status = new Array();
storage.setItem("status", JSON.stringify(status));
}
};

noteController.listNote = function() {
$("#ul-notes").html("");

if (notes.length > 0) {
for (var i in notes) {
var itemStatus = $.grep(status, function (e) { return e.id == notes[i].id; })[0].value;

$("#ul-notes").append('<li> <input class="ch-items" id="checkbox' + notes[i].id + '" type="checkbox" '
+ (eval(itemStatus)? "checked":"")
+ '> <label class="lbl-item" id="' + notes[i].id + '" for="checkbox' + notes[i].id + '">' + notes[i].content + '</label>' +
'<input type="button" class="btn-remove" value="-" /></li>');
}
} else {
$("#ul-notes").append("<li style='margin-left:12px'>   No Note...</li>");
}

};

noteController.addNote = function (text) {
var id = getId();

notes.push({ id: id, content: text });
status.push({ id: id, value: false });

storage.setItem("notes", JSON.stringify(notes));
storage.setItem("status", JSON.stringify(status));

};

noteController.removeNote = function (id) {

var itemStatus = $.grep(status, function (e) { return e.id == id; })[0];
var itemNote = $.grep(notes, function (e) { return e.id == id; })[0];
var statusIndex = status.indexOf(itemStatus);
var noteIndex = notes.indexOf(itemNote);

notes.splice(noteIndex, 1);
status.splice(statusIndex, 1);
storage.setItem("notes", JSON.stringify(notes));
storage.setItem("status", JSON.stringify(status));
};

noteController.setStatus = function (id,newStatus) {
for (var i in status) {
if (status[i].id == id) {
status[i].value = newStatus;
}
}
storage.setItem("status", JSON.stringify(status));
};

function getId() {
var text = "";
var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";

for (var i = 0; i < 5; i++)
text += possible.charAt(Math.floor(Math.random() * possible.length));

return text;
}

Go back to Main Page and update the script section in the end of file to look like :

<script type="text/javascript">
app.initialize();
noteController.initializeObjects();
$(document).ready(function() {

noteController.listNote();

$("#btn-add-note").on("click", function () {
var text = $("#txt-note").val();
noteController.addNote(text);
$("#txt-note").val("");
noteController.listNote();
});

$(".ch-items").live("change",function() {

var id = $(this).next().attr("id");
var status = $(this).is(':checked');
noteController.setStatus(id, status);
});
$(".btn-remove").live("click", function () {

var id = $(this).prev().attr("id");

noteController.removeNote(id);
noteController.listNote();
});

});

</script>

5- The result

We are ready now to see the results. Press F5 to run the application.

results

You can download this app from Windows Phone Market for Free.

Download

qrcode

Advertisement

My training session at Business & Decision Tunisia about Asp.net MVC

During 4 days, I presented a training session for the .Net team in Business & Decision Tunisia about Asp.net MVC.

In this training we talked about:

  • MVC as a design pattern the history and the utility
  • Microsoft’s approach in Asp.net MVC
  • What’s new in MVC 4
  • Data Access in Asp.net MVC
  • How to secure an Asp.net application
  • Dependency Injection in Asp.net MVC

Slides :

Demos :

My presentation in DevArena about Asp.net mvc 4 new Features with SignalR demo

Here you can find presentation file of my speaking session about Asp.net mvc new Features and the demo of SignalR (chat application).

SignalR demo : http://sdrv.ms/10Hc2uJ

Hello Windows Phone 8

At the Build developer conference that started at Microsoft’s Redmond campus the last month , the company has published its Windows Phone 8 Developer Platform to the general public. The SDK is available to download from  here.

The new SDK is aligned with the latest Microsoft technologies: it’s based on Visual Studio 2012  and it requires Windows 8 to run. Windows 8 Release Preview and Visual Studio 2012 RC are not supported.

The Windows Phone 8 SDK includes many new features, giving developers new opportunities and allowing them to create better apps. Some of them :

  • Easier to build Fast and fluid UI
  • Live Tiles, Lock Screen, and Wallpaper
  • Speech – launch and control apps with Speech
  • Improved Dev Center and Store
  • Deeper integration with Phone Experiences (opened up access to the camera)
  • Better multi-tasking
  • Advanced networking: Bluetooth data transfer, peer networking, proximity connect
  • Delivered 90% of the top developer requests

Let’s discover the new environment by creating Hello world application.

After the installation process completed with success:

Click on Launch to start Visual Studio 2012. Select File > Create New Project (Ctrl+Shift+N). Select the Windows Phone App template and give the application name as Hello wp8 :

the new sdk targets both windows phone  8 et windows phone 7.1 so the Visual studio ask us to select the windows phone we want to target for this. So we choose windows Phone OS 8.0.

After choosing the OS 8.0 Visual studio create the project, we add a button from the toolbox and drag it to the Xaml editor or the designer :

Double click on the button and add the code to display Hello world.

private void SayHelloClick(object sender, RoutedEventArgs e)

{

MessageBox.Show(“Hello Windows Phone 8”);

}

Press F5 and wait a few seconds to load the emulator.
Once the application is loaded press the button “Say Hello” and the application will display “Hello Windows Phone 8”

Using jQuery Mobile in an ASP.NET MVC 4 and SignalR Application

source : dotnetcurry
Abstract: The recently released ASP.NET MVC 4 beta has enhanced mobile support through the introduction of new APIs and out of box support for jQuery Mobile. In this article, we will explore how jQuery.Mobile.MVC package, that is a part of MVC4, leverages jQuery.mobile and HTML5 to create targeted views for each device

The recently released ASP.NET MVC 4 beta has enhanced mobile support through the introduction of new APIs and out of box support for jQuery Mobile. The Mobile web project template helps you develop targeted mobile web applications ground up.

MVC 4 also has a new ‘View Switching’ functionality that helps adding Mobile capabilities to an existing site in a very clean way. In this article, we will see how we can take an existing MVC 4 application that is built for the desktop browser only and add mobile views to it.

Download the non-mobile site’s code from here and follow along to make it mobile enabled.

Starting off

If we run the code now in a mobile browser, we will see the following Home page. As we can see, this is just a ‘re-arranged’ version of the desktop site. The following meta tag (in _Layout.cshtml) helps scale things to the smaller view port of the mobile device.

<meta name=”viewport” content=”width=device-width” />

initial-index-page-iphone-wp7

If we navigate to the ‘Review’ or ‘Edit’ pages, we will see due to the styles applied to the text area, the mobile views are warped. WP7 (Mango) emulator’s IE and an iPhone’s Safari render the ‘Review’ page as follows. Thus, dynamic views using CSS can only go so far.

initial-review-page-iphone-wp7

Bringing in jQuery Mobile

Nuget has two packages for jQuery mobile

  • jQuery.mobile – Pulls in only the jQuery Mobile js and css dependencies.
  • jQuery.Mobile.MVC – Pulls in all jQuery.mobile and MVC4 goodies like adding the ViewSwitcherController, adding the _Layout.mobile.cshtml

Through the Package Manager Console let’s install jQuery.Mobile.MVC. The following dependencies should get installed.

install-jquery-mobile-mvc-from-nuget

Patching a minor gotcha

Once you install jQuery mobile, since all the JavaScript’s get downloaded in the Scripts folder due to the bundling and minification setup by default (in _layout.cshtml), the jquery.mobile behavior will kick in for certain components like buttons and text boxes in non-mobile sites too.

update-web-config-for-mobile-dependencies

To fix this, we simply create a Mobile folder under Scripts folder and move the mobile related js into it.

mobile-dependencies-solution-explorer

Similarly for Content folder, create a mobile sub-folder and move the mobile related classes in it. Also copy the Content/images into Content/mobile/images.

mobile-content-solution-explorer

Now open the _Layout.mobile.cshtml and change the references to point to the correct ‘mobile’ folder.

image

Getting the View Switcher in Action

With the mobile references in place, let’s run the app and refresh our Mobile view. We will see a slightly different view. The original CSS has been stripped and we have a little heading that tells us that currently the Mobile View is being shown and we could toggle back to ‘Desktop View’ if we wanted. However rest of the page is pretty messed up.

display-view-switcher-wp7

Clicking on Desktop view at this point will throw an error saying ViewSwitcher controller not found. This is not the fault of MVC. It’s our code that bombed. Remember we have a custom controller factory that creates Controllers as required. We have not added the ViewSwitcher controller in the BlogControllerFactory class.

add-view-switcher-to-controller-factory

With that piece of code in place, we can now switch between Desktop and Mobile views, except that the mobile view looks tad worse than the Desktop view.

Adding Mobile specific Views

To introduce specific mobile views that are custom made for mobile screens, MVC4 allows you to add views with a custom display mode by appending ‘.displaymode’ to a view. We take advantage of this by adding Index.mobile view for the Index page. The ‘.mobile’ display mode comes by default with the jQuery.Mobile.MVC package. Later we will see how we can target different mobile browsers differently.

We use the standard MVC scaffold tooling to generate a ‘List’ type view for our BlogPost model class.

add-mobile-index-view

Now when we view the application instead of the modified desktop view, we get a custom Mobile view.

mobile-index-view-wp7

If you click on the ‘Desktop View’, you will see the original desktop view which is also available at the same URL on your desktop browser.

Recap

Let’s do a quick recap here, because what we’ve got here is the crux of mobile support in MVC.

  • We added jQuery.Mobile.MVC from Nuget
  • We added a custom view with a .mobile appended to the View’s name

And just like that we now can have two different views for the exact same Action or URL.

Under the Covers

The MVC4 infrastructure has under the covers done quite a bit of work for us here. There is a new DisplayModeProvider that by default ships with two modes, desktop and mobile. For desktop it checks browser agent information to see if the browser is on a mobile device or desktop device. To pick up the mobile view it checks if a corresponding .mobile view exists and if it does, uses that view.

Unless you want to add your custom views the DisplayModeProvider stays out of the picture. We will see a sample of how to use DisplayModeProvider a little later. For now let’s deep dive into jQuery mobile techniques to make our mobile views more attractive.

Enabling switching from Desktop to Mobile view on Desktop Browser

Now if you visit the application from the desktop browser you will not see a switch to mobile view link because no one really wants to see the mobile view on a desktop browser. But it is an awfully handy thing to do when debugging. To enable the view switcher in Desktop browser, open _ViewSwitcher.cshtml and in the first line’s if(…) condition, remove ‘Request.Browser.IsMobileDevice &&’. If we remove this we will see the view switcher in desktop browser as well.

Using jQuery.mobile

Since mobile views usually have lesser screen-space showing a table/grid layout isn’t usually an effective presentation layout. Instead, we keep things at a minimal and try to get users to navigate in small steps.

For our home screen instead of showing Details, Edit, Review, Delete for each row we let user navigate to a ‘Details’ page where there will be additional controls to do the Edit, Review and Delete. The markup for the Index.mobile.cshtml is thus reduced to the following.

index-mobile-markup

Understanding the Markup

– The ‘@section header’

The ‘@section header’ markup encapsulates a section that is designated as the ‘header’ tag in HTML5.

– <h1>@ViewBag.Title</h1>

Shows the title of the page.

– <div data-role=”button” data-icon=”plus”>

This is the line where we first encounter jquery mobile custom attributes. Here ‘data-role’ is an attribute that indicates to jQuery how the content in the container should be rendered. ‘data-icon’ is an attribute that defines the icon to use on the button. The various types of icons are defined here. Through all our walkthrough we will see various custom attributes that add meta information that helps jquery render the UI.

The section in this div gets rendered as a button to the right of the header.

– <div data-role=”content”>

This section indicates to jQuery.mobile that this div is the container for content

– <ul data-role=”listview”>

The data-role=”listview” indicates that jquery should apply special rendering for the list.

– <li data-role=”list-divider”>Current Posts</li>

The first list time has the data-role of a list-divider. This makes the list item un-clickable and it gives it different visual cues.

– Using razor sytax we add the rest of the posts. To keep things short, we only show the post.Title property.

Now when we browse to the Index page on a mobile browser the page looks as follows

new-mobile-index-view-wp7

As we see above this is a standard and easy on eyes view that provides a clean way to invoke the ‘Create’ action (header button) and the ‘Details’ action (list item). Next we create a Details.mobile.cshtml to create the mobile view.

Adding a Mobile Details View

When the user clicks or taps on the list items we take them to a details page where they see the entire post and can do Edit/Delete/Review if required.

We start off with a standard MVC view called Details.mobile.cshtml

We then modify the markup to give it a ‘mobile’ look.

The final markup looks as follows:

details-mobile-markup

– The header section has the data_icon attribute set to arrow-l (arrow left) and the data_rel attribute set to back. This means the button will mimic the browser back button and also the button is to the left of the header.

– The content section is standard

– I have used a List view here though we don’t really have a list, I just used the list-divider rendering to give the post title a different look from the post content.

– <pre style=”white-space: pre-line”>

This tag encapsulates the post content while maintaining the linefeeds.

– Button Rendering

We have three buttons for three different actions. Each with a built-in icon.

The final UI looks as follows on a mobile browser

details-mobile-wp7

Now arguably that’s not the best mobile UI by far, however we get an idea of how jQuery mobile helps us build a Mobile specific page.

Using similar markup we create the Review, Edit and Delete pages and when done they looks as follows:

edit-mobile-wp7

delete-mobile-wp7

Adding a view specifically for a Device

So far we have seen how we can add views that simply go by whether the browser is on a Mobile or Desktop device. What if we want to handle different devices separately; For example target WP7 separate from iOS? This is where we get DisplayModeProvider in the play.

We have to register a new DisplayMode in the Global.asax. To register the mode we have to give it a name and provide a condition that will help determine the device that we are targeting.

Let us say we want to add a special mode for iPhones only. The following code registers the new display mode

register-iphone-display-mode

We are providing a lambda expression that checks the HttpContext’s User Agent for the term iPhone, through do a case insensitive search. If the condition is satisfied the DisplayProvider will look for a view that ends with iphone.cshtml. Note we are inserting it at position 0 of all the modes so, this condition is evaluated first. If this condition fails the generic ‘mobile’ view kicks in which falls through to the generic view.

Adding an iPhone View

Just to test it out, we will add a Index view for the iphone that has two lines per list instead of the one line per list we have here.

Add a new Index view with the name Index.iphone.cshtml. The following markup adds two lines for each Post in the application.

iphone-index-view

There is nothing new from a markup perspective except that for each line item has two lines, one wrapped as header and another as a paragraph.

When we publish this code, it looks as follows on the iPhone:

iphone-screen-index

The same page on Windows Phone (emulator) looks as follows

wp7-screen-index

The same page on IE9 looks as follows

ie9-screen-index

To see the app in action, visit the application from different devices at http://apps.sumitmaitra.com/funwithsignalr/

The best part is SignalR works across ALL these browsers. Isn’t that cool?

Conclusion

We saw how we can use jQuery.Mobile.MVC package, that is a part of MVC4, leverages jQuery.mobile and HTML5 to create targeted views for each device. Best part is the routing is transparent so all devices are actually looking at the same URL.

We should use mobile views because

  1. It helps us tailor views to specific devices
  2. It helps reduce bandwidth usage over mobile devices by giving us option to remove un-necessary graphics from Mobile views.
  3. Gives us the ability to build UI much closer to native mobile devices in look and behavior.

The only major ‘con’ of this approach is initial increase in effort for new views, however this is mostly mitigated in the long run because maintaining separate views usually means lesser ‘cross-platform’ hacks hence cleaner and easier to maintain markup.

With ASP.NET MVC 4, we are really looking at a very powerful framework that enables you to develop rich web applications for any target device (and audience).

The entire source code of this article can be downloaded over here

First look at ASP.NET MVC 4 Templates

Source : dotnetexpertguide

With release of ASP.NET MVC 4 Developer Preview, ASP.NET MVC team introduces a new default project template for MVC 4. New template has following improvements.

Cosmetic Improvements
New MVC 4 project template has cosmetic improvements prior to MVC 3 project template. And it will help community to create good looking modern websites with the default template itself without investing more in template designing.

Adaptive Rendering
Along with cosmetic improvements, new MVC 4 template used technique called Adaptive Rendering which will help in proper rendering in desktop browser as well mobile browser also without making any changes. To see Adaptive Rendering in action, resize browser window to be smaller and accordingly MVC 4 template layout will be changed to fit with screen size.

Rich UI
Another major improvement with MVC 4 default project template is use of JavaScript and jQuery plugins to provide rich UI. Click on Register and Login link to see rich UI in action.

ASP.NET MVC 4 Mobile Features

Source : www.asp.net

This tutorial will teach you the basics of how to work with mobile features in an ASP.NET MVC 4 developer preview Web application. For this tutorial, you can use Microsoft Visual Web Developer 2010 Express Service Pack 1 (“Visual Web Developer”), which is a free version of Microsoft Visual Studio. Or you can use Visual Studio 2010 SP1 if you already have that.

Before you start, make sure you’ve installed the prerequisites listed below.

You will also need a mobile browser emulator. Any of the following will work:

This tutorial shows code in C#. However, the starter project and completed project will be available in Visual Basic. Visual Studio projects with Visual Basic and C# source code are available to accompany this topic:

What You’ll Build

For this tutorial, you’ll add mobile features to the simple conference-listing application that’s provided in the starter project. The following screenshot shows the tags page of the completed application as seen in the Windows 7 Phone Emulator (RC).

p1_Tags_CompletedProj

Skills You’ll Learn

Here’s what you’ll learn:

  • How the ASP.NET MVC 4 templates use the HTML5 viewport attribute and adaptive rendering to improve display on mobile devices.
  • How to create mobile-specific views.
  • How to create a view switcher that lets users toggle between a mobile view and a desktop view of the application.

Getting Started

Download the conference-listing application for the starter project using the following link: Download. Then in Windows Explorer, right-click the MvcMobile.zip file and choose Properties. In the MvcMobile.zip Properties dialog box, choose the Unblock button. (Unblocking prevents a security warning that occurs when you try to use a .zip file that you’ve downloaded from the web.)

p1_unBlock

Right-click the MvcMobile.zip file and select Extract All to unzip the file. In Visual Web Developer or Visual Studio 2010, open the MvcMobile.sln file.

Press CTRL+F5 to run the application, which will display it in your desktop browser. Start your mobile browser emulator, copy the URL for the conference application into the emulator, and then click the Browse by tag link. If you are using the Windows Phone Emulator, click in the URL bar and press the Pause key to get keyboard access. The image below shows the AllTags view (from choosing Browse by tag).

p1_browseTag

The display is very readable on a mobile device. Choose the ASP.NET link.

p1_tagged_ASPNET

The ASP.NET tag view is very cluttered. For example, the Date column is very difficult to read. Later in the tutorial you’ll create a version of the AllTags view that’s specifically for mobile browsers and that will make the display readable.

CSS Media Queries

CSS media queries are an extension to CSS for media types. They allow you to create rules that override the default CSS rules for specific browsers (user agents). A common rule for CSS that targets mobile browsers is defining the maximum screen size. The Content\Site.css file that’s created when you create a new ASP.NET MVC 4 Internet project contains the following media query:

@media only screen and (max-width: 850px) {

If the browser window is 850 pixels wide or less, it will use the CSS rules inside this media block. You can use CSS media queries like this to provide a better display of HTML content on small browsers (like mobile browsers) than the default CSS rules that are designed for the wider displays of desktop browsers.

The Viewport Meta Tag

Most mobile browsers define a virtual browser window width (the viewport) that’s much larger than the actual width of the mobile device. This allows mobile browsers to fit the entire web page inside the virtual display. Users can then zoom in on interesting content. However, if you set the viewport width to the actual device width, no zooming is required, because the content fits in the mobile browser.

The viewport <meta> tag in the ASP.NET MVC 4 layout file sets the viewport to the device width. The following line shows the viewport <meta> tag in the ASP.NET MVC 4 layout file.

 <meta name="viewport" content="width=device-width">

Examining the Effect of CSS Media Queries and the Viewport Meta Tag

Open the MvcMobile\Views\Shared\_Layout.cshtml file in the editor and comment out the viewport <meta> tag. The following markup shows the commented-out line.

@*<meta name="viewport" content="width=device-width">*@

Open the MvcMobile\Content\Site.css file in the editor and change the maximum width in the media query to zero pixels. This will prevent the CSS rules from being used in mobile browsers. The following line shows the modified media query:

@media only screen and (max-width: 0px) { ...

Save your changes and browse to the Conference application in a mobile browser emulator. The tiny text in the following image is the result of removing the viewport <meta> tag. With no viewport <meta> tag, the browser is zooming out to the default viewport width (850 pixels or wider for most mobile browsers.)

p1_noViewPort

Undo your changes — uncomment the viewport <meta> tag in the layout file and restore the media query to 850 pixels in the Site.css file. Save your changes and refresh the mobile browser to verify that the mobile-friendly display has been restored.

The viewport <meta> tag and the CSS media query are not specific to ASP.NET MVC 4, and you can take advantage of these features in any web application. But they are now built into the files that are generated when you create a new ASP.NET MVC 4 project.

For more information about the viewport <meta> tag, see A tale of two viewports — part two.

In the next section you’ll see how to provide mobile-browser specific views.

Overriding Views, Layouts, and Partial Views

A significant new feature in ASP.NET MVC 4 is a simple mechanism that lets you override any view (including layouts and partial views) for mobile browsers in general, for an individual mobile browser, or for any specific browser. To provide a mobile-specific view, you can copy a view file and add .Mobile to the file name. For example, to create a mobile Index view, copy Views\Home\Index.cshtml to Views\Home\Index.Mobile.cshtml.

In this section, you’ll create a mobile-specific layout file.

To start, copy Views\Shared\_Layout.cshtml to Views\Shared\_Layout.Mobile.cshtml. Open _Layout.Mobile.cshtml and change the title from MVC4 Conference to Conference (Mobile).

In each Html.ActionLink call, remove “Browse by” in each link ActionLink. The following code shows the completed body section of the mobile layout file.

<body>
    <div class="page">
        <div id="header">
            <div id="logindisplay"></div>
            <div id="title">
                <h1> Conference (Mobile)</h1>
            </div>
            <div id="menucontainer">
                <ul id="menu">
                    <li>@Html.ActionLink("Home", "Index", "Home")</li>
                    <li>@Html.ActionLink("Date", "AllDates", "Home")</li>
                    <li>@Html.ActionLink("Speaker", "AllSpeakers", "Home")</li>
                    <li>@Html.ActionLink("Tag", "AllTags", "Home")</li>
                </ul>
            </div>
        </div>
        <div id="main">
            @RenderBody()
        </div>
        <div id="footer">
        </div>
    </div>
</body>

Copy the Views\Home\AllTags.cshtml file to Views\Home\AllTags.Mobile.cshtml. Open the new file and change the <h2> element from “Tags” to “Tags (M)”:

<h2>Tags (M)</h2>

Browse to the tags page using a desktop browser and using mobile browser emulator. The mobile browser emulator shows the two changes you made.

p2m_layoutTags.mobile

In contrast, the desktop display has not changed.

p2_layoutTagsDesktop

Browser-Specific Views

In addition to mobile-specific and desktop-specific views, you can create views for an individual browser. For example, you can create views that are specifically for the iPhone browser. In this section, you’ll create a layout for the iPhone browser and an iPhone version of the AllTags view.

Open the Global.asax file and add the following code to the Application_Start method.

DisplayModes.Modes.Insert(0, new DefaultDisplayMode("iPhone")
{
    ContextCondition = (ctx => ctx.Request.UserAgent.IndexOf(
        "iPhone", StringComparison.OrdinalIgnoreCase) >= 0)
});

Here’s the complete Application_Start method:

protected void Application_Start() {
    AreaRegistration.RegisterAllAreas();

    RegisterGlobalFilters(GlobalFilters.Filters);
    RegisterRoutes(RouteTable.Routes);
    DisplayModes.Modes.Insert(0, new DefaultDisplayMode("iPhone")
    {
        ContextCondition = (ctx => ctx.Request.UserAgent.IndexOf(
            "iPhone", StringComparison.OrdinalIgnoreCase) >= 0)
    });
}

This code defines a new display mode named “iPhone” that will be matched against each incoming request. If the incoming request matches the condition you defined (that is, if the user agent contains the string “iPhone”), ASP.NET MVC will look for views whose name contains the “iPhone” suffix.

In the code, right-click DefaultDisplayMode, choose Resolve, and then choose using System.Web.WebPages;. This adds a reference to the System.Web.WebPages namespace, which is where the DisplayModes and DefaultDisplayMode types are defined.

p2_resolve

Alternatively, you can just manually add the following line to the using section of the file.

using System.Web.WebPages;

Save the changes. Copy the MvcMobile\Views\Shared\_Layout.Mobile.cshtml file to MvcMobile\Views\Shared\_Layout.iPhone.cshtml. Open the new file and then change the h1 heading from Conference (Mobile) to Conference (iPhone).

Copy the MvcMobile\Views\Home\AllTags.Mobile.cshtml file to MvcMobile\Views\Home\AllTags.iPhone.cshtml. In the new file, change the <h2> element from “Tags (M)” to “Tags (iPhone)”.

Run the application. Run a mobile browser emulator, make sure its user agent is set to “iPhone”, and browse to the AllTags view. The following screenshot shows the AllTags view rendered in the Safari browser.

p2_iphoneView

(For instructions on how to set the user agent string in an Safari browser to emulate an iPhone, see How to let Safari pretend it’s IE in David Alison’s blog.)

In this section we’ve seen how to create mobile layouts and views and how to create layouts and views for specific devices such as the iPhone. In the next section you’ll see how to leverage jQuery Mobile for more compelling mobile views.

Using jQuery Mobile

The jQuery Mobile library provides a user interface framework that works on all the major mobile browsers. jQuery Mobile applies progressive enhancement to mobile browsers that support CSS and JavaScript. Progressive enhancement allows all browsers to display the basic content of a web page, while allowing more powerful browsers and devices to have a richer display. The JavaScript and CSS files that are included with jQuery Mobile style many elements to fit mobile browsers without making any markup changes.

In this section you’ll install the jQuery.Mobile.MVC NuGet package, which installs jQuery Mobile and a view-switcher widget.

To start, delete the Shared\_Layout.Mobile.cshtml and Shared\_Layout.iPhone.cshtml files that you created earlier.

Rename Views\Home\AllTags.Mobile.cshtml to Views\Home\AllTags.iPhone.cshtml.hide. Because the files no longer have a .cshtml extension, they won’t be used by the ASP.NET MVC runtime to render the AllTags view.

Install the jQuery.Mobile.MVC  NuGet package by doing this:

  1. From the Tools menu, select Package Manager Console, and then select Library Package Manager.p3_packageMgr
  2. In the Package Manager Console, enter Install-Package jQuery.Mobile.MVCp3_packageMgrConsole

Note If you get the following error:
The file C:\my_script.ps1 cannot be loaded. The execution of scripts is disabled on this system. Please see “Get-Help about_signing” for more details

See Scott Hanselman’s blog Signing PowerShell Scripts. You can run the following in a power shell script to allow signed scripts to run:

Set-ExecutionPolicy AllSigned

This command requires administrator privileges.  Changes to the execution policy are recognized immediately.

The jQuery.Mobile.MVC NuGet package installs the following:

  • jQuery Mobile (jquery.mobile-1.0b3.js and the minified version jquery.mobile-1.0b3.min.js).
  • A jQuery Mobile-styled layout file (Views\Shared\_Layout.Mobile.cshtml).
  • A jQuery Mobile CSS file (jquery.mobile-1.0b3.css and the minified version jquery.mobile-1.0b3.min.css)
  • Several .png image files in the Content\images folder.
  • A view-switcher partial view (MvcMobile\Views\Shared\_ViewSwitcher.cshtml) that provides a link at the top of each page to switch from desktop view to mobile view and vice versa.
  • A ViewSwitcher controller widget (Controllers\ViewSwitcherController.cs).
  • jQuery.Mobile.MVC.dll, a DLL that provides view context extensions used to determine if view overrides exist.

The installation process also upgrades jQuery from version 1.62 to 1.63. The starter application uses jQuery 1.63. If you create a new ASP.NET MVC project, you’ll have to manually change the script references from jQuery version 1.62 to 1.63 in the layout file.

Note Verify that the jQuery and jQuery Mobile version numbers in the layout file match the version numbers in your project. If the NuGet package updates a new ASP.NET MVC 4 project you create, you will have to change MvcMobile\Views\Shared\_Layout.Mobile.cshtml to reference 1.6.3 instead of 1.6.2.

Open the MvcMobile\Views\Shared\_Layout.Mobile.cshtml file and add the following markup directly after the Html.Partial call:

<div data-role="header" align="center">
    @Html.ActionLink("Home", "Index", "Home")
    @Html.ActionLink("Date", "AllDates")
    @Html.ActionLink("Speaker", "AllSpeakers")
    @Html.ActionLink("Tag", "AllTags")
</div>

The complete body section looks like this:

<body>
    <div data-role="page" data-theme="a">
        @Html.Partial("_ViewSwitcher")
        <div data-role="header" align="center">
            @Html.ActionLink("Home", "Index", "Home")
            @Html.ActionLink("Date", "AllDates")
            @Html.ActionLink("Speaker", "AllSpeakers")
            @Html.ActionLink("Tag", "AllTags")
        </div>
        <div data-role="header">
            <h1>@ViewBag.Title</h1>
        </div>
        <div data-role="content">
            @RenderSection("featured", false)
            @RenderBody()
        </div>
    </div>
</body>

Build the application, and in your mobile browser emulator browse to the AllTags view. You see the following:

p3_afterNuGet

Note If your mobile browser doesn’t display the Home, Speaker, Tag, and Date links as buttons, the reference to the jQuery Mobile script file is probably not correct. Verify that the jQuery Mobile file version referenced in the mobile layout file matches the version in the Scripts folder.

In addition to the style changes, you see Displaying mobile view and a link that lets you switch from mobile view to desktop view. Choose the Desktop view link, and the desktop view is displayed.

p3_desktopView

The desktop view doesn’t provide a way to directly navigate back to the mobile view. You’ll fix that now. Open the Views\Shared\_Layout.cshtml file. Just under the page div element, add the following code, which renders the view-switcher widget:

@Html.Partial("_ViewSwitcher")

Here’s the completed code:

<body>
    <div class="page">
        @Html.Partial("_ViewSwitcher")
        <div id="header">

        @*Items removed for clarity.*@
        </div>
    </div>
</body>

Refresh the AllTags view is the mobile browser. You can now navigate between desktop and mobile views.

p3_desktopViewWithMobileLink

Browse to the AllTags page in a desktop browser. The view-switcher widget is not displayed in a desktop browser because it’s added only to the mobile layout page. Later in the tutorial you’ll see how you can add the view-switcher widget to the desktop view.

p3_desktopBrowser

Improving the Speakers List

In the mobile browser, select the Speakers link. Because there’s no mobile view(AllSpeakers.Mobile.cshtml), the default speakers display (AllSpeakers.cshtml) is rendered using the mobile layout view (_Layout.Mobile.cshtml).

p3_speakersDeskTop

You can globally disable a default (non-mobile) view from rendering inside a mobile layout by setting RequireConsistentDisplayMode to true in the Views\_ViewStart.cshtml file, like this:

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

When RequireConsistentDisplayMode is set to true, the mobile layout (_Layout.Mobile.cshtml) is used only for mobile views. (That is, the view file is of the form ViewName.Mobile.cshtml.) You might want to set RequireConsistentDisplayMode to true if your mobile layout doesn’t work well with your non-mobile views. The screenshot below shows how the Speakers page renders when RequireConsistentDisplayMode is set to true.

p3_speakersConsistent

You can disable consistent display mode in a view by setting RequireConsistentDisplayMode to false in the view file. The following markup in the Views\Home\AllSpeakers.cshtml file sets RequireConsistentDisplayMode to false:

@model IEnumerable<string>

@{
    ViewBag.Title = "All speakers";
    DisplayModes.RequireConsistentDisplayMode = false;
}

Creating a Mobile Speakers View

As you just saw, the Speakers view is readable, but the links are small and are difficult to tap on a mobile device. In this section, you’ll create a mobile-specific Speakers view that looks like a modern mobile application — it displays large, easy-to-tap links and contains a search box to quickly find speakers.

Copy AllSpeakers.cshtml to AllSpeakers.Mobile.cshtml. Open the AllSpeakers.Mobile.cshtml file and remove the <h2> heading element.

In the <ul> tag, add the data-role attribute and set its value to listview. Like other data-* attributes, data-role="listview" makes the large list items easier to tap. This is what the completed markup looks like:

@model IEnumerable<string>

@{
    ViewBag.Title = "All speakers";
}
<ul data-role="listview">
    @foreach(var speaker in Model) {
        <li>@Html.ActionLink(speaker, "SessionsBySpeaker", new { speaker })</li>
    }
</ul>

Refresh the mobile browser. The updated view looks like this:

p3_updatedSpeakerView1

Although the mobile view has improved, it’s difficult to navigate the long list of speakers. To fix this, in the <ul> tag, add the data-filter attribute and set it to true. The code below shows the ul markup.

<ul data-role="listview" data-filter="true">

The following image shows the search filter box at the top of the page that results from the data-filter attribute.

ps_Data_Filter

As you type each letter in the search box, jQuery Mobile filters the displayed list as shown in the image below.

ps_data_filter_SC

Improving the Tags List

Like the default Speakers view, the Tags view is readable, but the links are small and difficult to tap on a mobile device. In this section, you’ll fix the Tags view the same way you fixed the Speakers view.

Rename the Views\Home\AllTags.Mobile.cshtml.hide file to the Views\Home\AllTags.Mobile.cshtml. Open the renamed file and remove the <h2> element.

Add the data-role and data-filter attributes to the <ul> tag, as shown here:

<ul data-role="listview" data-filter="true">

The image below shows the tags page filtering on the letter J.

p3_tags_J

Improving the Dates List

You can improve the Dates view like you improved the Speakers and Tags views, so that it’s easier to use on a mobile device.

Copy the Views\Home\AllDates.Mobile.cshtml file to Views\Home\AllDates.Mobile.cshtml. Open the new file and remove the <h2> element.

Add data-role="listview" to the <ul> tag, like this:

<ul data-role="listview">

The image below shows what the Date page looks like with the data-role attribute in place.

p3_dates1

Replace the contents of the Views\Home\AllDates.Mobile.cshtml file with the following code:

@model IEnumerable<DateTime>
@{
    ViewBag.Title = "All dates";
    DateTime lastDay = default(DateTime);
}
<ul data-role="listview">
    @foreach(var date in Model) {
        if (date.Date != lastDay) {
            lastDay = date.Date;
            <li data-role="list-divider">@date.Date.ToString("ddd, MMM dd")</li>
        }
        <li>@Html.ActionLink(date.ToString("h:mm tt"), "SessionsByDate", new { date })</li>
    }
</ul>

This code groups all sessions by days. It creates a list divider for each new day, and it lists all the sessions for each day under a divider. Here’s what it looks like when this code runs:

p3_dates2

Improving the SessionsTable View

In this section, you’ll create a mobile-specific view of sessions. The changes we make will be more extensive than in other views we have created.

In the mobile browser, tap the Speaker button, then enter Sc in the search box.

ps_data_filter_SC

Tap the Scott Hanselman link.

p3_scottHa

As you can see, the display is difficult to read on a mobile browser. The date column is hard to read and the tags column is out of the view. To fix this, copy Views\Home\SessionsTable.cshtml to Views\Home\SessionsTable.Mobile.cshtml, and then replace the contents of the file with the following code:

@using MvcMobile.Models
@model IEnumerable<Session>

<ul data-role="listview">
    @foreach(var session in Model) {
        <li>
            <a href="@Url.Action("SessionByCode", new { session.Code })">
                <h3>@session.Title</h3>
                <p><strong>@string.Join(", ", session.Speakers)</strong></p>
                <p>@session.DateText</p>
            </a>
        </li>                                          
    }    
</ul>

The code removes the room and tags columns, and formats the title, speaker, and date vertically, so that all this information is readable on a mobile browser. The image below reflects the code changes.

ps_SessionsByScottHa

Improving the SessionByCode View

Finally, you’ll create a mobile-specific view of the SessionByCode view. In the mobile browser, tap the Speaker button, then enter Sc in the search box.

ps_data_filter_SC

Tap the Scott Hanselman link. Scott Hanselman’s sessions are displayed.

ps_SessionsByScottHa

Choose the An Overview of the MS Web Stack of Love link.

ps_love

The default desktop view is fine, but you can improve it.

Copy the Views\Home\SessionByCode.cshtml to Views\Home\SessionByCode.Mobile.cshtml and replace the contents of the Views\Home\SessionByCode.Mobile.cshtml file with the following markup:

@model MvcMobile.Models.Session

@{
    ViewBag.Title = "Session details";
}
<h2>@Model.Title</h2>
<p>
    <strong>@Model.DateText</strong> in <strong>@Model.Room</strong>
</p>

<ul data-role="listview" data-inset="true">
    <li data-role="list-divider">Speakers</li>
    @foreach (var speaker in Model.Speakers) {
        <li>@Html.ActionLink(speaker, "SessionsBySpeaker", new { speaker })</li>
    }
</ul>

<p>@Model.Description</p>
<h4>Code: @Model.Code</h4>

<ul data-role="listview" data-inset="true">
    <li data-role="list-divider">Tags</li>
    @foreach (var tag in Model.Tags) {
        <li>@Html.ActionLink(tag, "SessionsByTag", new { tag })</li>
    }
</ul>

The new markup uses the data-role attribute to improve the layout of the view.

Refresh the mobile browser. The following image reflects the code changes that you just made:

p3_love2

Wrapup and Review

This tutorial has introduced the new mobile features of ASP.NET MVC 4 Developer Preview. The mobile features include:

  • The ability to override layout, views, and partial views, both globally and for an individual view.
  • Control over layout and partial override enforcement using the RequireConsistentDisplayMode property.
  • A view-switcher widget for mobile views than can also be displayed in desktop views.
  • Support for supporting specific browsers, such as the iPhone browser.

ASP.NET MVC part 9 Improving the Details and Delete Methods

In this part of the tutorial, you’ll make some improvements to the automatically generated Details and Deletemethods. These changes aren’t required, but with just a few small bits of code, you can easily enhance the application.

Improving the Details and Delete Methods

When you scaffolded the Movie controller, ASP.NET MVC generated code that worked great, but that can be made more robust with just a few small changes.

Open the Movie controller and modify the Details method by returning HttpNotFound when a movie isn’t found. You should also modify the Details method to set a default value for the ID that’s passed to it. (You made similar changes to the Edit method in part 6 of this tutorial.) However, you must change the return type of the Details method from ViewResult to ActionResult, because the HttpNotFound method doesn’t return a ViewResult object. The following example shows the modified Details method.

public ActionResult Details(int id = 0)
{
    Movie movie = db.Movies.Find(id);
    if (movie == null)
    {
        return HttpNotFound();
    }
    return View(movie);
}

Code First makes it easy to search for data using the Find method. An important security feature that we built into the method is that the code verifies that the Find method has found a movie before the code tries to do anything with it. For example, a hacker could introduce errors into the site by changing the URL created by the links from http://localhost:xxxx/Movies/Details/1 to something like http://localhost:xxxx/Movies/Details/12345(or some other value that doesn’t represent an actual movie). If you don’t the check for a null movie, this could result in a database error.

Similarly, change the Delete and DeleteConfirmed methods to specify a default value for the ID parameter and to return HttpNotFound when a movie isn’t found. The updated Delete methods in the Movie controller are shown below.

 // GET: /Movies/Delete/5

public ActionResult Delete(int id = 0)
{
    Movie movie = db.Movies.Find(id);
    if (movie == null)
    {
        return HttpNotFound();
    }
    return View(movie);
}

//
// POST: /Movies/Delete/5

[HttpPost, ActionName("Delete")]
public ActionResult DeleteConfirmed(int id = 0)
{
    Movie movie = db.Movies.Find(id);
    if (movie == null)
    {
        return HttpNotFound();
    }
    db.Movies.Remove(movie);
    db.SaveChanges();
    return RedirectToAction("Index");
}

Note that the Delete method doesn’t delete the data. Performing a delete operation in response to a GET request (or for that matter, performing an edit operation, create operation, or any other operation that changes data) opens up a security hole. For more information about this, see Stephen Walther’s blog entry ASP.NET MVC Tip #46 — Don’t use Delete Links because they create Security Holes.

The HttpPost method that deletes the data is named DeleteConfirmed to give the HTTP POST method a unique signature or name. The two method signatures are shown below:

// GET: /Movies/Delete/5
public ActionResult Delete(int id = 0)

//
// POST: /Movies/Delete/5
[HttpPost, ActionName("Delete")]
public ActionResult DeleteConfirmed(int id = 0)

The common language runtime (CLR) requires overloaded methods to have a unique signature (same name, different list of parameters). However, here you need two Delete methods — one for GET and one for POST — that both require the same signature. (They both need to accept a single integer as a parameter.)

To sort this out, you can do a couple of things. One is to give the methods different names. That’s what we did in he preceding example. However, this introduces a small problem: ASP.NET maps segments of a URL to action methods by name, and if you rename a method, routing normally wouldn’t be able to find that method. The solution is what you see in the example, which is to add the ActionName("Delete") attribute to theDeleteConfirmed method. This effectively performs mapping for the routing system so that a URL that includes/Delete/ for a POST request will find the DeleteConfirmed method.

Another way to avoid a problem with methods that have identical names and signatures is to artificially change the signature of the POST method to include an unused parameter. For example, some developers add a parameter type FormCollection that is passed to the POST method, and then simply don’t use the parameter:

public ActionResult Delete(FormCollection fcNotUsed, int id = 0)
{
    Movie movie = db.Movies.Find(id);
    if (movie == null)
    {
        return HttpNotFound();
    }
    db.Movies.Remove(movie);
    db.SaveChanges();
    return RedirectToAction("Index");
}

Wrapping Up

You now have a complete ASP.NET MVC application that stores data in a SQL Server Compact database. You can create, read, update, delete, and search for movies.

This basic tutorial got you started making controllers, associating them with views, and passing around hard-coded data. Then you created and designed a data model. Entity Framework Code First created a database from the data model on the fly, and the ASP.NET MVC scaffolding system automatically generated the action methods and views for basic CRUD operations. You then added a search form that let users search the database. You changed the database to include a new column of data, and then updated two pages to create and display this new data. You added validation by marking the data model with attributes from the DataAnnotationsnamespace. The resulting validation runs on the client and on the server.

If you’d like to deploy your application, it’s helpful to first test the application on your local IIS 7 server. You can use this Web Platform Installer link to enable IIS setting for ASP.NET applications. See the following deployment links:

I now encourage you to move on to our intermediate-level Creating an Entity Framework Data Model for an ASP.NET MVC Application and MVC Music Store tutorials, to explore the ASP.NET articles on MSDN, and to check out the many videos and resources at http://asp.net/mvc to learn even more about ASP.NET MVC! The ASP.NET MVC forums are a great place to ask questions.

ASP.NET MVC part 8 Adding Validation to the Model

Adding Validation to the Model

In this section you’ll add validation logic to the Movie model, and you’ll ensure that the validation rules are enforced any time a user attempts to create or edit a movie using the application.

Keeping Things DRY

One of the core design tenets of ASP.NET MVC is DRY (“Don’t Repeat Yourself”). ASP.NET MVC encourages you to specify functionality or behavior only once, and then have it be reflected everywhere in an application. This reduces the amount of code you need to write and makes the code you do write much easier to maintain.

The validation support provided by ASP.NET MVC and Entity Framework Code First is a great example of the DRY principle in action. You can declaratively specify validation rules in one place (in the model class) and then those rules are enforced everywhere in the application.

Let’s look at how you can take advantage of this validation support in the movie application.

Adding Validation Rules to the Movie Model

You’ll begin by adding some validation logic to the Movie class.

Open the Movie.cs file. Add a using statement at the top of the file that references theSystem.ComponentModel.DataAnnotations namespace:

using System.ComponentModel.DataAnnotations;

The namespace is part of the .NET Framework. It provides a built-in set of validation attributes that you can apply declaratively to any class or property.

Now update the Movie class to take advantage of the built-in RequiredStringLength, and Range validation attributes. Use the following code as an example of where to apply the attributes.

public class Movie
{
    public int ID { get; set; }

    [Required(ErrorMessage = "Title is required")]
    public string Title { get; set; }

    [Required(ErrorMessage = "Date is required")]
    public DateTime ReleaseDate { get; set; }

    [Required(ErrorMessage = "Genre must be specified")]
    public string Genre { get; set; }

    [Required(ErrorMessage = "Price Required")]
    [Range(1, 100, ErrorMessage = "Price must be between $1 and $100")]
    public decimal Price { get; set; }

    [StringLength(5)]
    public string Rating { get; set; }
}

The validation attributes specify behavior that you want to enforce on the model properties they are applied to. The Required attribute indicates that a property must have a value; in this sample, a movie has to have values for the TitleReleaseDateGenre, and Price properties in order to be valid. The Range attribute constrains a value to within a specified range. The StringLength attribute lets you set the maximum length of a string property, and optionally its minimum length.

Code First ensures that the validation rules you specify on a model class are enforced before the application saves changes in the database. For example, the code below will throw an exception when the SaveChangesmethod is called, because several required Movie property values are missing and the price is zero (which is out of the valid range).

MovieDBContext db = new MovieDBContext();

Movie movie = new Movie();
movie.Title = "Gone with the Wind";
movie.Price = 0.0M;

db.Movies.Add(movie);
db.SaveChanges();        // <= Will throw validation exception

Having validation rules automatically enforced by the .NET Framework helps make your application more robust. It also ensures that you can’t forget to validate something and inadvertently let bad data into the database.

Here’s a complete code listing for the updated Movie.cs file:

using System;
using System.Data.Entity;
using System.ComponentModel.DataAnnotations;

namespace MvcMovie.Models
{
    public class Movie
    {
        public int ID { get; set; }

        [Required(ErrorMessage = "Title is required")]
        public string Title { get; set; }

        [Required(ErrorMessage = "Date is required")]
        public DateTime ReleaseDate { get; set; }

        [Required(ErrorMessage = "Genre must be specified")]
        public string Genre { get; set; }

        [Required(ErrorMessage = "Price Required")]
        [Range(1, 100, ErrorMessage = "Price must be between $1 and $100")]
        public decimal Price { get; set; }

        [StringLength(5)]
        public string Rating { get; set; }
    }

    public class MovieDBContext : DbContext
    {
        public DbSet<Movie> Movies { get; set; }
    }
}

Validation Error UI in ASP.NET MVC

Re-run the application and navigate to the /Movies URL.

Click the Create Movie link to add a new movie. Fill out the form with some invalid values and then click theCreate button.

8_validationErrors

Notice how the form has automatically used a background color to highlight the text boxes that contain invalid data and has emitted an appropriate validation error message next to each one. The error messages match the error strings you specified when you annotated the Movie class. The errors are enforced both client-side (using JavaScript) and server-side (in case a user has JavaScript disabled).

A real benefit is that you didn’t need to change a single line of code in the MoviesController class or in theCreate.cshtml view in order to enable this validation UI. The controller and views you created earlier in this tutorial automatically picked up the validation rules that you specified using attributes on the Movie model class.

How Validation Occurs in the Create View and Create Action Method

You might wonder how the validation UI was generated without any updates to the code in the controller or views. The next listing shows what the Create methods in the MovieController class look like. They’re unchanged from how you created them earlier in this tutorial.

//
// GET: /Movies/Create

public ActionResult Create()
{
    return View();
}

//
// POST: /Movies/Create

[HttpPost]
public ActionResult Create(Movie movie)
{
    if (ModelState.IsValid)
    {
        db.Movies.Add(movie);
        db.SaveChanges();
        return RedirectToAction("Index");
    }

    return View(movie);
}

The first action method displays the initial Create form. The second handles the form post. The second Createmethod calls ModelState.IsValid to check whether the movie has any validation errors. Calling this method evaluates any validation attributes that have been applied to the object. If the object has validation errors, theCreate method redisplays the form. If there are no errors, the method saves the new movie in the database.

Below is the Create.cshtml view template that you scaffolded earlier in the tutorial. It’s used by the action methods shown above both to display the initial form and to redisplay it in the event of an error.

@model MvcMovie.Models.Movie
@{
    ViewBag.Title = "Create";
}
<h2>
    Create</h2>
<script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
@using (Html.BeginForm())
{
    @Html.ValidationSummary(true)
    <fieldset>
        <legend>Movie</legend>
        <div class="editor-label">
            @Html.LabelFor(model => model.Title)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Title)
            @Html.ValidationMessageFor(model => model.Title)
        </div>
        <div class="editor-label">
            @Html.LabelFor(model => model.ReleaseDate)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.ReleaseDate)
            @Html.ValidationMessageFor(model => model.ReleaseDate)
        </div>
        <div class="editor-label">
            @Html.LabelFor(model => model.Genre)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Genre)
            @Html.ValidationMessageFor(model => model.Genre)
        </div>
        <div class="editor-label">
            @Html.LabelFor(model => model.Price)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Price)
            @Html.ValidationMessageFor(model => model.Price)
        </div>
        <div class="editor-label">
            @Html.LabelFor(model => model.Rating)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Rating)
            @Html.ValidationMessageFor(model => model.Rating)
        </div>
        <p>
            <input type="submit" value="Create" />
        </p>
    </fieldset>
}
<div>
    @Html.ActionLink("Back to List", "Index")
</div>

Notice how the code uses an Html.EditorFor helper to output the <input> element for each Movie property. Next to this helper is a call to the Html.ValidationMessageFor helper method. These two helper methods work with the model object that’s passed by the controller to the view (in this case, a Movie object). They automatically look for validation attributes specified on the model and display error messages as appropriate.

What’s really nice about this approach is that neither the controller nor the Create view template knows anything about the actual validation rules being enforced or about the specific error messages displayed. The validation rules and the error strings are specified only in the Movie class.

If you want to change the validation logic later, you can do so in exactly one place. You won’t have to worry about different parts of the application being inconsistent with how the rules are enforced — all validation logic will be defined in one place and used everywhere. This keeps the code very clean, and makes it easy to maintain and evolve. And it means that that you’ll be fully honoring the DRY principle.

Adding Formatting to the Movie Model

Open the Movie.cs file. The System.ComponentModel.DataAnnotations namespace provides formatting attributes in addition to the built-in set of validation attributes. You’ll apply the DisplayFormat attribute and aDataType enumeration value to the release date and to the price fields. The following code shows theReleaseDate and Price properties with the appropriate DisplayFormat attribute.

         [DataType(DataType.Date)] 
        public DateTime ReleaseDate { get; set; }
       
        [DataType(DataType.Currency)] 
        public decimal Price { get; set; }

Alternatively, you could explicitly set a DataFormatString value. The following code shows the release date property with a date format string (namely, “d”). You’d use this to specify that you don’t want to time as part of the release date.

        [DisplayFormat(DataFormatString = "{0:d}")]
       public DateTime ReleaseDate { get; set; }

The following code formats the Price property as currency.

        [DisplayFormat(DataFormatString = "{0:c}")]
       public decimal Price { get; set; }

The complete Movie class is shown below.

public class Movie
{
    public int ID { get; set; }

    [Required(ErrorMessage = "Title is required")]
    public string Title { get; set; }

    [Required(ErrorMessage = "Date is required")]
    [DisplayFormat(DataFormatString = "{0:d}")]
    public DateTime ReleaseDate { get; set; }

    [Required(ErrorMessage = "Genre must be specified")]
    public string Genre { get; set; }

    [Required(ErrorMessage = "Price Required")]
    [Range(1, 100, ErrorMessage = "Price must be between $1 and $100")]
    [DisplayFormat(DataFormatString = "{0:c}")]
    public decimal Price { get; set; }

    [StringLength(5)]
    public string Rating { get; set; }
}

Run the application and browse to the Movies controller.

8_format_SM

In the next part of the series, we’ll review the application and make some improvements to the automatically generated Details and Delete methods.

ASP.NET MVC part 7 Adding a New Field to the Movie Model and Table

Adding a New Field to the Movie Model and Database Table

In this section you’ll make some changes to the model classes and learn how you can update the database schema to match the model changes.

Adding a Rating Property to the Movie Model

Start by adding a new Rating property to the existing Movie class. Open the Movie.cs file and add the Ratingproperty like this one:

public string Rating { get; set; }

The complete Movie class now looks like the following code:

public class Movie
{
    public int      ID          { get; set; }
    public string   Title       { get; set; }
    public DateTime ReleaseDate { get; set; }
    public string   Genre       { get; set; }
    public decimal  Price       { get; set; }
    public string   Rating      { get; set; }
}

Recompile the application using the Debug > Build Movie menu command.

Now that you’ve updated the Model class, you also need to update the \Views\Movies\Index.cshtml and\Views\Movies\Create.cshtml view templates in order to support the new Rating property.

Open the \Views\Movies\Index.cshtml file and add a <th>Rating</th> column heading just after the Pricecolumn. Then add a <td> column near the end of the template to render the @item.Rating value. Below is what the updated Index.cshtml view template looks like:

<table>
    <tr>
        <th></th>
        <th>Title</th>
        <th>Release Date</th>
        <th>Genre</th>
        <th>Price</th>
        <th>Rating</th>
    </tr>

@foreach (var item in Model) {
    <tr>
        <td>
            @Html.DisplayFor(modelItem => item.Title)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.ReleaseDate)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.Genre)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.Price)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.Rating   )
        </td>
        <td>
            @Html.ActionLink("Edit Me", "Edit", new { id=item.ID }) |
            @Html.ActionLink("Details", "Details", new { id=item.ID }) |
            @Html.ActionLink("Delete", "Delete", new { id=item.ID })
        </td>
    </tr>
}
</table>

Next, open the \Views\Movies\Create.cshtml file and add the following markup near the end of the form. This renders a text box so that you can specify a rating when a new movie is created.

<div class="editor-label">
    @Html.LabelFor(model => model.Rating)
</div>
<div class="editor-field">
    @Html.EditorFor(model => model.Rating)
    @Html.ValidationMessageFor(model => model.Rating)
</div>

Managing Model and Database Schema Differences

You’ve now updated the application code to support the new Rating property.

Now run the application and navigate to the /Movies URL. When you do this, though, you’ll see the following error:

You’re seeing this error because the updated Movie model class in the application is now different than the schema of the Movie table of the existing database. (There’s no Rating column in the database table.)

By default, when you use Entity Framework Code First to automatically create a database, as you did earlier in this tutorial, Code First adds a table to the database to help track whether the schema of the database is in sync with the model classes it was generated from. If they aren’t in sync, the Entity Framework throws an error. This makes it easier to track down issues at development time that you might otherwise only find (by obscure errors) at run time. The sync-checking feature is what causes the error message to be displayed that you just saw.

There are two approaches to resolving the error:

  1. Have the Entity Framework automatically drop and re-create the database based on the new model class schema. This approach is very convenient when doing active development on a test database, because it allows you to quickly evolve the model and database schema together. The downside, though, is that you lose existing data in the database — so you don’t want to use this approach on a production database!
  2. Explicitly modify the schema of the existing database so that it matches the model classes. The advantage of this approach is that you keep your data. You can make this change either manually or by creating a database change script.

For this tutorial, we’ll use the first approach — you’ll have the Entity Framework Code First automatically re-create the database anytime the model changes.

Automatically Re-Creating the Database on Model Changes

Let’s update the application so that Code First automatically drops and re-creates the database anytime you change the model for the application.

Warning   You should enable this approach of automatically dropping and re-creating the database only when you’re using a development or test database, and never on a production database that contains real data. Using it on a production server can lead to data loss.

In Solution Explorer, right click the Models folder, select Add, and then select Class.

Name the class “MovieInitializer”. Update the MovieInitializer class to contain the following code:

using System;
using System.Collections.Generic;
using System.Data.Entity;

namespace MvcMovie.Models {
    public class MovieInitializer : DropCreateDatabaseIfModelChanges<MovieDBContext> {
        protected override void Seed(MovieDBContext context) {
            var movies = new List<Movie> {  
  
                 new Movie { Title = "When Harry Met Sally",   
                             ReleaseDate=DateTime.Parse("1989-1-11"),   
                             Genre="Romantic Comedy",  
                             Rating="R",  
                             Price=7.99M},  

                     new Movie { Title = "Ghostbusters ",   
                             ReleaseDate=DateTime.Parse("1984-3-13"),   
                             Genre="Comedy",  
                              Rating="R",  
                             Price=8.99M},   
  
                 new Movie { Title = "Ghostbusters 2",   
                             ReleaseDate=DateTime.Parse("1986-2-23"),   
                             Genre="Comedy",  
                             Rating="R",  
                             Price=9.99M},   

               new Movie { Title = "Rio Bravo",   
                             ReleaseDate=DateTime.Parse("1959-4-15"),   
                             Genre="Western",  
                             Rating="R",  
                             Price=3.99M},   
             };

            movies.ForEach(d => context.Movies.Add(d));
        }
    }
}

The MovieInitializer class specifies that the database used by the model should be dropped and automatically re-created if the model classes ever change. The code includes a Seed method to specify some default data to automatically add to the database any time it’s created (or re-created). This provides a useful way to populate the database with some sample data, without requiring you to manually populate it each time you make a model change.

Now that you’ve defined the MovieInitializer class, you’ll want to wire it up so that each time the application runs, it checks whether the model classes are different from the schema in the database. If they are, you can run the initializer to re-create the database to match the model and then populate the database with the sample data.

Open the Global.asax file that’s at the root of the MvcMovies project:

The Global.asax file contains the class that defines the entire application for the project, and contains anApplication_Start event handler that runs when the application first starts.

Let’s add two using statements to the top of the file. The first references the Entity Framework namespace, and the second references the namespace where our MovieInitializer class lives:

using System.Data.Entity;            // Database.SetInitialize
using MvcMovie.Models;              // MovieInitializer

Then find the Application_Start method and add a call to Database.SetInitializer at the beginning of the method, as shown below:

protected void Application_Start()
{
    Database.SetInitializer<MovieDBContext>(new MovieInitializer());

    AreaRegistration.RegisterAllAreas();
    RegisterGlobalFilters(GlobalFilters.Filters);
    RegisterRoutes(RouteTable.Routes);
}

The Database.SetInitializer statement you just added indicates that the database used by theMovieDBContext instance should be automatically deleted and re-created if the schema and the database don’t match. And as you saw, it will also populate the database with the sample data that’s specified in theMovieInitializer class.

Close the Global.asax file.

Re-run the application and navigate to the /Movies URL. When the application starts, it detects that the model structure no longer matches the database schema. It automatically re-creates the database to match the new model structure and populates the database with the sample movies:

7_MyMovieList_SM

Click the Create New link to add a new movie. Note that you can add a rating.

7_CreateRioII

Click Create. The new movie, including the rating, now shows up in the movies listing:

7_ourNewMovie_SM

In this section you saw how you can modify model objects and keep the database in sync with the changes. You also learned a way to populate a newly created database with sample data so you can try out scenarios. Next, let’s look at how you can add richer validation logic to the model classes and enable some business rules to be enforced.