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 :
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.
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.
3- Create the Main page for the application
This is the result we want for the Main 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.
You can download this app from Windows Phone Market for Free.
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
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” />
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.
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.
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.
To fix this, we simply create a Mobile folder under Scripts folder and move the mobile related js into it.
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.
Now open the _Layout.mobile.cshtml and change the references to point to the correct ‘mobile’ folder.
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.
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.
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.
Now when we view the application instead of the modified desktop view, we get a custom Mobile view.
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.
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
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:
– 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
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:
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
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.
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:
The same page on Windows Phone (emulator) looks as follows
The same page on IE9 looks as follows
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
-
It helps us tailor views to specific devices
-
It helps reduce bandwidth usage over mobile devices by giving us option to remove un-necessary graphics from Mobile views.
-
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:
- Windows Phone Emulator (RC). (This is the emulator that’s used in most of the screen shots in this tutorial.)
- Opera Mobile Emulator
- Apple Safari with the user agent set to iPhone. For instructions on how to set the user agent in Safari to “iPhone”, see How to let Safari pretend it’s IE on David Alison’s blog.
- FireFox with the FireFox User Agent Switcher.
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).
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.)
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).
The display is very readable on a mobile device. Choose the ASP.NET link.
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.)
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.
In contrast, the desktop display has not changed.
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.
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.
(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:
- From the Tools menu, select Package Manager Console, and then select Library Package Manager.
- In the Package Manager Console, enter
Install-Package jQuery.Mobile.MVC
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:
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.
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.
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.
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).
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
.
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:
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.
As you type each letter in the search box, jQuery Mobile filters the displayed list as shown in the image below.
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
.
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.
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:
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.
Tap the Scott Hanselman link.
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.
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.
Tap the Scott Hanselman link. Scott Hanselman’s sessions are displayed.
Choose the An Overview of the MS Web Stack of Love link.
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:
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 Delete
methods. 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 DataAnnotations
namespace. 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 Required
, StringLength
, 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 Title
, ReleaseDate
, Genre
, 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 SaveChanges
method 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.
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 Create
method 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.
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 Rating
property 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
column near the end of the template to render the <td>
@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:
- 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!
- 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:
Click the Create New link to add a new movie. Note that you can add a rating.
Click Create. The new movie, including the rating, now shows up in the movies listing:
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.