Sie sind auf Seite 1von 380

C# .NET 4: Web Development and User Interf ace Design Using .

NET
Lesso n 1: Int ro duct io n and We b De ve lo pm e nt Ove rvie w
Understanding the Learning Sandbo x Enviro nment
Co de Snippets
The OST Plug-In

Web and Internet Overview


Hello Wo rld - A Simple Website
Hello Wo rld - Adding Web Co ntro ls
Debugging
Quiz 1 Pro ject 1
Lesso n 2: Int ro duct io n t o We b Fo rm s
Intro ductio n to ASP.NET Web Fo rms
Web Calculato r - Understanding the ASP.NET Architecture

Quiz 1 Pro ject 1


Lesso n 3: We b Fo rm s
Web Fo rms
Creating the Pro ject
Examining the Auto -generated Website
Creating A Site

Quiz 1 Pro ject 1


Lesso n 4: Se curit y
Authenticatio n and Autho rizatio n
Membership and Ro les
Autho rizatio n
Quiz 1 Pro ject 1
Lesso n 5: Int ro duct io n t o MVC and Razo r
Defining MVC
Mo del
Co ntro ller
View
Mo del/View/Co ntro ller

MVC vs. Smart UI Design Pattern


Creating a New Pro ject
Pro ject Fo lders
The Co ntro ller
The View
Co ntro ller Actio ns
Intro ductio n to Razo r
URLs and Ro uting
Basics o f Ro uting
Ro uting Co nstraints

Quiz 1 Pro ject 1


Lesso n 6 : MVC
The Mo del
The Mo del
The Entity Framewo rk
MVC and the Entity Framewo rk
Mo dels and Entities
Filters
Quiz 1 Pro ject 1
Lesso n 7: Unit T e st ing
Testing with Visual Studio
Creating the Pro ject
So ftware Testing
Unit Testing
Unit and Integrated Testing
MVC Unit Testing
Additio nal Testing Templates
Final Tho ughts
Quiz 1 Pro ject 1
Lesso n 8 : Clie nt So lut io ns: J avaScript , jQue ry, Ajax, XML, and J SON
Client-Side and Server-Side Develo pment
JavaScript
jQuery
JSON
XML
Ajax
Partial Views and Ajax
Quiz 1 Pro ject 1
Lesso n 9 : ASP.NET and Dat abase s
Adding a Database
Creating a Menu Actio n
Creating, Editing, Deleting, Searching
Quiz 1 Pro ject 1
Lesso n 10 : Obje ct Re lat io nal Mapping: Ent it y Fram e wo rk and Dat a Abst ract io n
Entity Framewo rk
Data Abstractio n
Repo sito ry
Unit o f Wo rk

Quiz 1 Quiz 2
Lesso n 11: Obje ct Re lat io nal Mapping: CRUD and Aut o m at ic Co de Ge ne rat io n, LINQ, and Ent it y Dat a Mo de l
CRUD and Auto matic Co de Generatio n
Repo sito ry with CRUD
Unit o f Wo rk with CRUD

LINQ to Entities
Entity Data Mo del (EDM)
Quiz 1 Pro ject 1
Lesso n 12: Int e rf ace s and Ext e nsio ns
Interfaces and MVC
Interfaces and MVC
IQueryable Interface

Extensio n Metho ds
Filtering Using Extensio n Metho ds
Quiz 1 Pro ject 1
Lesso n 13: We b Se rvice s
What is a Web Service?
Our First Web Service
Web Services: SOAP and REST
Adding REST Metho ds
Accessing Public Web Services
Web Service Legacy and Windo ws Co mmunicatio n Fo undatio n (WCF)
Quiz 1 Pro ject 1
Lesso n 14: Final Pro je ct
Pro ject Functio nal Specificatio ns
Planning the Pro ject
Use Cases
UI Design
Data Mo deling: Entity Data Mo del
Data Mo deling: Database Tables
So ftware Develo pment and Unit Test Develo pment
Testing and Co mpletio n
Pro ject 1

Copyright © 1998-2014 O'Reilly Media, Inc.

This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.
See http://creativecommons.org/licenses/by-sa/3.0/legalcode for more information.
Introduction and Web Development Overview
Welco me to the O'Reilly Scho o l o f Techno lo gy's C# .NET 4: Web Develo pment and User Interface Design Using .NET co urse!

Course Objectives
When yo u co mplete this co urse, yo u will be able to :

use web develo pment terms and architecture.


create dynamic websites using ASP.NET Web Fo rms.
create dynamic websites using the ASP.NET MVC (Mo del-View-Co ntro ller) framewo rk and the Razo r pro gramming
syntax.
emplo y JavaScript, jQuery, AJAX, JSON and XML in web applicatio ns.
use ASP.NET security fo r authenticatio n and autho rizatio n with web applicatio ns.
co nnect databases and websites.
use the .NET Entity Framewo rk with o bjects and LINQ.
develo p multi-threaded co de.
wo rk with and create web services.
use the Silverlight framewo rk.
create user interfaces using .NET WPF (Windo ws Presentatio n Fo undatio n) and XAML.

Lesson Objectives
When yo u co mplete this lesso n, yo u will be able to :

use the Learning Sandbo x Enviro nment.


access an Overview o f the Web and Internet.
create A Simple Website.
add Web Co ntro ls to Yo ur Website.

Learning with O'Reilly School of T echnology Courses


As with every O'Reilly Scho o l o f Techno lo gy co urse, we'll take a user-active appro ach to learning. This means that yo u
(the user) will be active! Yo u'll learn by do ing, building live pro grams, testing them and experimenting with them—
hands-o n!

To learn a new skill o r techno lo gy, yo u have to experiment. The mo re yo u experiment, the mo re yo u learn. Our system
is designed to maximize experimentatio n and help yo u learn to learn a new skill.

We'll pro gram as much as po ssible to be sure that the principles sink in and stay with yo u.

Each time we discuss a new co ncept, yo u'll put it into co de and see what YOU can do with it. On o ccasio n we'll even
give yo u co de that do esn't wo rk, so yo u can see co mmo n mistakes and ho w to reco ver fro m them. Making mistakes
is actually ano ther go o d way to learn.

Abo ve all, we want to help yo u to learn to learn. We give yo u the to o ls to take co ntro l o f yo ur o wn learning experience.

When yo u co mplete an OST co urse, yo u kno w the subject matter, and yo u kno w ho w to expand yo ur kno wledge, so
yo u can handle changes like so ftware and o perating system updates.

Here are so me tips fo r using O'Reilly Scho o l o f Techno lo gy co urses effectively:

T ype t he co de . Resist the temptatio n to cut and paste the example co de we give yo u. Typing the co de
actually gives yo u a feel fo r the pro gramming task. Then play aro und with the examples to find o ut what else
yo u can make them do , and to check yo ur understanding. It's highly unlikely yo u'll break anything by
experimentatio n. If yo u do break so mething, that's an indicatio n to us that we need to impro ve o ur system!
T ake yo ur t im e . Learning takes time. Rushing can have negative effects o n yo ur pro gress. Slo w do wn and
let yo ur brain abso rb the new info rmatio n tho ro ughly. Taking yo ur time helps to maintain a relaxed, po sitive
appro ach. It also gives yo u the chance to try new things and learn mo re than yo u o therwise wo uld if yo u
appro ach. It also gives yo u the chance to try new things and learn mo re than yo u o therwise wo uld if yo u
blew thro ugh all o f the co ursewo rk to o quickly.
Expe rim e nt . Wander fro m the path o ften and explo re the po ssibilities. We can't anticipate all o f yo ur
questio ns and ideas, so it's up to yo u to experiment and create o n yo ur o wn. Yo ur instructo r will help if yo u
go co mpletely o ff the rails.
Acce pt guidance , but do n't de pe nd o n it . Try to so lve pro blems o n yo ur o wn. Go ing fro m
misunderstanding to understanding is the best way to acquire a new skill. Part o f what yo u're learning is
pro blem so lving. Of co urse, yo u can always co ntact yo ur instructo r fo r hints when yo u need them.
Use all available re so urce s! In real-life pro blem-so lving, yo u aren't bo und by false limitatio ns; in OST
co urses, yo u are free to use any reso urces at yo ur dispo sal to so lve pro blems yo u enco unter: the Internet,
reference bo o ks, and o nline help are all fair game.
Have f un! Relax, keep practicing, and do n't be afraid to make mistakes! Yo ur instructo r will keep yo u at it
until yo u've mastered the skill. We want yo u to get that satisfied, "I'm so co o l! I did it!" feeling. And yo u'll have
so me pro jects to sho w o ff when yo u're do ne.

Lesson Format
We'll try o ut lo ts o f examples in each lesso n. We'll have yo u write co de, lo o k at co de, and edit existing co de. The co de
will be presented in bo xes that will indicate what needs to be do ne to the co de inside.

Whenever yo u see white bo xes like the o ne belo w, yo u'll type the co ntents into the edito r windo w to try the example
yo urself. The CODE TO TYPE bar o n to p o f the white bo x co ntains directio ns fo r yo u to fo llo w:

CODE TO TYPE:

White boxes like this contain code for you to try out (type into a file to run).

If you have already written some of the code, new code for you to add looks like this.

If we want you to remove existing code, the code to remove will look like this.

We may also include instructive comments that you don't need to type.

We may run pro grams and do so me o ther activities in a terminal sessio n in the o perating system o r o ther co mmand-
line enviro nment. These will be sho wn like this:

INTERACTIVE SESSION:

The plain black text that we present in these INTERACTIVE boxes is


provided by the system (not for you to type). The commands we want you to type look lik
e this.

Co de and info rmatio n presented in a gray OBSERVE bo x is fo r yo u to inspect and absorb. This info rmatio n is o ften
co lo r-co ded, and fo llo wed by text explaining the co de in detail:

OBSERVE:
Gray "Observe" boxes like this contain information (usually code specifics) for you to
observe.

The paragraph(s) that fo llo w may pro vide additio n details o n inf o rm at io n that was highlighted in the Observe bo x.

We'll also set especially pertinent info rmatio n apart in "No te" bo xes:

Note No tes pro vide info rmatio n that is useful, but no t abso lutely necessary fo r perfo rming the tasks at hand.

T ip Tips pro vide info rmatio n that might help make the to o ls easier fo r yo u to use, such as sho rtcut keys.
When yo u see the ico n, save yo ur wo rk. Yo u can save whenever yo u like; we want yo u to get into the habit o f saving yo ur
pro jects frequently. In fact, whenever yo u pause to think, click the ico n o n the to o lbar (o r press Ct rl+S). When yo u see the
ico n, click that ico n o n the to o lbar to run the currently o pen pro gram.

Understanding the Learning Sandbox Environment


Code Snippets
Whenever we present snippets o f co de, yo u'll want to create a sample pro ject where yo u can enter that co de.
We'll pro mpt yo u to create test pro ject(s) fo r each lesso n. As we pro gress thro ugh tho se lesso ns, yo u'll
create metho ds and call them fro m the Fo rm co nstructo r under the InitializeCo mpo nent() line o f co de.
Eventually yo u'll create mo re to pic-specific metho ds, and co mment o ut the calls to the metho ds that yo u're
no t using.

T he OST Plug-In
We've added a new menu item to the Visual Studio system that we think yo u'll like. No w yo u can use the OST
menu to get to yo ur syllabus fo r this co urse at any time. Yo ur menu may display o ther co urses yo u've
enro lled in as well.

If yo ur Visual Studio menus start to get co nfusing, yo u can always get back to the default view by
T ip selecting this co urse fro m the OST menu.

Web and Internet Overview


Yo u'll be learning ho w to create web-based applicatio ns, so it's impo rtant that yo u understand the web.

The web (o r Wo rld Wide Web, WWW, o r W3) is the ever-expanding base o f hypertext do cuments accessible via the
internet, capable o f delivering a wealth o f info rmatio n via numero us fo rmats such as text, images, video , and so o n.
The internet is a netwo rk o f co mmunicatio n netwo rks; it allo ws access to web co ntent, as well as a ho st o f o ther
po ssible reso urces. Frequently, the terms web and internet are used interchangeably, but their definitio ns refer to two
distinct co ncepts. The image belo w po rtrays a typical o verview o f the web and internet.
We will be learning ho w to create web co ntent using a number o f .NET techno lo gies. Fro m the image abo ve, web
co ntent is retrieved, o r served, fro m server co mputers to client co mputers (co nsumers) via the Internet. Often, clo ud-
like images are used to po rtray the Internet because o f the numero us netwo rks and servers that serve a seemingly
unending quantity o f web co ntent fro m everywhere imaginable. Web co ntent is typically displayed using a web bro wser
(such as Firefo x, Internet Explo rer, Chro me, Safari).

Here's a brief o verview o f ho w co ntent is delivered to yo ur web bro wser:

1. A web do cument address (o r URL) is entered in the web bro wser.


2. The URL is reso lved to a co mputer server (o r web server) accessible thro ugh the internet.
3. A request is made thro ugh the internet fo r the web do cument fro m the web server.
4. The web server generates the web do cument and returns the web do cument co ntent to the requesting
co mputer (o r client co mputer) thro ugh the internet.
5. The web bro wser o n the client co mputer receives and renders the web do cument co ntent.

Hello World - A Simple Website


Yo u can create a basic website fairly quickly. Let's create o ne that displays the static co ntent, "Hello , Wo rld!"

Micro so ft Visual Studio includes a number o f techniques fo r creating websites, such as ASP, Web Fo rms,
and MVC. We'll start o ut learning Web Fo rms, then mo ve o n to mo re recent techno lo gies. Also , Visual
Studio suppo rts two different types o f web pro jects: web applicatio n pro jects and website pro jects.
Note Initially, we'll use website pro jects, and then mo ve o n to web applicatio n pro jects. Yo u can co mpare
these pro ject types, in this MSDN article: Web Applicatio n Pro jects versus Web Site Pro jects in Visual
Studio .

Create a test pro ject using File | Ne w | We b Sit e .... In the New Web Site dialo g bo x, select Visual C# under Installed
Templates, select .NET Fram e wo rk 4 .0 .3 in the middle to p dro pdo wn, and select the ASP.NET Em pt y We b Sit e
template. Select File Syst e m fro m the web lo catio n dro pdo wn bo x. Mo dify the fo lder lo catio n fro m My
Do cuments\Visual Studio 20 10 \WebSites\We bSit e 1 to My Do cuments\Visual Studio
20 10 \WebSites\We bHe llo Wo rld, and click OK.

Because we selected an Empty Web Site, Visual Studio generated no web co ntent. In the So lutio n Explo rer, yo u'll see
a single entry fo r a web.co nfig file. We'll discuss this file in so me detail in a later lesso n. Fo r no w, let's add a web page
fo r o ur website.

Befo re yo u do mo ve o n, no te that the new do cument yo u create will replace this lesso n text in the main Visual Studio
windo w. To see the lesso n co ntent again, yo u'll need to right-click the tab with the filename (De f ault .aspx) and select
Ne w Ho rizo nt al T ab Gro up.

To add a website do cument, right-click in the So lutio n Explo rer, select Add Ne w It e m ..., select We b Fo rm , make
sure the Name is De f ault .aspx, and click Add.

A new web do cument (so metimes referred to as a "web page") is created and added to the website pro ject, as sho wn:

If yo u're familiar with HTML syntax used with web do cuments, yo u may no tice a number o f HTML elements, but o ther
elements are unique to .NET. We'll explo re the .NET syntax o f a Web Fo rm web do cument in the next lesso n, but fo r
no w, we'll just add HTML text that will display "Hello , Wo rld!" Mo dify De f ault .aspx. Mo dify yo ur co de as sho wn:
CODE TO TYPE:
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Def
ault" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xh


tml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>
</head>
<body>
<form id="form1" runat="server">
<div>
<b>Hello, World!</b>
</div>
</form>
</body>
</html>

and to run the pro gram. A dialo g bo x may pro mpt yo u to enable debugging fo r this pro ject:

Select Mo dif y t he We b.co nf ig f ile t o e nable de bugging, and click OK. A bro wser (Internet Explo rer) windo w
appears, displaying the Default.aspx webpage, including the "Hello , Wo rld!" text.

Befo re clo sing the bro wser, minimize it and lo o k at Visual Studio . Visual Studio is in Debug mo de, and the debug
menu items and butto ns are enabled. Our website co de is "running" in a bro wser, rather than in an applicatio n. To exit
debug mo de and return to editing o ur pro ject, we need to either clo se the bro wser o r use Visual Studio to sto p
debugging. Clo se the bro wser by maximizing it and then clicking the X at the to p right o f the bro wser windo w.

Go ahead and run the website pro ject, and minimize and resto re the Visual Studio and bro wser windo ws to
T ip practice switching between these two applicatio ns.

Co ngratulatio ns, yo u've created yo ur first website pro ject!

Fo r this co urse, yo u need to have a wo rking kno wledge o f HTML. If yo u do n't, co nsider taking the OST
HTML co urses—fo r mo re info rmatio n, co ntact us. We'll explain so me HTML co ncepts as they co me up in
T ip the co urse, but ultimately yo u'll need to have a pretty go o d understanding o f HTML. Web develo pers
pro duce so ftware that generates web co ntent, and since web co ntent is expressed in HTML, a wo rking
kno wledge o f HTML is essential.

Hello World - Adding Web Controls


Let's mo dify o ur, "Hello , Wo rld!" website to include web co ntro ls, co ntro ls yo u can add to yo ur website. We'll also
learn ho w to to ggle the Co de Edito r views, and get a quick intro ductio n to the co ncept o f a co de-behind file. Let's go !

If yo u clo sed the WebHello Wo rld pro ject, o r clo sed Studio co mpletely, yo u'll need to o pen the website
Note pro ject. When o pening a website pro ject, rather than use Open Pro ject in Studio , use Open Web Site.

As the functio nality o f a website gro ws and interactio n with the user increases, the website is o ften
Note referred to as a web application.

Open and examine De f ault .aspx in the Co de Edito r. No te the co ntro ls alo ng the bo tto m left o f the Co de Edito r
Windo w: Design, Split, and So urce:

When we created the WebHello Wo rld website pro ject and added a web do cument, the co de edito r o pened in So urce
view so we co uld edit the co ntents o f the new web do cument. The web do cument so urce is no t what appears when we
run the website pro ject. Instead, the web do cument so urce is pro cessed by the web server, and the web server
generates the HTML co ntent that web bro wsers understand. When creating web do cuments, we want to be able to see
what the rendered HTML co ntent will be as we go . In the Co de Edito r, we can switch to the Design view to see what the
co ntent sho uld lo o k like, o r we can use the Split view o ptio n to see bo th the web do cument so urce and rendered
co ntent.

The rendered web co ntent sho uld match what yo u see in yo ur web bro wser, but no t all bro wsers render
HTML co ntent exactly the same way. Pro fessio nal web develo pers test their website applicatio ns with
Note multiple bro wsers to ensure a co nsistent rendering o f their applicatio n co de. Rendering the HTML in the
Co de Edito r may take a while.

Switch to the Co de Edito r Design view by clicking the De sign tab at the bo tto m o f the Co de Edito r windo w. No w
instead o f do cument so urce co de, yo u see the text, "Hello , Wo rld!":

Yo u may prefer to add o r edit web co ntent to yo ur web do cuments using the Design view, but o ften yo u may need to
switch to the So urce view to make sure that yo ur new co ntent is exactly what yo u want.

We're go ing to add a text bo x to o ur website to pro mpt fo r a name and a butto n. When yo u enter a name and click o n
the butto n, a greeting with the name o n the web page using a label will be displayed. To add co ntro ls to yo ur web
do cument, use the co ntro ls o n the To o lbo x under the Standard catego ry.

If yo u want to be able to add HTML co ntro ls using drag and dro p, yo u can find the HTML no n-ASP co ntro ls
T ip under the HTML sectio n o f the To o lbo x.

In the Co de Edito r, click after the Hello , Wo rld! text, and press Ent e r to go to the next line. Type Ent e r yo ur nam e : .
Select the To o lbo x windo w, and expand the Standard catego ry. Click and drag a T e xt Bo x co ntro l, and dro p it in after
the "Enter yo ur name:" text. Click and drag a But t o n co ntro l and dro p it after the TextBo x co ntro l. Press Ent e r again to
go to the next line. Click and drag a Labe l co ntro l o nto the new line.

As we've seen with o ther windo ws' co ntro ls, we can edit the co ntro l pro perties. Click o n the TextBo x co ntro l, and
change the ID pro perty to nam e T e xt Bo x. Click o n the Butto n co ntro l and change the ID pro perty to go But t o n, and
the T e xt pro perty to Go . Click the Label co ntro l and change the ID pro perty to re sult Labe l, and the Text pro perty to
.... Yo ur webpage will lo o k like this:
and to run the web applicatio n. Once the bro wser windo w appears, enter a name, and click the Go butto n. Did
anything happen?

As with o ther .NET pro grams, we need to pro gram the functio nality o f the Butto n.

Do uble-click the Go Butto n co ntro l. A new Co de Edito r windo w o pens that we use to edit the De f ault .aspx.cs file.
No tice that the name o f the file is almo st identical to the web do cument, except that the filename includes a .cs suffix.
.cs stands fo r C#, and is called the "co de-behind file." Befo re we edit, let's talk abo ut the aspx and aspx.cs files.

We initially edited the aspx so urce file. An aspx, o r the ASPX acro nym, means an Active Server Page Extended file. An
ASPX so urce file co ntains HTML and ASP.NET script co de used to create web co ntent. Each ASPX file has a co de-
behind file that co ntains co de in a .NET language (such as C#, VB, and so o n) and respo nds to and interacts with the
ASPX file. The suffix used with the co de-behind file sho uld match the .NET language, so aspx.cs fo r C#, and aspx.vb
fo r VB (Visual Basic). We'll co ntinue to learn mo re abo ut the architecture o f website develo pment using .NET. The
aspx.cs file will beco me very familiar to yo u because it co ntains C# co de.

Okay, let's get back to adding the necessary functio nality fo r o ur web applicatio n.

By do uble-clicking o n the Butto n co ntro l, we've added an event handler fo r this Butto n. Whenever this Butto n is clicked,
the event handler co de in the co de-behind file will be called.

Mo dify the Default.aspx.cs file as sho wn:

CODE TO TYPE:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

public partial class _Default : System.Web.UI.Page


{
protected void Page_Load(object sender, EventArgs e)
{

}
protected void goButton_Click(object sender, EventArgs e)
{
resultLabel.Text = "Hi, " + nameTextBox.Text;
}
}

Let's discuss this co de.


OBSERVE:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

public partial class _Default : System.Web.UI.Page


{
protected void Page_Load(object sender, EventArgs e)
{

}
protected void goButton_Click(object sender, EventArgs e)
{
resultLabel.Text = "Hi, " + nameTextBox.Text;
}
}

The Default.aspx.cs file fo llo ws the pattern we've seen with o ther C# so urce co de files; it has namespaces to be used,
and a class definitio n fo r the webpage that inherits fro m Syst e m .We b.UI.Page . This webpage class includes two
event handlers: a Page _Lo ad event, pro vided when the webpage was created and called whenever a webpage is
lo aded; and the go But t o n_Click event that was added when we do uble-clicked the Butto n co ntro l.

In the go But t o n_Click event handler, we can use the names o f co ntro ls we've added to the webpage, such as
re sult Labe l, and Intellisense which will help us to select the T e xt pro perty. We can use the nam e T e xt Bo x T e xt
pro perty to retrieve the entered name. Using these co ntro ls, we can respo nd to the Butto n click and pro duce an
appro priate greeting.

and to run the web applicatio n. When the bro wser windo w appears, enter a name and click Go . This time, yo u
see the greeting!

Debugging
Befo re yo u leave this first lesso n, re-run the lesso n pro ject co de using debugging to o ls. Just as with C# applicatio ns,
we can place a debugging breakpo int by clicking in the shaded area to the left o f a line o f co de, o r by right-clicking o n
any so urce co de line and selecting Bre akpo int | Inse rt Bre akpo int . Add a breakpo int to the line o f co de that sets
the Label co ntro l Text pro perty. When yo u run the website pro ject and click o n the Butto n co ntro l, the IDE will break o n
the breakpo int. Highlight o r ho ver the mo use o ver different variables o r pro perties to o bserve their values. Debugging
in an ASP.NET website pro ject wo rks just as yo u've seen with a C# applicatio n pro ject.

To clo se this pro ject, select File | Clo se So lut io n. To o pen a pro ject later, select File | Ope n | We b Sit e ..., select File
Syst e m , and navigate to Co m put e r, yo ur \\be am \winuse rs fo lder, My Do cum e nt s\Visual St udio 20 10 \We bSit e s, select
the website yo u want to o pen and click Ope n.

Befo re yo u mo ve o n to the next lesso n, make sure to do any pro jects and quizzes fo r this lesso n.

Copyright © 1998-2014 O'Reilly Media, Inc.

This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.
See http://creativecommons.org/licenses/by-sa/3.0/legalcode for more information.
Introduction to Web Forms
Lesson Objectives
In this lesso n yo u will:

use ASP.NET Web Fo rms and Web Co ntro ls


create a web calculato r pro ject using ASP.NET

The latest versio ns o f Micro so ft ASP.NET, .NET, and Visual Studio , include numero us enhancements to suppo rt Rapid
Applicatio n Develo pment (RAD). We will co ver these newer techno lo gies, but first we want to co ver ASP.NET Web Fo rms, a
techno lo gy still used by many web develo pers, and o ften mixed with the newer techniques. In this lesso n, we'll co ver the basics
o f Web Fo rms and Web Co ntro ls, including explo ring the mechanisms behind Web Fo rms. Let's get started.

Introduction to ASP.NET Web Forms


Web Calculator - Understanding the ASP.NET Architecture
Fo r this lesso n, we will create a web calculato r as we learn mo re abo ut Web Fo rms and Web Co ntro ls.

Create a new web pro ject using File | Ne w | We b Sit e . In the New Web Site dialo g bo x, make sure Visual
C# is selected under Installed Templates, and select the ASP.NET Em pt y We b Sit e template. Select File
Syst e m fro m the web lo catio n dro pdo wn bo x, and change the Name o f the Web Pro ject to We bCalculat o r.
Click OK.

To add a website do cument, right-click in the So lutio n Explo rer, select Add Ne w It e m ..., select We b Fo rm ,
make sure the Name is De f ault .aspx, and click Add.

Dynamic Code Blocks and Script Direct ives

After adding the new website do cument, the Co de Edito r o pens to the Default.aspx file. Let's take a clo ser
lo o k at the generated co de:

OBSERVE:

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherit


s="_Default" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.or


g/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>
</head>
<body>
<form id="form1" runat="server">
<div>

</div>
</form>
</body>
</html>

Yo u see specific sectio ns and syntax. The first sectio n begins with <% and ends with % >. These delimiters
mark a sectio n o f ASP.NET co de that may co ntain either dynamic co de to be executed o n the server, o r script
directives that co ntro l and direct ho w ASP.NET sho uld pro cess the co ntents o f the ASPX file. Script directives
add an at (@) symbo l after the <% markup co de. This specific sectio n is the Page directive used to define
page-specific attributes used by the ASP.NET page parser and co mpiler, and it can o nly be included in ASPX
files. No te that directives may have attributes; we'll discuss that sho rtly.
ASPX so urce file co ntent includes HTML co de co nsisting o f tags delimited by the angle brackets
< and >. HTML co de is co mmo nly referred to as markup language o r markup code. Many markup
Note tags also include attributes. Fo r example, in the co de generated when we added a new website
do cument, the f o rm HTML markup tag includes two attributes: id and runat .

In previo us versio ns o f .NET, these dynamic co de blo cks co ntained dynamic co de, but since .NET 2.0 , .NET
added and enco uraged the use o f co de-behind files rather than embedded dynamic co de. Fro m previo us
lesso ns and experience with o bject-o riented pro gramming, yo u might be able to guess why we wo uld want
the dynamic co de separate fro m the user interface (UI) co de: encapsulatio n, ease o f develo pment, and so o n.
We'll revisit this separatio n o f co de and UI later when we co ver MVC.

When yo u create an ASPX web do cument, typically yo u may accept the default settings fo r the Page directive.
In future lesso ns we'll explo re adding o ther attributes and mo difying the default values. Here is a list o f the
current attributes specified with the Page directive and their current values:

Language (C#): Specifies the language used when co mpiling all inline rendering (<% %> and <%=
%>) and co de declaratio n blo cks within the page. Values can represent any .NET Framewo rk-
suppo rted language, including Visual Basic, C#, o r JScript. Only o ne language can be used and
specified per page.
Auto EventWireup (true): Indicates whether the page's events are auto wired. If event auto wiring is
enabled, this will be true; o therwise, false. The default is true.
Co deFile: Specifies a path to the referenced co de-behind file fo r the page. This attribute is used
to gether with the Inherits attribute to asso ciate a co de-behind so urce file with a Web page. The
attribute is valid o nly fo r co mpiled pages.
Inherits: Defines a co de-behind class fo r the page to inherit. This can be any class derived fro m the
Page class. This attribute is used with the Co deFile attribute, which co ntains the path to the so urce
file fo r the co de-behind class. The Inherits attribute is case-sensitive when using C# as the page
language, and case-insensitive when using Visual Basic as the page language.

The descriptio n fo r each o f these attributes co mes directly fro m Micro so ft's MSDN website, at Directives fo r
ASP.NET Web Pages.

As we wo rk thro ugh ASP.NET, yo u may want to review the Wikipedia article devo ted to this to pic at
T ip ASP.NET.

Two o f the static HTML statements also include runat =" se rve r" . The runat attribute specifies that a specific
tag is to be handled in so me way o n the server co mputer. In the supplied co de, bo th the <head> HTML tag
and the <fo rm> HTML tag will be handled by ASP.NET and pro cessed o n the server. ASP.NET requires these
attributes; we'll see the reaso ns fo r that as we wo rk thro ugh the co de.

ASP Cont rols and View St at e

The last highlighted co mpo nent is the HTML div tag, which is a standard HTML tag. The div tag is used
frequently with ASP.NET web pages, essentially to create blo cks. Let's add a Panel co ntro l and then examine
the resulting changes to the co de.

Click the Pane l co ntro l in the To o lBo x, and drag it o nto the Co de Edito r, dro pping it in after the ending /div tag.
After adding the Panel co ntro l, mo dify the co de as sho wn (yo u do n't need to change the Co de Edito r to the
Design view; yo u can add co ntro ls in either So urce View o r Design View):
CODE TO TYPE:
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherit
s="_Default" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.or


g/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>
</head>
<body>
<form id="form1" runat="server">
<div>
Standard div tag
</div>
<asp:Panel ID="Panel1" runat="server">
ASP div tag
</asp:Panel>
</form>
</body>
</html>

ASP Co ntro ls are referred to as "markup co de" and are prefixed with asp:. Like HTML markup tags, ASP
markup tags include attributes. With the ASP Panel co ntro l, we see an ID tag to identify the name o f the ASP
Panel co ntro l instance, and the ubiquito us runat attribute that must be present in o rder fo r the ASP Co ntro l to
be pro cessed by the server.

We used a Panel because it creates an HTML div element, which o ur web applicatio n can use to display any
co ntro ls we want to add. Our web applicatio n generates web co ntent that is used by a bro wser, so let's run
o ur applicatio n and examine the rendered HTML co ntent.

and to run the web pro ject. If yo u see the "Debugging No t Enabled" dialo g bo x, enable debugging fo r
this pro ject.

Once the web pro ject is running in the bro wser, we need to examine the generated HTML co de. Yo u're likely
using Internet Explo rer, so to view the webpage so urce co de, right-click o n the webpage windo w and select
Vie w so urce . Yo u see rendered co ntent:
OBSERVE:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.or
g/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head><title>

</title></head>
<body>
<form method="post" action="Default.aspx" id="form1">
<div class="aspNetHidden">
<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="
/wEPDwUJNTA3NjE3NzI4ZGRQQgH5u9xxH62juA47QdfpREReZFQ6fUMatiVnKsjNag==" />
</div>

<div>
Standard div tag
</div>
<div id="Panel1">

ASP div tag

</div>
</form>
</body>
</html>

In the rendered co ntent, we can see the div tag that resulted fro m the ASP Panel co ntro l. Why wo uld we want
to differentiate between a standard HTML div tag and an ASP Panel co ntro l rendered as a div tag? Even
tho ugh the o nly rendered difference is the presence o f the id attribute, ASP Co ntro ls such as the Panel co ntro l
may be referenced in o ur co de-behind file. Because a div co ntro l and a Panel co ntro l are used to co ntain
o ther co ntro ls, yo u can use either a standard HTML div o r an ASP Panel co ntro l—the next co ncept o f view
state we go o ver will help yo u to decide which to use.

Part o f the rendered HTML co ntent includes a hidden fo rm element __VIEWST AT E, with a so mewhat cryptic
value co nsisting o f a sequence o f alphanumeric characters. When co ding using ASP.NET, yo u want to
understand the ASP.NET webpage life cycle, which includes the generatio n and use o f the view state co ncept.
Yo u can read a go o d explanatio n o f the life cycle and view state in the Micro so ft MSDN article Understanding
ASP.NET View State. We enco urage yo u to lo o k o ver the article, paying particular attentio n to the image
representing the life cycle, and perhaps review the descriptio ns o f each o f the steps.

ASP Co ntro ls are web fo rm co ntro ls, and as such need to send data to the web server fo r each o f the fo rm
co ntro ls. When web pages are returned, fo rm element data will need to be resto red based o n the previo us
state and entered user data. The MSDN article we mentio ned earlier explains the purpo se o f view state: "to
persist state acro ss po stbacks." A po stback is the name given whenever data is sent (po sted) back to the
server fro m the client, typically as part o f a fo rm co ntro l. State is the prio r and/o r current state o f all o f the
co ntro ls. We need state because the web bro wser and client-server architecture are stateless, unless so me
type o f state is maintained.

Note Because ASP co ntro ls are fo rm co ntro ls, always add ASP co ntro ls within a fo rm markup blo ck.

Finally, no te that the fo rm act io n attribute value is the name o f o ur ASPX web page: Default.aspx.

So , no w that we have a better understanding o f web co ntro ls and the ASP web page life cycle, let's create o ur
web calculato r.

Designing a Web User Int erface

To create a web calculato r, we'll add co ntro ls that will represent o ur web user interface. Our interface will
eventually lo o k like this:
We emphasize that o ur user interface is a web user interface, because a web bro wser is the target co mpo nent
that will render o ur user interface, and pro vide the framewo rk fo r all interactio n with o ur web calculato r
applicatio n. When designing o ur user interface, we design fo r the abilities o f a web bro wser.

When we lo o k o ver the web user interface in the image abo ve, we've used three primary co ntro ls: butto ns, text
bo x, and a table. The butto ns and text bo x are straightfo rward eno ugh, but the table was implied because o f
the layo ut. Keep in mind that this co urse do esn't pro vide instructio n o n ways to create incredible websites;
we'll just use whichever HTML co ntro ls we need to facilitate o ur discussio n o f the ASP.NET material. There
are multiple ways to represent web user interfaces tho ugh. Fo r example, yo u may prefer to use cascading
style sheets, o r HTML 5. We'll discuss tho se to pics thro ugho ut the co urse, but yo u might want to co nsider
taking additio nal HTML develo pment co urses to enhance yo ur web-develo pment abilities.

Let's add the co ntro ls we need to o ur web pro ject to create o ur UI.

We wo n't need the generated div tag, o r the ASP Panel co ntro l we added, so delete tho se lines fro m the
so urce co de. Then, find the Table co ntro l in the HTML sectio n o f the To o lBo x, and drag it o nto the
Default.aspx Co de Edito r windo w (So urce View) between the fo rm tags as sho wn:
CODE TO TYPE:
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherit
s="_Default" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.or


g/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>
</head>
<body>
<form id="form1" runat="server">
<div>
Standard div tag
</div>
<asp:Panel ID="Panel1" runat="server">
ASP div tag
</asp:Panel>
<table style="width: 100%;">
<tr>
<td>

</td>
<td>

</td>
<td>

</td>
</tr>
<tr>
<td>

</td>
<td>

</td>
<td>

</td>
</tr>
<tr>
<td>

</td>
<td>

</td>
<td>

</td>
</tr>
</table>
</form>
</body>
</html>

When yo u add a Table co ntro l fro m the To o lBo x, Visual Studio generates a 3x3 table by default, and the Table
markup co de is auto matically fo rmatted. Adding HTML co ntro ls fro m the HTML sectio n is useful, especially if
yo u can't remember the HTML syntax. Ho wever, o ften a develo per will mo dify the default HTML co de
significantly; typing the HTML co de directly is so metimes mo re efficient. We need a 4x6 table (fo ur ro ws, six
co lumns), where each ro w has similar syntax because it will co ntain an ASP Butto n co ntro l. Let's mo dify o ur
table so that it has o nly two ro ws, insert an ASP TextBo x co ntro l into the first ro w, and an ASP Butto n co ntro l
into the seco nd ro w, first co lumn. Change the TextBo x ID to resultTextBo x, add a ReadOnly attribute and set it
to true, then add a Style attribute to right-align the text. Change the Butto n co ntro l ID to clearErro rButto n,
change the Text attribute to "CE," then add a Width attribute and set the value to "30 ." The Default.aspx co de
will lo o k like this (Remember to click to save yo ur changes):

As o ur so urce files get larger, we will so metimes o mit co de that do esn't change. We'll indicate
Note this with a vertical ellipsis (three perio ds).

CODE TO TYPE:
.
.
.
<body>
<form id="form1" runat="server">
<table style="width: 100%;">
<tr><td colspan="4"><asp:TextBox ID="resultTextBox" runat="server" ReadOnly=
"true" Style="text-align: right" Width="200px" ></asp:TextBox></td></tr>
<tr>
<td><asp:Button ID="clearErrorButton" runat="server" Text="CE" Width
="40" /></td>
<td>

</td>
<td>

</td>
</tr>
<tr>
<td>

</td>
<td>

</td>
<td>

</td>
</tr>
<tr>
<td>

</td>
<td>

</td>
<td>

</td>
</tr>
</table>
</form>
</body>
.
.
.

Let's discuss this co de.


OBSERVE:
<body>
<form id="form1" runat="server">
<table style="width: 100%;">
<tr><td colspan="4"><asp:TextBox ID="resultTextBox" runat="server" ReadOnly=
"true" Style="text-align: right" Width="200px" ></asp:TextBox></td></tr>
<tr>
<td><asp:Button ID="clearErrorButton" runat="server" Text="CE" Width="40
" /></td>
</tr>
</table>
</form>
</body>

Let's address the layo ut and refo rmatting o f the table tags first. Like so urce co de, HTML markup language can
be a challenge to fo llo w, so we'll try to use fo rmatting to o ls like indentatio n and co nso lidate lo gical blo cks o n
the same line; this will make the co de easier to read and edit. We used a single line with the TextBo x co ntro l
because the entire ro w is o ne line, but we've used multiple lines and indentatio n with the seco nd ro w because
it will have multiple Butto n co ntro ls.

We add two ASP Co ntro ls: T e xt Bo x and But t o n. The markup co de fo r ASP Co ntro ls begins with <asp:, and
uses the same rules as HTML markup: yo u need a beginning and ending tag, o r a self-clo sed tag (the Butto n
is a self-clo sed tag, ending in />). Also , as with HTML co mpo nents, ASP Co ntro ls include several attributes;
yo u can use Intellisense to examine these attributes by clicking within the ASP co ntro l markup and pressing
the space bar to bring up a co ntext-sensitive list o f attributes. Fo r T e xt Bo x, we added a Re adOnly pro perty
to blo ck a user fro m directly entering text, and a St yle (CSS Style) to fo rce the TextBo x text to be right-aligned
just like a calculato r.

We also add a Widt h attribute to co ntro l the size o f each But t o n co ntro l.

We set the co lspan attribute to allo w the T e xt Bo x co ntro l to span all fo ur co lumns o f o ur table.

Let's add the rest o f the Butto n co ntro ls.

Highlight the Butto n line in the so urce co de, and make three co pies o f it within the same table ro w. Then, co py
each table ro w blo ck fo ur mo re times to create the twenty ro ws we need fo r o ur calculato r table. Change the
ID and T e xt attributes fo r each Butto n co ntro l acco rding to this table:

ID T e xt
clearErro rButto n CE
clearButto n C
backspaceButto n <-
divideButto n /
o neButto n 1
two Butto n 2
threeButto n 3
multiplyButto n *
fo urButto n 4
fiveButto n 5
sixButto n 6
subtractButto n -
sevenButto n 7
eightButto n 8
nineButto n 9
addButto n +
signButto n +/-
zero Butto n 0
po intButto n .
equalButto n =

and to run the pro gram. The UI is similar to the UI presented earlier (tho ugh so metimes yo u get
different results fro m different bro wsers). Here's the co de listing:
CODE TO TYPE:
.
.
.
<body>
<form id="form1" runat="server">
<table style="width: 100%;">
<tr><td colspan="4"><asp:TextBox ID="resultTextBox" runat="server" ReadOnly=
"true" Style="text-align: right"Width="200px" ></asp:TextBox></td></tr>
<tr>
<td><asp:Button ID="clearErrorButton" runat="server" Text="CE" Width="40
" /></td>
<td><asp:Button ID="clearButton" runat="server" Text="C" Width="40" /></
td>
<td><asp:Button ID="backspaceButton" runat="server" Text="<-" Width="40"
/></td>
<td><asp:Button ID="divideButton" runat="server" Text="/" Width="40" /><
/td>
</tr>
<tr>
<td><asp:Button ID="oneButton" runat="server" Text="1" Width="40" /></td
>
<td><asp:Button ID="twoButton" runat="server" Text="2" Width="40" /></td
>
<td><asp:Button ID="threeButton" runat="server" Text="3" Width="40" /></
td>
<td><asp:Button ID="multiplyButton" runat="server" Text="*" Width="40" /
></td>
</tr>
<tr>
<td><asp:Button ID="fourButton" runat="server" Text="4" Width="40" /></t
d>
<td><asp:Button ID="fiveButton" runat="server" Text="5" Width="40" /></t
d>
<td><asp:Button ID="sixButton" runat="server" Text="6" Width="40" /></td
>
<td><asp:Button ID="subtractButton" runat="server" Text="-" Width="40" /
></td>
</tr>
<tr>
<td><asp:Button ID="sevenButton" runat="server" Text="7" Width="40" /></
td>
<td><asp:Button ID="eightButton" runat="server" Text="8" Width="40" /></
td>
<td><asp:Button ID="nineButton" runat="server" Text="9" Width="40" /></t
d>
<td><asp:Button ID="addButton" runat="server" Text="+" Width="40" /></td
>
</tr>
<tr>
<td><asp:Button ID="signButton" runat="server" Text="+/-" Width="40" /><
/td>
<td><asp:Button ID="zeroButton" runat="server" Text="0" Width="40" /></t
d>
<td><asp:Button ID="pointButton" runat="server" Text="." Width="40" /></
td>
<td><asp:Button ID="equalButton" runat="server" Text="=" Width="40" /></
td>
</tr>
</table>
</form>
</body>
.
.
.
That co mpletes o ur web calculato r interface! We'll add a few mo re elements to o ur UI sho rtly, but first let's add
functio nality to o ur web pro ject.

Adding Funct ionalit y

No w that o ur UI is co mplete, it's time to edit o ur co de-behind file.

In the So lutio n Explo rer, select and expand the De f ault .aspx entry, then do uble-click the De f ault .aspx.cs
entry to o pen the Co de Edito r. Alternatively, yo u co uld switch to the Design View and do uble-click the
webpage away fro m the Table co ntro l. Yo u see this co de in the Co de Edito r:

OBSERVE:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

public partial class _Default : System.Web.UI.Page


{
protected void Page_Load(object sender, EventArgs e)
{

}
}

When yo u add a Web Fo rm webpage to a web pro ject, the class in the co de-behind file has the same name
as the webpage, prepended with an undersco re, in this case _De f ault , and includes a single metho d fo r the
Page _Lo ad event. This event is called whenever the page is lo aded, whether it's being called fo r the first
time, o r by clicking a fo rm submit element like a Butto n. ASP.NET pro vides a pro perty o f the Page o bject which
is the base class fo r o ur _De f ault class, called IsPo stBack. We need it in o rder to be able to determine if a
page is being lo aded fo r the first time, o r a fo rm submit element has been clicked. Let's add that co de no w:

Mo dify De f ault .aspx.cs as sho wn:

CODE TO TYPE:
.
.
.
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
// Initialize our calculator window
resultTextBox.Text = "0";
}
else
{
// Respond to postback
}
}
}
.
.
.

and to run the pro gram. The TextBo x no w co ntains 0 . Let's discuss this co de.
OBSERVE:
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
// Initialize our calculator window
resultTextBox.Text = "0";
}
else
{
// Respond to postback
}
}
}

We check the IsPo st Back bo o lean pro perty to make sure we're no t po sting back, so we can init ialize o ur
calculat o r windo w.

Next, let's add co de to handle the number Butto n click events. We'll also add a co uple o f class variables that
handle the calculato r windo w, and co de to po pulate these variables during a po stback actio n. Mo dify
De f ault .aspx.cs as sho w:
CODE TO TYPE:
.
.
.
public partial class _Default : System.Web.UI.Page
{
// Private class variables
private string _currentValueString = "";
private double _currentValueDouble = 0.0;

protected void Page_Load(object sender, EventArgs e)


{
if (!IsPostBack)
{
// Initialize our calculator window
resultTextBox.Text = "0";
}
else
{
// Respond to postback
// Grab calculator window, convert and save values as necessary
_currentValueString = resultTextBox.Text;
if (_currentValueString.Length > 0)
_currentValueDouble = double.Parse(_currentValueString);
}

// Add event delegates to number buttons


zeroButton.Click += new EventHandler(numberButton_Click);
oneButton.Click += new EventHandler(numberButton_Click);
twoButton.Click += new EventHandler(numberButton_Click);
threeButton.Click += new EventHandler(numberButton_Click);
fourButton.Click += new EventHandler(numberButton_Click);
fiveButton.Click += new EventHandler(numberButton_Click);
sixButton.Click += new EventHandler(numberButton_Click);
sevenButton.Click += new EventHandler(numberButton_Click);
eightButton.Click += new EventHandler(numberButton_Click);
nineButton.Click += new EventHandler(numberButton_Click);
}

void numberButton_Click(object sender, EventArgs e)


{
// Determine number by extracting number from Button Text property
int number = Int32.Parse((sender as Button).Text);

// Append number to calculator window


resultTextBox.Text = (_currentValueString+number.ToString());
}
}
.
.
.

and to run the pro gram. Yo u can no w click o n any o f the number Butto n co ntro ls, and have that number
added to the calculato r result windo w. No tice that the default 0 value in the calculato r windo w is kept as part o f
the number. We'll fix that so o n, but first let's discuss this co de:
OBSERVE:
public partial class _Default : System.Web.UI.Page
{
// Private class variables
private string _currentValueString = "";
private double _currentValueDouble = 0.0;

protected void Page_Load(object sender, EventArgs e)


{
if (!IsPostBack)
{
// Initialize our calculator window
resultTextBox.Text = "0";
}
else
{
// Respond to postback
// Grab calculator window, convert and save values as necessary
_currentValueString = resultTextBox.Text;
if (_currentValueString.Length > 0)
_currentValueDouble = double.Parse(_currentValueString);
}

// Add event delegates to number buttons


zeroButton.Click += new EventHandler(numberButton_Click);
oneButton.Click += new EventHandler(numberButton_Click);
twoButton.Click += new EventHandler(numberButton_Click);
threeButton.Click += new EventHandler(numberButton_Click);
fourButton.Click += new EventHandler(numberButton_Click);
fiveButton.Click += new EventHandler(numberButton_Click);
sixButton.Click += new EventHandler(numberButton_Click);
sevenButton.Click += new EventHandler(numberButton_Click);
eightButton.Click += new EventHandler(numberButton_Click);
nineButton.Click += new EventHandler(numberButton_Click);
}

void numberButton_Click(object sender, EventArgs e)


{
// Determine number by extracting number from Button Text property
int number = Int32.Parse((sender as Button).Text);

// Append number to calculator window


resultTextBox.Text = (_currentValueString+number.ToString());
}
}

We add two class variables, _curre nt Value St ring and _curre nt Value Do uble , that we'll use to ho ld the
string value and a numerical do uble equivalent o f the calculato r windo w. Earlier, we added the co de to set the
calculato r windo w when we're no t do ing a po stback; no w we add co de fo r a page lo ad resulting fro m a
po stback. When a Butto n is clicked and the Page_Lo ad event is called, we extract the re sult T e xt Bo x T e xt
value and sto re the result in _curre nt Value St ring. Then we test the length o f _curre nt Value St ring to
ensure we have so mething to co nvert, co nvert it to a do uble and sto ring the co nverted value in
_curre nt Value Do uble .

In o rder to handle the Butto n click events, we create delegates that use a single metho d
num be rBut t o n_Click. The num be rBut t o n_Click metho d determines which number was pressed and
saves the numerical value in num be r. We append this number to the calculato r windo w
re sult T e xt Bo x.T e xt pro perty to update o ur display. This so lutio n wo rks well to test o ur co de fo r no w, but
we're go ing to have to change this metho d sho rtly.

With Visual Studio we can generate the delegate event handler auto matically by typing in the o bject
T ip (such as ze ro But t o n, entering the metho d (such as Click), and then the += o perato r, and then
pressing the T ab key.

Next, we need to respo nd to the "actio n" events o f o ur web calculato r. In o rder to respo nd, tho ugh, we will
need a way to "remember" info rmatio n fro m previo us "states" o f o ur web page. A webpage is a stateless
enviro nment tho ugh, so each time the user submits a webpage to a web server, the o nly info rmatio n we have
is that which is sent to us fro m the client's web bro wser. If we want to preserve "state" info rmatio n, we need to
preserve that info rmatio n by sending it back to the client's co mputer when we generate the web page,
o therwise we wo uld need to preserve that info rmatio n o n the web server and asso ciate the server-saved
info rmatio n with a specific client bro wser sessio n. We'll discuss the latter technique invo lving sessio ns later;
fo r no w, we'll return info rmatio n and sto re it in "hidden" fields o n o ur web page.

Earlier, we examined ho w the ASP.NET uses View State to act essentially as memo ry fo r any ASP Co ntro l,
persisting info rmatio n acro ss page refreshes. We'll use the ASP HiddenField co ntro ls to create persistent
web page variables. Fo r o ur web calculato r, we'll need a hidden field variable fo r each o f these pieces o f state
info rmatio n:

First o peratio n (firstOperatio nField): Indicates whether the next o peratio n is the first o peratio n. We'll
use that info rmatio n to determine if we're appending a number, o r starting a new number; the
default will be true.
Previo us value (pendingValueField): Pro vides sto rage fo r any pending value; the default will be an
empty string.
Current actio n (pendingActio nField): Pro vides sto rage fo r any pending actio ns; the default will be an
empty string.

Use the To o lBo x to add the three ASP Hidde nFie ld co ntro ls to o ur markup co de in De f ault .aspx, as
sho wn:

CODE TO TYPE:

.
.
.
<body>
<form id="form1" runat="server">
<asp:HiddenField ID="firstOperationField" runat="server" Value="true" />
<asp:HiddenField ID="pendingValueField" runat="server" Value="" />
<asp:HiddenField ID="pendingActionField" runat="server" Value="" />
<table style="width: 100px;">
.
.
.

Let's discuss this co de.

OBSERVE:
<body>
<form id="form1" runat="server">
<asp:HiddenField ID="firstOperationField" runat="server" Value="true" />
<asp:HiddenField ID="pendingValueField" runat="server" Value="" />
<asp:HiddenField ID="pendingActionField" runat="server" Value="" />
<table style="width: 100px;">

These hidden fields, asp:Hidde nFie ld, do no t display in the client bro wser, but if yo u examine the rendered
HTML so urce, yo u'll see that they do render as HTML hidden fields. Fo r two o f the fields, we set the Value
at t ribut e t o e m pt y, but fo r f irst Ope rat io nFie ld, we set an initial value (o r state) o f t rue . The
f irst Ope rat io nFie ld will act as a bo o lean variable, so we'll be co nverting (parsing) the value o f
f irst Ope rat io nFie ld in o ur co de-behind file.

Next, let's add the delegates and metho d to handle the actio n Butto n co ntro ls. Even tho ugh we're go ing to
add the actio n handling co de incrementally, let's add all o f the delegates no w. Also , let's add a few mo re
private class variables fo r o ur hidden fields, add co de to sto re and co nvert the hidden field values, mo dify the
number event metho d handler to deal with adding a 0 to the beginning o f a number, and add a test to
determine if we need to reset the calculato r windo w. Mo dify De f ault .aspx.cs as sho wn:
CODE TO TYPE:
.
.
.
public partial class _Default : System.Web.UI.Page
{
// Private class variables
private string _currentValueString = "";
private double _currentValueDouble = 0;
private string _pendingAction = "";
private string _pendingValueString = "";
private double _pendingValueDouble = 0.0;
private bool _firstOperationField = true;

protected void Page_Load(object sender, EventArgs e)


{
if (!IsPostBack)
{
// Initialize our calculator window
resultTextBox.Text = "0";
}
else
{
// Respond to postback
// Grab calculator window, convert and save values as necessary
_currentValueString = resultTextBox.Text;
if (_currentValueString.Length > 0)
_currentValueDouble = double.Parse(_currentValueString);

// Save and convert hidden field values


_firstOperationField = bool.Parse(firstOperationField.Value);
_pendingAction = pendingActionField.Value;
_pendingValueString = pendingValueField.Value;
if (_pendingValueString.Length > 0)
_pendingValueDouble = double.Parse(_pendingValueString);
}

// Add event delegates to number buttons


zeroButton.Click += new EventHandler(numberButton_Click);
oneButton.Click += new EventHandler(numberButton_Click);
twoButton.Click += new EventHandler(numberButton_Click);
threeButton.Click += new EventHandler(numberButton_Click);
fourButton.Click += new EventHandler(numberButton_Click);
fiveButton.Click += new EventHandler(numberButton_Click);
sixButton.Click += new EventHandler(numberButton_Click);
sevenButton.Click += new EventHandler(numberButton_Click);
eightButton.Click += new EventHandler(numberButton_Click);
nineButton.Click += new EventHandler(numberButton_Click);

// Add event delegates for action buttons


divideButton.Click += new EventHandler(actionButton_Click);
multiplyButton.Click += new EventHandler(actionButton_Click);
subtractButton.Click += new EventHandler(actionButton_Click);
addButton.Click += new EventHandler(actionButton_Click);
equalButton.Click += new EventHandler(actionButton_Click);
clearErrorButton.Click += new EventHandler(actionButton_Click);
clearButton.Click += new EventHandler(actionButton_Click);
backspaceButton.Click += new EventHandler(actionButton_Click);
signButton.Click += new EventHandler(actionButton_Click);
pointButton.Click += new EventHandler(actionButton_Click);
}

void numberButton_Click(object sender, EventArgs e)


{
// Determine number by extracting number from Button Text property
int number = Int32.Parse((sender as Button).Text);
// Set/append number to calculator window
if (_firstOperationField)
resultTextBox.Text = number.ToString();
else
{
// Prevent prepending a 0 to a number
if (_currentValueString == "0") _currentValueString = "";
resultTextBox.Text = (_currentValueString + number.ToString());
}

// Indicate no longer first operation


firstOperationField.Value = "false";
}

void actionButton_Click(object sender, EventArgs e)


{
// Determine action
string currentAction = (sender as Button).Text;

}
}
.
.
.

and to run the pro gram. The leading 0 co ncatenatio n pro blem is fixed, but we still can't execute any
actio ns. Let's discuss this co de:
OBSERVE:
// Private class variables
private string _currentValueString = "";
private double _currentValueDouble = 0;
private string _pendingAction = "";
private string _pendingValueString = "";
private double _pendingValueDouble = 0.0;
private bool _firstOperationField = true;

protected void Page_Load(object sender, EventArgs e)


{
if (!IsPostBack)
{
// Initialize our calculator window
resultTextBox.Text = "0";
}
else
{
// Respond to postback
// Grab calculator window, convert and save values as necessary
_currentValueString = resultTextBox.Text;
if (_currentValueString.Length > 0)
_currentValueDouble = double.Parse(_currentValueString);

// Save and convert hidden field values


_firstOperationField = bool.Parse(firstOperationField.Value);
_pendingAction = pendingActionField.Value;
_pendingValueString = pendingValueField.Value;
if (_pendingValueString.Length > 0)
_pendingValueDouble = double.Parse(_pendingValueString);
}

// Add event delegates to number buttons


zeroButton.Click += new EventHandler(numberButton_Click);
oneButton.Click += new EventHandler(numberButton_Click);
twoButton.Click += new EventHandler(numberButton_Click);
threeButton.Click += new EventHandler(numberButton_Click);
fourButton.Click += new EventHandler(numberButton_Click);
fiveButton.Click += new EventHandler(numberButton_Click);
sixButton.Click += new EventHandler(numberButton_Click);
sevenButton.Click += new EventHandler(numberButton_Click);
eightButton.Click += new EventHandler(numberButton_Click);
nineButton.Click += new EventHandler(numberButton_Click);

// Add event delegates for action buttons


divideButton.Click += new EventHandler(actionButton_Click);
multiplyButton.Click += new EventHandler(actionButton_Click);
subtractButton.Click += new EventHandler(actionButton_Click);
addButton.Click += new EventHandler(actionButton_Click);
equalButton.Click += new EventHandler(actionButton_Click);
clearErrorButton.Click += new EventHandler(actionButton_Click);
clearButton.Click += new EventHandler(actionButton_Click);
backspaceButton.Click += new EventHandler(actionButton_Click);
signButton.Click += new EventHandler(actionButton_Click);
pointButton.Click += new EventHandler(actionButton_Click);
}

void numberButton_Click(object sender, EventArgs e)


{
// Determine number by extracting number from Button Text property
int number = Int32.Parse((sender as Button).Text);

// Set/append number to calculator window


if (_firstOperationField)
resultTextBox.Text = number.ToString();
else
{
// Prevent prepending a 0 to a number
if (_currentValueString == "0") _currentValueString = "";
resultTextBox.Text = (_currentValueString + number.ToString());
}

// Indicate no longer first operation


firstOperationField.Value = "false";
}

void actionButton_Click(object sender, EventArgs e)


{
// Determine action
string currentAction = (sender as Button).Text;

}
.
.
.

The co mments in the co de sho uld also help yo u to understand the functio nality o f the co de. Use
Note the co mments as a guideline fo r yo ur o wn co de co mments.

We added mo re class variables to represent the ASP HiddenField variables, and we set these pro perty
variables in the Page_Lo ad event.

We also added mo re event delegates, as well as a single metho d, act io nBut t o n_Click, to handle any
actio n events. The number event handler metho d num be rBut t o n_Click no w includes co de to test to
determine whether the calculato r windo w co ntains o nly 0 , and uses the class variable
_f irst Ope rat io nFie ld to determine whether we're executing a first o peratio n, which means we can o verwrite
the calculato r windo w. Once we've pro cessed a number actio n, we set the ASP HiddenField variable
f irst Ope rat io nFie ld to false.

Yo u may have no ticed that we're saving true and false in f irst Ope rat io nFie ld as a string rather than a
bo o lean, so we need to co nvert the string value to a bo o lean when we retrieve the value o f
f irst Ope rat io nFie ld, and we need to use a string representatio n o f true o r false when we set
f irst Ope rat io nFie ld.

Let's begin implementing the actio n event metho d. Mo dify the Default.aspx.cs as sho wn:
CODE TO TYPE:

.
.
.
void actionButton_Click(object sender, EventArgs e)
{
// Determine action
string currentAction = (sender as Button).Text;

// Determine if there is a pending operation


bool pendingOperation = (_pendingValueString.Length > 0 &&
_currentValueString.Length > 0 && _pendingAction.Length > 0);

// Perform previous action


if ("/*-+=".IndexOf(currentAction) >= 0)
{
// Handle arithmetic operation actions
if (pendingOperation)
resultTextBox.Text = doCalculation(_pendingAction, _pendingValueDoub
le, _currentValueDouble).ToString();

// Update first operation hidden field


firstOperationField.Value = "true";

// Update previous value and action hidden fields


if (currentAction != "=")
{
// Support chaining calculations
pendingActionField.Value = currentAction;
pendingValueField.Value = resultTextBox.Text;
}
else
{
pendingActionField.Value = "";
pendingValueField.Value = "";
}
}
}

private double doCalculation(string action, double left, double right)


{
// Perform arithmetic calculations
double result = 0.0;
switch (action)
{
case "/":
// Prevent divide by zero
if (right != 0)
result = left / right;
else
{
// TODO: Handle divide by zero error
}
break;
case "*":
result = left * right;
break;
case "-":
result = left - right;
break;
case "+":
result = left + right;
break;
}
return result;
}
.
.
.

and to run the pro gram. No w, yo u can perfo rm the basic arithmetic calculatio ns. No t o nly can yo u use
the equals sign to get a result, yo u can also string calculatio ns to gether, and the calculato r wo rks! Let's
discuss this co de.
OBSERVE:

void actionButton_Click(object sender, EventArgs e)


{
// Determine action
string currentAction = (sender as Button).Text;

// Determine if there is a pending operation


bool pendingOperation = (_pendingValueString.Length > 0 &&
_currentValueString.Length > 0 && _pendingAction.Length > 0);

// Perform previous action


if ("/*-+=".IndexOf(currentAction) >= 0)
{
// Handle arithmetic operation actions
if (pendingOperation)
resultTextBox.Text = doCalculation(_pendingAction, _pendingValueDoub
le, _currentValueDouble).ToString();

// Update first operation hidden field


firstOperationField.Value = "true";

// Update previous value and action hidden fields


if (currentAction != "=")
{
// Support chaining calculations
pendingActionField.Value = currentAction;
pendingValueField.Value = resultTextBox.Text;
}
else
{
pendingActionField.Value = "";
pendingValueField.Value = "";
}
}
}

private double doCalculation(string action, double left, double right)


{
// Perform arithmetic calculations
double result = 0.0;
switch (action)
{
case "/":
// Prevent divide by zero
if (right != 0)
result = left / right;
else
{
// TODO: Handle divide by zero error
}
break;
case "*":
result = left * right;
break;
case "-":
result = left - right;
break;
case "+":
result = left + right;
break;
}
return result;
}

The actio n event handler metho d sets pe ndingOpe rat io n as a bo o lean flag to indicate whether an
arithmetic calculatio n is pending. Then this metho d checks to see whether the curre nt Act io n variable
co ntains o ne o f the arithmetic actio n butto ns which wo uld indicate that the calculato r needs to co mplete a
calculatio n using a string literal " /*-+=" and the Inde xOf string metho d. If we do have a pending o peratio n,
we perfo rm the calculatio n by calling the do Calculat io n class helper metho d, resetting the
f irst Ope rat io nFie ld. The next co nditio nal statement suppo rts calculatio n chaining by testing to see if the
equals sign was clicked, setting the ASP HiddenField pending actio n and pending value fields.

The do Calculat io n metho ds wo rks as yo u wo uld expect fo r these arithmetic functio ns; no te that we prevent
divisio n by zero , but we didn't add any co de to alert the user.

Ho w might we have handled the divisio n by zero ? We co uld have used a try/catch blo ck, and then alerted the
user by setting an ASP Label co ntro l.

Let's add the remaining actio n co de to co mplete the web calculato r. Mo dify Default.aspx.cs as sho wn:
CODE TO TYPE:
.
.
.
void actionButton_Click(object sender, EventArgs e)
{
// Determine action
string currentAction = (sender as Button).Text;

// Determine if there is a pending operation


bool pendingOperation = (_pendingValueString.Length > 0 &&
_currentValueString.Length > 0 && _pendingAction.Length > 0);

// Perform previous action


if ("/*-+=".IndexOf(currentAction) >= 0)
{
// Handle arithmetic operation actions
if (pendingOperation)
resultTextBox.Text = doCalculation(_pendingAction, _pendingValueDoub
le, _currentValueDouble).ToString();

// Update first operation hidden field


firstOperationField.Value = "true";

// Update previous value and action hidden fields


if (currentAction != "=")
{
// Support chaining calculations
pendingActionField.Value = currentAction;
pendingValueField.Value = resultTextBox.Text;
}
else
{
pendingActionField.Value = "";
pendingValueField.Value = "";
}
}
else
{
// Handle remaining actions
switch (currentAction)
{
case "C":
// Reset calculator window and hidden fields
resultTextBox.Text = "0";
pendingActionField.Value = "";
pendingValueField.Value = "";
firstOperationField.Value = "true";
break;
case "CE":
// Clear error
resultTextBox.Text = "0";
firstOperationField.Value = "true";
break;
case "<-":
// Backspace - prevent leaving "bad" data in calculator window
string newResult = _currentValueString.Substring(0, _currentValu
eString.Length - 1);
if (newResult.Length == 0 || newResult == "-")
resultTextBox.Text = "0";
else
resultTextBox.Text = newResult;
break;
case ".":
// Decimal point
if (_currentValueString.IndexOf(".") < 0) resultTextBox.Text = _
currentValueString + ".";
break;
case "+/-":
// Sign
resultTextBox.Text = (_currentValueDouble * -1).ToString();
break;
}
}
}

private double doCalculation(string action, double left, double right)


.
.
.

and to run the pro gram. Test the remaining actio n butto ns. "C" (Clear) will reset the calculato r. "<-"
(Backspace) will delete the last typed number fro m the calculato r windo w, o r set the value to 0 if there is o nly a
single number. "CE" (Clear Erro r) resets the first o peratio n and the calculato r windo w to 0 . "." sets a decimal
in the number. "+/-" will to ggle the sign o f the number. Let's discuss this co de:

OBSERVE:

void actionButton_Click(object sender, EventArgs e)


{
.
.
.
else
{
// Handle remaining actions
switch (currentAction)
{
case "C":
// Reset calculator window and hidden fields
resultTextBox.Text = "0";
pendingActionField.Value = "";
pendingValueField.Value = "";
firstOperationField.Value = "true";
break;
case "CE":
// Clear error
resultTextBox.Text = "0";
firstOperationField.Value = "true";
break;
case "<-":
// Backspace - prevent leaving "bad" data in calculator window
string newResult = _currentValueString.Substring(0, _currentValu
eString.Length - 1);
if (newResult.Length == 0 || newResult == "-")
resultTextBox.Text = "0";
else
resultTextBox.Text = newResult;
break;
case ".":
// Decimal point
if (_currentValueString.IndexOf(".") < 0) resultTextBox.Text = _
currentValueString + ".";
break;
case "+/-":
// Sign
resultTextBox.Text = (_currentValueDouble * -1).ToString();
break;
}
}
}

Mo st o f this actio n co de will make sense to yo u; the inline co mments sho uld help. No te that the hidden fields
and calculato r windo w are reset with the Clear selectio n. Fo r Clear Erro r, we reset the calculato r windo w, and
reset the f irst Ope rat io nFie ld variable. To handle the backspace, we use the Subst ring string metho d to
trim o ff the rightmo st digit (o r character). We do have to test to make sure we have so mething left o ver after
remo ving the number, and that we aren't left with just the negative sign. We also have to make sure we do n't
have a seco nd decimal po int. Finally, to change the sign, we multiply by -1.

So , that's it! Yo u've co mpleted the web calculato r pro ject using ASP.NET! (Applause.)

Befo re yo u mo ve o n to the next lesso n, make sure to do yo ur ho mewo rk! (No t that yo u need reminding.) See yo u in the next
lesso n...

Copyright © 1998-2014 O'Reilly Media, Inc.

This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.
See http://creativecommons.org/licenses/by-sa/3.0/legalcode for more information.
Web Forms
Lesson Objectives
In this lesso n yo u will:

create an ASP.NET web fo rm pro ject.


use a template that creates a basic ASP.NET website.
custo mize the website with pages and ASP co ntro ls.

Web Forms
Visual Studio includes several templates that are used when creating a new pro ject. In this lesso n, we'll create an
ASP.NET web fo rm pro ject using a template that creates a basic ASP.NET website. We will custo mize the website by
adding pages and ASP co ntro ls, including validatio n co ntro ls.

Creating the Project


First, create the ASP.NET website to use with this pro ject by selecting File | Ne w | We b Sit e . In the New Web
Site dialo g bo x, make sure Visual C# is selected under Installed Templates, and select the ASP.NET We b
Sit e template. Select File Syst e m fro m the Web lo catio n dro pdo wn bo x, and change the Name o f the Web
Pro ject to Bio graphy. Click OK.

We are still creating websites rather than web pro jects, altho ugh we co uld have selected a New
Pro ject and then selected the ASP.NET Web Applicatio n template. The underlying co de wo uld
Note have been the same, except that o ur website wo uld have been part o f a Studio So lutio n and
Pro ject rather than just a website.

and to run the web pro ject. Yo u see a website like this:

Witho ut any co ding o n yo ur part, Visual Studio has generated the beginnings o f a basic dynamic website. As
yo u navigate the site, yo u'll find these features:

Page navigatio n using tabs to switch between a Ho me page and an Abo ut page
User registratio n page
Lo gin and lo go ut o f registered users
Passwo rd change fo r registered users

The term dynamic website is used frequently to refer to a website with co ntent that is no t fixed.
Note Currently, the default website dynamic co ntent o nly displays whether a registered user is lo gged
in and, if so , which registered user that is.

Befo re mo difying the co ntent, let's review what was generated, and ho w it wo rks.

Examining the Auto-generated Website


We'll start by examining and describing the website co ntents in the So lutio n Explo rer. Included with each
descriptio n is an indicatio n o f whether the item is accessible fro m the web. What do es that mean? When
creating an ASP.NET website, users will access yo ur website co ntent thro ugh a web bro wser. When we run
o ur website using Visual Studio , a web bro wser launches and o ur website is displayed just as if it were
deplo yed to a web server, including using a URL. Develo ping a website requires that we begin to understand
a web address, ho w a user enters a web address to access website co ntent we develo p, and what
info rmatio n is accessible.

T ip Fo r an excellent brief explanatio n o f URLs, see URL Definitio n - What Is a URL?

This fo lder co ntains registratio n, lo gin, and passwo rd-related markup and co de-behind
files. Pages that reside in the acco unt fo lder are accessible fro m the website. Users will
access these webpages if they attempt to lo gin o r register. No te that registratio n is no t
Acco unt
required to access any co ntent o f the website, but if a user is registered, o ur website can
Fo lde r
deliver user-specific co ntent. Register so yo u can see that the user's registered name will
appear at the to p-right o f every page o nce they've lo gged in. We'll learn mo re abo ut this
fo lder in a later lesso n.
This fo lder co ntains database files used by the applicatio n, and is inaccessible fro m the
web. The fo lder is empty unless a user registers. With the first attempt to register, a SQL
Express database file ASPNETDB.MDF is created in this fo lder and linked to yo ur website.
The database is created as a User Instance database. User instances require that SQL
App_Dat a
Express be installed and running. User instances are great fo r develo pment, but usually
Fo lde r
they're no t appro priate fo r pro ductio n web servers. As we stated earlier, o ur server set up
precludes using the no rmal database access features fo r user acco unts. Visual Studio
20 10 ships with SQLServer 20 0 8 , which do es no t handle netwo rk access o f its database
file well. No rmally, when yo u create a website o r web applicatio n, it just wo rks.
Script s This fo lder co ntains script files such as JavaScript, and is accessible fro m the web. We
Fo lde r will discuss this fo lder and scripts in mo re detail later in the co urse.
St yle s
This fo lder co ntains CSS (style sheet) files, and is accessible fro m the web.
Fo lde r
The Abo ut markup and co de-behind file, and the rendered web page is accessible fro m
Abo ut .aspx web. We'll mo dify this file to pro vide mo re info rmatio n abo ut o ur site. This file is also a
We bpage go o d place to mentio n any third-party techno lo gies yo u might use in yo ur site and to link
to o pen-so urce licenses.
This is the default page fo r an ASP.NET website. When the bro wser co nnects to the
De f ault .aspx website, this page will start auto matically. On mo st o ther techno lo gies, this wo uld be
We bpage analo go us to an inde x.ht m l o r inde x.php file. It co ntains the markup and co de-behind
file; the rendered web page is accessible fro m the web.
This file declares and handles applicatio n-level and sessio n-level events and o bjects. It is
Glo bal.asax
inaccessible fro m the web. Typically it's managed by Visual Studio ; yo u will rarely need to
So urce File
edit it directly.
This is th website layo ut markup template and co de-behind file that is inaccessible fro m
Sit e .m ast e r
the web. It is used when rendering pages that include the MasterPageFile attribute o f the
We bpage
Page directive. This is a co o l feature o f an ASP.NET website where we can manage
T e m plat e
features that we want to sho w up acro ss all o f o ur site pages.
This file co ntains XML settings and the co nfiguratio n file that is inaccessible fro m the web.
we b.co nf ig
In so me cases we will edit this file to turn o n features and to co ntro l access to all o r parts
File
o f the site.

The So lutio n Explo rer fo r this website includes a number o f fo lders. Fo r example, the Acco unt fo lder is
accessible; if yo u attempt to access it while running yo ur website, yo u'll see this in yo ur bro wser:

Fo r security reaso ns, a public web server typically will no t allo w directo ry listings, but fo r ease o f
develo pment, the ASP.NET Develo pment Server allo ws them. Ho w did we get this listing? We mo dified the
URL that Studio pro vided when the bro wser was launched. Initially, the URL was
ht t p://lo calho st :186 3/Bio graphy/ (o r so mething similar), but we can mo dify the URL directly by adding the
Acco unt fo lder name. The URL beco mes ht t p://lo calho st :186 3/Bio graphy/Acco unt , and will then
generate the page sho wn abo ve.

Yo u sho uld use the So lutio n Explo rer to o pen any fo lders and examine any so urce files. Let's discuss ho w
the different web pages are created, and ho w the navigatio n wo rks.

Layout and Navigat ion

The generated ASP.NET website includes a master website page. This page fo rms the backgro und fo r all o f
the webpages. Lo cate the Sit e .Mast e r page in the So lutio n Explo rer and o pen it, switching to So urce view if
necessary. As yo u examine the markup so urce, yo u'll see that:

rather than a Page directive, the Site.Master has a Master directive.


the markup so urce includes a co mplete set o f HTML tags, such as <html>, <head>, and <bo dy>.
site styles are referenced and lo aded in the Site.Master <head> sectio n.
the Site.Master includes a number o f ASP Co ntro ls: Co ntentPlaceHo lder, Lo ginView, and Menu.
the Co ntentPlaceHo lder ASP co ntro ls have unique IDs that will be expo sed as co ntent targets in
webpages.
the Site.Master includes a co de-behind file.

Let's take a lo o k at the Sit e .m ast e r markup as it is created by Visual Studio :


OBSERVE:

<%@ Master Language="C#" AutoEventWireup="true" CodeFile="Site.master.cs" Inheri


ts="SiteMaster" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/x


html1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head runat="server">
<title></title>
<link href="~/Styles/Site.css" rel="stylesheet" type="text/css" />
<asp:ContentPlaceHolder ID="HeadContent" runat="server">
</asp:ContentPlaceHolder>
</head>
<body>
<form runat="server">
<div class="page">
<div class="header">
<div class="title">
<h1>
My ASP.NET Application
</h1>
</div>
<div class="loginDisplay">
<asp:LoginView ID="HeadLoginView" runat="server" EnableViewState
="false">
<AnonymousTemplate>
[ <a href="~/Account/Login.aspx" ID="HeadLoginStatus" ru
nat="server">Log In</a> ]
</AnonymousTemplate>
<LoggedInTemplate>
Welcome <span class="bold"><asp:LoginName ID="HeadLoginN
ame" runat="server" /></span>!
[ <asp:LoginStatus ID="HeadLoginStatus" runat="server" L
ogoutAction="Redirect" LogoutText="Log Out" LogoutPageUrl="~/"/> ]
</LoggedInTemplate>
</asp:LoginView>
</div>
<div class="clear hideSkiplink">
<asp:Menu ID="NavigationMenu" runat="server" CssClass="menu" Ena
bleViewState="false" IncludeStyleBlock="false" Orientation="Horizontal">
<Items>
<asp:MenuItem NavigateUrl="~/Default.aspx" Text="Home"/>
<asp:MenuItem NavigateUrl="~/About.aspx" Text="About"/>
</Items>
</asp:Menu>
</div>
</div>
<div class="main">
<asp:ContentPlaceHolder ID="MainContent" runat="server"/>
</div>
<div class="clear">
</div>
</div>
<div class="footer">

</div>
</form>
</body>
</html>

The Sit e .m ast e r file is the "template" fo r all o f the pages in yo ur site. It co ntains the basic HTML markup fo r
the entire site. It also co ntains the reference to the CSS st yle she e t that the site will use, which is the
/St yle s/Sit e .css file. No tice that, in the entire file, we do n't specifically indicate any co lo rs o r po sitio ning o f
o ur HTML tags o r ASP.NET co ntro ls. Everything will be co ntro lled by the Sit e .css file.

Also no tice that the Sit e .m ast e r page do es no t use the Page directive at the to p o f the file. Instead, the
Sit e .m ast e r uses the Mast e r directive to tell the server that this is a master page and sho uld be used as a
Sit e .m ast e r uses the Mast e r directive to tell the server that this is a master page and sho uld be used as a
template.

Yo u can have multiple master pages. Master pages can also extend o ther master files. Fo r
Note details, see the MSDN article o n Nested ASP.NET Master Pages

The ASP.NET Lo ginVie w co ntro l sits in the upper right hand co rner o f every page, since it is in the
Sit e .m ast e r file. Ano nymo us users will see the text in the Ano nym o usT e m plat e sectio n o f the co ntro l;
lo gged-in users will see the text in the Lo gge dInT e m plat e .

The last co ntro l in the header o f the page is the Me nu co ntro l. Inside the It e m s element, yo u can add as
many Me nuIt e m s as yo u want. Keep in mind that this is the Sit e .m ast e r page, so this menu will be o n all
pages o f the site.

The ASP.NET co ntro l, <asp:Co nt e nt Place Ho lde r ID=" MainCo nt e nt " runat =" se rve r" />, is the real
heart o f the Sit e .m ast e r file. All o f the o ther pages in the site will place their co ntent inside this co ntro l.

Yo u can have multiple Co nt e nt Place Ho lde r co ntro ls in yo ur Sit e .m ast e r file, just make
Note sure they have different IDs so yo u can add o r remo ve co ntent in yo ur pages just based o n
which placeho lder they use. Mo st pages will o nly need to use the default placeho lder.

OBSERVE: Default.aspx
<%@ Page Title="Home Page" Language="C#" MasterPageFile="~/Site.master" AutoEven
tWireup="true"
CodeFile="Default.aspx.cs" Inherits="_Default" %>

<asp:Content ID="HeaderContent" runat="server" ContentPlaceHolderID="HeadContent


">
</asp:Content>
<asp:Content ID="BodyContent" runat="server" ContentPlaceHolderID="MainContent">
<h2>
Welcome to ASP.NET!
</h2>
<p>
To learn more about ASP.NET visit <a href="http://www.asp.net" title="AS
P.NET Website">www.asp.net</a>.
</p>
<p>
You can also find <a href="http://go.microsoft.com/fwlink/?LinkID=152368
&clcid=0x409"
title="MSDN ASP.NET Docs">documentation on ASP.NET at MSDN</a>.
</p>
</asp:Content>

The Co nt e nt co ntro l in the De f ault .aspx file is linked to the Co nt e nt Place Ho lde rID fro m the
Sit e .m ast e r file. When the De f ault .aspx file is lo aded, the Sit e .m ast e r file is used as its template. The
co ntent inside this co ntro l is placed within that template and the page is lo aded as De f ault .aspx.

Creating A Site
Let's take the site that Visual Studio give us and mo dify it fo r o ur purpo ses.

Our fo cus here is o n using different navigatio n co ntro ls o n the site and to utilize the
Sit e .m ast e r page to o ur advantage. Currently we are no t fo cused o n the Sit e .css file. We
Note aren't really creating a full-fledged wo rking site right no w, but just using the Bio graphy site as a
to o l to sho wcase the navigatio n co ntro ls available in ASP.NET.

Edit the De f ault .aspx file as sho wn:


CODE TO TYPE:
<%@ Page Title="Home Page" Language= : MasterPageFile="~/Site.master" AutoEventW
ireup="true"
CodeFile="Default.aspx.cs" Inherits="_Default" %>

<asp:Content ID="HeaderContent" runat="server" ContentPlaceHolderID="HeadContent


">
</asp:Content>
<asp:Content ID="BodyContent" runat="server" ContentPlaceHolderID="MainContent">
<h2>
Welcome to ASP.NET!
</h2>
<p>
To learn more about ASP.NET visit <a href="http://www.asp.net" title="AS
P.NET Website">
www.asp.net</a>.
</p>
<p>
You can also find <a href="http://go.microsoft.com/fwlink/?LinkID=152368
&clcid=0x409"
title="MSDN ASP.NET Docs">documentation on ASP.NET at MSDN</a>.
</p>
<asp:Panel ID="IntroductionPanel" runat="server">
<h1>
Introduction</h1>
<p>
Put some text here about yourself, or some historic figure, as an in
troduction.
Notice that you can use <i>any</i> HTML that you want and, since man
y
elements are styled by the <b>Site.css</b> file, they will pick up t
hat style.</p>
</asp:Panel>
</asp:Content>

and . If yo u have so me kno wledge o f CSS, yo u can edit the Sit e .css file to change the styling o f the
site, such as co lo rs, fo nts, and such. If yo u do n't edit the CSS file, it's fine to o ; this co urse is no t fo cused o n
CSS.

Let's create so me o ther files no w.

T ip One o f the best CSS reso urces o n the web is the W3C's CSS Tuto rial.

Add so me new fo lders to yo ur website and name them: Public, Privat e , Fam ily, and Im age s.
Right-click o n the Public directo ry and select Add Ne w It e m . Select a We b Fo rm fro m the Visual C# list.
Check the Se le ct Mast e r Page check bo x. Leave the name as De f ault .aspx. (In the next dialo g, click OK to
select the Sit e .m ast e r as the master file.)

No w, create the same file in the Fam ily and Privat e directo ries.

We've created these files so that we can illustrate o ne o f the Navigatio n co ntro ls available. Open the
Sit e .m ast e r file and add the co ntro l as sho wn:
CODE TO TYPE:
<%@ Master Language="C#" AutoEventWireup="true" CodeFile="Site.master.cs" Inheri
ts="SiteMaster" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/x


html1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head runat="server">
<title></title>
<link href="~/Styles/Site.css" rel="stylesheet" type="text/css" />
<asp:ContentPlaceHolder ID="HeadContent" runat="server">
</asp:ContentPlaceHolder>
</head>
<body>
<form runat="server">
<div class="page">
<div class="header">
<div class="title">
<h1>
My ASP.NET Application
</h1>
</div>
<div class="loginDisplay">
<asp:LoginView ID="HeadLoginView" runat="server" EnableViewState
="false">
<AnonymousTemplate>
[ <a href="~/Account/Login.aspx" ID="HeadLoginStatus" ru
nat="server">Log In</a> ]
</AnonymousTemplate>
<LoggedInTemplate>
Welcome <span class="bold"><asp:LoginName ID="HeadLoginN
ame" runat="server" /></span>!
[ <asp:LoginStatus ID="HeadLoginStatus" runat="server" L
ogoutAction="Redirect" LogoutText="Log Out" LogoutPageUrl="~/"/> ]
</LoggedInTemplate>
</asp:LoginView>
</div>
<div class="clear hideSkiplink">
<asp:Menu ID="NavigationMenu" runat="server" CssClass="menu" Ena
bleViewState="false" IncludeStyleBlock="false" Orientation="Horizontal">
<Items>
<asp:MenuItem NavigateUrl="~/Default.aspx" Text="Home"/>
<asp:MenuItem NavigateUrl="~/About.aspx" Text="About"/>
</Items>
</asp:Menu>
</div>
<asp:SiteMapPath ID="SiteMapPath1" runat="server">
</asp:SiteMapPath>
</div>
<div class="main">
<asp:ContentPlaceHolder ID="MainContent" runat="server"/>
</div>
<div class="clear">
</div>
</div>
<div class="footer">

</div>
</form>
</body>
</html>

and . Yo u get an erro r message:


That's because we haven't added the We b.sit e m ap file yet. Let's do that no w.

Right-click o n the pro ject and select Add Ne w It e m . In the dialo g, select XML File , and change the name in
the text bo x to We b.sit e m ap. Mo dify the co de as sho wn belo w:

CODE TO TYPE:
<?xml version="1.0" encoding="utf-8" ?>
<siteMap>
<siteMapNode title="Home" description="Home" url="~/Default.aspx" >
<siteMapNode title="Public" description="Public Information"
url="~/Public/Default.aspx" >
</siteMapNode>
<siteMapNode title="Private" description="Private Information"
url="~/Private/Default.aspx">
</siteMapNode>
<siteMapNode title="Family" description="Information For Family"
url="~/Family/Default.aspx">
</siteMapNode>
<siteMapNode title="About" description="About us"
url="~/About.aspx">
</siteMapNode>
</siteMapNode>
</siteMap>

There is an o uter Ho m e site map no de and the o ther no des are children o f that no de. This ensures that the
Ho m e link will always be sho wn in the "breadcrumb" (fo r o ur purpo ses, "breadcrumbs" are links to pages
yo u've visited within the site), since lo gically, everything in the site is within the Ho me page o f the site. The
<siteMapNo de>s can be nested as needed:

OBSERVE:
<?xml version="1.0" encoding="utf-8" ?>
<siteMap>
<siteMapNode title="Home" description="Home" url="~/Default.aspx" >
<siteMapNode title="Public" description="Public Information"
url="~/Public/Default.aspx" >
</siteMapNode>
<siteMapNode title="Private" description="Private Information"
url="~/Private/Default.aspx">
</siteMapNode>
<siteMapNode title="Family" description="Information For Family"
url="~/Family/Default.aspx">
</siteMapNode>
<siteMapNode title="About" description="About us"
url="~/About.aspx">
</siteMapNode>
</siteMapNode>
</siteMap>

T ip Fo r mo re info rmatio n o n site maps, see the MSDN article o n ASP.NET Site Maps

No w, let's update o ur menu so that we can actually get to o ur new pages. Edit yo ur Sit e .m ast e r file as
sho wn:
CODE TO TYPE:
<%@ Master Language="C#" AutoEventWireup="true" CodeFile="Site.master.cs" Inheri
ts="SiteMaster" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/x


html1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head runat="server">
<title></title>
<link href="~/Styles/Site.css" rel="stylesheet" type="text/css" />
<asp:ContentPlaceHolder ID="HeadContent" runat="server">
</asp:ContentPlaceHolder>
</head>
<body>
<form runat="server">
<div class="page">
<div class="header">
<div class="title">
<h1>
My ASP.NET Application
</h1>
</div>
<div class="loginDisplay">
<asp:LoginView ID="HeadLoginView" runat="server" EnableViewState
="false">
<AnonymousTemplate>
[ <a href="~/Account/Login.aspx" id="HeadLoginStatus" ru
nat="server">Log In</a>
]
</AnonymousTemplate>
<LoggedInTemplate>
Welcome <span class="bold">
<asp:LoginName ID="HeadLoginName" runat="server" />
</span>! [
<asp:LoginStatus ID="HeadLoginStatus" runat="server" Log
outAction="Redirect" LogoutText="Log Out"
LogoutPageUrl="~/" />
]
</LoggedInTemplate>
</asp:LoginView>
</div>
<div class="clear hideSkiplink">
<asp:Menu ID="NavigationMenu" runat="server" CssClass="menu" Ena
bleViewState="false"
IncludeStyleBlock="false" Orientation="Horizontal">
<Items>
<asp:MenuItem NavigateUrl="~/Default.aspx" Text="Home" /
>
<asp:MenuItem NavigateUrl="~/About.aspx" Text="About" />
<asp:MenuItem NavigateUrl="~/Public/Default.aspx" Text="
Public" />
<asp:MenuItem NavigateUrl="~/Private/Default.aspx" Text=
"Private" />
<asp:MenuItem NavigateUrl="~/Family/Default.aspx" Text="
Family" />
</Items>
</asp:Menu>
</div>
</div>
<div class="main">
<asp:SiteMapPath ID="SiteMapPath1" runat="server">
</asp:SiteMapPath>
<asp:ContentPlaceHolder ID="MainContent" runat="server" />
</div>
<div class="clear">
</div>
</div>
<div class="footer">
</div>
</form>
</body>
</html>

and . No tice that the menu has been updated so that we can navigate to any page and the breadcrumb
sho ws where we are in the site.

When yo u add o r remo ve pages fro m yo ur site, do n't fo rget to update the sitemap and menu so
Note that they reflect the changes.

No w, let's explo re ano ther co ntro l that can aid in navigatio n, the T re e Vie w co ntro l. In yo ur Fam ily directo ry,
create two new files the same way yo u created the De f ault .aspx files. Name them Mo t he r.aspx and
Fat he r.aspx.

Edit yo ur We b.sit e m ap file to reflect the new pages, as sho wn:

CODE TO TYPE:
<?xml version="1.0" encoding="utf-8" ?>
<siteMap>
<siteMapNode title="Home" description="Home" url="~/Default.aspx" >
<siteMapNode title="Public" description="Public Information"
url="~/Public/Default.aspx" >
</siteMapNode>
<siteMapNode title="Private" description="Private Information"
url="~/Private/Default.aspx">
</siteMapNode>
<siteMapNode title="Family" description="Information For Family"
url="~/Family/Default.aspx">
<siteMapNode title="Mother" description="Mother"
url="~/Family/Mother.aspx" >
</siteMapNode>
<siteMapNode title="Father" description="Father"
url="~/Family/Father.aspx" >
</siteMapNode>
</siteMapNode>
<siteMapNode title="About" description="About us"
url="~/About.aspx">
</siteMapNode>
</siteMapNode>
</siteMap>

Save yo ur file but do n't run it. We still need to add to o ur De f ault .aspx page:
CODE TO TYPE:
<%@ Page Title="Home Page" Language="C#" MasterPageFile="~/Site.master" AutoEven
tWireup="true"
CodeFile="Default.aspx.cs" Inherits="_Default" %>

<asp:Content ID="HeaderContent" runat="server" ContentPlaceHolderID="HeadContent


">
</asp:Content>
<asp:Content ID="BodyContent" runat="server" ContentPlaceHolderID="MainContent">
<asp:Panel ID="TreeViewSiteMapPanel" runat="server">
<asp:TreeView ID="TreeView1" runat="server" DataSourceID="SiteMapDataSou
rce1">
</asp:TreeView>
<asp:SiteMapDataSource ID="SiteMapDataSource1" runat="server" />
</asp:Panel>
<asp:Panel ID="IntroductionPanel" runat="server">
<h1>
Introduction</h1>
<p>
Put some text here about yourself, or some historic figure, as an in
troduction.
Notice that we can use any HTML that we want and since many elements
are styled
by the <b>Site.css</b> file, they will pick up that style.</p>
</asp:Panel>
</asp:Content>

and . No w, we have a T re e Vie w co ntro l o n the Ho me page that reflects o ur site structure, acco rding to
the We b.sit e m ap file:

OBSERVE:

<%@ Page Title="Home Page" Language="C#" MasterPageFile="~/Site.master" AutoEven


tWireup="true"
CodeFile="Default.aspx.cs" Inherits="_Default" %>

<asp:Content ID="HeaderContent" runat="server" ContentPlaceHolderID="HeadContent


">
</asp:Content>
<asp:Content ID="BodyContent" runat="server" ContentPlaceHolderID="MainContent">
<asp:Panel ID="TreeViewSiteMapPanel" runat="server">
<asp:TreeView ID="TreeView1" runat="server" DataSourceID="SiteMapDataSou
rce1">
</asp:TreeView>
<asp:SiteMapDataSource ID="SiteMapDataSource1" runat="server" />
</asp:Panel>
<asp:Panel ID="IntroductionPanel" runat="server">
<h1>
Introduction</h1>
<p>
Put some text here about yourself, or some historic figure, as an in
troduction.
Notice that we can use any HTML that we want and since many elements
are styled
by the <b>Site.css</b> file, they will pick up that style.</p>
</asp:Panel>
</asp:Content>

Yo u just added a fairly co mplex co ntro l to yo ur site. All yo u need to do is add a T re e Vie w co ntro l and a
Sit e MapDat aSo urce co ntro l and link the two by using the Sit e MapDat aSo urce 's ID as the
Dat aSo urce ID o f the T re e Vie w.

Befo re yo u mo ve o n to the next lesso n, be sure to do yo ur ho mewo rk!

Copyright © 1998-2014 O'Reilly Media, Inc.


Copyright © 1998-2014 O'Reilly Media, Inc.

This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.
See http://creativecommons.org/licenses/by-sa/3.0/legalcode for more information.
Security
Lesson Objectives
In this lesso n, yo u will:

use ASP.NET co mpo nents to add security elements to yo ur website.


use authenticatio n and autho rizatio n.

Authentication and Authorization


To create a website that has autho rized users, we want a system that "authenticates" users' identities. The system
also needs to grant autho rizatio n fo r users to enter certain areas o f the site, o r see certain info rmatio n. We may also
want to co ntro l a user's access based o n their role.

In o rder to acco mplish these go als, we'll maintain a database o f user acco unts and ro les asso ciated with tho se
acco unts. Visual Studio has a set o f built-in to o ls to help us manage user acco unts, as well as their ro les.

Create a new pro ject named Fo rm Aut he nt icat io n.

Do not name yo ur pro ject Fo rm sAut he nt icat io n because there is already a C# class with that
WARNING name. Yo u can read mo re abo ut this class, in the MSDN article, Fo rmsAuthenticatio n Class.

Visual Studio creates a bunch o f files and directo ries in the So lutio n Explo rer:
We are interested primarily in the Acco unt directo ry. We'll also be using o ther files like the vario us We b.co nf ig files.

Click o n to see this website. Click the Lo g In link in the upper-right co rner to see the lo gin pro cess in actio n. Even if
we aren't lo gged in, we can still see all pages within the site fo r no w, because we haven't set up any pages o r URLs
that are pro tected. Let's see ho w to determine whether we are lo gged in.

The lo gin, change passwo rd, and register pages are all lo cated in the Acco unt directo ry. Also , each
Note directo ry has a We b.co nf ig file that co ntro ls ho w that directo ry is co nfigured.

Befo re we go any further, let's enable ro les fo r o ur applicatio n. Change the ro le Manage r e nable d setting fro m false
to t rue in the We b.co nf ig file as sho wn:
CODE TO TYPE:
<?xml version="1.0"?>

<!--
For more information on how to configure your ASP.NET application, please visit
http://go.microsoft.com/fwlink/?LinkId=169433
-->

<configuration>
<connectionStrings>
<add name="ApplicationServices"
connectionString="data source=.\SQLEXPRESS;Integrated Security=SSPI;AttachDBFi
lename=|DataDirectory|\aspnetdb.mdf;User Instance=true"
providerName="System.Data.SqlClient" />
</connectionStrings>

<system.web>
<compilation debug="true" targetFramework="4.0" />

<authentication mode="Forms">
<forms loginUrl="~/Account/Login.aspx" timeout="2880" />
</authentication>

<membership>
<providers>
<clear/>
<add name="AspNetSqlMembershipProvider" type="System.Web.Security.SqlMembership
Provider" connectionStringName="ApplicationServices"
enablePasswordRetrieval="false" enablePasswordReset="true" requiresQuestio
nAndAnswer="false" requiresUniqueEmail="false"
maxInvalidPasswordAttempts="5" minRequiredPasswordLength="6" minRequiredNo
nalphanumericCharacters="0" passwordAttemptWindow="10"
applicationName="/" />
</providers>
</membership>

<profile>
<providers>
<clear/>
<add name="AspNetSqlProfileProvider" type="System.Web.Profile.SqlProfileProvide
r" connectionStringName="ApplicationServices" applicationName="/"/>
</providers>
</profile>

<roleManager enabled="falsetrue">
<providers>
<clear/>
<add name="AspNetSqlRoleProvider" type="System.Web.Security.SqlRoleProvider" co
nnectionStringName="ApplicationServices" applicationName="/" />
<add name="AspNetWindowsTokenRoleProvider" type="System.Web.Security.WindowsTok
enRoleProvider" applicationName="/" />
</providers>
</roleManager>

</system.web>

<system.webServer>
<modules runAllManagedModulesForAllRequests="true"/>
</system.webServer>
</configuration>

and clo se the We b.co nf ig file. Mo dify the website's De f ault .aspx page as sho wn:
CODE TO TYPE:
<%@ Page Title="Home Page" Language="C#" MasterPageFile="~/Site.master" AutoEventWireup
="true"
CodeBehind="Default.aspx.cs" Inherits="FormAuthentication1._Default" >

<asp:Content ID="HeaderContent" runat="server" ContentPlaceHolderID="HeadContent">


</asp:Content>
<asp:Content ID="BodyContent" runat="server" ContentPlaceHolderID="MainContent">
<asp:Panel runat="server" ID="LoggedInMessagePanel">
<asp:Label runat="server" ID="WelcomeBackMessage"></asp:Label>
</asp:Panel>
<asp:Panel runat="Server" ID="NotLoggedInMessagePanel">
<asp:HyperLink runat="server" ID="lnkLogin" Text="Log In" NavigateUrl="~/Accoun
t/Login.aspx"></asp:HyperLink>
</asp:Panel>
<h2>
Welcome to ASP.NET!
</h2>
<p>
To learn more about ASP.NET visit <a href="http://www.asp.net" title="ASP.NET W
ebsite">
www.asp.net</a>.
</p>
<p>
You can also find <a href="http://go.microsoft.com/fwlink/?LinkID=152368&clcid=
0x409"
title="MSDN ASP.NET Docs">documentation on ASP.NET at MSDN</a>.
</p>
</asp:Content>

Save, but do n't run it yet. We still need to do so me wo rk o n the co de-behind file:

OBSERVE: Default.aspx
<%@ Page Title="Home Page" Language="C#" MasterPageFile="~/Site.master" AutoEventWireup
="true"
CodeBehind="Default.aspx.cs" Inherits="FormAuthentication1._Default" >

<asp:Content ID="HeaderContent" runat="server" ContentPlaceHolderID="HeadContent">


</asp:Content>
<asp:Content ID="BodyContent" runat="server" ContentPlaceHolderID="MainContent">
<asp:Panel runat="server" ID="LoggedInMessagePanel">
<asp:Label runat="server" ID="WelcomeBackMessage"></asp:Label>
</asp:Panel>
<asp:Panel runat="Server" ID="NotLoggedInMessagePanel">
<asp:HyperLink runat="server" ID="lnkLogin" Text="Log In" NavigateUrl="~/Accoun
t/Login.aspx"></asp:HyperLink>
</asp:Panel>
<h2>
Welcome to ASP.NET!
</h2>
<p>
To learn more about ASP.NET visit <a href="http://www.asp.net" title="ASP.NET W
ebsite">
www.asp.net</a>.
</p>
<p>
You can also find <a href="http://go.microsoft.com/fwlink/?LinkID=152368&clcid=
0x409"
title="MSDN ASP.NET Docs">documentation on ASP.NET at MSDN</a>.
</p>
</asp:Content>

We added two ASP Panel o bjects to o ur page. They will be pro cessed by the server befo re being delivered to the web
bro wser. We'll lo o k at the co de-behind file in just a minute to see ho w they get pro cessed. Initially, the
Lo gge dInMe ssage Pane l will be set to be invisible; the No t Lo gge dInMe ssage Pane l panel will be set to be visible.
We can see that the No t Lo gge dInMe ssage Pane l's Label item has a predefined T e xt =" Lo g In" attribute that
displays "Lo g In" o n the screen when the panel is visible. The Lo gge dInMe ssage Pane l's Label item will have text
assigned to it later. We can access this panel via its ID attribute.

Either right-click o n the De f ault .aspx file and select Vie w Co de , o r expand its tree and do uble-click o n the
Default.aspx.cs file. Edit the file as sho wn:

CODE TO TYPE:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace FormAuthentication1
{
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
if (Request.IsAuthenticated)
{
WelcomeBackMessage.Text = "You are Logged In, " + User.Identity.Name;
LoggedInMessagePanel.Visible = true;
NotLoggedInMessagePanel.Visible = false;
}
else
{
LoggedInMessagePanel.Visible = false;
NotLoggedInMessagePanel.Visible = true;
}
}
}
}

and . Lo g in using the link o n the page, no t at the upper-right co rner o f the app. Yo u'll have to register fro m the
lo gin page first. After registering and lo gging in, yo u'll be returned to the main page. When the Lo g In link is go ne and
yo u get a welco me message, sto p debugging:
OBSERVE: Default.aspx.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace FormAuthentication1
{
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
if (Request.IsAuthenticated)
{
WelcomeBackMessage.Text = "You are Logged In, " + User.Identity.Name;
LoggedInMessagePanel.Visible = true;
NotLoggedInMessagePanel.Visible = false;
}
else
{
LoggedInMessagePanel.Visible = false;
NotLoggedInMessagePanel.Visible = true;
}
}
}
}

As yo u saw when yo u ran the applicatio n initially, the Lo gge dInMe ssage Pane l was set to be invisible by the co de-
behind file because o ur Re que st o bject was no t authenticated. After yo u lo gged in, the page was relo aded and we go t
o ur welco me message, " Yo u are Lo gge d In, username" . When the page initially lo ads, o r relo ads, the co de-behind
file's Page _Lo ad() metho d runs its lo gic, and checks to determine whether we are lo gged in. Then it sets the panels'
Visible pro perties to the co rrect values.

Note The Use r class can be used to get info rmatio n abo ut the currently lo gged-in user.

Membership and Roles


ASP.NET will create a database fo r Me m be rship (User Acco unts) in the App_Dat a directo ry. The co ntents o f this
directo ry are hidden by default. Ho wever, by clicking o n the So lut io n Explo re r's Sho w All File s butto n, yo u can
reveal the hidden files.
No w let's talk abo ut Membership, User Acco unts, and Ro les.

The data fo r user acco unts and ro les is sto red in the /App_Dat a/aspne t db.m df file. Let's create a fo lder named
Adm in in yo ur pro ject. Inside the Adm in fo lder, create three files named Cre at e Use rs.aspx, Manage Ro le s.aspx,
and Manage Use rsWit hRo le s.aspx. Use the We b Fo rm using Mast e r Page template to create the files. We'll use
these files to create users and ro les fo r o ur web app.

Mo dify yo ur Manage Ro le s.aspx file as sho wn:


CODE TO TYPE: ManageRo les.aspx
<%@ Page Title="" Language="C#" MasterPageFile="~/Site.Master" AutoEventWireup="true"
CodeBehind="ManageRoles.aspx.cs" Inherits="FormAuthentication.Admin.ManageRoles" %>

<asp:Content ID="Content1" ContentPlaceHolderID="HeadContent" runat="server">


</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<asp:Panel ID="RoleGridPanel" runat="server">
<b>Current Roles</b>
<asp:GridView ID="CurrentRolesGrid" runat="server" AutoGenerateColumns="false"
OnRowDeleting="CurrentRolesGrid_RowDeleting">
<Columns>
<asp:TemplateField HeaderText="Role">
<ItemTemplate>
<asp:Label runat="server" ID="RoleNameLabel" Text='<%# Containe
r.DataItem %>' />
</ItemTemplate>
</asp:TemplateField>
<asp:CommandField DeleteText="Delete" ShowDeleteButton="True" />
</Columns>
</asp:GridView>
</asp:Panel>
<br />
<asp:Panel ID="CreateRolePanel" runat="server">
<b>Create a New Role:</b>
<asp:TextBox ID="RoleNameTextBox" runat="server"></asp:TextBox>
<asp:Button ID="CreateRoleButton" runat="server" Text="Create Role" OnClick="Cr
eateRoleButton_Click" />
</asp:Panel>
</asp:Content>

but do n't run it. We still need to do so me wo rk o n the co de-behind file.

OBSERVE: ManageRo les.aspx

<%@ Page Title="" Language="C#" MasterPageFile="~/Site.Master" AutoEventWireup="true"


CodeBehind="ManageRoles.aspx.cs" Inherits="FormAuthentication.Admin.ManageRoles" %>

<asp:Content ID="Content1" ContentPlaceHolderID="HeadContent" runat="server">


</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<asp:Panel ID="RoleGridPanel" runat="server">
<b>Current Roles</b>
<asp:GridView ID="CurrentRolesGrid" runat="server" AutoGenerateColumns="false"
OnRowDeleting="CurrentRolesGrid_RowDeleting">
<Columns>
<asp:TemplateField HeaderText="Role">
<ItemTemplate>
<asp:Label runat="server" ID="lblRoleName" Text='<%# Container.Data
Item %>' />
</ItemTemplate>
</asp:TemplateField>
<asp:CommandField DeleteText="Delete" ShowDeleteButton="True" />
</Columns>
</asp:GridView>
</asp:Panel>
<br />
<asp:Panel ID="CreateRolePanel" runat="server">
<b>Create a New Role:</b>
<asp:TextBox ID="RoleNameTextBox" runat="server"></asp:TextBox>
<asp:Button ID="CreateRoleButton" runat="server" Text="Create Role" OnClick="Cr
eateRoleButton_Click" />
</asp:Panel>
</asp:Content>
Here we put an ASP.NET GridView co ntro l o nto o ur page. We do n't want it to Aut o Ge ne rat e Co lum ns because we
want the co lumn we are go ing to sho w to have a meaningful name, rather than the wo rd It e m . The T e m plat e Fie ld
we're creating co ntains two co lumns. The first co lumn will be o ur Ro le; the seco nd co lumn will co ntain a delete link so
that we can remo ve ro les as needed. No tice that we used <% # Co nt aine r.Dat aIt e m % > as o ur label's text. In o ur
co de-behind file, we'll bind the GridView to the ro les table in the database. <% # Co nt aine r.Dat aIt e m % > will
po pulate this co lumn's label with the ro le name auto matically. In the GridView's declaratio n, we indicate that when a
ro le is deleted, we want o ur co de-behind files, Curre nt Ro le sGrid_Ro wDe le t ing metho d to run. This is ho w we link
events in ASP.NET. Yo u can go to the design view and click o n the GridView co ntro l and access the events in the
pro perties windo w as well.

The GridView class is po werful and its full use is beyo nd the sco pe o f this lesso n. Ho wever, if yo u want
Note to learn mo re abo ut it o n yo ur o wn, see the MSDN article o n the Gridview Class.

Okay, no w, let's lo o k at the co de-behind file fo r o ur Manage Ro le s page. Mo dify yo ur Manage Ro le s.aspx.cs co de-
behind file as sho wn:

CODE TO TYPE: ManageRo les.aspx.cs


using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.Security;

namespace FormAuthentication.Admin
{
public partial class ManageRoles : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
DisplayRolesInGrid();
}

protected void CreateRoleButton_Click(object sender, EventArgs e)


{
string newRoleName = RoleNameTextBox.Text.Trim();
if (!Roles.RoleExists(newRoleName))
{
Roles.CreateRole(newRoleName);
DisplayRolesInGrid();
}
RoleNameTextBox.Text = string.Empty;
}

private void DisplayRolesInGrid()


{
CurrentRolesGrid.DataSource = Roles.GetAllRoles();
CurrentRolesGrid.DataBind();
}

protected void CurrentRolesGrid_RowDeleting(object sender, GridViewDeleteEventA


rgs e)
{
Label lblRoleName = CurrentRolesGrid.Rows[e.RowIndex].FindControl("lblRoleN
ame") as Label;
Roles.DeleteRole(lblRoleName.Text, false);
DisplayRolesInGrid();
}
}
}

Save the file and start debugging. If yo ur applicatio n do es no t start at the


ht t p://lo calho st :xxxxx/Adm in/Manage Ro le s.aspx page, navigate to it. Create two ro les, Adm in and Me m be r.
Later, we'll create users and put them in these ro les.

Let's take a lo o k at this, metho d by metho d, starting with the Page _Lo ad() metho d. Here, we are telling o ur pro gram
that, if this is the initial lo ad o f the page, display what is in the ro les database. (If we are po sting this page back fro m
o ne o f o ur metho ds, they will handle this fo r us):

OBSERVE: ManageRo les.aspx.cs


using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.Security;

namespace FormAuthentication.Admin
{
public partial class ManageRoles : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
DisplayRolesInGrid();
}

protected void CreateRoleButton_Click(object sender, EventArgs e)


{
string newRoleName = RoleNameTextBox.Text.Trim();
if (!Roles.RoleExists(newRoleName))
{
Roles.CreateRole(newRoleName);
DisplayRolesInGrid();
}
RoleNameTextBox.Text = string.Empty;
}

private void DisplayRolesInGrid()


{
CurrentRolesGrid.DataSource = Roles.GetAllRoles();
CurrentRolesGrid.DataBind();
}

protected void CurrentRolesGrid_RowDeleting(object sender, GridViewDeleteEventA


rgs e)
{
Label lblRoleName = CurrentRolesGrid.Rows[e.RowIndex].FindControl("lblRoleN
ame") as Label;
Roles.DeleteRole(lblRoleName.Text, false);
DisplayRolesInGrid();
}
}
}

The Cre at e Ro le But t o n_Click() metho d is an event handler. When the Cre at e Ro le But t o n o n the
Manage Ro le s.aspx page is clicked, this metho d will run. We will get the ne wRo le Nam e fro m the
Ro le Nam e T e xt Bo x o n the .aspx page and remo ve any spaces o n the ends. If that ro le name do es no t exist already,
we'll enter it into the ro les table in o ur database, and po st the page back with the new data. If the ro le already exists, we
clear the Ro leNameTextBo x.

In the DisplayRo le sInGrid() metho d, we use the ASP.NET Syst e m .We b.Se curit y.Ro le s class to get all o f the
ro les fro m the database. Then we bind that data to o ur Curre nt Ro le sGrid. This causes the grid to be refreshed.

Finally, the Curre nt Ro le sGrid_Ro wDe le t ing() metho d is run when the Curre nt Ro le sGrid fires a deleting event,
when we click the delete butto n beside a ro le. We are using the FindCo nt ro l() metho d to find o ut which ro w in the grid
is being deleted. FindCo nt ro l can be tricky because it lo o ks o nly in the co ntro l fro m which it is called, so nesting calls
might be necessary. Once we find the label, we can get its text and then delete the ro le. Then we call
DisplayRo le sInGrid() to rebind the data and sho w the new ro les, less o ur deletio n.

Note The Ro le s class can be used to get and mo dify info rmatio n abo ut the ro les set in the database.

No w let's take a lo o k at ho w we can create users with ro les. Mo dify yo ur Cre at e Use rs.aspx file as sho wn:

CODE TO TYPE: CreateUsers.aspx


<%@ Page Title="" Language="C#" MasterPageFile="~/Site.Master" AutoEventWireup="true"
CodeBehind="CreateUsers.aspx.cs" Inherits="FormAuthentication.Admin.CreateUsers" %>

<asp:Content ID="Content1" ContentPlaceHolderID="HeadContent" runat="server">


</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<asp:Panel ID="CreateUserWizardPanel" runat="server">
<asp:CreateUserWizard ID="CreateUserWithRoles" runat="server" ContinueDestinati
onPageUrl="~/Default.aspx"
LoginCreatedUser="False" OnActiveStepChanged="CreateUserWithRoles_ActiveSte
pChanged">
<WizardSteps>
<asp:CreateUserWizardStep ID="CreateUserWithRolesWizardStep" runat="ser
ver">
</asp:CreateUserWizardStep>
<asp:WizardStep ID="RoleStep" runat="server" StepType="Step" Title="Rol
es" AllowReturn="False">
<asp:CheckBoxList ID="RoleCheckBoxList" runat="server">
</asp:CheckBoxList>
</asp:WizardStep>
<asp:CompleteWizardStep ID="CompleteWizardStep" runat="server">
</asp:CompleteWizardStep>
</WizardSteps>
</asp:CreateUserWizard>
</asp:Panel>
</asp:Content>

Save, but do n't run it yet. We still need to mo dify the co de-behind file:
OBSERVE: CreateUsers.aspx
<%@ Page Title="" Language="C#" MasterPageFile="~/Site.Master" AutoEventWireup="true"
CodeBehind="CreateUsers.aspx.cs" Inherits="FormAuthentication.Admin.CreateUsers" %>

<asp:Content ID="Content1" ContentPlaceHolderID="HeadContent" runat="server">


</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<asp:Panel ID="CreateUserWizardPanel" runat="server">
<asp:CreateUserWizard ID="CreateUserWithRoles" runat="server" ContinueDestinati
onPageUrl="~/Default.aspx"
LoginCreatedUser="False" OnActiveStepChanged="CreateUserWithRoles_ActiveSte
pChanged">
<WizardSteps>
<asp:CreateUserWizardStep ID="CreateUserWithRolesWizardStep" runat="ser
ver">
</asp:CreateUserWizardStep>
<asp:WizardStep ID="RoleStep" runat="server" StepType="Step" Title="Rol
es" AllowReturn="False">
<asp:CheckBoxList ID="RoleCheckBoxList" runat="server">
</asp:CheckBoxList>
</asp:WizardStep>
<asp:CompleteWizardStep ID="CompleteWizardStep" runat="server">
</asp:CompleteWizardStep>
</WizardSteps>
</asp:CreateUserWizard>
</asp:Panel>
</asp:Content>

Here, we create a wizard using the built-in wizard functio ns o f Visual Studio and ASP.NET. We use the
Cre at e Use rWizardSt e p to create o ur user. Then we use Ro le St e p to select a ro le(s) fo r the user. We use
Co m ple t e WizardSt e p to finish the pro cess. Ho wever, no ne o f this will wo rk co rrectly until we mo dify the co de-
behind file to link up the lo gic.

Mo dify the Cre at e Use rs.aspx.cs co de-behind file as sho wn:


CODE TO TYPE: CreateUsers.aspx.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.Security;

namespace FormAuthentication.Admin
{
public partial class CreateUsers : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
{
WizardStep RolesStep = CreateUserWithRoles.FindControl("RoleStep") as W
izardStep;
CheckBoxList RoleList = RolesStep.FindControl("RoleCheckBoxList") as Ch
eckBoxList;
RoleList.DataSource = Roles.GetAllRoles();
RoleList.DataBind();
}
}
protected void CreateUserWithRoles_ActiveStepChanged(object sender, EventArgs e
)
{
if (CreateUserWithRoles.ActiveStep.Title == "Complete")
{
WizardStep RoleStep = CreateUserWithRoles.FindControl("RoleStep") as Wi
zardStep;
CheckBoxList RoleCheckBoxList = RoleStep.FindControl("RoleCheckBoxList"
) as CheckBoxList;
foreach (ListItem item in RoleCheckBoxList.Items)
{
if (item.Selected)
Roles.AddUserToRole(CreateUserWithRoles.UserName, item.Text);
}
}
}
}
}

and . If the bro wser do es no t start up o n the CreateUsers page, navigate to the page
ht t p://lo calho st :xxxxx/Adm in/Cre at e Use rs.aspx. Create a new user named Adm in. Yo u can use any email
address, but make sure to give the user the passwo rd o f Adm in123. This way, yo ur mento r will have the ability to get
into the site witho ut having to mo dify the We b.co nf ig file, which we'll add at the end o f this lesso n.

Admin users sho uld be members o f All ro les, unless there is so me reaso n yo u do n't want them to view
Note tho se reso urces.
OBSERVE: CreateUsers.aspx.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.Security;

namespace FormAuthentication.Admin
{
public partial class CreateUsers : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
{
WizardStep RolesStep = CreateUserWithRoles.FindControl("RoleStep") as W
izardStep;
CheckBoxList RoleList = RolesStep.FindControl("RoleCheckBoxList") as Ch
eckBoxList;
RoleList.DataSource = Roles.GetAllRoles();
RoleList.DataBind();
}
}
protected void CreateUserWithRoles_ActiveStepChanged(object sender, EventArgs e
)
{
if (CreateUserWithRoles.ActiveStep.Title == "Complete")
{
WizardStep RoleStep = CreateUserWithRoles.FindControl("RolesStep") as W
izardStep;
CheckBoxList RoleCheckBoxList = RoleStep.FindControl("RoleCheckBoxList"
) as CheckBoxList;
foreach (ListItem item in RoleCheckBoxList.Items)
{
if (item.Selected)
Roles.AddUserToRole(CreateUserWithRoles.UserName, item.Text);
}
}
}
}
}

The Page _Lo ad() metho d is similar to o ur last listing. If we are no t po sting back fro m o ne o f o ur metho ds, we just fill
o ur Ro leCheckBo xList with the ro les that are already in the database.

The meat o f this class is the Cre at e Use rWit hRo le s_Act ive St e pChange d() metho d. Here, we check to see if we
have reached the "Co mplete" step, meaning that the user has been created and all o f the ro les that we want them to
have, have been selected. The "Co mplete" step is a built-in feature o f the Wizard. Once we get there, we can iterate
thro ugh the Ro le Che ckBo xList and assign the checked ro les to o ur user.

The next page we wo rk with allo ws us to add and remo ve ro les fo r specific users. Mo dify yo ur
Manage Use rsWit hRo le s.aspx file as sho wn:
CODE TO TYPE: ManageUsersWithRo les.aspx

<%@ Page Title="" Language="C#" MasterPageFile="~/Site.Master" AutoEventWireup="true"


CodeBehind="ManageUsersWithRoles.aspx.cs" Inherits="FormAuthentication.Admin.Manage
UsersWithRoles" %>

<asp:Content ID="Content1" ContentPlaceHolderID="HeadContent" runat="server">


</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<asp:Panel ID="UserListDropDownPanel" runat="server">
<b>Select User:</b>
<asp:DropDownList ID="UserListDropDown" runat="server" AutoPostBack="True" Data
TextField="UserName"
DataValueField="UserName" OnSelectedIndexChanged="UserDropDownList_Selected
IndexChanged">
</asp:DropDownList>
</asp:Panel>
<br />
<asp:Panel ID="UsersRoleListPanel" runat="server">
<asp:Repeater ID="UsersRoleList" runat="server">
<ItemTemplate>
<asp:CheckBox runat="server" ID="RoleCheckBox" AutoPostBack="true" Text
='<%# Container.DataItem %>'
OnCheckedChanged="RoleCheckBox_CheckChanged" />
<br />
</ItemTemplate>
</asp:Repeater>
</asp:Panel>
<br />
<asp:Panel ID="RoleListDropDownPanel" runat="server">
</asp:Panel>
<asp:DropDownList ID="RoleDropDownList" runat="server" AutoPostBack="true" OnSelect
edIndexChanged="RoleDropDownList_SelectedIndexChanged">
</asp:DropDownList>
<br />
<asp:Panel ID="RolesUserGridPanel" runat="server">
<asp:GridView ID="RolesUserGrid" runat="server" AutoGenerateColumns="false" Emp
tyDataText="There are no users in this role."
OnRowDeleting="RolesUserGrid_RowDeleting">
<Columns>
<asp:TemplateField HeaderText="Users">
<ItemTemplate>
<asp:Label runat="server" ID="UserNameLabel" Text='<%# Containe
r.DataItem %>'></asp:Label>
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
</asp:Panel>
<br />
<asp:Panel ID="AddUserToRolePanel" runat="server">
<b>UserName:</b>
<asp:TextBox ID="AddUserToRole" runat="server"></asp:TextBox>
<asp:Button ID="AddUserToRoleButton" runat="server" Text="Add User to Role" OnClick
="AddUserToRoleButton_Click" />
</asp:Panel>
</asp:Content>

Save, but do n't run it. We still need to mo dify the co de-behind file:
OBSERVE: ManageUsersWithRo les.aspx
<%@ Page Title="" Language="C#" MasterPageFile="~/Site.Master" AutoEventWireup="true"
CodeBehind="ManageUsersWithRoles.aspx.cs" Inherits="FormAuthentication.Admin.Manage
UsersWithRoles" %>

<asp:Content ID="Content1" ContentPlaceHolderID="HeadContent" runat="server">


</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<asp:Panel ID="UserListDropDownPanel" runat="server">
<b>Select User:</b>
<asp:DropDownList ID="UserListDropDown" runat="server" AutoPostBack="True" Data
TextField="UserName"
DataValueField="UserName" OnSelectedIndexChanged="UserDropDownList_Selected
IndexChanged">
</asp:DropDownList>
</asp:Panel>
<br />
<asp:Panel ID="UsersRoleListPanel" runat="server">
<asp:Repeater ID="UsersRoleList" runat="server">
<ItemTemplate>
<asp:CheckBox runat="server" ID="RoleCheckBox" AutoPostBack="true" Text
='<%# Container.DataItem %>'
OnCheckedChanged="RoleCheckBox_CheckChanged" />
<br />
</ItemTemplate>
</asp:Repeater>
</asp:Panel>
<br />
<asp:Panel ID="RoleListDropDownPanel" runat="server">
<asp:DropDownList ID="RoleDropDownList" runat="server" AutoPostBack="true" OnSe
lectedIndexChanged="RoleDropDownList_SelectedIndexChanged">
</asp:DropDownList>
</asp:Panel>
<br />
<asp:Panel ID="RolesUserGridPanel" runat="server">
<asp:GridView ID="RolesUserGrid" runat="server" AutoGenerateColumns="false" Emp
tyDataText="There are no users in this role."
OnRowDeleting="RolesUserGrid_RowDeleting">
<Columns>
<asp:TemplateField HeaderText="Users">
<ItemTemplate>
<asp:Label runat="server" ID="UserNameLabel" Text='<%# Containe
r.DataItem %>'></asp:Label>
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
</asp:Panel>
<br />
<asp:Panel ID="AddUserToRolePanel" runat="server">
<b>UserName:</b>
<asp:TextBox ID="AddUserToRole" runat="server"></asp:TextBox>
<asp:Button ID="AddUserToRoleButton" runat="server" Text="Add User to Role" OnClick
="AddUserToRoleButton_Click" />
</asp:Panel>
</asp:Content>

It may seem like we're do ing a lo t here, but mo st o f it is wrapping panel o bjects. Our Use rDro pDo wnList co ntro l will
be po pulated by the co de-behind file. It will allo w us to select a user fro m o ur user acco unts. When the selectio n
changes, the Use rDro pDo wnList _Se le ct e dInde xChange d metho d in o ur co de-behind file will be executed.

The Use rsRo le List repeater co ntains o ur Ro le Che ckBo x items. This list o f checkbo xes will be po pulated by o ur
co de-behind file. We are using the same metho d as befo re by getting this item fro m the <% # Co nt aine r.Dat aIt e m
% >. When a checkbo x is checked o r unchecked, the Ro le Che ckBo x_Che ckChange d() metho d is run fro m o ur
co de-behind file.

Similar to the Use rList Dro pDo wn co ntro l abo ve, the Ro le Dro pDo wnList co ntro l will be po pulated by the co de-
behind file; we will be able to select ro les to see what users are in that ro le, using the Ro le sUse rGrid co ntro l.

Finally, we have o ur AddUse rT o Ro le text bo x and o ur AddUse rT o Ro le But t o n which allo w us to add a user to the
selected ro le.

No w, let's put to gether the co de-behind file. Mo dify yo ur Manage Use rsWit hRo le s.aspx.cs file as sho wn:
CODE TO TYPE: ManageUsersWithRo les.aspx.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.Security;

namespace FormAuthentication.Admin
{
public partial class ManageUsersWithRoles : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
{
BindUsersToUserList();
BindRolesToList();
CheckForUserRoleSelection();
UserIsInRole();
}
}

private void BindUsersToUserList()


{
MembershipUserCollection users = Membership.GetAllUsers();
UserListDropDown.DataSource = users;
UserListDropDown.DataBind();
}

private void BindRolesToList()


{
string[] roles = Roles.GetAllRoles();
UsersRoleList.DataSource = roles;
UsersRoleList.DataBind();
RoleDropDownList.DataSource = roles;
RoleDropDownList.DataBind();
}

private void CheckForUserRoleSelection()


{
string userNameSelection = UserListDropDown.SelectedValue;
string[] userRolesSelection = Roles.GetRolesForUser(userNameSelection);
foreach (RepeaterItem item in UsersRoleList.Items)
{
CheckBox RoleCheckBox = item.FindControl("RoleCheckBox") as CheckBox;
if (userRolesSelection.Contains<string>(RoleCheckBox.Text))
RoleCheckBox.Checked = true;
else
RoleCheckBox.Checked = false;
}
}

protected void UserDropDownList_SelectedIndexChanged(object sender, EventArgs e


)
{
CheckForUserRoleSelection();
}

protected void RoleCheckBox_CheckChanged(object sender, EventArgs e)


{
CheckBox RoleCheckBox = sender as CheckBox;
string selectedUser = UserListDropDown.SelectedValue;
string roleName = RoleCheckBox.Text;
if (RoleCheckBox.Checked)
Roles.AddUserToRole(selectedUser, roleName);
else
Roles.RemoveUserFromRole(selectedUser, roleName);
UserIsInRole();
}

private void UserIsInRole()


{
string selectedRoleName = RoleDropDownList.SelectedValue;
string[] userInRole = Roles.GetUsersInRole(selectedRoleName);
RolesUserGrid.DataSource = userInRole;
RolesUserGrid.DataBind();
}

protected void RoleDropDownList_SelectedIndexChanged(object sender, EventArgs e


)
{
UserIsInRole();
}

protected void RolesUserGrid_RowDeleting(object sender, GridViewDeleteEventArgs


e)
{
string selectedRoleName = RoleDropDownList.SelectedValue;
Label UserNameLabel = RolesUserGrid.Rows[e.RowIndex].FindControl("UserNameL
abel") as Label;
Roles.RemoveUserFromRole(UserNameLabel.Text, selectedRoleName);
UserIsInRole();
CheckForUserRoleSelection();
}

protected void AddUserToRoleButton_Click(object sender, EventArgs e)


{
string selectedRoleName = RoleDropDownList.SelectedValue;
string userNameToAddToRole = AddUserToRole.Text;
if (userNameToAddToRole.Trim().Length == 0)
{
return;
}
MembershipUser userInfo = Membership.GetUser(userNameToAddToRole);
if (userInfo == null)
{
return;
}
if (Roles.IsUserInRole(userNameToAddToRole, selectedRoleName))
{
return;
}
Roles.AddUserToRole(userNameToAddToRole, selectedRoleName);
AddUserToRole.Text = string.Empty;
UserIsInRole();
CheckForUserRoleSelection();
}

}
}

and . Add a user named Me m be r with passwo rd Me m be r123 and place them in the Me m be r ro le.

Let's take it metho d by metho d. The Page_Lo ad metho d is familiar. It displays the initial info rmatio n fo r o ur page, if it is
no t a po st back:
OBSERVE: ManageUsersWithRo les.aspx.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.Security;

namespace FormAuthentication.Admin
{
public partial class ManageUsersWithRoles : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
{
BindUsersToUserList();
BindRolesToList();
CheckForUserRoleSelection();
UserIsInRole();
}
}

private void BindUsersToUserList()


{
MembershipUserCollection users = Membership.GetAllUsers();
UserListDropDown.DataSource = users;
UserListDropDown.DataBind();
}

private void BindRolesToList()


{
string[] roles = Roles.GetAllRoles();
UsersRoleList.DataSource = roles;
UsersRoleList.DataBind();
RoleDropDownList.DataSource = roles;
RoleDropDownList.DataBind();
}

private void CheckForUserRoleSelection()


{
string userNameSelection = UserListDropDown.SelectedValue;
string[] userRolesSelection = Roles.GetRolesForUser(userNameSelection);
foreach (RepeaterItem item in UsersRoleList.Items)
{
CheckBox RoleCheckBox = item.FindControl("RoleCheckBox") as CheckBox;
if (userRolesSelection.Contains<string>(RoleCheckBox.Text))
RoleCheckBox.Checked = true;
else
RoleCheckBox.Checked = false;
}
}

protected void UserDropDownList_SelectedIndexChanged(object sender, EventArgs e


)
{
CheckForUserRoleSelection();
}

protected void RoleCheckBox_CheckChanged(object sender, EventArgs e)


{
CheckBox RoleCheckBox = sender as CheckBox;
string selectedUser = UserListDropDown.SelectedValue;
string roleName = RoleCheckBox.Text;
if (RoleCheckBox.Checked)
Roles.AddUserToRole(selectedUser, roleName);
else
Roles.RemoveUserFromRole(selectedUser, roleName);
UserIsInRole();
}

private void UserIsInRole()


{
string selectedRoleName = RoleDropDownList.SelectedValue;
string[] userInRole = Roles.GetUsersInRole(selectedRoleName);
RolesUserGrid.DataSource = userInRole;
RolesUserGrid.DataBind();
}

protected void RoleDropDownList_SelectedIndexChanged(object sender, EventArgs e


)
{
UserIsInRole();
}

protected void RolesUserGrid_RowDeleting(object sender, GridViewDeleteEventArgs


e)
{
string selectedRoleName = RoleDropDownList.SelectedValue;
Label UserNameLabel = RolesUserGrid.Rows[e.RowIndex].FindControl("UserNameL
abel") as Label;
Roles.RemoveUserFromRole(UserNameLabel.Text, selectedRoleName);
UserIsInRole();
CheckForUserRoleSelection();
}

protected void AddUserToRoleButton_Click(object sender, EventArgs e)


{
string selectedRoleName = RoleDropDownList.SelectedValue;
string userNameToAddToRole = AddUserToRole.Text;
if (userNameToAddToRole.Trim().Length == 0)
{
return;
}
MembershipUser userInfo = Membership.GetUser(userNameToAddToRole);
if (userInfo == null)
{
return;
}
if (Roles.IsUserInRole(userNameToAddToRole, selectedRoleName))
{
return;
}
Roles.AddUserToRole(userNameToAddToRole, selectedRoleName);
AddUserToRole.Text = string.Empty;
UserIsInRole();
CheckForUserRoleSelection();
}
}
}

The BindUse rsT o List () metho d gets all users fro m the database and binds them to the Use rList Dro pDo wn
co ntro l.

The BindRo le sT o List () do es the same fo r ro les, ho wever, it binds them to two co ntro ls: the Use rsRo le List and
the Ro leDro pDo wnList.

The Che ckFo rUse rSe le ct io n metho d lo o ks at the Use rList Dro pDo wn co ntro l to see which user is selected and
then gets all o f the ro les o f which the user is a member. It lo o ks thro ugh all o f the items in the Use rRo le List and
marks the ro les the user is in as "selected," and makes sure that any ro le that the user is no t in is marked as "no t
selected."

The Use rDro pDo wnList _Se le ct e dInde xChange d() event handler calls the Che ckFo rUse rRo le Se le ct io n()
metho d any time the user is changed o n the Use rDro pDo wnList co ntro l.
The Ro le Che ckBo x_Che ckChange d event handler adds o r remo ves a user fro m a ro le based o n the status o f the
Use rList Dro pDo wn and the Ro le Che ckBo x. Then it calls the Use rIsInRo le () metho d to update the
Ro le sUse rGrid co ntro l.

The Ro le Dro pDo wnList _Se le ct e dInde xChange d event handler calls Use rIsInRo le () to update the page
sho wing which users are in this ro le when the dro p-do wn list selectio n changes.

The Ro le sUse rGrid_Ro wDe le t ing() event handler determines which ro le we are wo rking with and then remo ves the
user listed in that ro w o f the grid fro m the ro le. Then it calls Use rIsInRo le and Che ckFo rUse rRo le Se le ct io n to
update the page.

Finally, the AddUse rT o Ro le But t o n_Click() event handler adds a user to the selected ro le, if the user exists in the
database. This metho d and its asso ciated co ntro l is useful if yo u kno w the name o f the user and do n't want to use the
dro p-do wn list.

Note The Me m be rship class can be used to get and set info rmatio n abo ut the users in the database.

Authorization
At the beginning o f this lesso n, we edited a file named We b.co nf ig. There is o ne main We b.co nf ig file fo r the entire
site, lo cated in the pro ject's ro o t directo ry. Ho wever, each subdirecto ry can have its o wn Web.co nfig as well. While the
main We b.co nf ig file manages the entire site, the subdirecto ry We b.co nf ig files manage access to that particular
directo ry and the files therein. There are two ways o f co ntro lling access to files in an ASP.NET site using the
We b.co nf ig files. Yo u can co ntro l everything fro m the main We b.co nf ig file o r yo u can delegate access to the
subdirecto ry We b.co nf ig files.

Let's lo o k at ho w to co ntro l access fro m the main site's We b.co nf ig file first.

Note The fo llo wing file is NOT fro m o ur pro ject. It is just fo r example purpo ses.

OBSERVE: /Web.co nfig


<configuration>
<system.web>
<authentication mode="Forms"/>
<authorization> <deny users="?"/>
</authorization>
</system.web>
<location path="Account/register.aspx">
<system.web>
<authorization>
<allow users="*"/>
</authorization>
</system.web>
</location>
</configuration>

In this /We b.co nf ig file, the first <system.web> tag's autho rizatio n tag de nie s acce ss to the entire site to all
ano nymo us users. In the next sectio n, we explicitly give access to all users to view the Acco unt /re gist e r.aspx file.
The ? in the <de ny use rs=" ?" /> indicates all no n-authenticated users. The * in the <allo w use rs=" *" /> indicates all
users.

We can co ntro l access to the site and to specific files and directo ries fro m the main We b.co nf ig file.
Note Tho ugh o n a large site, this file can beco me quite large. To mo dularize access rules, we can allo w each
directo ry to co ntro l access to itself and its o wn files.

No w, let's create a new We b.co nf ig file fo r o ur Adm in directo ry. In the Adm in directo ry, create a new file named
We b.co nf ig as sho wn:

Note Make sure yo u have created the Adm in user and put that user in the Adm in ro le befo re do ing this.
CODE TO TYPE: Admin\Web.co nfig

<?xml version="1.0"?>
<configuration>
<system.web>
<authorization>
<allow roles="Admin"/>
<deny users="*"/>
</authorization>
</system.web>
</configuration>

and . Lo g in as a no n-admin user and attempt to go to the ht t p://lo calho st :xxxxx/Adm in/Cre at e Use rs.aspx
page. Yo u are redirected to the Lo gin.aspx page. Lo g in as Adm in using the Adm in123 passwo rd. Try to go to the
ht t p://lo calho st :xxxxx/Adm in/Cre at e Use rs.aspx page again and no te that yo u can no w create a new user:

OBSERVE: Admin\Web.co nfig

<?xml version="1.0"?>
<configuration>
<system.web>
<authorization>
<allow roles="Admin"/>
<deny users="*"/>
</authorization>
</system.web>
</configuration>

No te that the <system.web> sectio n o f the file is read to p to bo tto m, so if yo u put the de ny use rs tag befo re the allo w
ro le s tag, yo u wo n't ever get into that directo ry when running the site. Yo u can allo w multiple ro les by adding them to
the allo w ro le s tag, separated by co mmas. Yo u can <allo w use rs=" Use rNam e , Use rNam e , Use rNam e " />. The
"*" character can be used to allo w all users.
Fo r mo re info rmatio n o n the allo w tag, see the MSDN article o n allo w Element fo r autho rizatio n. Ano ther
Note go o d so urce o n the We b.co nf ig file is this blo g, Setting autho rizatio n rules fo r a particular page o r
fo lder in web.co nfig.

Keep the ASP.NET website o pen in a bro wser when wo rking with ASP.NET pro jects. It's the definitive
T ip so urce fo r ASP.NET applicatio ns, and can pro vide much mo re info rmatio n than this lesso n can ho pe to
pro vide.

As always, co mplete this lesso n's ho mewo rk befo re yo u mo ve o n to the next...

Copyright © 1998-2014 O'Reilly Media, Inc.

This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.
See http://creativecommons.org/licenses/by-sa/3.0/legalcode for more information.
Introduction to MVC and Razor
Lesson Objectives
In this lesso n, yo u will:

use the Mo del/View/Co ntro ller (MVC) design pattern and each o f its co mpo nents.
use these co mpo nents with o ne ano ther to create a rich applicatio n.
experiment to co mpare and co ntrast MVC with the Smart UI design pattern.
use Ro uteCo nfig.cs file is and manipulate and mo dify the ro ute maps.
use basic Razo r syntax.

Defining MVC
Model
The Mo del is respo nsible fo r handling data fro m the applicatio n. It implements the logic fo r the applicatio ns
data do main and is co mpletely independent o f the View and Co ntro ller.

Controller
The Controller translates the data fro m the user (server-side requests and HTTP requests), whether it be mo use
clicks o r any o ther fo rm o f input. It info rms the Mo del and/o r View o f the changes.

View
The View is respo nsible fo r rendering the user interface (bro wser windo w).

Model/View/Controller

The View and Co ntro ller are dependent o n the Mo del, but the Mo del is independent o f bo th the
Note View and the Co ntro ller.

MVC vs. Smart UI Design Pattern


The Smart UI design pattern is co mmo nly referred to as the "anti-pattern," because the design pattern do es no t
separate the lo gic fro m the interface. Essentially, it implements all o f the business lo gic within the user interface and
perfo rms updates by altering event handlers. It can be beneficial when yo u're creating a small applicatio n, because the
implementatio n, user interface, and lo gic can be co ded and designed quickly. Ho wever, if the applicatio n ever needs to
expand, this metho d can beco me cumberso me and make it difficult to find bugs.

The MVC design pattern separates all instances and creates a lo gical pattern fo r co mmunicatio n between different
o bjects. We have a View, which renders o ur user interface and no tifies the Co ntro ller o f any changes. The Co ntro ller
perfo rms the business lo gic that can be retrieved fro m the Mo del. The Co ntro ller then sends the updated info rmatio n to
the View to be rendered to the user. The Mo del is an "interface" where the variables fo r o ur database interactio ns and
user input will be defined and retrieved.

So , the MVC design pattern is metho do lo gy that is implemented to allo w fo r co de reusability, and to allo w fo r the
creatio n o f a readily expandable and mo difiable co de base. Debugging an MVC applicatio n is mo re straightfo rward
since each co mpo nent is kept separate; no co mpo nent is enco mpassed within ano ther co mpo nent. With the Smart UI
pattern, we lo se co de reusability and debugging takes lo nger because there is no separatio n o f the individual
co mpo nents.

Creating a New Project


Create a new MVC C# 4 pro ject. Click File | Ne w | Pro je ct .

Select the Visual C# template and ASP.NET MVC 4 We b Applicat io n, and name yo ur pro ject MvcDe sign:

Click OK. Next, yo u're asked which type o f applicatio n yo u want to create. Cho o se Basic. Make sure the Razo r View
engine is enabled:
Click OK to create the pro ject.

Project Folders
Visual Studio creates so me fo lders and files fo r us in So lutio n Explo rer:

Pro pe rt ie s Fo lde r: Co ntains the file Asse m blyInf o .cs and has so me general info rmatio n regarding the
assembly. In this file, yo u can change vario us default parameters pertaining to the build number, co pyright
info rmatio n, co mpany info rmatio n, and so o n.
Re f e re nce s Fo lde r: Co ntains all yo ur C# API classes.
App_St art Fo lde r: Co ntains info rmatio n abo ut ho w the applicatio n lo ads vario us items at the start. It also
defines the URL ro utes, and required .js files.
Co nt e nt Fo lde r: Co ntains a default CSS file named Site.css, which the applicatio n references fo r its
styling. It also co ntains an Images fo lder.
Co nt ro lle rs Fo lde r: Will co ntain the Co ntro llers fo r yo ur applicatio n.
Mo de ls Fo lde r: Will co ntain the Mo dels fo r yo ur applicatio n.
Vie ws Fo lde r: Will co ntain the Views fo r yo ur applicatio n.
Glo bal.asax Fo lde r: Co ntains the Glo bal.asax.cs file, which declares and handles applicatio n and
sessio n level events and o bjects.
Package s.co nf ig File : An XML file that defines all the packages and dependencies needed to run yo ur
applicatio n.

T he Controller
Right-click the Co nt ro lle rs fo lder and select Add | Co nt ro lle r...

In the Add Co ntro ller dialo g, change the name fro m De f ault 1Co nt ro lle r to Ho m e Co nt ro lle r, set the Template to
Em pt y MVC Co nt ro lle r, and click Add:
Visual Studio creates a Ho m e Co nt ro lle r.cs file with so me default co de.

Note Visual Studio also adds a new fo lder and files to the Views fo lder.

Save and run. Yo u see this message:

This o ccurs because there is no View o bject fo r the Ho meCo ntro ller to interact with. Mo dify
/Co nt ro lle rs/Ho m e Co nt ro lle r.cs so it do esn't need a View o bject:
CODE TO TYPE:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace MvcDesign.Controllers
{
public class HomeController : Controller
{
//
// GET: /Home/

public ActionResult Index()


public string Index()
{
return View();
return "A Controller without a View!";
}
}
}

and . The co ntro ller returns the text.

When the applicatio n is lo aded, it's set to lo ad the /Ho me/ co ntro ller and Index metho d as defined in the
/App_St art /Ro ut e Co nf ig.cs file:

OBSERVE: /App_Start/Ro uteCo nfig.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;

namespace MvcDesign
{
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParamete
r.Optional }
);
}
}
}

The ro ut e s.MapRo ut e (...) functio n defines ho w the applicatio n will handle URL paths. The url: line defines the
default path fo r the applicatio n to interpret. The de f ault s: line defines the default co ntro ller actio n (in this case,
Ho m e /Inde x/), and an Opt io nal parameter. The Opt io nal parameter is generally an argument we want to pass to
the co ntro ller fo r the View to display. This co ncept will be discussed in mo re depth a little later.

T he View
Change Ho m e Co nt ro lle r.cs back to the way it was:
CODE TO TYPE:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace MvcDesign.Controllers
{
public class HomeController : Controller
{
//
// GET: /Home/

public string Index()


public ActionResult Index()
{
return "A Controller without a View!";
return View();
}
}
}

No w, create a View fo r the Index() metho d. Using Visual Studio , right-click o n the Index() metho d and select Add
Vie w...:

Leave the defaults as they are fo r no w:


Click Add. Visual Studio names the View after the Co ntro ller metho d, and o pens the new Inde x.csht m l file that is
sto red in the Vie ws/Ho m e fo lder auto matically. The fo lder is named after o ur Co ntro ller:
No w that we have a View to wo rk with, let's mo dify the /Vie ws/Share d/_Layo ut .csht m l file so we can begin to see
the differences o f o ur Co ntro ller metho ds by adding a navigatio n menu.

Note _Layo ut.cshtml persists thro ugh all Views. That's where we wo uld make majo r changes to the design.

CODE TO TYPE: /Views/Shared/_Layo ut.cshtml


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

<div>
<li>
<ul>
<a href="/">Home</a>
</ul>
<ul>
<a href="/Home/About/">About</a>
</ul>
<ul>
<a href="/Home/Contact/">Contact</a>
</ul>
</li>
</div>

@RenderBody()

@Scripts.Render("~/bundles/jquery")
@RenderSection("scripts", required: false)
</body>
</html>

and .

We no w need to add a View o bject fo r each o f o ur links in Ho m e Co nt ro lle r.cs:


CODE TO TYPE: /Co ntro llers/Ho meCo ntro ller.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace MvcDesign.Controllers
{
public class HomeController : Controller
{
//
// GET: /Home/

public ActionResult Index()


{
return View();
}

public ActionResult About()


{
return View();
}

public string Contact()


{
return "This is the Contact page!";
}

}
}

OBSERVE:
public ActionResult About()
{
return View();
}

public string Contact()


{
return "This is the Contact page!";
}

One o f the Ho meCo ntro ller's new metho ds returns an Act io nRe sult type; the o ther returns a st ring type. Yo u'll see
these differences mo re clearly when we run the applicatio n, but first we need to create Views fo r o ur new Abo ut () and
Co nt act () metho ds. Fo llo w the same steps as befo re fo r creating a View:
Mo dify /Vie ws/Ho m e /Abo ut .csht m l as sho wn:

CODE TO TYPE: /Views/Ho me/Abo ut.cshtml


@{
ViewBag.Title = "About";
}

<h2>AboutThis is the About page!</h2>

and .

The page displays the links we just added. Navigate using the links, paying attentio n to the address bar and ho w the
URLs are displayed:
OBSERVE: URL listing
Home page: localhost:xxxxx
About page: http://localhost:xxxxx/Home/About/
Contact page: http://localhost:xxxxx/Home/Contact/

Note No tice ho w the URLs match the names o f the Co ntro ller Actio ns; we'll discuss this in mo re detail sho rtly.

Yo u can see the change we made to the Abo ut page:

Controller Actions
Co ntro ller Actio ns get expo sed when a user either enters the URL in the navigatio n bar, o r clicks o n a link. An actio n is
a Co ntro ller metho d that gets invo ked when a certain URL is accessed. Fo r example, we to ld the applicatio n that we
want the Ho m e Co nt ro lle r to use the Co nt act metho d (which is a Co ntro ller Actio n) when we navigate to
" lo calho st :xxxxx/Ho m e /Co nt act /" .

Act io nRe sult is an Actio n that returns the View co rrespo nding to the co ntro ller (in o ur example); this enables us to
have different web pages rendered fo r different metho ds. There are many different types o f Co ntro ller Actio n Metho ds,
but they are all derived fro m the Actio nResult class.

Let's take a lo o k at a few o f them to get a feel fo r ho w they wo rk. Mo dify /Co nt ro lle rs/Ho m e Co nt ro lle r.cs as
sho wn:
CODE TO TYPE: /Co ntro llers/Ho meCo ntro ller.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace MvcDesign.Controllers
{
public class HomeController : Controller
{
//
// GET: /Home/

public ViewResult Index()


{
return View();
}

//
// GET: /Home/About/
public ActionResult About()
{
return View();
}

//
// GET: /Home/Contact/
public string Contact()
{
return "This is the Contact page!";
}
//
// GET: /Home/Google
// Redirect from our application to http://www.google.com
public RedirectResult Google()
{
return Redirect("http://www.google.com");
}

public RedirectResult RedirectContact()


{
return Redirect("/Home/Contact/");
}
}
}

Save, but do n't run the applicatio n; we have a bit mo re editing to do .

The Re dire ct Re sult return type allo ws yo u to redirect to ano ther actio n metho d by using its URL. As yo u can see in
o ur Go o gle () metho d, we redirect the user to Go o gle. In o ur Re dire ct Co nt act () metho d, we render o ur Co ntact
view using the URL. Mo dify yo ur /Vie ws/Share d/_Layo ut .csht m l as sho wn.
CODE TO TYPE: /Views/Shared/_Layo ut.cshtml

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>@ViewBag.Title</title>
@Styles.Render("~/Content/css")
@Scripts.Render("~/bundles/modernizr")
</head>
<body>
<div>
<li>
<ul>
<a href="/">Home</a>
</ul>
<ul>
<a href="/Home/About/">About</a>
</ul>
<ul>
<a href="/Home/Contact/">Contact</a>
</ul>
<ul>
<a href="/Home/Google/">Google</a>
</ul>
<ul>
<a href="/Home/RedirectContact/">Redirect to Contact()</a>
</ul>
</li>
</div>
@RenderBody()

@Scripts.Render("~/bundles/jquery")
@RenderSection("scripts", required: false)
</body>
</html>

Save and run, then test the Go o gle and Re dire ct t o Co nt act () links.

There are many o ther metho ds that are derived fro m the Actio nResult class, but they're beyo nd the sco pe o f this
lesso n. We enco urage yo u to read abo ut the vario us redirect metho ds at Redirect Metho ds.

Introduction to Razor
We have been using the Razo r syntax with ASP.NET and C# thro ugho ut the lesso n. Let's talk a bit mo re abo ut Razo r
itself, where we have used it, and why we care abo ut it.

First let's lo o k at o ur recently mo dified Abo ut .csht m l file:


OBSERVE: Abo ut.cshtml

@{
ViewBag.Title = "About";
}

<h2>This is the About page!</h2>

@{
if (ViewBag.id != 0)
{
<p>We retrieved a value of: @ViewBag.id</p>
}
else
{
<p>There was nothing in the id value.</p>
}
}

The @ character allo ws us to use co de in o ur HTML pages using the Razo r syntax. @ Vie wBag.id is an inline co de
statement. <p></p> is HTML fo rmatting that is sto red in o ur Razo r co de, with Razo r co de placed in the HTML
fo rmatting. Any time we want to use a co de statement that is in HTML fo rmatting, we need to use the @ symbo l.

If we want to define a string literal, we need to append the @ symbo l befo re o ur string:

OBSERVE: String Literal

@{ var a_string = @"This is a string literal" }

Note Syntax and variable definitio ns are case-sensitive.

This is ho w we retrieve values fro m a fo rm:

OBSERVE:

/* Multi-Statement Block */
@{
var input_1 = Request["form_box_1"];
var input_2 = Request["form_box_2"];
int x = 5;
}

/* Inline-block statement */
<p>@Request.UrlPath</p>
// HTML form code

Re que st retrieves values fro m a fo rm POST actio n. @ Re que st .UrlPat h returns the abso lute path o f the web page.
We can declare a variable o f any type using the var keywo rd o r an explicit data type declaratio n such as int .

Let's take a lo o k at so me co mments in the Razo r syntax:

OBSERVE: Razo r Co mments

@* This is an inline Razor comment *@


@*
And this
is
a
Razor
Block Comment *@

@ **@ sho wed an inline co mment, whereas @ **@ sho wed a blo ck co mment.

Let's practice the Razo r syntax.


Right-click the Mo de ls fo lder and add a new class named BasicMo de l.cs:

Mo dify /Mo de ls/BasicMo de l.cs as sho wn:

CODE TO TYPE: /Mo dels/BasicMo del.cs


using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace MvcDesign.Models
{
public class BasicModel
{
public int IntegerValue { get; set; }
public string StringValue { get; set; }
}
}

We no w need to mo dify the /Co nt ro lle rs/Ho m e Co nt ro lle r.cs file:


CODE TO TYPE: /Co ntro llers/Ho meCo ntro ller.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using MvcDesign.Models;

namespace MvcDesign.Controllers
{
public class HomeController : Controller
{
//
// GET: /Home/

public ViewResult Index()


{
return View();
}

//
// GET: /Home/About/
public ActionResult About()
{
View();
}

//
// GET: /Home/Contact/
public string Contact()
{
return "This is the Contact page!";
}

//
// GET: /Home/Google
// Redirect from our application to http://www.google.com
public RedirectResult Google()
{
return Redirect("http://www.google.com");
}

//
// GET: /Home/Contact
// Redirect from our link to the Contact view
public RedirectResult RedirectContact()
{

return Redirect("/Home/Contact/");
}

public ActionResult IntMethod()


{
return View();
}

public ActionResult StrMethod()


{
return View();
}
}
}

No w that we have two actio n metho ds, we need views fo r each. Right-click Int Me t ho d() and create a view fo r it. Make
sure the Cre at e a st ro ngly-t ype d vie w o ptio n is checked, and cho o se BasicMo de l (MvcDe sign.Mo de ls). If yo u
do n't see the BasicMo de l o ptio n, try rebuilding the so lutio n:
No w, add this co de to Int Me t ho d.csht m l:
CODE TO TYPE: IntMetho d.cshtml
@model MvcDesign.Models.BasicModel

@{
ViewBag.Title = "IntMethod";
}

<h2>IntMethod</h2>

<p>
@using (Html.BeginForm())
{
<fieldset>
<legend>IntMethod</legend>
Enter an Integer: @Html.EditorFor(model => model.IntegerValue)
<input type="submit" value="Submit" />
</fieldset>
}

@if (Model.IntegerValue == 0)
{
<div>Invalid integer has been entered.</div>
}
else
{
<div>Integer value entered: @Model.IntegerValue</div>
}
</p>

Create a view fo r St rMe t ho d just as we did with Int Me t ho d, and insert this co de into St rMe t ho d.csht m l:

CODE TO TYPE: StrMetho d.cshtml


@model MvcDesign.Models.BasicModel

@{
ViewBag.Title = "StrMethod";
}

<h2>StrMethod</h2>

<p>
@using (Html.BeginForm())
{
<fieldset>
<legend>StrMethod</legend>
Enter a string: @Html.EditorFor(model => model.StringValue)
<input type="submit" value="Submit" />
</fieldset>
}

@if(Model.StringValue == null)
{
<div>The value of the string was empty.</div>
}
else
{
<div>The string entered was: @Model.StringValue</div>
}
</p>

Let's go o ver the new syntax:


OBSERVE:
@using (Html.BeginForm())
{
<fieldset>
<legend>StrMethod</legend>
Enter a string: @Html.EditorFor(model => model.StringValue)
<input type="submit" value="Submit" />
</fieldset>
}

Ht m l.Be ginFo rm () creates an HTML fo rm using the Ht m l helper o bject.


@ Ht m l.Edit o rFo r is a functio n that takes an o bject as an argument that identifies the value.
Mo de l.St ringValue gives us access to all o f o ur Mo del's members.

No w, edit Ho m e Co nt ro lle r.cs as sho wn:


CODE TO TYPE: Ho meCo ntro ller.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using MvcDesign.Models;

namespace MvcDesign.Controllers
{
public class HomeController : Controller
{
//
// GET: /Home/

public ViewResult Index()


{
return View();
}

//
// GET: /Home/About/
public ActionResult About()
{
View();
}

//
// GET: /Home/Contact/
public string Contact()
{
return "This is the Contact page!";
}

//
// GET: /Home/Google
// Redirect from our application to http://www.google.com
public RedirectResult Google()
{
return Redirect("http://www.google.com");
}

//
// GET: /Home/Contact
// Redirect from our link to the Contact view
public RedirectResult RedirectContact()
{

return Redirect("/Home/Contact/");
}

public ActionResult IntMethod(BasicModel model)


{
model.IntegerValue = Convert.ToInt32(model.IntegerValue) + 10;
return View(model);
}

public ActionResult StrMethod(BasicModel model)


{
return View(model);
}

}
}
We pass an o bject into the IntMetho d and StrMetho d functio ns so we can access o ur Mo del's members directly. No w
we have the IntelliSense capabilities that are o ffered by Visual Studio .

We need to mo dify the /Vie ws/Ho m e /Inde x.csht m l file befo re we can run the applicatio n:

CODE TO TYPE: /Views/Ho me/Index.csthml


@{
ViewBag.Title = "Index";
}

<h2>Index</h2>

<form method="post" action="/Home/IntMethod">


<table>
<tr>
<td>
Input an Integer:
</td>
<td>
<input id="IntegerValue" name="IntegerValue" type="text" value="" />
</td>
</tr>
</table>
<input type="submit" id="IntMethod" value="Return IntMethod" />
</form>

<form method="post" action="/Home/StrMethod">


<table>
<tr>
<td>
Input a string:
</td>
<td>
<input id="StringValue" name="StringValue" type="text" value="" />
</td>
</tr>
</table>
<input type="submit" id="StrMethod" value="String Method" />
</form>

Save and run it.

We do n't see any change to the index page, but when we enter a value fo r IntMetho d o r StrMetho d we see a mino r
mo dificatio n:
In essence, we have been using the Razo r syntax thro ugho ut this entire lesso n. It's a po werful to o l to use with
ASP.NET with C# applicatio ns. It allo ws us to perfo rm tasks just as we wo uld using any o ther web pro gramming
language.

URLs and Routing


We intro duced the Ro ut e Co nf ig.cs file earlier. No w, we'll discuss it in mo re detail so yo u can begin to create custo m
ro utes and co ntro l yo ur URLs.

Basics of Routing
Open /App_St art /Ro ut e Co nf ig.cs and insert this co de:
CODE TO TYPE: /App_Start/Ro uteCo nfig.cs

.
.
.
routes.MapRoute(
name: "About",
url: "Home/About/",
defaults: new { controller = "Home", action = "About" }
);

routes.MapRoute(
name: "Int Method",
url: "Home/IntMethod/{id}",
defaults: new { controller = "Home", action = "IntMethod", id = @"\d+" }
);

routes.MapRoute(
name: "Str Method",
url: "Home/StrMethod/{id}",
defaults: new { controller = "Home", action = "StrMethod", id = "[A-Z][a
-z].+" }
);
.
.
.

id = @ " \d+" specifies that this metho d will take o nly integers and id = " [A-Z ][a-z].+" specifies strings.

The regular expressio ns are no t required, but they co uld be useful if yo u're using the same
Note metho d with different data types.

The abo ve ro utes fo r Int Me t ho d and St rMe t ho d help prevent the ro utes fro m beco ming to o "greedy" and
causing a misuse o f the id values.

Create a view in yo ur /App_St art /Ro ut e Co nf ig.cs fo r each new metho d in Ho m e Co nt ro lle r.cs, and then
add the co de sho wn in the fo llo wing sectio ns.

Routing Constraints
Co nstraints o n ro uting can help to minimize the po ssibility o f erro rs in o ur URLs.

Currently, if we enter the URL to the Abo ut page (localhost:xxxxx/Home/About/) manually, and append a value
at the end, we receive an erro r. This is where we want to use co nstraints.

In /App_St art /Ro ut e Co nf ig.cs, add the Abo ut ro ute as sho wn:

CODE TO TYPE: /App_Start/Ro uteCo nfig.cs fo r Abo ut

.
.
.
routes.MapRoute(
name: "About",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "About", id = UrlParameter.Opt
ional },
constraints: new { id = @"^[0-9]+$" }
);
.
.
.
The co nst raint s setting allo ws us to define ho w we want to handle the po ssibility o f an o ptio nal id value in
the URL. No w we need to mo dify the Abo ut metho d in o ur /Co nt ro lle rs/Ho m e Co nt ro lle r.cs file:

CODE TO TYPE: /Co ntro llers/Ho meCo ntro ller.cs Abo ut metho d
.
.
.
public ActionResult About(int? id)
{
ViewBag.id = id
return View();
}
.
.
.

Here we accept a nullable integer item in o ur Abo ut actio n metho d, and assign it to the ViewBag o bject. Let's
test this new idea and see ho w o ur ro uting co nstraint handles a wo rd.

Let's add a little bit o f lo gic to o ur /Vie ws/Ho m e /Abo ut .csht m l file:
CODE TO TYPE: /Views/Ho me/Abo ut.cshtml
@{
ViewBag.Title = "About";
}

<hgroup class="title">
<h1>@ViewBag.Title.</h1>
<h2>@ViewBag.Message</h2>
</hgroup>

<article>
<p>
Use this area to provide additional information.
</p>

<p>
Use this area to provide additional information.
</p>

<p>
Use this area to provide additional information.
</p>
</article>

<aside>
<h3>Aside Title</h3>
<p>
Use this area to provide additional information.
</p>
<ul>
<li>@Html.ActionLink("Home", "Index", "Home")</li>
<li>@Html.ActionLink("About", "About", "Home")</li>
<li>@Html.ActionLink("Contact", "Contact", "Home")</li>
</ul>
</aside>
<h2>This is the About page!</h2>

@{
if (ViewBag.id != 0)
{
<p>We retrieved a value of: @ViewBag.id</p>
}
else
{
<p>There was nothing in the id value.</p>
}
}

Save and run it. Navigate to the Abo ut page using the previo usly established link. It displays "There
was no thing in the id value." Append an integer value to the URL to see what happens. If yo u use a URL like
lo calho st :xxxxx/Ho m e /Abo ut /12 and enter a string so the URL resembles
localhost:xxxxx/Ho m e /Abo ut /t e st , yo u'll see these results in the images:
save all the files and run the applicatio n using the butto n, and navigate to
lo calho st:xxxxx/Ho me/Abo ut

Let's change the Abo ut ro ute in o ur Ro ut e Co nf ig.cs file to make the ro ute mo re strict:

CODE TO TYPE: Remo ving UrlParameter.Optio nal fro m Ro uteCo nfig.cs

.
.
.

routes.MapRoute(
name: "About",
url: "{Home}/{About}/{id}",
defaults: new { controller = "Home", action = "About", id = UrlParameter.Opt
ional },
constraints: new { id = @"^[0-9]+$" }
);
.
.
.

And no w let's mo dify the Abo ut actio n metho d in the Ho meCo ntro ller.cs file.
CODE TO TYPE: Remo ving nullable int parameter fro m Abo ut()

.
.
.
public ActionResult About(int? id)
{
ViewBag.id = id;
return View();
}
.
.
.

and . Navigate to the Abo ut and enter a string so the URL resembles
localhost:xxxxx/Ho m e /Abo ut /t e st . Yo u'll see this result:

We get an erro r stating what has go ne wro ng with the applicatio n. It's up to the yo u to decide ho w erro rs and
ro uting co nstraints sho uld be handled by the applicatio n.

Yo u're do ing really well so far! Practice this stuff so me mo re in the ho mewo rk then mo ve o n to the next lesso n.

Copyright © 1998-2014 O'Reilly Media, Inc.

This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.
See http://creativecommons.org/licenses/by-sa/3.0/legalcode for more information.
MVC
Lesson Objectives
In this lesso n, yo u will:

use MVC with the Entity Framewo rk.


co mbine mo dels and entities.
add filters when wo rking with entities.

T he Model
Since the Mo del implements the data o f o ur applicatio n, we are go ing to create a new pro ject to mo re effectively
implement o ur Mo del with the default setup given by Visual Studio .

Select File | Ne w | Pro je ct . Select the ASP.NET MVC 4 We b Applicat io n, and name yo ur applicatio n MvcMo de l:

Click OK. In the next dialo g, select Int e rne t Applicat io n:


Click OK. Observe the fo lders that are created by default in o ur new applicatio n:
Our applicatio n creates a Ho m e Co nt ro lle r fo r us, as well as an entire web applicatio n template fo r us to use and
mo dify, which reduces the amo unt o f time we need to invest in a pro ject startup. Mo dify
/Co nt ro lle rs/Ho m e Co nt ro lle r.cs as sho wn:

/Co ntro llers/Ho meCo ntro ller.cs


using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace MvcModel.Controllers
{
public class HomeController : Controller
{
public ActionResult Index()
{
ViewBag.Message = "Modify this template to jump-start your ASP.NET MVC appl
ication.Welcome to the OST Forum!";

return View();
}

public ActionResult About()


{
ViewBag.Message = "Your app description page.";

return View();
}

public ActionResult Contact()


{
ViewBag.Message = "Your contact page.";

return View();
}
}
}

and navigate aro und to get a feeling fo r the pro ject.

Mo dify /Vie ws/Ho m e /Inde x.csht m l file as sho wn:


/Views/Ho me/Index.cshtml

@{
ViewBag.Title = "Home PageOST Forum";
}
@section featured {
<section class="featured">
<div class="content-wrapper">
<hgroup class="title">
<h1>@ViewBag.Title.</h1>
<h2>@ViewBag.Message</h2>
</hgroup>
<p>
To learn more about ASP.NET MVC visit
<a href="http://asp.net/mvc" title="ASP.NET MVC Website">http://asp.net
/mvc</a>.
The page features <mark>videos, tutorials, and samples</mark> to help y
ou get the most from ASP.NET MVC.
If you have any questions about ASP.NET MVC visit
<a href="http://forums.asp.net/1146.aspx/1?MVC" title="ASP.NET MVC Foru
m">our forums</a>.
</p>
</div>
</section>
}
<h3>We suggest the following:</h3>
<ol class="round">
<li class="one">
<h5>Getting Started</h5>
ASP.NET MVC gives you a powerful, patterns-based way to build dynamic websites
that
enables a clean separation of concerns and that gives you full control over mar
kup
for enjoyable, agile development. ASP.NET MVC includes many features that enabl
e
fast, TDD-friendly development for creating sophisticated applications that use
the latest web standards.
<a href="http://go.microsoft.com/fwlink/?LinkId=245151">Learn more</a>
</li>

<li class="two">
<h5>Add NuGet packages and jump-start your coding</h5>
NuGet makes it easy to install and update free libraries and tools.
<a href="http://go.microsoft.com/fwlink/?LinkId=245153">Learn more</a>
</li>

<li class="three">
<h5>Find Web Hosting</h5>
You can easily find a web hosting company that offers the right mix of features
and price for your applications.
<a href="http://go.microsoft.com/fwlink/?LinkId=245157">Learn more</a>
</li>
</ol>

Yo ur updated Index.cshtml lo o ks like this:


OBSERVE: Updated Index.cshtml
@{
ViewBag.Title = "OST Forum";
}
@section featured {
<section class="featured">
<div class="content-wrapper">
<hgroup class="title">
<h1>@ViewBag.Title.</h1>
<h2>@ViewBag.Message</h2>
</hgroup>
<p>
</p>
</div>
</section>
}

@ { Vie wBag.T it le = " Ho m e Page " } assigns the name "Ho me Page" to the T it le o f a Vie wBag o bject.
@ Vie wBag.Me ssage is defined in the Ho m e Co nt ro lle r.cs file, and displays a message to the user o n the
/Ho me/Index page.

T he Entity Framework
Let's discuss the vario us appro aches o f the Entity Framewo rk.

Dat abase First : This appro ach is used when we have an existing database and wo uld like the Entity
Framewo rk to co nstruct a data mo del fo r us. The database schema, mo del data, and mapping between
them, are sto red in an XML file with the extensio n .edmx.
Mo de l First : This appro ach is used when we do n't have a database already and want Visual Studio 's Entity
Framewo rk designer to generate the DDL (Data Definition Language) statements fo r us.
Co de First : This is the appro ach we'll use fo r this lesso n. We use it when we do n't have a database and
want to create o ne. This appro ach uses info rmatio n fro m the Mo del to create the schema o f o ur database.
We will use the DbCo nt e xt class API. This API allo ws us to create, dro p, o r recreate the database if the
mo del changes.

MVC and the Entity Framework


Let's build o ur applicatio n. Open /Vie ws/Share d/_Layo ut .csht m l and add this co de:
CODE TO TYPE: /Views/Shared/_Layo ut.cshtml
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>@ViewBag.Title - My ASP.NET MVC ApplicationOST Forum</title>
<link href="~/favicon.ico" rel="shortcut icon" type="image/x-icon" />
<meta name="viewport" content="width=device-width" />
@Styles.Render("~/Content/css")
@Scripts.Render("~/bundles/modernizr")
</head>
<body>
<header>
<div class="content-wrapper">
<div class="float-left">
<p class="site-title">@Html.ActionLink("your logo hereOST Forum", "
Index", "Home")</p>
</div>
<div class="float-right">
<section id="login">
@Html.Partial("_LoginPartial")
</section>
<nav>
<ul id="menu">
<li>@Html.ActionLink("Home", "Index", "Home")</li>
<li>@Html.ActionLink("About", "About", "Home")</li>
<li>@Html.ActionLink("Forums", "Index", "Forum")</li>
<li>@Html.ActionLink("Posts", "Index", "Posts")</li>
<li>@Html.ActionLink("Contact", "Contact", "Home")</li>
</ul>
</nav>
</div>
</div>
</header>
<div id="body">
@RenderSection("featured", required: false)
<section class="content-wrapper main-content clear-fix">
@RenderBody()
</section>
</div>
<footer>
<div class="content-wrapper">
<div class="float-left">
<p>© @DateTime.Now.Year - My ASP.NET MVC ApplicationOST Forum</p>
</div>
</div>
</footer>

@Scripts.Render("~/bundles/jquery")
@RenderSection("scripts", required: false)
</body>
</html>

There are a co uple o f items to no te here:

OBSERVE:

<title>@ViewBag.Title - OST Forum</title>

@ Vie wBag is used to pass data fro m a co ntro ller to a view; ViewBag is a dynamic o bject that beco mes null
during redirectio n.
T it le assigns the name o f the View o bject o f each co ntro ller metho d.

@ Vie wBag.T it le is wrapped in a <t it le ></t it le > tag, so when we run o ur applicatio n, the title o f the bro wser tab
reflects the name o f the "link" to which we have navigated. In the Inde x.csht m l file, the @ Vie wBag.T it le is set to
" Ho m e Page " , and the titles are set similarly fo r each .cshtml file in the View fo lder.

@ViewBag is a dynamic o bject and can be extended to use any name we decide (fo r example,
Note @ViewBag.Message).

OBSERVE:
<ul id="menu">
<li>@Html.ActionLink("Home", "Index", "Home")</li>
<li>@Html.ActionLink("About", "About", "Home")</li>
<li>@Html.ActionLink("Contact", "Contact", "Home")</li>
</ul>

In @ Ht m l.Act io nLink, @ Ht m l is a helper o bject that builds all the HTML co ntro ls needed fo r the input and
o utput o f data. Act io nLink is a metho d o f the o bject that allo ws us to define the path o f the link we want.

If we want to have an ancho r tag, we can use <a href=@Url.Actio n("{Actio nMetho d}", "{Co ntro ller}">Link
Note Name</a>

Add a few Mo dels by right-clicking the Mo de ls fo lder in the So lutio n Explo rer and selecting Add | . We'll need Po st s,
Use rs, Re plie s, and Fo rum s Mo dels.

Edit /Mo de ls/Po st s.cs as sho wn:


CODE TO TYPE: /Mo dels/Po sts.cs
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Web;

namespace MvcModel.Models
{
public class Posts
{
[Key]
public int PostId { get; set; }
public string postTitle { get; set; }
public string postValue { get; set; }
public string postForum { get; set; }
public DateTime date { get; set; }
}
}

Edit /Mo de ls/Use rs.cs as sho wn:

CODE TO TYPE: /Mo dels/Users.cs


using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Web;

namespace MvcModel.Models
{
public class Users
{
[Key]
public int UserId { get; set; }
public string userName { get; set; }
}
}

Edit /Mo de ls/Re plie s.cs as sho wn:

CODE TO TYPE: /Mo dels/Replies.cs


using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Web;

namespace MvcModel.Models
{
public class Replies
{
[Key]
public int ReplyId { get; set; }
public string reply { get; set; }
public DateTime replyDate { get; set; }
}
}

Edit /Mo de ls/Fo rum s.cs as sho wn:


CODE TO TYPE: /Mo dels/Fo rums.cs
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Web;

namespace MvcModel.Models
{
public class Forums
{
public int id { get; set; }
}
}

Create ano ther new class named /Fo rum Co nt e xt .cs. This class will co ntain the Database info rmatio n fo r o ur
different classes.

Mo dify /Fo rum Co nt e xt .cs as sho wn:

CODE TO TYPE: Fo rumCo ntext.cs


using System;
using System.Collections.Generic;
using System.Data.Entity;
using MvcModel.Models;
using System.Data.Entity.ModelConfiguration.Conventions;
using System.Linq;
using System.Web;

namespace MvcModel.EFData
{
public class ForumContext : DbContext
{
public DbSet<Forums> forums { get; set; }
public DbSet<Replies> reply { get; set; }
public DbSet<Users> users { get; set; }
public DbSet<Posts> posts { get; set; }

protected override void OnModelCreating(DbModelBuilder modelBuilder)


{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
}
}
}

Let's take a clo ser lo o k.

OBSERVE:
public class ForumContext : DbContext
{
public DbSet<Forums> forums { get; set; }
public DbSet<Replies> reply { get; set; }
public DbSet<Users> users { get; set; }
public DbSet<Posts> posts { get; set; }

protected override void OnModelCreating(DbModelBuilder modelBuilder)


{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
}
}
Fo rum Co nt e xt : DbCo nt e xt creates o ur Fo rum Co nt e xt class, but inherits fro m the DbCo nt e xt class, so we can
use the Entity Framewo rk fo r o ur Data Mo dels. DbSe t creates a database fo r o ur Entity Set; ro ws in a database are
generally referred to as an Entity. So in o ur co de, DbSe t < Fo rum s> f o rum s creates an Ent it y Se t fro m the
Fo rum s class that we name Fo rum s.

m o de lBuilde r.Co nve nt io ns.Re m o ve <PluralizingT able Nam e Co nve nt io n>(); prevents o ur tables fro m having
plural names such as Fo rums, Users, and Po sts. Instead they will be named Fo rum, User, and Po st.

No w, we need to add a Co nne ct io n St ring to o ur /We b.co nf ig file:

CODE TO TYPE: /Web.co nfig


.
.
.
<configuration>
<configSections>
<!-- For more information on Entity Framework configuration, visit http://go.micros
oft.com/fwlink/?LinkID=237468 -->
</configSections>
<connectionStrings>
<add name="DefaultConnection" connectionString="Data Source=.\SQLEXPRESS;Initial Ca
talog=aspnet-MvcModel-20130516195933;Integrated Security=SSPI" providerName="System.Dat
a.SqlClient"/>

<add name ="ForumContext"


connectionString="Data Source=.\SQLEXPRESS;Initial Catalog=Forums;AttachDBFile
name=|DataDirectory|\Forums.mdf;Integrated Security=SSPI;User Instance=true"
providerName ="System.Data.SqlClient"/>

</connectionStrings>
.
.
.

The Entity Framewo rk auto matically lo o ks fo r a co nnectio n string named the same as o ur co ntext class.

and then select Build | Build So lut io n to co mpile the pro ject, but do n't run the applicatio n.

No w that we have a fairly decent layo ut o f a fo rum, let's inco rpo rate the main class that inco rpo rates the Entity
Framewo rk functio nality fo r a Data Mo del. Create a new fo lder named EFDat a:
We no w need to po pulate o ur database with default info rmatio n. Create a new class in the /EFDat a fo lder named
Fo rum Init ialize r.cs and mo dify it as sho wn (it's o kay to co py and paste this sectio n, it's pretty repetitive):
CODE TO TYPE: /EFData/Fo rumInitializer.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Data.Entity;
using MvcModel.Models;

namespace MvcModel.EFData
{
public class ForumInitializer : DropCreateDatabaseAlways<ForumContext>
{
protected override void Seed(ForumContext context)
{
var replies = new List<Replies>
{
new Replies { reply = "Neque porro quisquam est qui dolorem ipsum quia do
lor sit amet, consectetur, adipisci velit...", replyDate = DateTime.Parse("2012-09-23")
},
new Replies { reply = "Neque porro quisquam est qui dolorem ipsum quia do
lor sit amet, consectetur, adipisci velit...", replyDate = DateTime.Parse("2001-05-12")
},
new Replies { reply = "Neque porro quisquam est qui dolorem ipsum quia do
lor sit amet, consectetur, adipisci velit...", replyDate = DateTime.Parse("2002-07-11")
},
new Replies { reply = "Neque porro quisquam est qui dolorem ipsum quia do
lor sit amet, consectetur, adipisci velit...", replyDate = DateTime.Parse("2011-07-04")
},
new Replies { reply = "Neque porro quisquam est qui dolorem ipsum quia do
lor sit amet, consectetur, adipisci velit...", replyDate = DateTime.Parse("2000-09-02")
},
new Replies { reply = "Neque porro quisquam est qui dolorem ipsum quia do
lor sit amet, consectetur, adipisci velit...", replyDate = DateTime.Parse("2010-05-14")
}
};
replies.ForEach(s => context.reply.Add(s));
context.SaveChanges();

var users = new List<Users>


{
new Users { userName = "User_a" },
new Users { userName = "User_b" },
new Users { userName = "User_c" },
new Users { userName = "User_d" },
new Users { userName = "User_e" },
new Users { userName = "User_f" },
};
users.ForEach(s => context.users.Add(s));
context.SaveChanges();

var posts = new List<Posts>


{
new Posts { postTitle = "A new Post", postValue = "this is a test post"
, postForum = "Forum_A", date = DateTime.Parse("2010-05-05") },
new Posts { postTitle = "Another post", postValue = "This is another po
st", postForum = "Forum_B", date = DateTime.Parse("2010-12-03") },
new Posts { postTitle = "A Third Post", postValue = "some kind of post
again", postForum = "Forum_C", date = DateTime.Parse("2005-01-04") },
new Posts { postTitle = "Some post", postValue = "This is \"SomePost\""
, postForum = "Forum_A", date = DateTime.Parse("2003-04-04") },
new Posts { postTitle = "the Fifth Post", postValue = "This is a fifth
post", postForum = "Forum_B", date = DateTime.Parse("2002-05-19") },
new Posts { postTitle = "the sixth post", postValue = "This is the sixt
h post!", postForum = "Forum_C", date = DateTime.Parse("2006-08-19") },
new Posts { postTitle = "Posting", postValue = "Yet Another Post", post
Forum = "Forum_A", date = DateTime.Parse("2012-12-12") },
new Posts { postTitle = "Still posting", postValue = "We're coming to a
close on posts", postForum = "Forum_A", date = DateTime.Parse("1999-12-23") },
new Posts { postTitle = "Still Filling", postValue = "Isn't seeding a d
atabase fun?!", postForum = "Forum_C", date = DateTime.Parse("2006-09-09") },
new Posts { postTitle = "Final Post Seed", postValue = "A final seed po
st value!", postForum = "Forum_B", date = DateTime.Parse("2004-08-19") }
};
posts.ForEach(s => context.posts.Add(s));
context.SaveChanges();
}
}
}

Again, and build the so lutio n to make sure we do n't have any erro rs, but do n't run the applicatio n yet—we still
have a co uple mo re files to mo dify. Mo dify /Glo bal.asax | Glo bal.asax.cs as sho wn:

CODE TO TYPE: /Glo bal.asax.cs


using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Http;
using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;
using System.Data.Entity;
using MvcModel.Models;
using MvcModel.EFData;

namespace MvcModel
{
// Note: For instructions on enabling IIS6 or IIS7 classic mode,
// visit http://go.microsoft.com/?LinkId=9394801

public class MvcApplication : System.Web.HttpApplication


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

WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
AuthConfig.RegisterAuth();
Database.SetInitializer<ForumContext>(new ForumInitializer());
}
}
}

We set up the applicatio n so that when it is run, the database is co mpared against o ur mo dels; if the mo dels have
changed since the last run, the database will be dro pped and recreated. The using statements we added allo w the
Glo bal.asax.cs file to access the necessary mo dels, whereas Dat abase .Se t Init ialize r<Fo rum Co nt e xt >(ne w
Fo rum Init ialize r()); allo ws fo r o ur database to be initialized with o ur default info rmatio n.

T ip If we set o ur applicatio n up fo r deplo yment, we wo uld want to remo ve the co de that seeds o ur database.

and . The applicatio n lo o ks like this:


No we'll begin using the Entity Framewo rk with the MVC design pattern. The Entity Framewo rk makes it co nvenient fo r
us to do the stuff we want to do witho ut having to fo cus o n the preliminary co ding.

Create a co ntro ller fo r the Po st s mo del. Right-click the Co nt ro lle rs fo lder and select Add | Co nt ro lle r.... Enter the
Name Po st sCo nt ro lle r; fo r Template, select MVC Co nt ro lle r wit h re ad/writ e act io ns and vie ws, using Ent it y
Fram e wo rk; fo r Mo del class, select Po st s (MvcMo de l.Mo de ls); fo r Data co ntext class, select Fo rum Co nt e xt
(MvcMo de l.EFDat a); and fo r Views, select Razo r (CSHT ML). When yo u're ready, click Add:
OBSERVE: Po stsCo ntro ller.cs snippet
public class PostsController : Controller
{
private ForumContext db = new ForumContext();

//
// GET: /Posts/

public ActionResult Index()


{
return View(db.post.ToList());
}

No tice ho w Visual Studio creates a new variable privat e Fo rum Co nt e xt db = ne w Fo rum Co nt e xt ();. This
variable instantiates a new database o bject. db.Po st s.T o List () retrieves a list o f database items fro m the Po sts
pro perty o f o ur database co ntext instance, and since o ur database co ntext co ntains Entities o f o ur o ther mo dels, Po sts
acts as a parent class.

and and select Po st s to see ho w the applicatio n lo o ks no w.


The fo lder in /Vie ws/Po st s that co ntains Cre at e .csht m l, Edit .csht m l, De le t e .csht m l, and De t ails.csht m l is
scaffolded co de that is generated fo r yo u by default. Use the Cre at e links to add a po st and explo re the o ther o ptio ns
created by the scaffo ld.

Note In a pro ductio n applicatio n, we wo uld want to validate the data in o ur Edit and Cre at e metho ds

Models and Entities


No w that we have an understanding o f the Entity Framewo rk, let's begin the transitio n fro m Mo dels to Entities. We have
an applicatio n that is ready to run, no w let's go o ver the differences between a Mo del and an Entity:

Mo de l: A basic abstractio n o f info rmatio n to separate the business lo gic o f the co ntro ller. It is an o bject that
retrieves and delivers info rmatio n fro m a user that is then manipulated by a co ntro ller.
Ent it y: An entity is basically a mo del, but it represents an o bject o r element o f an o bject that yo u want to
place in a sto rage element (such as a database). It interacts with o ther entities to create a "Data Mo del" that
has a one-to-one, zero-or-one-to-many, o r many-to-many relatio nship.

T he f ive st at e s o f an Ent it y

An Entity has five po ssible states, which is ho w a database co ntext keeps entities in sync with the database:

Adde d: An entity do es no t yet exist in a database. A metho d must issue an INSERT statement to the
database. Ours uses SaveChanges.
Unchange d: The default when an entity is read fro m the database and no thing needs to be do ne.
Mo dif ie d: So me o r all o f the entities' pro perties have changed and SaveChanges must issue an UPDATE
statement to the database.
De le t e d: An entity has been marked fo r deletio n and SaveChanges issues a DELETE co mmand to the
database.
De t ache d: An entity is being tracked by the database co ntext (Fo rumCo ntext in this lesso n).

Let's remo ve unnecessary clutter in o ur pro ject. Delete these items:

/App_Start/Aut hCo nf ig.cs


/Co ntro llers/Acco unt Co nt ro lle r.cs
/Filters/Init ialize Sim ple Me m be rshipAt t ribut e .cs
/Mo dels/Acco unt Mo de ls.cs
/Views/Acco unt (fo lder)
/Views/Shared/_Lo ginPart ial.csht m l

Remo ve the reference to _Lo ginPartial fro m Layo ut .csht m l as sho wn:
CODE TO TYPE: /Views/Shared/_Layo ut.cshtml
.
.
.
<div class="float-left">
<p class="site-title">@Html.ActionLink("OST Forum", "Index", "Home"
)</p>
</div>
<div class="float-right">
<section id="login">
@Html.Partial("_LoginPartial")
</section>
<nav>
<ul id="menu">
<li>@Html.ActionLink("Home", "Index", "Home")</li>
<li>@Html.ActionLink("About", "About", "Home")</li>
<li>@Html.ActionLink("Forums", "Index", "Forum")</li>
<li>@Html.ActionLink("Posts", "Index", "Posts")</li>
<li>@Html.ActionLink("Contact", "Contact", "Home")</li>
</ul>
</nav>
</div>
.
.
.

Mo dify /Mo de ls/Po st s.cs as sho wn:

CODE TO TYPE: /Mo dels/Po sts.cs

.
.
.
[Key]
public int PostId { get; set; }

public int? ReplyId { get; set; }

[Required]
[Display(Name = "Title")]
[StringLength(100, ErrorMessage = "{0} must be a minimum of {2} and a maximum of {1} ch
aracters.", MinimumLength = 3)]
public string postTitle { get; set; }

[Required]
[Display(Name = "Post")]
[StringLength(1200, ErrorMessage = "Post must be less than {1} characters.")]
public string postValue { get; set; }

[Required]
[Display(Name = "Forum")]
public string postForum { get; set; }

[Display(Name = "Date")]
[DisplayFormat(DataFormatString = "{0:d}", ApplyFormatInEditMode = true)]
public DateTime date { get; set; }

public virtual Replies replies { get; set; }


public virtual Forums forum { get; set; }
public virtual ICollection<Forums> forums { get; set; }
.
.
.

The [Re quire d] attribute makes an Entity pro perty a required item.
St ringLe ngt h(10 0 , Erro rMe ssage = " {0 } m ust be a m inim um o f {2} and a m axim um o f {1} charact e rs." ,
Minim um Le ngt h = 3) sets the maximum string length that can be entered. Erro rMe ssage is the message to display
if the length exceeds o r do es no t meet the standards. Minim um Le ngt h is the sho rtest length a string can be.

No w, make the necessary changes to /Mo de ls/Fo rum s.cs:

CODE TO TYPE: /Mo dels/Fo rums.cs

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace MvcModel.Models
{
public class Forum
{
[Key]
public int idForumId { get; set; }

public int? PostId { get; set; }

[Display(Name = "Forum")]
public string ForumName { get; set; }

public virtual ICollection<Posts> posts { get; set; }


public virtual ICollection<Users> users { get; set; }
}
}

We need to add so me attributes to the Replies entity in /Mo de ls/Re plie s:

/Mo dels/Replies
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace MvcModel.Models
{
public class Replies
{
[Key]
public int ReplyId { get; set; }

[Required(ErrorMessage = "Can not have an empty reply.")]


[MaxLength(500, ErrorMessage = "Reply must be less then {1} characters.")]
public string reply { get; set; }

[DisplayFormat(DataFormatString = "{0:d}", ApplyFormatInEditMode=true)]


public DateTime replyDate { get; set; }
}
}

And we no w need to add so me data anno tatio ns to o ur Users entity:


CODE TO TYPE: Adding data anno tatio ns to /Mo dels/Users.cs

[Key]
public int UserId { get; set; }

public int? PostId { get; set; }

[Display(Name = "User")]
public string userName { get; set; }

public virtual Posts posts { get; set; }


public virtual ICollection<Forums> forum { get; set; }
public virtual ICollection<Replies> reply { get; set; }

Let's add a bit o f erro r checking to make o ur applicatio n just a bit mo re ro bust. Mo dify
/Co nt ro lle rs/Po st sCo nt ro lle r.cs as sho wn:

CODE TO TYPE: Po stsCo ntro ller.cs

using System.Web.Routing;
.
.
.
[HttpPost]
public ActionResult Create(Posts posts)
{
try
{
if (ModelState.IsValid)
{
db.posts.Add(posts);
db.SaveChanges();
return RedirectToAction("Index");
}
}
catch (DataException err)
{
ModelState.AddModelError("Error in creating a new post. If the problem persists
contact Customer Support.", "Error " + err);
}

return View(posts);
}

Let's try and create a new po st; if it fails we want to lo g the erro r, let the user kno w abo ut the erro r and what to do abo ut
it. We also attach info rmatio n abo ut the exceptio n with the Dat aExce pt io n e rr variable.

In /Co nt ro lle rs/Po st sCo nt ro lle r.cs, edit the Delete Ge t metho d as sho wn:

CODE TO TYPE: /Co ntro llers/Po stsCo ntro ller.cs HttpGet Delete()
//
// GET: /Posts/Delete/5

public ActionResult Delete(bool? saveDeleteError, int? id = 0)


{
Posts posts = db.posts.Find(id);
if (posts == null!saveDeleteError.GetValueOrDefault())
{
ViewBag.Error = "An error has occurred. We are working hard to resolve it. Than
k you.";
return HttpNotFound();
return View();
}
return View(posts db.posts.Find(id));
}
Finally, let's increase the efficiency o f o ur De le t e Co nf irm e d functio n:

CODE TO TYPE: /Co ntro llers/Po stsCo ntro ller.cs


//
// POST: /Posts/Delete/5

[HttpPost, ActionName("Delete")]
public ActionResult DeleteConfirmed(int id)
{
Posts post = db.post.Find(id);
db.posts.Remove(post);
db.SaveChanges();
try
{
var delete = new Posts() { PostId = id };
db.Entry(delete).State = EntityState.Deleted;
db.SaveChanges();
}
catch (DataException err)
{
return RedirectToAction("Delete", new RouteValueDictionary{ { "id", id }, { "sa
veChangesError", true} });
}

return RedirectToAction("Index");
}

var de le t e = ne w Po st s() { Po st Id = id } ; instantiates a new Po sts() o bject with o nly the ro w we are referencing,
using the primary key Po stId.

db.Ent ry(de le t e ).St at e = Ent it ySt at e .De le t e d; uses the Entity Framewo rk to set the state o f the entity to deleted.
This is all the Entity Framewo rk needs in o rder to delete an entity.

Re dire ct T o Act io n(" De le t e " , ne w Ro ut e Value Dict io nary{ { " id" , id } , { " save Change sErro r" , t rue } } )
catches an erro r with the delete and redirects to the Delete page, displaying the item that the user attempted to delete.

Mo dify /Vie ws/Po st s/De le t e .csht m l as sho wn:

CODE TO TYPE: Delete.cshtml

@model MvcModel.Models.Posts

@{
ViewBag.Title = "Delete";
}

<h2>Delete</h2>
<p class="error">@ViewBag.Error</p>
<h3>Are you sure you want to delete this?</h3>
<fieldset>
<legend>Posts</legend>
.
.
.

As yo u may have no ticed, o ur fo rum is no t to o fo rum-like. It sho ws no users and has no links. Let's fix that. Mo dify
/EFDat a/Fo rum Init ialize r.cs as sho wn (co py and paste this co de, if yo u like):
CODE TO TYPE: /EFData/Fo rumInitializer.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Data.Entity;
using MvcModel.Models;

namespace MvcModel.EFData
{
public class ForumInitializer : DropCreateDatabaseIfModelChanges<ForumContext>
{
protected override void Seed(ForumContext context)
{
var replies = new List<Replies>
{
new Replies { reply = "Neque porro quisquam est qui dolorem ipsum quia
dolor sit amet, consectetur, adipisci velit...", replyDate = DateTime.Parse("2012-09-23
") },
new Replies { reply = "Neque porro quisquam est qui dolorem ipsum quia
dolor sit amet, consectetur, adipisci velit...", replyDate = DateTime.Parse("2001-05-12
") },
new Replies { reply = "Neque porro quisquam est qui dolorem ipsum quia
dolor sit amet, consectetur, adipisci velit...", replyDate = DateTime.Parse("2002-07-11
") },
new Replies { reply = "Neque porro quisquam est qui dolorem ipsum quia
dolor sit amet, consectetur, adipisci velit...", replyDate = DateTime.Parse("2011-07-04
") },
new Replies { reply = "Neque porro quisquam est qui dolorem ipsum quia
dolor sit amet, consectetur, adipisci velit...", replyDate = DateTime.Parse("2000-09-02
") },
new Replies { reply = "Neque porro quisquam est qui dolorem ipsum quia
dolor sit amet, consectetur, adipisci velit...", replyDate = DateTime.Parse("2010-05-14
") },
new Replies { reply = "Neque porro quisquam est qui dolorem ipsum quia
dolor sit amet, consectetur, adipisci velit...", replyDate = DateTime.Parse("2002-07-11
") },
new Replies { reply = "Neque porro quisquam est qui dolorem ipsum quia
dolor sit amet, consectetur, adipisci velit...", replyDate = DateTime.Parse("2001-12-04
") },
new Replies { reply = "Neque porro quisquam est qui dolorem ipsum quia
dolor sit amet, consectetur, adipisci velit...", replyDate = DateTime.Parse("1990-09-02
") },
new Replies { reply = "Neque porro quisquam est qui dolorem ipsum quia
dolor sit amet, consectetur, adipisci velit...", replyDate = DateTime.Parse("1992-07-11
") }
};
replies.ForEach(s => context.reply.Add(s));
context.SaveChanges();

var users = new List<Users>


{
new Users { UserId = 0, userName = "User_z", reply = new List<Replies>(
) },
new Users { UserId = 1, userName = "User_a", reply = new List<Replies>(
) },
new Users { UserId = 2, userName = "User_b", reply = new List<Replies>(
) },
new Users { UserId = 3, userName = "User_c", reply = new List<Replies>(
) },
new Users { UserId = 4, userName = "User_d", reply = new List<Replies>(
) },
new Users { UserId = 5, userName = "User_e", reply = new List<Replies>(
) },
new Users { UserId = 6, userName = "User_f", reply = new List<Replies>(
) },
};
users.ForEach(s => context.user.Add(s));
context.SaveChanges();

var posts = new List<Posts>


{
new Posts { postTitle = "A new Post", postValue = "this is a test post"
, postForum = "Forum_A", date = DateTime.Parse("2010-05-05") },
new Posts { postTitle = "Another post", postValue = "This is another po
st", postForum = "Forum_B", date = DateTime.Parse("2010-12-03") },
new Posts { postTitle = "A Third Post", postValue = "some kind of post
again", postForum = "Forum_C", date = DateTime.Parse("2005-01-04") },
new Posts { postTitle = "Some post", postValue = "This is \"SomePost\""
, postForum = "Forum_A", date = DateTime.Parse("2003-04-04") },
new Posts { postTitle = "the Fifth Post", postValue = "This is a fifth
post", postForum = "Forum_B", date = DateTime.Parse("2002-05-19") },
new Posts { postTitle = "the sixth post", postValue = "This is the sixt
h post!", postForum = "Forum_C", date = DateTime.Parse("2006-08-19") },
new Posts { postTitle = "Posting", postValue = "Yet Another Post", post
Forum = "Forum_A", date = DateTime.Parse("2012-12-12") },
new Posts { postTitle = "Still posting", postValue = "We're coming to a
close on posts", postForum = "Forum_A", date = DateTime.Parse("1999-12-23") },
new Posts { postTitle = "Still Filling", postValue = "Isn't seeding a d
atabase fun?!", postForum = "Forum_C", date = DateTime.Parse("2006-09-09") },
new Posts { postTitle = "Final Post Seed", postValue = "A final seed po
st value!", postForum = "Forum_B", date = DateTime.Parse("2004-08-19") }
};
posts.ForEach(s => context.posts.Add(s));
context.SaveChanges();

var forum = new List<Forums>


{
new Forums { ForumName = "Forum_A" , posts = new List<Posts>()},
new Forums { ForumName = "Forum_B" , posts = new List<Posts>()},
new Forums { ForumName = "Forum_C" , posts = new List<Posts>()}
};
forum.ForEach(s => context.forums.Add(s));
context.SaveChanges();

forum[0].posts.Add(posts[0]);
forum[0].posts.Add(posts[3]);
forum[0].posts.Add(posts[6]);
forum[0].posts.Add(posts[7]);
forum[1].posts.Add(posts[1]);
forum[1].posts.Add(posts[4]);
forum[1].posts.Add(posts[9]);
forum[2].posts.Add(posts[2]);
forum[2].posts.Add(posts[5]);
forum[2].posts.Add(posts[8]);
}
}
}

Here, we just seeded so me mo re values and assigned Po st s to Fo rum s. No w, make the necessary mo dificatio ns to
/Fo rum Co nt e xt .cs:
CODE TO TYPE: /Fo rumCo ntext.cs
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using MvcModel.Models;
using System.Data.Entity.ModelConfiguration.Conventions;

namespace MvcModel.EFData
{
public class ForumContext : DbContext
{
public DbSet<Forums> forum { get; set; }
public DbSet<Users> user { get; set; }
public DbSet<Posts> post { get; set; }
public DbSet<Replies> reply { get; set; }

protected override void OnModelCreating(DbModelBuilder modelBuilder)


{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();

/* (One -> zero) or (one -> many) relationship */


modelBuilder.Entity<Users>().HasOptional(u => u.posts);

/* Many to Many relationship */


modelBuilder.Entity<Forums>().HasMany(m => m.posts).WithMany(p => p.forums)
.Map(k => k.MapLeftKey("ForumId")
.MapRightKey("PostId")
.ToTable("ForumTitlePost"));

modelBuilder.Entity<Posts>().HasOptional(r => r.replies);


}
}
}

Let's discuss these new Entity framewo rk o bjects:

OBSERVE:

modelBuilder.Entity<Users>().HasOptional(u => u.posts);

/* Many to Many relationship */


modelBuilder.Entity<Forums>().HasMany(m => m.posts).WithMany(p => p.forums
)
.Map(k => k.MapLeftKey("ForumId")
.MapRightKey("PostId")
.ToTable("ForumTitlePost"));

m o de lBuilde r.Ent it y creates a relatio nship between Entities. .HasOpt io nal creates a o ne-to -zero o r o ne-to -many
relatio nship between two entities. Fo r example, a user may have zero o r many po sts. .HasMany | .Wit hMany creates
a many-to -many relatio nship. It can be read like this: A fo rum entity HasMany po sts Wit hMany fo rums. Map |
MapLe f t Ke y | MapRight Ke y creates a mapping between two entities. The MapLe f t Ke y is an ID o f o ne entity and
MapRight Ke y is an ID o f the o ther entity. T o T able creates a table fro m the mapping that we just discussed.

T ip This is kno wn as the Entity Framewo rk Fluent API. Fo r additio nal reso urces, see Fluent API.

and . Add /Po st s/De le t e /10 0 to the URL. When asked if yo u want to delete it, click De le t e . Yo ur web page
displays an erro r message because item 10 0 do esn't exist in the database:
What if we want to display o nly Fo rum_A o r we want to find a certain po st? We can do this with Searching. Mo dify
/Co nt ro lle rs/Po st sCo nt ro lle r.cs as sho wn:

CODE TO TYPE: /Co ntro llers/Po stsCo ntro ller.cs

public ActionResult Index(string SortValue)


{
ViewBag.ForumSort = String.IsNullOrEmpty(SortValue) ? "Forum" : "";
ViewBag.DateSort = SortValue == "Date" ? "Date desc" : "Date";

var post = from s in db.posts select s;


switch (SortValue)
{
case "Forum":
post = post.OrderByDescending(s => s.postForum);
break;
case "Date":
post = post.OrderBy(s => s.date);
break;
case "Date desc":
post = post.OrderBy(s => s.date);
break;
default:
post = post.OrderByDescending(s => s.postForum);
break;
}
return View(db.post.ToList());
}

The fo llo wing co de accepts a string value in the Index metho d and po pulates the appro priate ViewBag value, then
creates a co llectio n o bject that results fro m a single query:

OBSERVE:
public ActionResult Index(string SortValue)
{
ViewBag.ForumSort = String.IsNullOrEmpty(SortValue) ? "Forum" : "";
ViewBag.DateSort = SortValue == "Date" ? "Date desc" : "Date";

var post = from s in db.post select s;


Vie wBag.Fo rum So rt = St ring.IsNullOrEm pt y(So rt Value ) ? " Fo rum " : " " ; is a ternary o perato r that sets the
value o f Vie wBag.Fo rum So rt with either Forum o r an empty string. This ternary o perato r has the same functio nality
as this if-else co nstruct:

OBSERVE: If-Else and Ternary


if (!String.IsNullOrEmpty(SortValue))
ViewBag.ForumSort = "Forum";
else
ViewBag.ForumSort = "";

Ternary o perato rs can clean up yo ur co de, but sho uld no t be used excessively; they can be difficult to
Note read because lo ts o f co de can be crammed into a single line.

var po st = f ro m s in db.po st se le ct s; uses LINQ to Entities to specify a co lumn by which to so rt. It creates an
IQueryable variable po st at first. After the switch statement, the IQueryable o bject beco mes a co llectio n o nly after
calling a metho d like ToList(). This metho d results in a single query o f the database.

No w mo dify /Vie ws/Po st s/Inde x.csht m l as sho wn:

CODE TO TYPE: Index.cshtml (Po sts)


<tr>
<th>
@Html.DisplayNameFor(model => model.postTitle)
</th>
<th>
@Html.DisplayNameFor(model => model.postValue)
</th>
<th>
@Html.ActionLink("Forum", "Index", new { SortValue = ViewBag.ForumSort })
@Html.DisplayNameFor(model => model.postForum)
</th>
<th>
@Html.ActionLink("Date", "Index", new { SortValue = ViewBag.DateSort })
@Html.DisplayNameFor(model => model.date)
</th>

and . Click o n either the Fo rum o r Dat e heading to so rt the po sts by the value (the Fo rum so rt is descending).
Yo u can use the Actio nLink with metho ds like ViewBag.Fo rumSo rt o r ViewBag.DateSo rt fo r so rting by
Note any co lumn o f yo ur data. It's a handy way to so rt things, and saves yo u fro m having to write a lo t o f co de.

No w, let's add a search bar so we can search o ur fo rum fo r specific elements. Edit /Vie ws/Po st s/Inde x.csht m l as
sho wn:

CODE TO TYPE: /Views/Po sts/Index.cshtml


<h2>Index</h2>

<p>
@Html.ActionLink("Create New", "Create")
</p>
<table>
@using (Html.BeginForm())
{
<p>
<b>Search Forum:</b> @Html.TextBox("SearchQuery", null, new { style = "width: 2
00px; height: 10px; font-size: 12px;" })
<input type = "submit" value = "Search" />
</p>
}

We've created an HTML Fo rm that co ntains a bo x fo r search queries. Let's discuss the TextBo x pro perties:

OBSERVE:

<b>Search Forum:</b> @Html.TextBox("SearchQuery", null, new { style = "width: 200px


; height: 10px; font-size: 12px;" })

T e xt Bo x creates a text bo x where o ur user can input queries. We sto re different Object attributes we may want to use
in null. Ours has no ne, so it's null. (We'd give a different specific name if we wanted to use particular o bject attributes.)
ne w { st yle = " widt h: 20 0 px; he ight : 14 px; f o nt -size : 12px;" } defines a new inline CSS style element.

No w, let's add the necessary LINQ query fo r o ur search bo x. Mo dify the Index metho d in
/Co nt ro lle rs/Po st sCo nt ro lle r.cs as sho wn:
CODE TO TYPE: Po stsCo ntro ller.cs

public ActionResult Index(string SortValue, string SearchQuery)


{
ViewBag.ForumSort = String.IsNullOrEmpty(SortValue) ? "Forum" : "";
ViewBag.DateSort = SortValue == "Date" ? "Date desc" : "Date";

var post = from s in db.posts select s;

if (!String.IsNullOrEmpty(SearchQuery))
{
post = post.Where(s => s.postTitle.ToUpper().Contains(SearchQuery.ToUpper()) ||
s.postValue.ToUpper().Contains(SearchQuery.ToUpper()) ||
s.postForum.ToUpper().Contains(SearchQuery.ToUpper()));
}
switch (SortValue)
{
case "Forum":
post = post.OrderByDescending(s => s.postForum);
break;
case "Date":
post = post.OrderBy(s => s.date);
break;
case "Date desc":
post = post.OrderBy(s => s.date);
break;
default:
post = post.OrderByDescending(s => s.postForum);
break;
}
return View(post.ToList());
}

Let's discuss o ur new LINQ query.

OBSERVE:

post = post.Where(s => s.postTitle.ToUpper().Contains(SearchQuery.ToUpper()) ||

Whe re selects o nly items fro m the database that return t rue fro m the query. T o Uppe r() causes a string element to
be tempo rarily upper case. Co nt ains returns t rue if the po st T it le , po st Value , o r po st Fo rum co ntains an element
o f the search query.

No w we are able to search o ur fo rum:

and , then go to the Po sts page and enter T e st .


Filters
Filters are custom attributes that pro vide po st/pre-actio n behavio r o n co ntro ller actio n metho ds. Filters can execute
their lo gic either before o r after an actio n metho d.

While we can create custo m filters, ASP.NET MVC 4 has fo ur default filters that we can use "o ut o f the bo x":

Aut ho rizat io n: Makes security decisio ns based o n authenticatio n o r pro perty validatio n.
Act io n: Wraps the actio n metho d executio n. It can pro vide extra data to metho d, inspect return value, o r
cancel executio n o f an actio n metho d.
Re sult : Wraps executio n o f ActionResult. It can also perfo rm additio nal pro cessing o f a result, o r mo dify
HTTP respo nses.
Exce pt io n: Executes if an unhandled exceptio n erro r o ccurs in the actio n metho d. It pro vides a hierarchy
fo r handling exceptio ns. It begins at the to p with Authorization and cascades do wn to executio n o f the result.
These are useful fo r displaying erro r pages and lo gging.

Create a Fo rumLo g mo del. Right-click the Mo de ls fo lder and add a new class named Fo rum Lo g:
Mo dify /Mo de ls/Fo rum Lo g.cs as sho wn:
CODE TO TYPE: /Mo dels/Fo rumLo g.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace MvcModel.Models
{
public class ForumLog
{
[Key]
[Display(Name = "Log ID")]
public int logID { get; set; }

[Display(Name = "Controller")]
public string Controller { get; set; }

[Display(Name = "Action")]
public string Action { get; set; }

[Display(Name = "Header")]
public string BrowserHeader { get; set; }

[Display(Name = "Browser Type")]


public string BrowserType { get; set; }

[Display(Name = "IP Address")]


public string IP { get; set; }

[Display(Name = "Access Date")]


public DateTime Date { get; set; }
}
}

Separate the fo rum database fro m the fo rum lo g database. Create a Fo rum Lo gCo nt e xt .cs class file in the EFDat a
fo lder:
No w add so me co de to it so we can create the database.

We wo n't create a seed metho d because the table, o r "fo rum lo g," receives data o nly after a fo rum user
Note visits the site.

CODE TO TYPE: /EFData/Fo rumLo gCo ntext.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Data.Entity;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using MvcModel.Models;
using System.Data.Entity.ModelConfiguration.Conventions;

namespace MvcModel.EFData
{
public class ForumLogContext : DbContext
{
public DbSet<ForumLog> forumLog { get; set; }

protected override void OnModelCreating(DbModelBuilder modelBuilder)


{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
}
}
}

We no w need to add a new co nnectio n string to /We b.co nf ig so we can save o ur results.
CODE TO TYPE: /Web.co nfig

<connectionStrings>

<add name ="ForumContext"


connectionString="Data Source=.\SQLEXPRESS;Initial Catalog=Forum;Integrated Sec
urity=SSPI;AttachDBFilename=|DataDirectory|\Forums.mdf;User Instance=true"
providerName ="System.Data.SqlClient"/>

<add name ="ForumLogContext"


connectionString="data source=.\SQLEXPRESS;Initial Catalog=ForumLog;Integrated
Security=SSPI;AttachDBFilename=|DataDirectory|\ForumLog.mdf;User Instance=true"
providerName ="System.Data.SqlClient"/>

</connectionStrings>

Add a custo m filter class. Right-click the Filt e rs fo lder and add a new class named Fo rum Filt e r.cs:

Add thisco de to o ur custo m filter:


CODE TO TYPE: /Filters/Fo rumFilter.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Data.Entity;
using System.Web.Mvc;
using MvcModel.Models;
using MvcModel.EFData;

namespace MvcModel.Filters
{
public class ForumFilter : ActionFilterAttribute, IActionFilter
{
private ForumLogContext logDB = new ForumLogContext();

void IActionFilter.OnActionExecuting(ActionExecutingContext actionContext)


{
ForumLog log = new ForumLog()
{
Controller = actionContext.ActionDescriptor.ControllerDescriptor.Contro
llerName,
Action = actionContext.ActionDescriptor.ActionName + "(ForumFilter)",
BrowserHeader = actionContext.HttpContext.Request.UserAgent,
BrowserType = actionContext.HttpContext.Request.Browser.Browser,
IP = actionContext.HttpContext.Request.UserHostAddress,
Date = actionContext.HttpContext.Timestamp
};

logDB.forumLog.Add(log);
logDB.SaveChanges();

this.OnActionExecuting(actionContext);
}
}
}

Let's discuss so me o f this co de.

/Filters/Fo rumFilter.cs

public class ForumFilter : ActionFilterAttribute, IActionFilter


{
private ForumLogContext logDB = new ForumLogContext();

void IActionFilter.OnActionExecuting(ActionExecutingContext actionContext)


{
ForumLog log = new ForumLog()
{
...
};

logDB.forumLog.Add(log);
logDB.SaveChanges();

this.OnActionExecuting(actionContext);
}
}

Fo rum Filt e r : Act io nFilt e rAt t ribut e , IAct io nFilt e r: inherits fro m Act io nFilt e rAt t ribut e , and
implement the IAct io nFilt e r interface.
Fo rum Lo gCo nt e xt lo gDB = ne w Fo rum Lo gCo nt e xt ();: gets access to o ur fo rum lo g database.
vo id IAct io nFilt e r.OnAct io nExe cut ing: makes Fo rumFilter o verride the OnAct io nExe cut ing metho d.
Fo rum Lo g lo g = ne w Fo rum Lo g(): instantiates a new Fo rumLo g entity with the info rmatio n gathered
Fo rum Lo g lo g = ne w Fo rum Lo g(): instantiates a new Fo rumLo g entity with the info rmatio n gathered
fro m the actio n.
t his.OnAct io nExe cut ing(act io nCo nt e xt );: OnAct io nExe cut ing uses the Entity Framewo rk to create
and po pulate an entity instance o f actionContext.

Add a line to /Glo bal.asax.cs in the Application_Start metho d so we can initialize the database:

CODE TO TYPE: /Glo bal.asax.cs

protected void Application_Start()


{
AreaRegistration.RegisterAllAreas();

WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
Database.SetInitializer<ForumContext>(new ForumInitializer());
Database.SetInitializer<ForumLogContext>(new DropCreateDatabaseIfModelChanges<Forum
LogContext>());
}

and Build (F6 ) the applicatio n, but do n't run it yet.

No w instead o f sending users to Fo rum s/Po st s/, we'll create an actual fo rum-like o ptio n using a Fo rum co ntro ller.
Right-click the Co nt ro lle rs fo lder and select Add | Co nt ro lle r. Name it Fo rum Co nt ro lle r.cs, set the Mo del class
to Fo rum s (MvcMo de l.Mo de ls) and set the Data co ntext class set to Fo rum Co nt e xt (MvcMo de l.EFDat a).

Our So lutio n Explo rer lo o ks like this:


Add the custo m filter to the metho ds we want to use it o n. Mo dify /Co nt ro lle rs/Fo rum Co nt ro lle r.cs as sho wn:
CODE TO TYPE: /Co ntro llers/Fo rumCo ntro ller.cs snippet

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using MvcModel.Models;
using MvcModel.EFData;
using MvcModel.Filters;

namespace MvcModel.Controllers
{
[ForumFilter]
public class ForumController : Controller
{
private ForumContext db = new ForumContext();

//
// GET: /Forum/

We added o ur custo m filter to all the actio ns in the Fo rumCo ntro ller. When we access any o f the actio ns that use the
fo rum co ntro ller, o ur custo m filter will be invo ked.

No w we need a way to see the info rmatio n we sto re in the Fo rumLo g database. Fo r this, we need a co ntro ller and a
view. Add a co ntro ller to the Co nt ro lle rs fo lder named Fo rumLo gCo ntro ller.cs:

Open _Layo ut .csht m l and create a link to o ur lo g:


CODE TO TYPE: _Layo ut.cshtml

<ul id="menu">
<li>@Html.ActionLink("Home", "Index", "Home")</li>
<li>@Html.ActionLink("About", "About", "Home")</li>
<li>@Html.ActionLink("Forums", "Index", "Forum")</li>
<li>@Html.ActionLink("Posts", "Index", "Posts")</li>
<li>@Html.ActionLink("Forum Log", "Index", "ForumLog")</li>
<li>@Html.ActionLink("Contact", "Contact", "Home")</li>
</ul>

T ip We can also inject the custo m filter into individual metho ds o f co ntro ller classes.

Mo dify /Vie ws/Fo rum Lo g/Inde x.csht m l as sho wn:

CODE TO TYPE:
@model IEnumerable<MvcModel.Models.ForumLog>

@{
ViewBag.Title = "IndexForum Log";
}

<h2>IndexForum Log</h2>
.
.
.

and . Go to the Fo rum Lo g page and create a fo rum lo g entry; the Forum Log lo o ks like this.

No w we want to add multiple filters into a co ntro ller:

Add a Po st Lo g.cs class in the Models fo lder:


Mo dify /Mo de ls/Po st Lo g.cs as sho wn:
CODE TO TYPE: Po stLo g.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace MvcModel.Models
{
public class PostLog
{
[Key]
public int postLogID { get; set; }

[Display(Name = "Controller")]
public string Controller { get; set; }

[Display(Name = "Action")]
public string Action { get; set; }

[Display(Name = "Browser")]
public string BrowserType { get; set; }

[Display(Name = "Browser Header")]


public string BrowserHeader { get; set; }

[Display(Name = "IP Address")]


public string IP { get; set; }

[Display(Name = "Access Date")]


public DateTime Date { get; set; }
}
}

Create a new class in the Filt e rs fo lder named Po st Filt e r.cs:


This filter is identical to the Fo rumFilter. We're using it here to sho w ho w to use two different filters o n the same class:
CODE TO TYPE: /Filters/Po stFilter.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

using System.Data.Entity;
using System.Web.Mvc;
using MvcModel.Models;
using MvcModel.EFData;

namespace MvcModel.Filters
{
public class PostFilter : ActionFilterAttribute, IActionFilter
{
private ForumLogContext logDB_2 = new ForumLogContext();

void IActionFilter.OnActionExecuting(ActionExecutingContext actionContext)


{
ForumLog log = new ForumLog()
{
Controller = actionContext.ActionDescriptor.ControllerDescriptor.Contro
llerName,
Action = actionContext.ActionDescriptor.ActionName + "(PostFilter)",
BrowserHeader = actionContext.HttpContext.Request.UserAgent,
BrowserType = actionContext.HttpContext.Request.Browser.Browser,
IP = actionContext.HttpContext.Request.UserHostAddress,
Date = actionContext.HttpContext.Timestamp
};
logDB_2.forumLog.Add(log);
logDB_2.SaveChanges();

this.OnActionExecuting(actionContext);
}
}
}

Note We are using the same database, just adding a new table to it.

No w o pen Fo rum Lo gCo nt e xt .cs in the EFData fo lder and add the Po stLo g filter to it:

CODE TO TYPE: /EFData/Fo rumLo gCo ntext.cs

public class ForumLogContext : DbContext


{
public DbSet<ForumLog> forumLog { get; set; }
public DbSet<PostLog> postLog { get; set; }

protected override void OnModelCreating(DbModelBuilder modelBuilder)


{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
}
}

Add the filter to the class. Mo dify /Co nt ro lle rs/Fo rum Co nt ro lle r.cs as sho wn:
CODE TO TYPE: /Co ntro llers/Fo rumCo ntro ller.cs
namespace MvcModel.Controllers
{
[PostFilter(Order = 1)]
[ForumFilter(Order = 2)]
public class ForumController : Controller
{
private ForumContext db = new ForumContext();

//
// GET: /Forum/

public ActionResult Index()


{
return View(db.forum.ToList());
}

We added Orde r to the filters to establish a hierarchy fo r ho w the filters will be applied.

and .

No tice the o rder in which the filters are used. First Po st Filt e r is accessed and then Fo rum Filt e r is accessed. We
can use these metho ds to establish a hierarchy o f filters that have varied levels o f authenticatio n o r validatio n.

T ip Yo u can find lo ts o f go o d info rmatio n abo ut filtering at MVC Filters.

Phew! That was a fairly lengthy lesso n. Go o d jo b sticking with it. Practice what yo u've learned here in the ho mewo rk to make
sure it sticks with yo u!
Copyright © 1998-2014 O'Reilly Media, Inc.

This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.
See http://creativecommons.org/licenses/by-sa/3.0/legalcode for more information.
Unit Testing
Lesson Objectives
In this lesso n, yo u will:

add test a test pro ject to yo ur ASP.NET MVC pro jects.


co nduct testing as part o f the so ftware develo pment pro cess.
use unit testing, as well as integrated testing.
emplo y an auto mated testing pro cess.
test to validate so ftware.

T esting with Visual Studio


When yo u auto mate the so ftware testing pro cess, yo u can identify erro rs in yo ur so ftware co de quickly, and enfo rce its
o riginal functio nal specificatio ns and requirements. There are many different testing strategies:

Having a user run the so ftware and test fo r erro rs.


Utilize so me type o f testing framewo rk o r so ftware.
Integrate testing functio ns within the same pro ject (with o r witho ut a testing framewo rk).
Create a separate testing pro ject that "exercises" all o f the so ftware metho ds (with o r witho ut a testing
framewo rk).

Fo r this lesso n, we'll emplo y Visual Studio Test Pro fessio nal integrated into Visual Studio . We'll create a co mpanio n
test pro ject that will co -exist as a separate pro ject within o ur Visual Studio So lutio n. Let's get started!

Creating the Project


First, let's create the Studio So lutio n that will co ntain o ur applicatio n pro ject and test pro ject. Fo r this lesso n, we'll
recreate the calculato r pro ject fro m an earlier lesso n, but this time we will create the pro ject using MVC.

To create the pro ject, select File | Ne w | Pro je ct . In the New Pro ject dialo g bo x, make sure Visual C# is selected
under Installed Templates, and select the ASP.NET MVC 4 We b Applicat io n template. Change the Name o f the
Pro ject to MVCCalculat o r. Click OK. In the New ASP.NET MVC 4 Pro ject dialo g bo x, select the Basic template, make
sure the Cre at e a unit t e st pro je ct is checked, make sure the Test pro ject name is MVCCalculat o r.T e st s, and
click OK:
It's a co mmo n naming co nventio n to give the test pro ject the same name as the pro ject being tested, with
T ip .T e st s appended to the o riginal pro ject name.

When Visual Studio finishes creating yo ur pro jects, the So lutio n Explo rer sho ws a So lutio n, as well as bo th the
applicatio n pro ject (MVCCalculato r) and the test pro ject (MVCCalculato r.Tests). A Studio So lutio n enables yo u to
gro up two o r mo re pro jects o r related items into a single unit.
Befo re we begin to wo rk thro ugh testing co ncepts and add tests, let's recreate o ur calculato r using MVC and Razo r.
We wo n't spend much time explaining ho w the calculato r wo rks, except where we've mo dified o ur co de to wo rk as an
MVC applicatio n by using a mo del, view (using Razo r), and a co ntro ller.

Add a mo del fo r the calculato r. Right-click the Mo de ls fo lder and select Add | Class. In the Add New Item dialo g bo x,
make sure that the Visual C# template is selected, enter Calculat o r fo r the Name, and click Add. Mo dify
/Mo de ls/Calculat o r.cs as sho wn:
CODE TO TYPE: /Mo dels/Calculato r.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace MVCCalculator.Models
{
public class Calculator
{
public double CurrentValue { get; set; }
public bool FirstOperation { get; set; }
public double PendingValue { get; set; }
public string PendingAction { get; set; }

public Calculator()
{
CurrentValue = 0.0D;
FirstOperation = true;
PendingValue = 0.0D;
PendingAction = "";
}

public void Process(string key)


{
if (string.IsNullOrEmpty(PendingAction)) PendingAction = "";

double number;
if (double.TryParse(key, out number))
ProcessNumber(number);
else
ProcessAction(key);
}

private void ProcessAction(string currentAction)


{
}

private void ProcessNumber(double number)


{
}

private double DoCalculation(string action, double left, double right)


{
}
}
}

We'll add co de to the metho ds sho rtly, but first let's discuss this co de and ho w it will wo rk differently fro m the web
fo rms co de we used earlier.
/Mo dels/Calculato r.cs
.
.
.
public double CurrentValue { get; set; }
public bool FirstOperation { get; set; }
public double PendingValue { get; set; }
public string PendingAction { get; set; }

public Calculator()
{
CurrentValue = 0.0D;
FirstOperation = true;
PendingValue = 0.0D;
PendingAction = "";
}

public void Process(string key)


{
if (string.IsNullOrEmpty(PendingAction)) PendingAction = "";

double number;
if (double.TryParse(key, out number))
ProcessNumber(number);
else
ProcessAction(key);
}

private void ProcessAction(string currentAction)


{
}

private void ProcessNumber(double number)


{
}

private double DoCalculation(string action, double left, double right)


{
}
.
.
.

When yo u create a mo del, keep in mind that we will have access to the public member pro perties o f the mo del within
the view. We will be able to po pulate the mo del pro perties auto matically within o ur Razo r co de, then have access to
tho se pro perties in the co ntro ller via o ur mo del. Each public class pro perty (Curre nt Value , First Ope rat io n,
Pe ndingValue , Pe ndingAct io n) helps enable the calculato r functio nality. We've o mitted the string equivalent values
o f Curre nt Value and Pe ndingValue because they're no t as useful in this revisio n. We've added a co nstructo r
Calculat o r to make sure that o ur class pro perties are initialized co rrectly, then we added metho ds that we'll call to
distribute the actual calculato r functio nality. The o nly public metho d we're using is Pro ce ss which is called to initiate
each calculato r respo nse. The pro cessing changes, depending o n whether a number was selected
(Pro ce ssNum be r), o r we have an actio n (Pro ce ssAct io n). We've bro ken do wn the pro cessing a bit further, into a
calculatio n metho d (Do Calculat io n).

Thro ugho ut these lesso ns, we've enco uraged yo u to create small, encapsulated metho ds which helps to
T ip streamline co de maintenance. This technique also simplifies the pro cess o f auto mated testing, which
allo ws us to create iso lated test scenario s mo re efficiently.

We use the st ring.IsNullOrEm pt y metho d to address fo rm submissio n behavio r where o ur Pe ndingAct io n string
pro perty beco mes null. In o ther wo rds, the metho d handles the case where a user tries to submit the fo rm with an
empty PendingActio n string.

Let's add the actual co de fo r the metho ds:


CODE TO TYPE: /Mo dels/Calculato r.cs
.
.
.
private void ProcessAction(string currentAction)
{
bool pendingOperation = (PendingAction.Length > 0);
if ("/*-+=".IndexOf(currentAction) >= 0)
{
if (pendingOperation)
CurrentValue = DoCalculation(PendingAction, PendingValue, CurrentValue);

FirstOperation = true;

if (currentAction != "=")
{
PendingAction = currentAction;
PendingValue = CurrentValue;
}
else
{
PendingAction = "";
PendingValue = 0.0D;
}
}
else
{
switch (currentAction)
{
case "C":
// Reset calculator window and hidden fields
CurrentValue = 0.0D;
PendingAction = "";
PendingValue = 0.0D;
FirstOperation = true;
break;
case "CE":
// Clear error
CurrentValue = 0.0D;
FirstOperation = true;
break;
case "<-":
// Backspace - prevent leaving "bad" data in calculator window
// Not implemented
break;
case ".":
// Decimal point
// Not implemented
break;
case "+/-":
// Sign
CurrentValue *= -1;
break;
}
}
}

private void ProcessNumber(double number)


{
if (FirstOperation)
CurrentValue = number;
else
CurrentValue = CurrentValue * 10.0 + number;

FirstOperation = false;
}
private double DoCalculation(string action, double left, double right)
{
// Perform arithmetic calculations
double result = 0.0;
switch (action)
{
case "/":
// Prevent divide by zero
if (right != 0)
result = left / right;
else
{
// TODO: Handle divide by zero error
}
break;
case "*":
result = left * right;
break;
case "-":
result = left - right;
break;
case "+":
result = left + right;
break;
}
return result;
}
.
.
.

We've left in the implementatio n co nversio n o f the backspace and the decimal po int actio ns fo r the lesso n pro ject.

Next, add a co ntro ller fo r o ur pro ject. Right-click the Co nt ro lle rs fo lder and select Add | Co nt ro lle r. Change the
Co ntro llerName to Ho m e Co nt ro lle r, verify that Em pt y MVC Co nt ro lle r is selected as the Template under the
Scaffo lding o ptio ns, and click Add:

Mo dify /Co nt ro lle rs/Ho m e Co nt ro lle r.cs as sho wn:


CODE TO TYPE:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using MVCCalculator.Models;

namespace MVCCalculator.Controllers
{
public class HomeController : Controller
{
//
// GET: /Home/

public ActionResult Index()


{
return View(new Calculator());
}

[HttpPost]
public ActionResult Index(Calculator calculator)
{
calculator.Process(Request.Form["key"]);
ModelState.Clear();
return View(calculator);
}
}
}

Mo st o f this co de will be familiar to yo u, but let's go o ver a co uple o f lines fo r clarificatio n:

Ho meCo ntro ller.cs


public ActionResult Index()
{
return View(new Calculator());
}

[HttpPost]
public ActionResult Index(Calculator calculator)
{
calculator.Process(Request.Form["key"]);
ModelState.Clear();
return View(calculator);
}

When the page first lo ads, a new Calculat o r is created. In respo nse to POST actio ns fro m the fo rm (Ht t pPo st ), a
calculat o r instance is returned, but this time the instance was returned fro m the view, po pulated with previo us values.
We pro cess the actio n based o n the "ke y," and then we cle ar Mo de lSt at e . The Mo delState co ntains the info rmatio n
fro m the POST actio n. We need the POST actio n o bject because it allo ws us to differentiate the previo us values fro m
the current values. We clear the current values so that o ur calculato r mo del can po pulate the fo rm fields again.

Finally, we'll add a view to enable the calculato r to wo rk. Right-click o n the first Index metho d (no t the versio n
deco rated with the HttpPo st attribute), and select Add Vie w. In the Add View dialo g bo x, accept the default name Inde x,
ensure the View engine is Razo r (CSHT ML), and that Cre at e a st ro ngly-t ype d vie w is checked. Select the
Calculat o r class as the mo del (if yo u do n't see it in the list, rebuild the pro ject), and click Add:
Mo dify the Index.cshtml co de as sho wn:
CODE TO TYPE: /Views/Ho me/Index.cshtml
@model MVCCalculator.Models.Calculator

@{
ViewBag.Title = "IndexMVC Calculator";
string[,] buttons = new string[,]
{
{"CE", "C", "<-", "/"},
{"1", "2", "3", "*"},
{"4", "5", "6", "-"},
{"7", "8", "9", "+"},
{"+/-", "0", ".", "="}
};
}

<h2>Index@ViewBag.Title</h2>

@using (Html.BeginForm())
{
@Html.HiddenFor(m => m.FirstOperation)
@Html.HiddenFor(m => m.PendingValue)
@Html.HiddenFor(m => m.PendingAction)

<div class="editor-field">
@Html.TextBoxFor(m => m.CurrentValue, new { style = "text-align: right;" })
</div>

<table>
@for (int row = 0; row < buttons.GetLength(0); row++)
{
<tr>
@for (int col = 0; col < buttons.GetLength(1); col++)
{
<td><input type="submit" name="key" id="key" value="@buttons[row, col]"
style="width:40px" /></td>
}
</tr>
}
</table>
}

There are no surprises in the co de, but it's simpler than o ur previo us versio ns, and we can use an array fo r butto ns.

and . Yo u see the calculato r website:

No w that we have a running MVC pro gram, let's mo ve o n to testing.

Software T esting
Auto mating yo ur testing is go o d! Let's review the pro cess o f so ftware testing and its benefits.

So ftware testing is the pro cess o f validating and verifying that a so ftware pro duct meets requirements, satisfies the
needs o f the stakeho lders, and o perates as expected. Tho ro ugh testing helps determine the quality o f a so ftware
pro duct.

In the pro cess o f so ftware testing we develo p a series o f tests that can be run against the so ftware to co nfirm that the
so ftware wo rks as it sho uld, and if no t, to determine where the so ftware fails. The extent to which a so ftware applicatio n
can be validated and verified using auto mated test to o ls depends o n the quality o f the auto mated tests.

We'll start by adding a new test to o ur MVCCalculat o r.T e st s pro ject to demo nstrate the pro cess o f creating a test,
and co nducting a test run.

Unit T esting
Right-click the MVCCalculat o r.T e st s pro ject, and select Add | Ne w T e st . In the Add New Test dialo g bo x, select
Basic Unit T e st , change the Test Name to Unit T e st s.cs, and click OK.

Mo dify /MVCCalculat o r.T e st s/Unit T e st s.cs as sho wn:


CODE TO TYPE: /MVCCalculato r.Tests/UnitTests.cs
using System;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MVCCalculator.Models;

namespace MVCCalculator.Tests
{
[TestClass]
public class UnitTests
{
// Description: Enter number "123"
[TestMethod]
public void TestMethod1EnterNumber()
{
// Arrange
Calculator calculator = new Calculator();
calculator.CurrentValue = 0.0D;
calculator.FirstOperation = true;

// Act
// Update display
calculator.Process("1");
// Assert
Assert.AreEqual(calculator.CurrentValue.ToString(), "1");

// Act
calculator.Process("2");

// Assert
Assert.AreEqual(calculator.CurrentValue.ToString(), "12");

// Act
calculator.Process("3");

// Assert
Assert.AreEqual(calculator.CurrentValue.ToString(), "123");
}
}
}

Let's discuss this co de.


UnitTests.cs
[TestClass]
public class UnitTests
{
// Description: Enter number "123"
[TestMethod]
public void EnterNumber()
{
// Arrange
Calculator calculator = new Calculator();
calculator.CurrentValue = 0.0D;
calculator.FirstOperation = true;

// Act
calculator.Process("1");
// Assert
Assert.AreEqual(calculator.CurrentValue.ToString(), "1");

.
.
.
}
}

First, we renamed the test metho d Ent e rNum be r to reflect the purpo se o f the test. Fo r this first test, we want to verify
that if the user clicks o n number butto ns, they'll see the number displayed. So , is that really what we're do ing?

No , it isn't. We're actually creating an instance o f o ur Calculat o r class, setting the initial state (which isn't really
necessary since the Calculato r co nstructo r sets the initial state, but we're do ing it anyway to illustrate the po ssibility),
calling the Pro ce ss metho d to simulate the user clicking o n the "1" butto n, and then calling the Are Equal metho d o f
the Asse rt o bject to test the calculato r Curre nt Value fo r the co rrect value. The Asse rt class encapsulates the
co ncept o f an assertio n, which verifies an assumptio n o f truth abo ut co mpared co nditio ns. If the assertio n fails, the
Assert class will thro w an AssertFailedExceptio n exceptio n. The integrated testing co mpo nent o f Visual Studio will
detect this assert failure.

We can emplo y vario us techniques to execute o ur tests, but fo r no w, make sure yo u have the UnitTests.cs o pen, and
then select T e st | Run | T e st s in Curre nt Co nt e xt . The Test Results tabbed pane will appear in Visual Studio , and
the results o f the test run will be displayed:

No w let's change o ur co de to fo rce an assertio n failure. Mo dify the UnitTests.cs co de as sho wn in the co de snippet
belo w, then re-run the test:

UnitTests.cs
.
.
.
Assert.AreEqual(calculator.CurrentValue.ToString(), "129");
.
.
.

Yo u see a failed test:


The failed test result line indicates which assertio n failed, why it failed ("12" was expected, but "19 " was fo und), and the
name o f the test that failed. Yo u can also do uble-click o n the failed test to go directly to it in the co de.

Reset yo ur assert statement and re-run the test to be sure yo ur co de passes the test.

Adding a test is pretty straightfo rward, but there are lo ts o f po ssible scenario s to test fo r and vario us patterns to use.
We used the Arrange, Act, Assert pattern to test o ur co de:

Arrange: set up the co nditio ns necessary fo r the test.


Act: co nduct the test.
Assert: evaluate the results using an assertio n statement.

That wasn't so bad, was it? We'll execute o ur next test o n o ur MVC co de, but befo re we do , let's discuss unit testing,
and co mpare it to integrated testing.

When running tests, yo u may see a "Test Run Result limit exceeded" dialo g bo x similar to the o ne belo w.
This dialo g bo x indicates that Studio Test has reached yo ur current thresho ld o r limit (by default, 25
results) fo r saving and reco rding results. Co nducting ano ther test run will cause Studio Test to remo ve
Note o ne (o r mo re) o f the currently saved results; that's o kay. If yo u were testing a large pro ject, yo u'd want to
increase the thresho ld in T o o ls | Opt io ns | T e st T o o ls. Whenever yo u see this dialo g bo x during the
co urse, just click OK.

Unit and Integrated T esting


The test we created abo ve is kno wn as a unit test. A unit is the smallest testable part o f a so ftware applicatio n. Where
po ssible, we sho uld create tests fo r these smallest co mpo nents. Ho wever, o ur applicatio n is built upo n these smaller
units, and the larger co mpo nents can also be co nsidered units. As pro grams beco me mo re co mplex, yo u will need to
test blo cks o f units. This is kno wn as integrated testing. We wo n't specifically co ver integrated testing in this co urse, so
fo r no w, yo u can just think o f it as testing larger units.

MVC Unit T esting


In o ur previo us unit testing, we were essentially testing o ur mo del. No w, let's test o ur co ntro ller, which will also include
testing o ur view, and even mo re mo del testing. Let's add a test fo r o ur co ntro ller.

Right-click o n the MVCCalculat o r.T e st s pro ject, and select Add | Ne w T e st . In the Add New Test dialo g bo x, select
Basic Unit T e st , change the Test Name to Ho m e Co nt ro lle rUnit T e st s.cs, and click OK.

Mo dify /MVCCalculat o r.T e st s/Ho m e Co nt ro lle rUnit T e st s.cs as sho wn:


CODE TO TYPE: /MVCCalculato r.Tests/Ho meCo ntro llerUnitTests.cs

using System;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Web.Mvc;
using MVCCalculator.Models;
using MVCCalculator.Controllers;

namespace MVCCalculator.Tests
{
[TestClass]
public class HomeControllerUnitTests
{
[TestMethod]
public void TestMethod1TestIndexView()
{
// Arrange
var controller = new HomeController();

// Act
var result = controller.Index() as ViewResult;

// Assert
Assert.AreEqual("Index", result.ViewName);
}
}
}

Let's discuss this co de.

/MVCCalculato r.Tests/Ho meCo ntro llerUnitTests.cs


.
.
.
// Arrange
var controller = new HomeController();

// Act
var result = controller.Index() as ViewResult;

// Assert
Assert.AreEqual("Index", result.ViewName);
.
.
.

This test co de creates an instance o f the Ho m e Co nt ro lle r, then uses the instance variable co nt ro lle r to call the
Index metho d and return a Vie wRe sult o bject. Then we test the assertio n that Vie wNam e is Inde x. Let's run the test
and see what happens.

Make sure yo u have the Ho meCo ntro llerUnitTests.cs o pen. Select T e st | Run | T e st s in Curre nt Co nt e xt . The
Test Results tabbed pane appears in Studio , sho wing yo u the results o f the test run.

The test failed! Why? In o ur Ho meCo ntro ller.cs file, when returning a ViewResult, we use an o verlo aded versio n that
do esn't specify a name fo r the view. MVC attempts to infer the name o f the view, but is unsuccessful in the Index
metho d. Let's add a name fo r o ur view, then re-run the test. Mo dify /MVCCalculat o r.T e st s/Ho m e Co nt ro lle r.cs as
sho wn:

CODE TO TYPE: Ho meCo ntro ller.cs

.
.
.
public ActionResult Index()
{
return View("Index", new Calculator());
}
.
.
.

We've added a new first parameter to the View co nstructo r, which specifies the view name. Rerun the test; it passes.

Do es yo ur Test Results tabbed pane sho w bo th o f the tests we've added, rather than just o ne test? We can
use keybo ard sho rtcuts to run the tests; these allo w yo u to select which tests to run, fo r example, yo u may
want to run just the tests that failed the last run. Yo u can right-click o n the test so urce file and select Run
T e st s, o r yo u can use o ne o f these sho rtcuts:
T ip
Ct rl + R, then press A: Runs all the tests in all pro jects.
Ct rl + R, then press D: Runs all tests that were run in the last test run.
Ct rl + R, then press F: Runs all tests in the last test run that did no t pass.

So , what if we want to test the data returned fro m a metho d in the co ntro ller? Let's try that test next.

Add the fo llo wing test metho d to /MVCCalculat o r.T e st s/Ho m e Co nt ro lle rUnit T e st s.cs:
CODE TO TYPE: Ho meCo ntro llerUnitTests.cs
.
.
.
[TestMethod]
public void TestIndexViewEnterNumber()
{
// Arrange
HomeController controller = new HomeController();
Calculator setupCalculator = new Calculator
{
CurrentValue = 0.0D,
FirstOperation = true,
PendingValue = 0.0D,
PendingAction = ""
};

// Act
var result = controller.Index(setupCalculator) as ViewResult;
var resultCalculator = (Calculator)result.ViewData.Model;

// Assert
Assert.AreEqual(1.0D, resultCalculator.CurrentValue);
}
.
.
.

Let's discuss this test co de befo re we run it.

Ho meCo ntro llerUnitTests.cs


[TestMethod]
public void TestIndexViewEnterNumber()
{
// Arrange
HomeController controller = new HomeController();
Calculator setupCalculator = new Calculator
{
CurrentValue = 0.0D,
FirstOperation = true,
PendingValue = 0.0D,
PendingAction = ""
};

// Act
var result = controller.Index(setupCalculator) as ViewResult;
var resultCalculator = (Calculator)result.ViewData.Model;

// Assert
Assert.AreEqual(1.0D, resultCalculator.CurrentValue);
}

We set up a co ntro ller, but this time we'll call the o verlo aded co ntro ller Index metho d that requires a Calculat o r o bject,
so we'll need to create o ne. Once we've returned the ViewResult re sult , we'll use re sult to retrieve the Mo del fro m
the ViewData, casting it as o ur Calculat o r to create re sult Calculat o r. Using re sult Calculat o r, we can test the
assertio n that the Curre nt Value is 1.0 D.

Run the test. Did it wo rk? No ? Let's run it again, but this time, let's actually debug into o ur test co de to see if we can find
o ut what's go ing o n.

Add a Breakpo int to the first line o f co de in the Act sectio n o f o ur co de, the line that starts with var re sult = ..., and
then select T e st | De bug | T e st s in Curre nt Co nt e xt . When the executio n o f the test sto ps at the breakpo int, step
into the co de. Eventually, yo u'll see an erro r message o n the line o f co de in the Ho meCo ntro ller.cs file that calls the
calculato r Pro cess metho d.
Yo u may need to review ho w to set breakpo ints and ho w to step into co de using Studio debugging
Note techniques fo und o n the De bug menu.

When testing co ntro ller metho ds that are deco rated with Ht t pPo st (o r HttpGet), yo ur co ntro ller metho d expects to
have been called fro m a web page; underlying o bjects will have been created and po pulated with info rmatio n fro m the
web po sting pro cess. There are vario us techniques available to help yo u test fo r such scenario s, we'll begin by using
o ne o f the basic techniques.

We'll take advantage o f features built into MVC to test the o verlo aded co ntro ller Index metho d. If we pro vide a class
with pro perties that match the web fo rm fields when a co ntro ller metho d is called in respo nse to a view, the ASP.NET
MVC engine will po pulate this class auto matically, setting the values o f the matching pro perties to the fo rm field values.
We will use this technique, which also makes o ur co ntro ller co de easier to read. then add a new class named
Calculat o rFo rm to this fo lder. Add a single pro perty called ke y as sho wn:

CODE TO TYPE: /Fo rmMo dels/Calculato rFo rm.cs


using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace MVCCalculator
{
public class CalculatorForm
{
public string key { get; set; }
}
}

Mo dify /Co nt ro lle rs/Ho m e Co nt ro lle r.cs to use the new Calculato rFo rm class:

CODE TO TYPE: /Co ntro llers/Ho meCo ntro ller.cs


.
.
.
using MVCCalculator.FormModels;
.
.
.
[HttpPost]
public ActionResult Index(Calculator calculator, CalculatorForm calculatorForm)
{
calculator.Process(Request.Form["key"]calculatorForm.key);
ModelState.Clear();
return View(calculator);
}
.
.
.

We've added a seco nd parameter to o ur Index metho d, then used the key pro perty to retrieve the id o f the butto n
clicked by the user. Run yo ur MVCCalculato r applicatio n to make sure it still wo rks as yo u'd expect.

We changed the Index() metho d (in the previo us LISTING) by requiring an additio nal Calculato rFo rm parameter, so we
need to change the test pro gram (which calls the Index() metho d) to pass an additio nal Calculato rFo rm parameter.

Update yo ur test co de to use this new metho d. Mo dify /MVCCalculat o r.T e st s/Ho m e Co nt ro lle rUnit T e st s.cs as
sho wn:
CODE TO TYPE: /MVCCalculato r.Tests/Ho meCo ntro llerUnitTests.csHo meCo ntro llerUnitTests.cs
.
.
.
using MVCCalculator.FormModels;
.
.
.
[TestMethod]
public void TestIndexViewEnterNumber()
{
// Arrange
HomeController controller = new HomeController();
Calculator setupCalculator = new Calculator
{
CurrentValue = 0.0D,
FirstOperation = true,
PendingValue = 0.0D,
PendingAction = ""
};

// Act
var result = controller.Index(setupCalculator, new CalculatorForm { key = "1" }) as
ViewResult;
var resultCalculator = (Calculator)result.ViewData.Model;

// Assert
Assert.AreEqual(1.0D, resultCalculator.CurrentValue);
}
.
.
.

We mo dified the co de to create and po pulate an instance o f the Calculato rFo rm. No w when yo u run the test, it will pass
witho ut generating an exceptio n.

No w that yo u've wo rked thro ugh the test examples in this lesso n, yo u can create yo ur o wn test metho ds! With practice
and so me o nline research, yo u can create an extensive test suite fo r all o f yo ur future applicatio ns.

Additional T esting T emplates


We'll o nly be using so me o f the existing testing templates during the co urse, but there are mo re available:

Orde re d T e st : allo ws yo u to execute existing tests in a specific o rder, which is useful when testing applicatio n
scenario s o r use cases.

Unit T e st : pro vides fo r additio nal test metho ds fo r initializatio n and cleanup.

Unit T e st Wizard: will analyze yo ur pro ject and create unit tests classes and stubs based o n the co ntents o f yo ur
pro ject. Try this template and check o ut the resulting testing classes. Yo u can always delete any o f the classes (o r all o f
them).

Final T houghts
Adding so ftware testing is a valuable reso urce to ensure that yo ur co de perfo rms as yo u expect. There are lo ts o f
articles o nline that discuss testing strategies, breakdo wn what yo u sho uld test, and ho w yo u sho uld test. Mo st large
develo pment co mpanies emplo y auto mated testing, but even as a so le develo per, yo ur co de and develo pment
pro cess will benefit fro m the develo pment o f test cases.

As always, dig in and practice this stuff in yo ur ho mewo rk. See yo u when yo u're do ne.

Copyright © 1998-2014 O'Reilly Media, Inc.

This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.
This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.
See http://creativecommons.org/licenses/by-sa/3.0/legalcode for more information.
Client Solutions: JavaScript, jQuery, Ajax, XML, and
JSON
Lesson Objectives
In this lesso n yo u will:

read abo ut client-side and server-side develo pment.


use client-side so lutio ns.
use JavaScript, jQuery, and Ajax, while emplo ying XML and JSON data fo rmats.

Client-Side and Server-Side Development


As yo u've wo rked thro ugh the lesso ns in this co urse so far, we've discussed the co ncept o f client-side and server-
side; no w I want to emphasize the difference.

We've primarily written web applicatio ns (with o r witho ut MVC) in C#, HTML, Aspx, Razo r, and SQL. Fo r o ur purpo ses,
C# and SQL are server-side techno lo gies, which means they are lo aded and executed o n a co mputer o ther than the
co mputer o r device the user is using. Aspx and Razo r are also server-side techno lo gies, but they're used specifically
to create co ntent that enables a web user interface. Fo r the purpo ses o f this lesso n, we'll fo cus o n techno lo gies
designed to execute o n the client-side device, o r as in the case o f XML and JSON, used to allo w the exchange and
interpretatio n o f meaningful data with and o n the client-side device.

Befo re pro ceeding, be warned: this lesso n will no t teach JavaScript, jQuery, Ajax, XML o r JSON. Each o f tho se
techno lo gies co uld be a co urse unto itself. We'll be making limited use o f them so yo u understand ho w they wo rk with
C#, ASP.NET, and MVC. (We o ffer co urses n these to o ls as well. If yo u'd like to learn mo re abo ut them, yo u kno w what
to do . See o ur co urse catalo g.)

This lesso n will fo cus o n techno lo gies utilized o n the client-side device, but the techno lo gies are no t
limited to client co mputing. They are used in server enviro nments as well. Fo r example, JavaScript can
Note also be co ded and executed fo r server-side develo pment. XML and JSON are data enco ding schemes
that can be used in almo st any type o f enviro nment.

JavaScript
Fo r o ur purpo ses, JavaScript is a client-side language that executes o n the client device, such as a web bro wser
running o n a deskto p co mputer, lapto p, tablet, o r mo bile pho ne. JavaScript co de can be added to web page co ntent to
respo nd to user interactio ns, manipulate web page co ntent, and many o ther actio ns. Hundreds o f tho usands o f web
pages use JavaScript as a key feature o f their functio nality.

Mo st web devices that can display web co ntent and execute JavaScript have the ability to disable
JavaScript. Current standards stro ngly disco urage the creatio n web co ntent that requires JavaScript, but
Note many develo pers igno re this reco mmendatio n. Yo u can detect whether JavaScript suppo rt is enabled
and if it isn't, yo u can redirect the user to an alternate, no n-JavaScript-reliant versio n o f yo ur website, o r
no tify the user that JavaScript is required.

Okay, let's get started using JavaScript—we'll create a new MVC pro ject specifically to add JavaScript to o ur view.

Select File | Ne w | Pro je ct . Then select Visual C# | ASP.NET MVC 4 We b Applicat io n, change the pro ject Name
to MVCJ avaScript , and click OK. On the New ASP.NET MVC Pro ject dialo g, select the Basic template and the Razo r
View engine, uncheck Cre at e a unit t e st pro je ct , and click OK.

Once the MVCJavaScript Basic MVC so lutio n and pro ject is created, add an empty co ntro ller. Right-click the
Co nt ro lle rs fo lder, select Add | Co nt ro lle r, change the co ntro ller name to Ho m e Co nt ro lle r, set the Scaffo lding
o ptio ns Template to Em pt y MVC co nt ro lle r, and click Add.

Once the Ho meCo ntro ller.cs file is displayed in Studio , right-click in the Index metho d, select Add Vie w, leave the View
Name Inde x and the View engine Razo r (CSHT ML), uncheck Cre at e a st ro ngly-t ype d vie w, uncheck Cre at e as a
part ial vie w, leave checked Use a layo ut o r m ast e r page , and click Add.
and to ensure yo u can see the basic Index view page:

Next, let's add co de to display the current date and time, using JavaScript co de and Razo r co de. Mo dify the
Index.cshtml file as sho wn:

CODE TO TYPE: Index.cshtml


@{
ViewBag.Title = "Index";
}

<h2>Index</h2>

<p>
Razor current date/time: @DateTime.Now<br />
JavaScript current date/time: <script type="text/javascript">window.onload = document.w
rite(new Date());</script><br />
</p>

and . Yo u see the index page with the date/time o utput fro m Razo r and JavaScript:
JavaScript can be added into any Razo r view page. Keep in mind that there's a difference between the Razo r date/time
call and the JavaScript date/time call: the Razo r co de was interpreted and reso lved (the date/time determined) o n the
server, and the date/time result was sent fro m the server to the client co mputer as literal text and displayed by the
bro wser; the JavaScript date/time was determined when the JavaScript co de was lo aded and executed within the web
bro wser. This is an impo rtant distinctio n to understand. I'll describe an example to help illustrate. Suppo se we want to
create a current date/time display o n o ur web page. Using Razo r, we'd need to refresh the page co nstantly to execute
the server-side Razo r co de to get the current date/time. Also , the date/time we wo uld see is the date/time o n the server.
If we are in a different time zo ne than that o f the server, the date/time wo uld be inco rrect. Using JavaScript, we co uld
have a JavaScript timer co nstantly refresh a call to a functio n that gets the current date/time, and display the updated
date/time witho ut having to relo ad co ntent fro m the server.

When using JavaScript (o r any o ther client-side techno lo gies o r languages) in yo ur Razo r co de, the Razo r view
engine will interpret the co ntent either explicitly, such was when yo u use the @ symbo l o r script tag, o r implicitly by
inferring the language based o n co ntext. Naturally, we can mix Razo r and JavaScript, but so metimes yo u'll need to
help the Razo r view engine in determining the co rrect language. Let's wo rk thro ugh an example where the co de will
create a Razo r (C#) array that ho lds the days o f the week, and then use JavaScript and Razo r to lo o p thro ugh the
Razo r array, pushing each day o f the week into a JavaScript array. Ultimately, the co de will use JavaScript to o utput the
days o f the week fro m the JavaScript array.

Mo dify yo ur Inde x.csht m l file as sho wn:

CODE TO TYPE: Index.cshtml


@{
ViewBag.Title = "Index";
string[] daysOfWeek = { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Fr
iday", "Saturday" };
}

<h2>Index</h2>

<p>
Razor current date/time: @DateTime.Now<br />
JavaScript current date/time: <script type="text/javascript">window.onload = document.w
rite(new Date());</script><br />
</p>

<p>
<script type="text/javascript">
var daysOfWeek = [];
@foreach (var weekDay in daysOfWeek)
{
<text>
daysOfWeek.push('@weekDay');
</text>
}
document.write("Days of the week pushed into JavaScript array from Razor<br />\n");
for (var i = 0; i < daysOfWeek.length; i++)
document.write(daysOfWeek[i] + "<br/>\n");
</script>
</p>

and . Yo u see this:


No w remo ve the <t e xt > and </t e xt > tags, and take a lo o k at the erro r highlighted in red:

The <t e xt > and <t e xt > tags tell Razo r that the co de inside tho se tags is not Razo r C# co de. Then, within the <t e xt >
blo ck, we use @weekDay to indicate that weekDay is a Razo r variable.

We reco mmend yo u view the HTML so urce o f yo ur web page that is generated by Studio . Take no te o f the
T ip way the Razo r co de was rendered in the HTML co de. As a web develo per, yo u'll examine rendered HTML
frequently to make sure yo ur so urce co de is rendered into HTML as expected. .

Also , when yo u use Razo r and JavaScript (o r o ther languages) to gether, yo u may see a warning, underlined in green
in the Studio Co de Edito r:

The erro r means exactly what it says: that yo u have co nditio nal co mpilatio n turned o ff in Studio . Acco rding to Micro so ft,
"Co nditio nal co mpilatio n enables JScript to use new language features witho ut sacrificing co mpatibility with o lder
versio ns that do no t suppo rt the features. So me typical uses fo r co nditio nal co mpilatio n include using new features in
JScript, embedding debugging suppo rt into a script, and tracing co de executio n." We do n't need this functio nality fo r
o ur co de to wo rk, so the view engine suggests that perhaps we meant so mething different. Since it's just a warning, we
can igno re it and o ur co de will still wo rk. If it had been an erro r (with a red underline, rather than a green o ne), we still
wo uldn't need to turn o n co nditio nal co mpilatio n, but instead determine which Razo r erro r created this co nditio nal
co mpilatio n erro r.

Yo u can debug into yo ur JavaScript co de using Studio . Lo o k into using a bro wser-based JavaScript
T ip debugging to o l. Many bro wsers have such capability built in; many develo pers use a free add-in pro duct
called TARGET="_blank">FireBug.

If yo u find yo urself struggling with Razo r syntax, especially the inline syntax when yo u mix Razo r and JavaScript,
there's an excellent article entitled Inline Razo r Syntax Overview by Mike Brind that yo u may find helpful.

jQuery
If yo u've spent much time studying JavaScript, yo u've likely run acro ss references to jQuery. jQuery is a cro ss-bro wser
JavaScript library created to help streamline scripting. While mo st o f the capabilities o f jQuery co uld be co ded in
JavaScript, jQuery pro vides a library o f co de fo r co mmo n functio ns, saving yo u the tro uble o f writing co de yo urself.
jQuery is used by so many develo pers that MVC included it. Unfo rtunately, the jQuery co de in o ur example is in the
wro ng place; let's mo ve it and then add a call to get the current date and time using jQuery.

Mo dify /Vie ws/Share d/_Layo ut .csht m l as sho wn:

CODE TO TYPE: _Layo ut.cshtml


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

@Scripts.Render("~/bundles/jquery")
@RenderSection("scripts", required: false)
</body>
</html>

jQuery is lo cated at the bo tto m o f the rendered view intentio nally fo r perfo rmance purpo ses, but fo r this
lesso n we'll mo ve it so we can access the jQuery o bject and metho ds mo re easily. Here's a great article
Note that addresses website perfo rmance issues like script placement: Best Practices fo r Speeding Up Yo ur
Web Site.

Next, mo dify the view co de as sho wn:

CODE TO TYPE: /Views/Ho me/Index.cshtml


<p>
Razor current date/time: @DateTime.Now<br />
JavaScript current date/time: <script type="text/javascript">window.onload = do
cument.write(new Date());</script><br />
jQuery current date/time: <script type="text/javascript">window.onload = docume
nt.write($.now());</script><br />
</p>

and . The jQuery date/time is displayed under the JavaScript date/time. Next up: JSON and XML data and
Razo r and MVC.

JSON
An effective user interface is essential to any well-develo ped website, but the interface wo uld be useless witho ut
meaningful info rmatio n o r data. JavaScript and o ther pro grammers co mmo nly use a standard data fo rmat named
JSON JSON is an acro nym fo r JavaScript Object No tatio n, a text-based, human-readable data interchange fo rmat.
JSON is used to represent data structures and asso ciate arrays as o bjects. Even tho ugh it is related to JavaScript
JSON, as a data fo rmat, has beco me language-independent because a variety o f languages implement the ability to
enco de and deco de data into JSON fo rmat.

Ano ther reaso n to use a to o l that understands JavaScript (such as FireBug) is that mo st o f these to o ls also
T ip understand JSON.

With MVC, the Co ntro ller o bject includes a metho d to create JSON fro m an o bject. Let's mo dify o ur view o nce again,
so that it returns a JSON data structure.
Mo dify /Co nt ro lle rs/Ho m e Co nt ro lle r.cs as sho wn:

CODE TO TYPE: /Co ntro llers/Ho meCo ntro ller.cs


.
.
.
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}

[AcceptVerbs(HttpVerbs.Get)]
public ActionResult GetContent(string format = "JSON")
{
Person[] person = new Person[] { new Person { Name = "Tom Jones", Age = 21 }, new P
erson { Name = "Bob Lee", Age = 24} };
if (format.ToLower().IndexOf("json") >= 0)
return Json(person, JsonRequestBehavior.AllowGet);
else
return this.Content("Invalid format", "text/html");
}
.
.
.

Save and run the web applicatio n. Add /Ho m e /Ge t Co nt e nt to the URL. Yo u can specify the fo rmat by adding
/Ho m e /Ge t Co nt e nt ?f o rm at =J SON to the URL, but the metho d defaults to JSON in the parameter list, so either
URL wo rks. Yo u may be pro mpted to co nfirm that yo u want to o pen this URL (click Ope n), and to specify which
applicatio n to use to o pen it (cho o se Int e rne t Explo re r). Once yo u co nfirm and specify, yo u see a windo w
co ntaining the JSON data:

Let's discuss this co de:

/Co ntro llers/Ho meCo ntro ller.cs


public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}

[AcceptVerbs(HttpVerbs.Get)]
public ActionResult GetContent(string format = "JSON")
{
Person[] persons = new Person[] { new Person { Name = "Tom Jones", Age = 21 }, new Pers
on { Name = "Bob Lee", Age = 24} };
if (format.ToLower().IndexOf("json") >= 0)
return Json(persons, JsonRequestBehavior.AllowGet);
else
return this.Content("Invalid format", "text/html");
}
We create a Pe rso n class, and then an array o f new Pe rso n instances. Our metho d has a string parameter f o rm at
that defaults to "JSON." We'll use that to allo w the user to specify a fo rmat (we'll use this feature when we discuss XML
in a bit). Next, we call the J so n metho d to co nvert o ur pe rso ns array into a JSON o bject. We add an o ptio n to the
Jso n call J so nRe que st Be havio r.Allo wGe t to allo w us to make a GET call to this metho d with JSON invo lved.
(Yo u can read mo re abo ut this issue at JSON Pro blem - Jso nRequestBehavio r to Allo wGet.) When yo u save and run
the co de, yo u see this o utput in yo ur bro wser:

No w that yo u have so me understanding o f JSON data and MVC, let's mo ve o n to XML.

XML
XML, o r eXtensible Markup Language, has evo lved o ver time to wo rk in many different enviro nments, and o ffers and
ever increasing menu o f standards-based functio nality and capability. XML lo o ks a lo t like HTML; it uses tags and tag
attributes to wrap and describe data. Here's what the Perso n data we used in o ur JSON example lo o ks like in XML:

OBSERVE:
<ArrayOfPerson>
<Person>
<Name>Tom Jones</Name>
<Age>21</Age>
</Person>
<Person>
<Name>Bob Lee</Name>
<Age>24</Age>
</Person>
</ArrayOfPerson>

This XML is "well-fo rmed" in that it adheres to basic XML-fo rmatting standards. Typically the do cument wo uld also
include a header line describing the file type and versio n with an o ptio nal enco ding descriptio n, and an o ptio nal
schema statement that references a file that describes ho w to interpret the XML data elements. Online reso urces have
extensive co verage o f XML, but fo r o ur purpo ses, the basic well-fo rmed XML abo ve is sufficient.

Let's mo dify o ur co ntro ller co de to suppo rt returning o ur data in XML fo rmat. Mo dify Ho meCo ntro ller.cs as sho wn
(include the additio nal namespace):
CODE TO TYPE: Ho meCo ntro ller.cs
.
.
.
using System.Xml;
using System.Xml.Serialization;
using System.IO;
using System.Text;
.
.
.
[AcceptVerbs(HttpVerbs.Get)]
public ActionResult GetContent(string format = "JSON")
{
Person[] persons = new Person[] { new Person { Name = "Tom Jones", Age = 21 }, new
Person { Name = "Bob Lee", Age = 24} };
if (format.ToLower().IndexOf("json") >= 0)
return Json(persons, JsonRequestBehavior.AllowGet);
else if (format.ToLower().IndexOf("xml") >= 0)
{
string xml = ObjectToXML(persons);
return this.Content(xml, "text/xml");
}
else
return this.Content("Invalid format", "text/html");
}

private string ObjectToXML(object obj)


{
StringBuilder strXML = new StringBuilder();
try
{
Type objectType = obj.GetType();
XmlSerializer xmlSerializer = new XmlSerializer(objectType);
MemoryStream memoryStream = new MemoryStream();
try
{
using (XmlTextWriter xmlTextWriter = new XmlTextWriter(memoryStream, Encodi
ng.UTF8) { Formatting = Formatting.Indented })
{
xmlSerializer.Serialize(xmlTextWriter, obj);
memoryStream = (MemoryStream)xmlTextWriter.BaseStream;
strXML.Append(new UTF8Encoding().GetString(memoryStream.ToArray()));
}
}
finally
{
memoryStream.Dispose();
}
}
catch (Exception ex)
{
strXML.Clear();
strXML.Append("<Error>" + ex.Message + "</Error>\n");
}
return strXML.ToString();
}
.
.
.

Save and run the web applicatio n, this time using ht t p://lo calho st :xxxx/Ho m e /Ge t Co nt e nt ?f o rm at =XML fo r the
URL.

Let's discuss the co de:


Ho meCo ntro ller.cs
.
.
.
[AcceptVerbs(HttpVerbs.Get)]
public ActionResult GetContent(string format = "JSON")
{
Person[] persons = new Person[] { new Person { Name = "Tom Jones", Age = 21 }, new
Person { Name = "Bob Lee", Age = 24} };
if (format.ToLower().IndexOf("json") >= 0)
return Json(persons, JsonRequestBehavior.AllowGet);
else if (format.ToLower().IndexOf("xml") >= 0)
{
string xml = ObjectToXML(persons);
return this.Content(xml, "text/xml");
}
else
return this.Content("Invalid format", "text/html");
}
.
.
.

ASP.NET do es no t have a simple call to co nvert an o bject to XML like it do es with JSON, so we had to use the XML
suppo rt within ASP.NET to serialize o ur o bject. If yo u search o nline, yo u'll find several ways to co nvert an o bject to
XML, but the co de we used here is fairly versatile. We return XML by co nverting o ur perso ns o bject array to XML by
calling o ur new metho d Obje ct T o XML, then we set the result co ntent to the string co ntaining the XML, and then we
specify the MIME type o f the result co ntent so the bro wser can interpret it. We co uld have used the same metho d with
the JSON data, specifying the MIME type as "text/jso n," but mo st bro wsers wo uld try to do wnlo ad the data. Mo st
bro wsers can display the XML data, as sho wn belo w fo r Firefo x.

Ajax
No w that we've discussed JavaScript, jQuery, JSON, and XML, let's turn o ur attentio n to Ajax. Ajax is a gro up o f
interrelated web develo pment techniques used to create asynchro no us co mmunicatio n fro m within a web applicatio n
to the web server. Using Ajax, full page relo ads are no lo nger required to retrieve info rmatio n o r po pulate o r mo dify
web page co ntent, and individual elements o n a web page can be refreshed fro m the server witho ut impacting o ther
elements. Ajax is implemented fo r deplo yment o n the client side, and may be written in a variety o f client-side
languages, including JavaScript, but is co mmo nly used in the jQuery fo rm, so we'll use that fo rm fo r this part o f the
lesso n. Let's make a few mo re mo dificatio ns to o ur pro ject to add Ajax functio nality. This time, we'll just update the
Index.cshtml view. Add the co de belo w to the bo tto m o f /Vie ws/Ho m e /Inde x.csht m l:
/Views/Ho me/Index.cshtml
.
.
.
<p>
<script type="text/javascript">
// Handle getJSON button click
$().ready(function () {
$("#getJSON").bind("click", function () {
$("#divJSONResults").empty();
$.ajax({
type: "GET",
contentType: "application/json; charset=utf-8",
dataType: "json",
url: '/Home/GetContent',
data: { "format": "JSON" },
success: function (data) {
if (data == null)
$("#divJSONResults").append("No data returned.");
else {
for (var i = 0; i < data.length; i++) {
$("#divJSONResults").append("Name/Age: " + data[i].Name + "
, " + data[i].Age + "<br />\n");
}
}
}
});
});
});
// Handle getXML button click
$().ready(function () {
$("#getXML").bind("click", function () {
$.ajax({
type: "GET",
contentType: "application/json; charset=utf-8",
dataType: "text",
url: '/Home/GetContent',
data: { "format": "XML" },
success: function (data) {
if (data == null)
document.getElementById('textAreaXMLResults').value = "No data
returned";
else {
document.getElementById('textAreaXMLResults').value = data;
}
}
});
});
});
</script>
</p>
<input type="submit" id="getXML" name="getXML" value="GET XML" /><br />
XML Results<br />
<textarea rows="15" cols="70" id="textAreaXMLResults" name="textAreaXMLResults"></texta
rea><br />
<input type="submit" id="getJSON" name="getJSON" value="Get JSON" /><br />
JSON Results:<br />
<div id="divJSONResults" /><br />
.
.
.

When yo u save and run this co de, and click o n either butto n, yo u'll see results. We "tricked" the Ajax call fo r XML by
specifying the data type is "text" so we co uld display the XML in a textarea HTML co ntro l. Fo r JSON, we parsed the data
and fo rmatted the o utput.

In this sectio n we've taken a pretty straightfo rward appro ach to using Ajax with MVC, but there is an MVC-standard way
In this sectio n we've taken a pretty straightfo rward appro ach to using Ajax with MVC, but there is an MVC-standard way
o f co nnecting data. MVC suppo rts the co ncept o f a partial view, a view that yo u wo uld embed within ano ther view. With
Ajax, each partial view sho uld be tied to a mo del. Altho ugh yo u do n't need Ajax to enable and use partial views, when
using Ajax fo r asynchro no us co mmunicatio n yo u sho uld use a partial view mo del and partial view fo r the display o f
any Ajax-generated results. Let's finish this lesso n by develo ping a partial view.

Partial Views and Ajax


To demo nstrate partial views and Ajax, let's update o ur pro ject to display a list o f students with paging capability. We
co uld use JavaScript to receive JSON data and then create a student list, but let's take advantage o f MVC. We'll need
two new views (o ne will be a partial view) and asso ciated co ntro ller metho ds, a co uple o f new mo dels, and we'll use
Ajax to switch the student listing depending o n which page we're accessing.

Right-click the /Mo de ls fo lder, select Add | Class, name the class St ude nt , and click Add. Mo dify
/Mo de ls/St ude nt .cs as sho wn.

CODE TO TYPE: /Mo dels/Student.cs


.
.
.
namespace MVCJavaScript.Models
{
public class Student
{
public string LastName { get; set; }
public string FirstName { get; set; }
public string MiddleName { get; set; }
public int Age { get; set; }
}
}

Next, let's add a mo del fo r o ur list o f students fo r the partial view that includes paging pro perties.

Right-click the /Mo de ls fo lder, select Add | Class, name the class St ude nt s, and click Add. Mo dify
/Mo de ls/St ude nt s.cs as sho wn:

CODE TO TYPE: /Mo dels/Students.cs


using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace MVCJavaScript.Models
{
public class Students<Student>
{
public IEnumerable<Student> Data { get; set; }
public int NumberOfPages { get; set; }
public int CurrentPage { get; set; }
}
}

The Students class mo del will serve as a co ntainer fo r a gro up o f students, as well as to track paging info rmatio n.

Next, we need to make a new view that will ho ld o ur partial view and Ajax co de.

Right-click the /Vie ws/Ho m e fo lder, select Add | Vie w, set the view name to St ude nt , verify that o nly Use a layo ut
o r m ast e r page is checked, and click Add.

Mo dify /Vie ws/Ho m e /St ude nt .csht m l as sho wn:


CODE TO TYPE: /Views/Ho me/Student.cshtml
@{
ViewBag.Title = "Student List";
}

<script type="text/javascript">
function linkClick(pageNumber) {
$.ajax({
url: '@Url.Action("PartialStudent")',
data: { "pageNumber": pageNumber },
success: function (data) {
$("#divPartialView").html(data);
}
});
}
</script>

<h2>@ViewBag.Title</h2>

<div id="divPartialView">
@Html.Partial("PartialStudent")
</div>

We use a <div tag> to encapsulate o ur partial view. We also add a JavaScript functio n that we can call whenever a
paging link is clicked, making a jQuery Ajax call requesting the co rrect page o f data. No w we just need to create o ur
partial view and update the co ntro ller.

When creating a stro ngly-typed view, yo u may need to clean yo ur pro ject (Build | Cle an So lut io n), and
T ip then rebuild it so the Add View dialo g is aware o f any new mo dels.

Right-click the /Vie ws/Ho m e fo lder, select Add | Vie w, set the view name to Part ialSt ude nt , check and set the
stro ngly-typed view dro pdo wn to St ude nt , check Cre at e as a Part ial Vie w, and click Add. Mo dify
/Vie ws/Ho m e /Part ialSt ude nt .csht m l as sho wn:
CODE TO TYPE: /Views/Ho me/PartialStudent.cshtml
@model MVCJavaScript.Models.Students<MVCJavaScript.Models.Student>

<table>
<thead>
<tr>
<th>Last</th>
<th>First</th>
<th>Middle</th>
<th>Age</th>
</tr>
</thead>
<tbody>
@foreach (var person in Model.Data)
{
<tr>
<td>@person.LastName</td>
<td>@person.FirstName</td>
<td>@person.MiddleName</td>
<td>@person.Age</td>
</tr>
}
</tbody>
<tfoot>
<tr>
<td colspan="4">
@for (int pgNum = 1; pgNum <= Model.NumberOfPages; pgNum++)
{
if (pgNum == Model.CurrentPage)
{
@pgNum
}
else
{
<a onclick="linkClick(@pgNum)" href="javascript:void(0)")>@
pgNum</a>
}
}
</td>
</tr>
</tfoot>

</table>

We mo dify the mo del used fo r the view to use the Students mo del rather than Student, and turn the mo del into a typed
list fo r enumeratio ns, which matches the Students mo del. Then we lo o p thro ugh each o f the students, list the student
info rmatio n, and then display the page info rmatio n. No w we'll update o ur co ntro ller.

Mo dify /Co nt ro lle rs/Ho m e Co nt ro lle r.cs as sho wn.


CODE TO TYPE: /Co ntro llers/Ho meCo ntro ller.cs

.
.
.

private Student[] studentData = new Student[]


{
new Student { FirstName = "Tom", LastName = "Jones", Age = 21 },
new Student { FirstName = "Bob", LastName = "Lee", Age = 24 } ,
new Student { FirstName = "A", LastName = "Lee", Age = 25 },
new Student { FirstName = "B", LastName = "Lee", Age = 26 },
new Student { FirstName = "C", LastName = "Lee", Age = 27 },
new Student { FirstName = "D", LastName = "Lee", Age = 28 },
new Student { FirstName = "E", LastName = "Lee", Age = 29 },
new Student { FirstName = "F", LastName = "Lee", Age = 30 },
new Student { FirstName = "G", LastName = "Lee", Age = 31 },
new Student { FirstName = "H", LastName = "Lee", Age = 32 },
new Student { FirstName = "I", LastName = "Lee", Age = 33 },
new Student { FirstName = "J", LastName = "Lee", Age = 34 },
new Student { FirstName = "K", LastName = "Lee", Age = 35 },
new Student { FirstName = "L", LastName = "Lee", Age = 36 }
};

private const int PageSize = 5;

public ActionResult Student()


{
return View(GetStudents(1));
}

public ActionResult PartialStudent(int pageNumber = 1)


{
return PartialView(GetStudents(pageNumber));
}

private Students<Student> GetStudents(int pageNumber)


{
var students = new Students<Student>();
students.Data = studentData.OrderBy(p => p.LastName).Skip(PageSize * (pageNumber -
1)).Take(PageSize).ToList();
students.NumberOfPages = Convert.ToInt32(Math.Ceiling((double)studentData.Count() /
PageSize));
students.CurrentPage = pageNumber;
return students;
}

.
.
.

In the co ntro ller, we add a data o bject seeded with test data fo r o ur demo nstratio n, two metho ds that co rrespo nd to o ur
views, and a metho d to extract the co rrect data fro m o ur data o bject. Fo r a real applicatio n, we'd use a real database.

and and add /Ho m e /St ude nt to the URL. Yo u see o utput like this:
Befo re yo u mo ve o n to the ho mewo rk, here's a brief intro ductio n to "view mo dels" (they appear in the ho mewo rk fo r
this lesso n). If this lesso n's pro ject was using a database, the Students mo del wo uld no t be represented in that
database, because we do n't sto re paging info rmatio n there.

Frequently, when wo rking with MVC, we'll add an additio nal mo del layer between the database mo del and the views,
o ften referred to as view mo dels. View mo dels are usually kept in a separate fo lder, o ften called /ViewMo dels, to
differentiate a mo del intended fo r user interface fro m a mo del that represents database schema info rmatio n.

Alright, o ff yo u go !

Copyright © 1998-2014 O'Reilly Media, Inc.

This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.
See http://creativecommons.org/licenses/by-sa/3.0/legalcode for more information.
ASP.NET and Databases
Lesson Objectives
In the lesso n, yo u will:

add a database to an ASP.NET MVC Web Applicatio n.


write co de the system can use to auto matically build a database.
create an actio n yo u can use to add to a menu.
use LINQ and data binding.

There are two main pro cesses fo r wo rking with databases in C#. The o lder metho d is to build the database and then co de to
that database. The newer metho d is to co de first and have the system build the database fro m that co de. We use the newer
metho d in o ur lesso ns that co ver databases.

Adding a Database
As we've seen befo re, adding a database to an ASP.NET pro ject is a fairly straightfo rward pro cess. Create a new
ASP.NET MVC 4 We b Applicat io n pro ject named AspNe t Dat abase s.:

Be sure to cho o se Int e rne t Applicat io n and make sure the View Engine is set to Razo r:
In a deplo yable applicatio n, we wo uld want to implement security measures fo r vario us users and
Note different degrees o f access.

Creating a Menu Action


Open the Ho m e Co nt ro lle r.cs file and mo dify it as sho wn:
CODE TO TYPE: /Co ntro llers/Ho meCo ntro ller.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace AspNetDatabases.Controllers
{
public class HomeController : Controller
{
public ActionResult Index()
{
ViewBag.Message = "Modify this template to jump-start your ASP.NET MVC appl
ication.ASP.NET Database Lab";

return View();
}

public ActionResult About()


{
ViewBag.Message = "Your app description page.";

return View();
}

public ActionResult Contact()


{
ViewBag.Message = "Your contact page.";

return View();
}
}
}

We've remo ved the Abo ut and Co ntact actio n metho ds, so we sho uld remo ve them fro m o ur pro ject. Delete them fro m
the Views fo lder:
There's also so me co de we do n't really need in the /Vie ws/Share d/_Layo ut .csht m l file. Open the file and mo dify it
as sho wn:
CODE TO TYPE: /Views/Shared/_Layo ut.cshtml
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>@ViewBag.Title - My ASP.NET MVC Application</title>
<link href="~/favicon.ico" rel="shortcut icon" type="image/x-icon" />
<meta name="viewport" content="width=device-width" />
@Styles.Render("~/Content/css")
@Scripts.Render("~/bundles/modernizr")
</head>
<body>
<header>
<div class="content-wrapper">
<div class="float-left">
<p class="site-title">@Html.ActionLink("your logo hereASP.NET Datab
ase Lab", "Index", "Home")</p>
</div>
<div class="float-right">
<section id="login">
@Html.Partial("_LoginPartial")
</section>
<nav>
<ul id="menu">
<li>@Html.ActionLink("Home", "Index", "Home")</li>
<li>@Html.ActionLink("About", "About", "Home")</li>
<li>@Html.ActionLink("Contact", "Contact", "Home")</li>
</ul>
</nav>
</div>
</div>
</header>
<div id="body">
@RenderSection("featured", required: false)
<section class="content-wrapper main-content clear-fix">
@RenderBody()
</section>
</div>
<footer>
<div class="content-wrapper">
<div class="float-left">
<p>© @DateTime.Now.Year - My ASP.NET MVC Application</p>
</div>
</div>
</footer>

@Scripts.Render("~/bundles/jquery")
@RenderSection("scripts", required: false)
</body>
</html>

Open the /Vie ws/Ho m e /Inde x.csht m l file and mo dify it as sho wn:

Note We'll leave the HTML structure in place, in case yo u need it later.
CODE TO TYPE: /Views/Ho me/Index.cshtml
@{
ViewBag.Title = "Home Page";
}
@section featured {
<section class="featured">
<div class="content-wrapper">
<hgroup class="title">
<h1>@ViewBag.Title.</h1>
<h2>@ViewBag.Message</h2>
</hgroup>
<p>
To learn more about ASP.NET MVC visit
<a href="http://asp.net/mvc" title="ASP.NET MVC Website">http://asp.net
/mvc</a>.
The page features <mark>videos, tutorials, and samples</mark> to help y
ou get the most from ASP.NET MVC.
If you have any questions about ASP.NET MVC visit
<a href="http://forums.asp.net/1146.aspx/1?MVC" title="ASP.NET MVC Foru
m">our forums</a>.
</p>
</div>
</section>
}
<h3>We suggest the following:</h3>
<ol class="round">
<li class="one">
<h5>Getting Started</h5>
ASP.NET MVC gives you a powerful, patterns-based way to build dynamic websites
that
enables a clean separation of concerns and that gives you full control over mar
kup
for enjoyable, agile development. ASP.NET MVC includes many features that enabl
e
fast, TDD-friendly development for creating sophisticated applications that use
the latest web standards.
<a href="http://go.microsoft.com/fwlink/?LinkId=245151">Learn more...</a>
</li>

<li class="two">
<h5>Add NuGet packages and jump-start your coding</h5>
NuGet makes it easy to install and update free libraries and tools.
<a href="http://go.microsoft.com/fwlink/?LinkId=245153">Learn more...</a>
</li>

<li class="three">
<h5>Find Web Hosting</h5>
You can easily find a web hosting company that offers the right mix of features
and price for your applications.
<a href="http://go.microsoft.com/fwlink/?LinkId=245157">Learn more...</a>
</li>
</ol>

Create a new fo lder named Dat abase s in yo ur pro ject:


Yo ur new fo lder appears in the So lutio n Explo rer:

No w, in the /Dat abase s fo lder, create a new class named CdDbInit ialize r.

Mo dify the /Dat abase s/CdDbInit ialize r class as sho wn:


CODE TO TYPE: /Databases/CdDbInitializer.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Data.Entity;
using AspNetDatabases.Models;

namespace AspNetDatabases.Databases
{
public class CdDbInitializer : DropCreateDatabaseIfModelChanges<CdDbContext>
{
protected override void Seed(CdDbContext context)
{
var CdDbItems = new List<CdDb>
{
new CdDb{ ID = 0, Title = "Desire", Artist = "Bob Dylan" },
new CdDb{ ID = 1, Title = "Crossroads", Artist = "Eric Clapton"}
};
CdDbItems.ForEach(m => context.cddb.Add(m));
context.SaveChanges();
}
}
}

but do n't run the applicatio n yet.

OBSERVE: /Databases/CdDbInitializer.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Data.Entity;
using AspNetDatabases.Models;

namespace AspNetDatabases.Databases
{
public class CdDbInitializer : DropCreateDatabaseIfModelChanges<CdDbContext>
{
protected override void Seed(CdDbContext context)
{
var CdDbItems = new List<CdDb>
{
new CdDb{ ID = 0, Title = "Desire", Artist = "Bob Dylan" },
new CdDb{ ID = 1, Title = "Crossroads", Artist = "Eric Clapton" }
};
CdDbItems.ForEach(m => context.cddb.Add(m));
context.SaveChanges();
}
}
}

The Dro pCre at e Dat abase If Mo de lChange s class will recreate o ur database the first time the co ntext is used in o ur
app do main. We are extending this class and o verriding the Se e d metho d so that when the database is created, it will
be seeded with o ur default values.

Our CdDbIt e m s list ho lds instances o f o ur CdDb class, which will be used to seed the database's CdDb table.

When we save Change s(), the database is seeded. It wo n't appear o n o ur menu yet—we'll get to that later.

No w, we need to create a CdDbCo nt e xt class in the /Dat abase s fo lder that will serve as the co ntext fo r o ur database
CdDb table.

Mo dify the /Dat abase s/CdDbCo nt e xt class as sho wn:


CODE TO TYPE: /Databases/CdDbCo ntext.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Data.Entity;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using AspNetDatabases.Models;
using System.Data.Entity.ModelConfiguration.Conventions;

namespace AspNetDatabases.Databases
{
public class CdDbContext : DbContext
{
public DbSet<CdDb> cddb { get; set; }

protected override void OnModelCreating(DbModelBuilder modelBuilder)


{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
}
}
}

but again, do n't run it.

OBSERVE: /Databases/CdDbCo ntext.cs


.
.
.
namespace AspNetDatabases.Databases
{
public class CdDbContext : DbContext
{
public DbSet<CdDb> cddb { get; set; }

protected override void OnModelCreating(DbModelBuilder modelBuilder)


{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
}
}
}

Here we set up the co ntext by which to reference o ur CdDb table. We'll be able to access this co ntext thro ugh o ur cddb
pro perty. When we Re m o ve () the PluralizingT able Nam e Co nve nt io n o bject fro m the co nventio ns o f o ur
mo delBuilder, we are indicating that we do n't want o ur table names to be plural versio ns o f the o bjects that create
them. That is, o ur DBSe t <CdDb> creates a CdDb table name rather than CdDbs.

In the /Mo de ls fo lder, create a new mo del named CdDb, and mo dify it as sho wn:
CODE TO TYPE: /Mo dels/CdDb.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace AspNetDatabases.Models
{
public class CdDb
{
[Key]
public int ID { get; set; }
public string Title { get; set; }
public string Artist { get; set; }
}
}

but do n't run it.

OBSERVE: /Mo dels/CdDb.cs

.
.
.
namespace AspNetDatabases.Models
{
public class CdDb
{

[Key]
public int ID { get; set; }
public string Title { get; set; }
public string Artist { get; set; }

}
}

Here the CdDb class represents o ur CdDb table. The pro perties we are creating will map to the co lumns in the table.
So we will have co lumns named ID, T it le , and Art ist , representing the ID o f the CD, which is the primary key, the title
o f the album, and the artist. This is by no means all o f the data we co uld put in this table.

We'll also need to mo dify the /Glo bal.asax file to add the ability to use o ur database in o ur pro ject. Let's do that no w:
CODE TO TYPE: /Glo bal.asax
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Http;
using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;
using System.Data.Entity;
using AspNetDatabases.Models;
using AspNetDatabases.Databases;

namespace AspNetDatabases
{
// Note: For instructions on enabling IIS6 or IIS7 classic mode,
// visit http://go.microsoft.com/?LinkId=9394801

public class MvcApplication : System.Web.HttpApplication


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

WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
AuthConfig.RegisterAuth();

Database.SetInitializer<CdDbContext>(new CdDbInitializer());

}
}
}

but do n't run it.

OBSERVE: /Glo bal.asax


.
.
.
Database.SetInitializer<CdDbContext>(new CdDbInitializer());
.
.
.

Here we tell o ur applicatio n to use the CdDbInit ialize r() metho d to initialize CdDbCo nt e xt , and set up o ur
database. We'll add the co nnectio n string to o ur database file later in the /We b.co nf ig file.

In the /Co nt ro lle rs fo lder, create a new co ntro ller named CdDbCo nt ro lle r and mo dify it as sho wn:
CODE TO TYPE: /Co ntro llers/CdDbCo ntro ller.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using AspNetDatabases.Databases;
using AspNetDatabases.Models;

namespace AspNetDatabases.Controllers
{
public class CdDbController : Controller
{
private CdDbContext DB = new CdDbContext();
//
// GET: /CdDb/

public ActionResult Index()


{
return View(DB.cddb.ToList());
}

}
}

but do n't run it.


OBSERVE: /Co ntro llers/CdDbCo ntro ller.cs
.
.
.
namespace AspNetDatabases.Controllers
{
public class CdDbController : Controller
{
private CdDbContext DB = new CdDbContext();
//
// GET: /CdDb/

public ActionResult Index()


{
return View(DB.cddb.ToList());
}

}
}

This co ntro ller returns a view that will display the CdDb table. We create a new variable named DB that is an instance o f
CdDbCo nt e xt . We can get all o f o ur ro ws fro m the DB as a list that we can then iterate thro ugh in the view.

No w we'll create a view fro m CdDbCo ntro ller:


Mo dify /Vie ws/CdDb/Inde x.csht m l as sho wn:

CODE TO TYPE: /Views/CdDb/Index.cshtml


@model IEnumerable<AspNetDatabases.Models.CdDb>

@{
ViewBag.Title = "Index";
}

<h2>Index</h2>

<table>
<tr>
<th>
@Html.DisplayNameFor(model => model.Title)
</th>
<th>
@Html.DisplayNameFor(model => model.Artist)
</th>
</tr>
@foreach (var item in Model)
{
<tr>
<td>
@Html.DisplayFor(model => item.Title)
</td>
<td>
@Html.DisplayFor(model => item.Artist)
</td>
</tr>
}
</table>

but do n't run it. We still have a co uple mo re things to do .


OBSERVE: /Views/CdDb/Index.cshtml
@model IEnumerable<AspNetDatabases.Models.CdDb>

@{
ViewBag.Title = "Index";
}

<h2>Index</h2>

<table>
<tr>
<th>
@Html.DisplayNameFor(model => model.Title)
</th>
<th>
@Html.DisplayNameFor(model => model.Artist)
</th>
</tr>
@foreach (var item in Model)
{
<tr>
<td>
@Html.DisplayFor(model => item.Title)
</td>
<td>
@Html.DisplayFor(model => item.Artist)
</td>
</tr>
}
</table>

In this view, we create an HTML table that sho ws the co ntents o f the database. We use the CdDb class as the mo del
and we sho w the HTML table header with the name o f the T it le and Art ist co lumns in o ur database. Then, we sho w
the co ntents o f tho se co lumns fo r each ro w in the database, using the it e m set up in o ur f o re ach lo o p. The it e m
variable will represent a ro w in the database, and we can reference each data item by co lumn name.

We're almo st do ne, but we still need to co nfigure o ur We b.co nf ig file so that we can use the database.

Add the co nnectio n string to /We b.co nf ig as sho wn:


CODE TO TYPE: /Web.co nfig
...

<configuration>
<configSections>
<!-- For more information on Entity Framework configuration, visit http://go.micros
oft.com/fwlink/?LinkID=237468 -->
<section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.Entity
FrameworkSection, EntityFramework, Version=4.4.0.0, Culture=neutral, PublicKeyToken=b77
a5c561934e089" requirePermission="false" />
</configSections>
<connectionStrings>

<add name="DefaultConnection"
connectionString="Data Source=.\SQLEXPRESS;Initial Catalog=aspnet-AspNetDataba
ses-20130608134956;Integrated Security=SSPI"
providerName="System.Data.SqlClient"
/>

<add name ="CdDbContext"


connectionString="Data Source=.\SQLEXPRESS;Initial Catalog=CdDb;AttachDBFilena
me=|DataDirectory|\CdDb.mdf;User Instance=true;Integrated Security=SSPI"
providerName ="System.Data.SqlClient"/>

</connectionStrings>

...

The co nnectio n string abo ve o urs is put into the file by Visual Studio . (Yo urs might differ slightly fro m the
Note o ne sho wn here.) The CdDbCo nt e xt co nnectio n string gives us a name we can use to co nnect to the
database.

Befo re we test it, we need to tell the system to add o ur actio n to the menu. Mo dify /Vie ws/Share d/_Layo ut .sht m l as
sho wn:

CODE TO TYPE: /Views/Shared/_Layo ut.shtml

...

<header>
<div class="content-wrapper">
<div class="float-left">
<p class="site-title">@Html.ActionLink("ASP.NET Database Lab", "Ind
ex", "Home")</p>
</div>
<div class="float-right">
<section id="login">
@Html.Partial("_LoginPartial")
</section>
<nav>
<ul id="menu">
<li>@Html.ActionLink("Home", "Index", "Home")</li>
<li>@Html.ActionLink("CD Database", "Index", "CdDb")</li>
</ul>
</nav>
</div>
</div>
</header>

...

and . Click the CD Dat abase menu item.


OBSERVE: /Views/Shared/_Layo ut.shtml

...

<header>
<div class="content-wrapper">
<div class="float-left">
<p class="site-title">@Html.ActionLink("ASP.NET Database Lab", "Ind
ex", "Home")</p>
</div>
<div class="float-right">
<section id="login">
@Html.Partial("_LoginPartial")
</section>
<nav>
<ul id="menu">
<li>@Html.ActionLink("Home", "Index", "Home")</li>
<li>@Html.ActionLink("CD Database", "Index", "CdDb")</li>
</ul>
</nav>
</div>
</div>
</header>

...

The Html.Act io nLink() metho d uses the CD Dat abase parameter as the clickable text fo r the link and will use the
co ntro ller, CdDb, to invo ke the actio n Inde x.

Yo u may get an erro r the first time yo u run the applicatio n. If yo u do , exit debugging and run the
applicatio n again. Generally, this happens o nly the first time yo u run the app. Ho wever, if it co ntinues, yo u
Note may need to delete the /App_Dat a/CdDb.m df file. In o rder to see if that's the case, click the Sho w All
File s butto n in the So lutio n Explo rer. If the m o de l changes and the database and mo del are o ut o f sync,
delete the database file and let the app create a new o ne.
Creating, Editing, Deleting, Searching
No w we'll go o ver ho w to create, edit, delete, and search the data we get fro m the database. We'll update
/Vie ws/CdDb/Inde x.sht m l, then we'll update the co ntro ller so that we can act o n tho se requests. Finally, we'll create
views as needed so that the user can co mplete the actio ns.

Mo dify /Vie ws/CdDb/Inde x.sht m l as sho wn:


CODE TO TYPE: /Views/CdDb/Index.shtml
@model IEnumerable<AspNetDatabases.Models.CdDb>
@{
ViewBag.Title = "Index";
}

<h2>
Index</h2>
<table>
<tr>
<th>
Action
</th>
<th>
@Html.DisplayNameFor(model => model.Title)
</th>
<th>
@Html.DisplayNameFor(model => model.Artist)
</th>
</tr>
@foreach (var item in Model)
{
<tr>
<td>
@Html.ActionLink("Edit", "Edit", new { ID = item.ID })
@Html.ActionLink("Delete", "Delete", new { ID = item.ID })
</td>
<td>
@Html.DisplayFor(model => item.Title)
</td>
<td>
@Html.DisplayFor(model => item.Artist)
</td>
</tr>
}
</table>

<p>
@Html.ActionLink("Create New", "Create")
</p>

Save yo ur file and start debugging. Click the CD Dat abase menu item. No w the view has so me additio nal links, but
they do n't wo rk. We'll fix that in a mo ment.
OBSERVE: /Views/CdDb/Index.shtml
.
.
.
@foreach (var item in Model)
{
<tr>

<td>
@Html.ActionLink("Edit", "Edit", new { ID = item.ID })
@Html.ActionLink("Delete", "Delete", new { ID = item.ID })
</td>

<td>
@Html.DisplayFor(model => item.Title)
</td>
<td>
@Html.DisplayFor(model => item.Artist)
</td>
</tr>
}
</table>

<p>
@Html.ActionLink("Create New", "Create")
</p>

We created two actio n links that, when clicked, will call the Edit and De le t e metho ds in o ur co ntro ller. We'll pass back
the ID o f the item to tho se metho ds.

Speaking o f the co ntro ller, mo dify the /Co nt ro lle rs/CdDbCo nt ro lle r.cs file no w:
CODE TO TYPE: /Co ntro llers/CdDbCo ntro ller.cs

using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using AspNetDatabases.Databases;
using AspNetDatabases.Models;

namespace AspNetDatabases.Databases
{
public class CdDbController : Controller
{
private CdDbContext DB = new CdDbContext();
//
// GET: /CdDb/
public ActionResult Index()
{
return View(DB.cddb.ToList());
}

//
// GET: /CdDb/Create
public ActionResult Create()
{
return View();
}

//
// POST: /CdDb/Create
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create([Bind(Exclude = "ID")] CdDb cdToCreate)
{
if (!ModelState.IsValid)
{
return View();
}
DB.cddb.Add(cdToCreate);
DB.SaveChanges();
return RedirectToAction("Index");
}
}
}

. Let's discuss the new co de.


OBSERVE: /Co ntro llers/CdDbCo ntro ller.cs
.
.
.
//
// GET: /CdDb/Create
public ActionResult Create()
{
return View();
}

//
// POST: /CdDb/Create
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create([Bind(Exclude = "ID")] CdDb cdToCreate)
{
if (!ModelState.IsValid)
{
return View();
}
DB.cddb.Add(cdToCreate);
DB.SaveChanges();
return RedirectToAction("Index");
}

Here, we add two metho ds. The first metho d, Cre at e (), returns the same view because we o nly want o ur data to be
passed via the HTTP POST metho d. Since we are creating a new CD, this wo rks well because it will give us an empty
fo rm. (Later we'll use the Edit () metho d to get the item we want.) The seco nd metho d, Cre at e (), submits an HTML
fo rm, which uses the HTML POST metho d. The line [Acce pt Ve rbs(Ht t pVe rbs.Po st )] means that the Cre at e ()
metho d is o nly used with the HTTP POST metho d.

We want o ur ID field to be updated auto matically, so we exclude it fro m everything, kno wing that SQL Server will enter it
fo r us. We exclude o ur ID using the co mmand [Bind(Exclude = " ID" )] in the parameters o f the metho d. The view that
we'll create in a mo ment, will send the CdDb mo del o bject as the parameter fo r this metho d.

If o ur mo del's state is no t valid, we do n't want to update the database; if it is no t valid, we return the view with no
changes. If it is valid, we Add() the new o bject to the database and save the changes. Then we redirect to the Index
actio n, which relo ads o ur Index view.

Here co mes the co o l part. No rmally, we'd have to build a fo rm to call o f these actio ns, but since o ur requirements are
fairly co mmo n, Visual Studio can lo o k at o ur Mo del and get all o f the info rmatio n it needs to create o ur View. All we will
have to do is fill in so me data in a dialo g.

Right-click in the seco nd Cre at e () metho d and select Add Vie w and co mplete the dialo g as sho wn:
and . Click the CD Dat abase menu item and then click the Cre at e Ne w link. Fill in the fo rm fo r a new CD and
click the Cre at e butto n. Ho w co o l is that?

We have the exact file structure we need in o rder to add an item to o ur database. Let's take a lo o k at what Visual Studio
did:
OBSERVE: /Views/CdDb/Create.cshtml
.
.
.
@using (Html.BeginForm()) {
@Html.ValidationSummary(true)

<fieldset>
<legend>CdDb</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.Artist)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.Artist)
@Html.ValidationMessageFor(model => model.Artist)
</div>

<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}

<div>
@Html.ActionLink("Back to List", "Index")
</div>

@section Scripts {
@Scripts.Render("~/bundles/jqueryval")
}

First, the @ using (Ht m l.Be ginFo rm ()) statement indicates that we're go ing to be using an HTML fo rm. The "@using
(Html.BeginFo rm())" statement will set up all the fo rm co ntent o n the page and basic fo rm-pro cessing co de fo r us.

@ Ht m l.Validat io nSum m ary(t rue ) allo ws the fo rm to sho w a summary o f validatio n erro rs when they appear. We
do n't have any validatio n co de yet, so @ Ht m l.Validat io nSum m ary(t rue ) do esn't do anything right no w.

The @Html.Edit o rFo r metho ds give us an edito r area that is tied to a field in o ur mo del o bject. Whatever we type into
tho se fields will be added to the o bject that is passed to the Cre at e () event in the co ntro ller.

The last item, @sectio n Scripts, uses the Script s.Re nde r() metho d to bundle a bunch o f jQuery scripts that are
required to make this all wo rk. (JavaScript will be discussed in greater detail in a later lesso n.)

The Scripts.Render() metho d bundles several scripts into a single JavaScript file befo re lo ading them.
This is called m inif ying the scripts. It cuts do wn o n the number o f times the server has to be co ntacted.
Note Witho ut using this type o f system, each file wo uld have to be lo aded separately. Fo r mo re info rmatio n o n
minifying scripts, read this article

If yo u do n't put anything in the text bo xes o n the Create page, then run the pro gram again, the o bject will be added to
the database with empty items. Let's fix that no w.

Mo dify /Mo de ls/CdDb.cs as sho wn:


CODE TO TYPE: /Mo dels/CdDb.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace AspNetDatabases.Models
{
public class CdDb
{
[Key]
public int ID { get; set; }
[Required(ErrorMessage = "Please enter a Title.")]
public string Title { get; set; }
[Required(ErrorMessage = "Please enter an Artist.")]
public string Artist { get; set; }
}
}

and . Click CD Dat abase and then click Cre at e Ne w. Try to click Cre at e with no thing in the text bo xes. Co o l,
huh?

The Re quire d attribute will fo rce validatio n co de to make sure that the data item is no t empty o r null. It will cause
validatio n messages to be displayed using the erro r message in the attribute. It will also set the Mo de lSt at e .IsValid
to false:

OBSERVE: /Mo dels/CdDb.cs


.
.
.
namespace AspNetDatabases.Models
{
public class CdDb
{
[Key]
public int ID { get; set; }
[Required(ErrorMessage = "Please enter a Title.")]
public string Title { get; set; }
[Required(ErrorMessage = "Please enter an Artist.")]
public string Artist { get; set; }
}
}

By adding the Re quire d o ptio n to each o f the fields, we've made it so they canno t be empty strings.

Let's add co de that will enable us to edit an entry that already exists in the database. Mo dify
/Co nt ro lle rs/CdDbCo nt ro lle r.cs as sho wn:
CODE TO TYPE: /Co ntro llers/CdDbCo ntro ller.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using AspNetDatabases.Databases;
using AspNetDatabases.Models;
using System.Data;
using System.Data.Entity;

namespace AspNetDatabases.Databases
{
public class CdDbController : Controller
{
private CdDbContext DB = new CdDbContext();
//
// GET: /CdDb/
public ActionResult Index()
{
return View(DB.cddb.ToList());
}

//
// GET: /CdDb/Create
public ActionResult Create()
{
return View();
}

//
// POST: /CdDb/Create
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create([Bind(Exclude = "ID")] CdDb cdToCreate)
{
if (!ModelState.IsValid)
{
return View();
}
DB.cddb.Add(cdToCreate);
DB.SaveChanges();
return RedirectToAction("Index");
}

//
// GET: /CdDb/Edit/
public ActionResult Edit(int id = 0)
{
CdDb item = DB.cddb.Find(id);
if (item == null)
{
return RedirectToAction("Index");
}
return View(item);
}
//
// POST: /CdDb/Edit/
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(CdDb cddbToEdit)
{
if (!ModelState.IsValid)
{
return View(cddbToEdit);
}
DB.Entry(cddbToEdit).State = EntityState.Modified;
DB.SaveChanges();
return RedirectToAction("Index");
}
}
}

Save yo ur file but do n't run it.

OBSERVE: /Co ntro ller/CdDbCo ntro ller.cs

.
.
.
//
// GET: /CdDb/Edit/
public ActionResult Edit(int id = 0)
{
CdDb item = DB.cddb.Find(id);
if (item == null)
{
return RedirectToAction("Index");
}
return View(item);
}
//
// POST: /CdDb/Edit/
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(CdDb cddbToEdit)
{
if (!ModelState.IsValid)
{
return View(cddbToEdit);
}
DB.Entry(cddbToEdit).State = EntityState.Modified;
DB.SaveChanges();
return RedirectToAction("Index");
}
}
}

Here's what happens when a user edits an entry:

1. The users clicks the Edit link beside an entry.


2. The bro wser sends a GET request to the Edit actio n with the ID o f the requested entry.
3. The Edit (int id=0 ) metho d returns a View with that entry, which fills the text bo xes with their data.
4. The user edits the entry and saves it.
5. The save submissio n is sent back to the server using a POST request.
6 . The Edit (CdDb cddbT o Edit ) metho d is called.
7. The Edit (CdDb cddbT o Edit ) metho d sets the St at e o f the entry to Mo dif ie d and saves the changes.
8 . The Edit (CdDb cddbT o Edit ) metho d redirects the user to the Index page where the listing is updated to
sho w the changes in the database.

Of co urse, we can't do that yet since we do n't have a view fo r this part o f the co ntro ller.

Right-click in the seco nd Edit () metho d and select Add Vie w. Add a view as sho wn:
Mo dify /Vie ws/CdDb/Edit .csht m l as sho wn:
CODE TO TYPE: /Views/CdDb/Edit.cshtml

@model AspNetDatabases.Models.CdDb

@{
ViewBag.Title = "Edit";
}

<h2>Edit</h2>
@using (Html.BeginForm()) {
@Html.ValidationSummary(true)

<fieldset>
<legend>CdDb</legend>

@Html.HiddenFor(model => model.ID)

<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.Artist)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.Artist)
@Html.ValidationMessageFor(model => model.Artist)
</div>

<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}

<div>
@Html.ActionLink("Back to List", "Index")
</div>

@section Scripts {
@Scripts.Render("~/bundles/jqueryval")
}

and , click CD Dat abase , select an entry to edit, mo dify the entry, and then save it. The changes are saved and
displayed when yo u return to the Index page:
/Views/CdDb/Edit.cshtml

.
.
.
@using (Html.BeginForm()) {
@Html.ValidationSummary(true)

<fieldset>
<legend>CdDb</legend>

@Html.HiddenFor(model => model.ID)

<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.Artist)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.Artist)
@Html.ValidationMessageFor(model => model.Artist)
</div>

<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}

<div>
@Html.ActionLink("Back to List", "Index")
</div>

@section Scripts {
@Scripts.Render("~/bundles/jqueryval")
}

This pro bably lo o ks familiar. It's similar to the /Vie ws/Cre at e .csht m l file. The o nly real difference is that there is a
hidde n e le m e nt f o r t he ID and the subm it but t o n says Save instead o f Create.

Finally, we want to enable the user to delete an entry. Mo dify /Co nt ro lle rs/CdDbCo nt ro lle r.cs as sho wn:
CODE TO TYPE: /Co ntro llers/CdDbCo ntro ller.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using AspNetDatabases.Databases;
using AspNetDatabases.Models;
using System.Data;
using System.Data.Entity;

namespace AspNetDatabases.Databases
{
public class CdDbController : Controller
{
.
.
.
//
// POST: /CdDb/Edit/
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(CdDb cddbToEdit)
{

if (!ModelState.IsValid)
{
return View(cddbToEdit);
}
DB.Entry(cddbToEdit).State = EntityState.Modified;
DB.SaveChanges();
return RedirectToAction("Index");
}

//
// GET: /CdDb/Delete/
public ActionResult Delete(int id = 0)
{
CdDb item = DB.cddb.Find(id);
if (item == null)
{
return RedirectToAction("Index");
}
return View(item);
}
//
// POST: /CdDb/Delete/
[AcceptVerbs(HttpVerbs.Post), ActionName("Delete")]
public ActionResult DeleteConfirmed(int id = 0)
{
CdDb item = DB.cddb.Find(id);
if (item == null)
{
return RedirectToAction("Index");
}
DB.cddb.Remove(item);
DB.SaveChanges();
return RedirectToAction("Index");
}
}
}
OBSERVE: /Views/CdDbCo ntro ller.cs

.
.
.
//
// GET: /CdDb/Delete/
public ActionResult Delete(int id = 0)
{
CdDb item = DB.cddb.Find(id);
if (item == null)
{
return RedirectToAction("Index");
}
return View(item);
}
//
// POST: /CdDb/Delete/
[AcceptVerbs(HttpVerbs.Post), ActionName("Delete")]
public ActionResult DeleteConfirmed(int id = 0)
{
CdDb item = DB.cddb.Find(id);
if (item == null)
{
return RedirectToAction("Index");
}
DB.cddb.Remove(item);
DB.SaveChanges();
return RedirectToAction("Index");
}
}
}

Here, the first De le t e () metho d finds the item and displays it by returning the view with the item as a parameter. The
seco nd metho d, De le t e Co nf irm e d() is called when the user co nfirms the deletio n. The Re m o ve () metho d marks
the item to be remo ved fro m the database. The Save Change s() metho d actually causes the deletio n.

Make a view fo r the page (using the first Delete() metho d) as we did befo re; make sure to use the De le t e Scaffo ld
Template.

and . Delete an entry in the database, using the new view yo u created.

The last to pic we'll co ver here is "searching."

Mo dify /Co nt ro lle rs/CdDbCo nt ro lle r.cs as sho wn.


CODE TO TYPE: /Co ntro llers/CdDbCo ntro ller.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using AspNetDatabases.Databases;
using AspNetDatabases.Models;
using System.Data;
using System.Data.Entity;

namespace AspNetDatabases.Databases
{
public class CdDbController : Controller
{
.
.
.
//
// POST: /CdDb/Delete/
[AcceptVerbs(HttpVerbs.Post), ActionName("Delete")]
public ActionResult DeleteConfirmed(int id = 0)
{
CdDb item = DB.cddb.Find(id);
if (item == null)
{
return RedirectToAction("Index");
}
DB.cddb.Remove(item);
DB.SaveChanges();
return RedirectToAction("Index");
}

//
// GET: /CdDb/SearchIndex
public ActionResult SearchIndex(string searchString)
{
var discs = from cd in DB.cddb
select cd;
if (!String.IsNullOrEmpty(searchString))
{
discs = discs.Where(disc => disc.Title.Contains(searchString));
}
return View(discs);
}
}
}

but do n't run it.


OBSERVE: /Co ntro llers/CdDbCo ntro ller.cs

.
.
.
//
// GET: /CdDb/SearchIndex
public ActionResult SearchIndex(string searchString)
{
var discs = from cd in DB.cddb
select cd;
if (!String.IsNullOrEmpty(searchString))
{
discs = discs.Where(disc => disc.Title.Contains(searchString));
}
return View(discs);
}
}
}

We used the variable names cd and disc, but there is no indicatio n o f what these are. This co uld be o ne o f the mo re
frustrating aspects o f C# and LINQ / lambda expressio ns. Ho w and where they are used determines what they are. The
quickest way to determine what they are to ho ver the mo use curso r o ver them in the edito r. The co de insight will tell
yo u what they represent. In this case, they represent a mo del o bject, CdDb. The cd variable is a range variable,
because it is a range in a LINQ query. disc is a parameter to the Whe re () metho d. The names o f the variables are
arbitrary; yo u can name them whatever yo u want.

We select all o f the CDs fro m the CdDb table in o ur database. Then, we use the Whe re () metho d to match tho se
o bjects with o ur se archSt ring. When the View is rendered, the query is run o n the list we gave it and o nly the o bjects
that match will be displayed.

We need to create a view fro m this metho d, so do that no w:


Mo dify the generated /Vie ws/CdDb/Se archInde x.csht m l as sho wn:
CODE TO TYPE: /Views/SearchIndex.cshtml

@model IEnumerable<AspNetDatabases.Models.CdDb>

@{
ViewBag.Title = "SearchIndex";
}

<h2>SearchIndex</h2>

<p>
@Html.ActionLink("Create New", "Create")
</p>
@using (Html.BeginForm()){
<p> Title: @Html.TextBox("SearchString")<br />
<input type="submit" value="Search" /></p>
}
<table>
<tr>
<th>
@Html.DisplayNameFor(model => model.Title)
</th>
<th>
@Html.DisplayNameFor(model => model.Artist)
</th>
<th></th>
</tr>

@foreach (var item in Model) {


<tr>
<td>
@Html.DisplayFor(modelItem => item.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.Artist)
</td>
<td>
@Html.ActionLink("Edit", "Edit", new { id=item.ID }) |
@Html.ActionLink("Details", "Details", new { id=item.ID }) |
@Html.ActionLink("Delete", "Delete", new { id=item.ID })
</td>
</tr>
}

</table>

Let's discuss the new co de.


OBSERVE: /Views/SearchIndex.cshtml

@model IEnumerable<AspNetDatabases.Models.CdDb>

@{
ViewBag.Title = "SearchIndex";
}

<h2>SearchIndex</h2>

<p>
@Html.ActionLink("Create New", "Create")
</p>
@using (Html.BeginForm()){
<p> Title: @Html.TextBox("SearchString")<br />
<input type="submit" value="Search" /></p>
}
<table>
<tr>
<th>
@Html.DisplayNameFor(model => model.Title)
</th>
<th>
@Html.DisplayNameFor(model => model.Artist)
</th>
<th></th>
</tr>

@foreach (var item in Model) {


<tr>
<td>
@Html.DisplayFor(modelItem => item.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.Artist)
</td>
<td>
@Html.ActionLink("Edit", "Edit", new { id=item.ID }) |
@Html.ActionLink("Delete", "Delete", new { id=item.ID })
</td>
</tr>
}

</table>

Here, we e nable t he use r t o t ype a se arch t e rm . If we didn't add that capability, the user wo uld have to tack the
search term o nto the URL, which isn't very user-friendly. We also remo ved the "Details" link, since we haven't
implemented that functio nality.

Note Yo u rarely need to mo dify the generated co de like this, but we wanted to sho w yo u ho w just in case.

No w let's give the user a way to get to the search page. Mo dify /Vie ws/CdDb/Inde x.csht m l as sho wn:
CODE TO TYPE: /Views/CdDb/Index.cshtml

@model IEnumerable<AspNetDatabases.Models.CdDb>
@{
ViewBag.Title = "Index";
}

<h2>Index</h2>
@Html.ActionLink("Search", "SearchIndex")
<table>
.
.
.
</table>
<p>
@Html.ActionLink("Create New", "Create")
</p>

and , and enter a title to test o ut the search functio nality.

Great wo rk in this lesso n. Practice what yo u've learned here in yo ur ho mewo rk. We'll meet again in the next lesso n!

Copyright © 1998-2014 O'Reilly Media, Inc.

This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.
See http://creativecommons.org/licenses/by-sa/3.0/legalcode for more information.
Object Relational Mapping: Entity Framework and
Data Abstraction
Lesson Objectives
In this lesso n, yo u will:

implement and understand ho w the Entity Framewo rk creates and implements mappings between entities and types.
implement Co mplex type to simplify an abstract entity design.
implement data abstractio n in singular and generalized repo sito ries.
use the Unit o f Wo rk design pattern and interact with a generic repo sito ry.
lo ck do wn the "behind-the-scenes" co de to limit o ther develo pers fro m changing ho w the applicatio n interacts with
the database.
keep entities in sync and limit database co ntext creatio ns to a single co nnectio n, rather than creating a different
co ntext fo r each entity.

During this lesso n, yo u will create and implement a specialized repo sito ry, a generic repo sito ry, and a Unit o f Wo rk design
pattern to abstract data fro m the applicatio n and begin to build an API fo r an applicatio n that can be used in a co llabo rative
design team enviro nment.

Entity Framework
The Entity Framewo rk allo ws us to wo rk at a higher level o f abstractio n witho ut wo rrying abo ut the way data is sto red in
a database. This framewo rk also allo ws us to write less co de to perfo rm the same o peratio ns in traditio nal
applicatio ns. Entity Framewo rk (EF) allo ws us to wo rk with data in the fo rm o f do main-specific o bjects and pro perties.

This basic design appro ach to an applicatio n divides the applicatio n into three parts:

Physical Mo de l: addresses and defines capabilities o f data engine's specifying sto rage details.
Lo gical Mo de l: SQL queries and sto res pro cedure calls, no rmalizes entities and relatio nships into tables,
using fo reign key co nstraints.
Do m ain Mo de l: defines entities and relatio nships in the mo deled system (Mappings).

The Entity Framewo rk using the Co de First appro ach allo ws us to use o ur o wn do main classes to represent the mo del
that EF will use to perfo rm queries, updates, and track changes. This appro ach uses a pro gramming pattern kno wn as
Convention over Configuration. This means that EF will assume we are using the EF co nventio ns.

Let's begin! Create a new pro ject named EF_Co de _First . Set the pro ject type to ASP.NET MVC 4 We b Applicat io n:
Use the Int e rne t Applicat io n template and set Razo r as the View Engine:
No w that we have a clean slate to wo rk o n, let's create o ur first Entity. Right-click the Mo de ls fo lder and create a new
mo del named Ware ho use :
Yo u see the basic co de that is created fo r every mo del:

OBSERVE: Default generated co de fo r Wareho use.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace EF_Code_First.Models
{
public class Warehouse
{
}
}

A wareho use sends and receives shipments, but the shipments are separate, smaller entities o f the wareho use that
must co mmunicate with the wareho use to be handled pro perly. Add a new entity named Shipm e nt s, fo llo wing the
same instructio ns we used fo r Wareho use.

Yo u see the same generated co de as fo r the Wareho use entity:


OBSERVE: Default generated co de fo r /Mo dels/Shipments.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace EF_Code_First.Models
{
public class Shipments
{
}
}

A shipment has a sender address, receiver address, tracking number (defined to be a package's Primary Key), a sent
and received date/time, and a pro perty fo r the number o f items per shipment. Let's add these pro perties to o ur
/Mo de ls/Shipm e nt s entity. Mo dify yo ur co de as sho wn:

CODE TO TYPE: /Mo dels/Shipments.cs


using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel.DataAnnotations;

namespace EF_Code_First.Models
{
public class Shipments
{
[Key]
public int TrackingNumber { get; set; }
public int NumberOfItems { get; set; }
public string SendToAddress { get; set; }
public string SentFromAddress { get; set; }
public DateTime ShippedDateTime { get; set; }
public DateTime ReceivedDateTime { get; set; }
}
}

EF's primary key naming co nventio n is Id o r {EntityName}Id (fo r Wareho use, the default primary key wo uld be Id o r
Ware ho use Id). We added the DataAnno tatio n [Ke y] to let the Entity Framewo rk kno w that we want to use the
TrackingNumber as a primary key, because we didn't fo llo w the default naming co nventio n. Data anno tatio ns are
lo cated in the System.Co mpo nentMo del.DataAnno tatio ns namespace.

No w that we have a shipment entity, we need to give the Wareho use entity so me pro perties. Mo dify yo ur co de as
sho wn:

CODE TO TYPE: /Mo dels/Wareho use.cs


using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel.DataAnnotations;

namespace EF_Code_First.Models
{
public class Warehouse
{
public int Id { get; set; }
public string Name { get; set; }
public string Address { get; set; }
}
}
We didn't need the Ke y data anno tatio n abo ve Id, because EF auto matically reco gnizes this as the primary key.

Currently, the mo dels are no t entities because they have no relatio nship to o ne ano ther. A wareho use may have many
shipments, but a shipment may o nly go to o ne wareho use o r be shipped fro m o ne wareho use. Let's add so me
relatio nships and create pro per Entities.

Mo dify /Mo de ls/Shipm e nt s.cs as sho wn:

CODE TO TYPE: /Mo dels/Shipments.cs


using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel.DataAnnotations;

namespace EF_Code_First.Models
{
public class Shipments
{
[Key]
public int TrackingNumber { get; set; }
public int NumberOfItems { get; set; }
public string SendToAddress { get; set; }
public string SentFromAddress { get; set; }
public DateTime ShippedDateTime { get; set; }
public DateTime ReceivedDateTime { get; set; }
public int WarehouseId { get; set; }
}
}

No w mo dify /Mo de ls/Ware ho use .cs as sho wn:

CODE TO TYPE: /Mo dels/Wareho use.cs


using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel.DataAnnotations;

namespace EF_Code_First.Models
{
public class Warehouse
{
public int Id { get; set; }
public string Name { get; set; }
public string Address { get; set; }
public ICollection<Shipments> shipments { get; set; }
}
}

In the Shipments entity, we added Ware ho use Id. By adding this pro perty, we tell the Entity Framewo rk that there is a
relatio nship between Wareho use and Shipments. ICo lle ct io n<Shipm e nt s> defines a co llectio n o f Shipments
entities.

No w that we have a co uple o f entities with a o ne-to -many relatio nship, let's see ho w EF represents them in a
database.

Open /We b.co nf ig and add the co de belo w (yo ur /We b.co nf ig file may differ):
CODE TO TYPE: /Web.co nfig
<?xml version="1.0" encoding="utf-8"?>
<!--
For more information on how to configure your ASP.NET application, please visit
http://go.microsoft.com/fwlink/?LinkId=169433
-->
<configuration>
<configSections>
<!-- For more information on Entity Framework configuration, visit http://go.micros
oft.com/fwlink/?LinkID=237468 -->
<section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.Entity
FrameworkSection, EntityFramework, Version=4.4.0.0, Culture=neutral, PublicKeyToken=b77
a5c561934e089" requirePermission="false" />
</configSections>
<connectionStrings>

<add name="DefaultConnection" connectionString="Data Source=.\SQLEXPRESS;Initial Ca


talog=aspnet-EF_Code_First-20130614163900;Integrated Security=SSPI" providerName="Syste
m.Data.SqlClient" />

<add name="WarehouseContext"
connectionString="Data Source=.\SQLEXPRESS;Initial Catalog=Warehouse;AttachDBF
ilename=|DataDirectory|\Warehouse.mdf;Integrated Security=SSPI;User Instance=true"
providerName="System.Data.SqlClient"
/>
</connectionStrings>
.
.
.

Create a new fo lder named /Ware ho use Dat a and add two new classes to it: Ware ho use Co nt e xt and
Ware ho use Init ialize r.

Yo ur So lutio n Explo rer lo o ks like this:


Mo dify /Ware ho use Dat a/Ware ho use Co nt e xt .cs as sho wn:
CODE TO TYPE: /Wareho useData/Wareho useCo ntext.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Data.Entity;
using EF_Code_First.Models;
using System.Data.Entity.ModelConfiguration.Conventions;

namespace EF_Code_First.WarehouseData
{
public class WarehouseContext : DbContext
{
public DbSet<Warehouse> Warehouse { get; set; }
public DbSet<Shipments> Shipments { get; set; }

protected override void OnModelCreating(DbModelBuilder modelBuilder)


{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
}
}
}

Let's discuss what we just did.

OBSERVE:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Data.Entity;
using EF_Code_First.Models;
using System.Data.Entity.ModelConfiguration.Conventions;

namespace EF_Code_First.WarehouseData
{
public class WarehouseContext : DbContext
{
public DbSet<Warehouse> Warehouse { get; set; }
public DbSet<Shipments> Shipments { get; set; }

protected override void OnModelCreating(DbModelBuilder modelBuilder)


{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
}
}
}

Syst e m .Dat a.Ent it y.Mo de lCo nf igurat io n.Co nve nt io ns is the namespace that allo ws us to mo dify
EF co nventio ns.
We inherit the DbCo nt e xt class so we can create and use database o bjects.
public DbSe t <Ware ho use > creates a table in o ur database with the name o f o ur entity.
OnMo de lCre at ing defines what to do when o ur mo dels are created. We can perfo rm many o peratio ns in
this functio n to pro perly define o ur entities if we so cho o se.
m o de lBuilde r.Co nve nt io ns.Re m o ve <PluralizingT able Nam e Co nve nt io n>(); remo ves the
co nventio n to pluralize the entity names when they are created as tables.

No w we need to set up the /Ware ho use Dat a/Ware ho use Init ialize r so we can create a database and insert o ur
tables. Mo dify yo ur co de as sho wn:
CODE TO TYPE: /Wareho useData/Wareho useInitializer.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Data.Entity;
using EF_Code_First.Models;

namespace EF_Code_First.WarehouseData
{
public class WarehouseInitializer : DropCreateDatabaseAlways<WarehouseContext>
{
protected override void Seed(WarehouseContext context)
{
}
}
}

Let's discuss what we did in the Wareho useInitializer.

CODE TO TYPE: /Wareho useData/Wareho useInitializer.cs


namespace EF_Code_First.WarehouseData
{
public class WarehouseInitializer : DropCreateDatabaseAlways<WarehouseContext>
{
protected override void Seed(WarehouseContext context)
{
}
}
}

Dro pCre at e Dat abase Always will always dro p and recreate the database, and o ptio nally seed the
database the first time a co ntext is used in the applicatio n do main.
pro t e ct e d o ve rride vo id Se e d is a member functio n o f the namespace System.Data.Entity that seeds
the database with default values if we cho o se to do so .

We need to mo dify o ne mo re file befo re we can run o ur applicatio n. Mo dify /Glo bal.asax.cs as sho wn:
CODE TO TYPE: /Glo bal.asax.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Http;
using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;
using System.Data.Entity;
using EF_Code_First.WarehouseData;

namespace EF_Code_First
{
// Note: For instructions on enabling IIS6 or IIS7 classic mode,
// visit http://go.microsoft.com/?LinkId=9394801

public class MvcApplication : System.Web.HttpApplication


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

WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
AuthConfig.RegisterAuth();
var context = new WarehouseContext();
context.Database.Initialize(true);
}
}
}

/Glo bal.asax.cs
var context = new WarehouseContext();
context.Database.Initialize(true);

var co nt e xt = ne w Ware ho use Co nt e xt (); instantiates a new Wareho useCo untext o bject.
co nt e xt .Dat abase .Init ialize (t rue ); fo rces o ur database to be initialized and inserts o ur tables.

and . All o f o ur changes were o n the back end o f the applicatio n, so there is no visible change to the default
applicatio n.

We've created a database named Wareho use, and inserted tables named Shipments and Wareho use. Click the Sho w

All Files butto n in So lutio n Explo rer and expand the App_Dat a fo lder, and yo u'll see o ur Wareho use.mdf file.
Do uble-click the Ware ho use .m df file; then, in the Server Explo rer o n the left, select and expand Ware ho use .m df .
Yo u see all the tables under Dat a Co nne ct io ns | T able s.

A co mmo n practice in do main drive design is to create mo dels that are co nsidered Co mplex. These Co mplex types
are layered to create a co mplete entity. Our Shipments mo del co ntains pro perties that co uld be abstracted to a
Co mplex type. Let's remo ve the Se ndT o Addre ss and Se nt Fro m Addre ss pro perties and place these in a co mplex
type called Addre ss.
Create a new class in the /Mo de ls fo lder and name it Addre ss, then mo dify it as sho wn:

CODE TO TYPE: /Mo dels/Address.cs


using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Data.Entity;
using System.ComponentModel.DataAnnotations.Schema;

namespace EF_Code_First.Models
{
[ComplexType]
public class Address
{
public string Country { get; set; }
public string State { get; set; }
public string City { get; set; }
public string Street { get; set; }
public int Zip { get; set; }
public string HousingNumber { get; set; }
}
}

We created a new Mo del named Address that is a co mplex type, meaning we do n't want to add it to any table, and we
do n't want to create any entities with it. No w we need to add this to o ur Shipments and Wareho use entities. Mo dify
/Mo de ls/Shipm e nt s.cs as sho wn:

CODE TO TYPE: /Mo dels/Shipments.cs


using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel.DataAnnotations;

namespace EF_Code_First.Models
{
public class Shipments
{
public Shipments()
{
Sender = new Address();
Recipient = new Address();
}

[Key]
public int TrackingNumber { get; set; }
public int NumberOfItems { get; set; }
public string SendToAddress { get; set; }
public string SentFromAddress { get; set; }
public DateTime ShippedDateTime { get; set; }
public DateTime ReceivedDateTime { get; set; }
public Address Sender { get; set; }
public Address Recipient { get; set; }
public int WarehouseId { get; set; }
public virtual Warehouse WarehouseNavigation { get; set; }
}
}

public virt ual Ware ho use Ware ho use Navigat io n { ge t ; se t ; } defines a navigatio n pro perty to o ur Wareho use
entity. We'll explain this co ncept in mo re depth in the LINQ to Entities sectio n.

Mo dify /Mo de ls/Ware ho use .cs again, as sho wn:


CODE TO TYPE: /Mo dels/Wareho use.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel.DataAnnotations;

namespace EF_Code_First.Models
{
public class Warehouse
{
public Warehouse() { WarehouseAddress = new Address(); }
public int Id { get; set; }
public string Name { get; set; }
public string Address { get; set; }
public Address WarehouseAddress { get; set; }
public ICollection<Shipments> shipments { get; set; }
}
}

Befo re we can co mpile and run o ur applicatio n, we need to change o ur Seed metho d to include this new info rmatio n.
Mo dify /Ware ho use Dat a/Ware ho use Init ialize r.cs as sho wn.
CODE TO TYPE: /Wareho useData/Wareho useInitializer.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Data.Entity;
using EF_Code_First.Models;

namespace EF_Code_First.WarehouseData
{
public class WarehouseInitializer : DropCreateDatabaseIfModelChanges<WarehouseConte
xt>
{
protected override void Seed(WarehouseContext context)
{
var _warehouse = new List<Warehouse>()
{
new Warehouse { Name = "Warehouse 1", WarehouseAddress = { Country ="Ja
pan", State = "Province B", City = "Xiangzhou", Zip = 65147, Street ="Warehouse Way", H
ousingNumber = "1000"}, shipments = new List<Shipments>() },
new Warehouse { Name = "Warehouse 2", WarehouseAddress = { Country ="Un
ited States", State = "LA", City = "Tabasco", Zip = 91542, Street = "Warehouse Way", Ho
usingNumber = "2000"}, shipments = new List<Shipments>() },
new Warehouse { Name = "Warehouse 3", WarehouseAddress = { Country ="Br
azil", State = "Small", City = "Beach Town", Zip = 89245, Street = "Warehouse Way", Hou
singNumber = "3000"}, shipments = new List<Shipments>() }
};
_warehouse.ForEach(x => context.Warehouse.Add(x));
context.SaveChanges();

var _shipments = new List<Shipments>()


{
new Shipments { NumberOfItems = 5, Recipient = {Country = "United State
s", State = "NV", City ="Tahoe", Zip = 89712, Street = "Main St", HousingNumber = "588
Ste. A"}, Sender = {Country = "United States", State = "MN", City ="Lakeview", Zip = 56
487, Street = "Kilimanjaro Ln", HousingNumber = "6589"}, ShippedDateTime = new System.D
ateTime(2001, 12, 28), ReceivedDateTime = new System.DateTime(2001, 12, 22), WarehouseI
d = _warehouse[0].Id },
new Shipments { NumberOfItems = 1, Recipient = {Country = "United State
s", State = "CA", City ="San Diego", Zip = 92120, Street = "Testing Circle", HousingNum
ber = "415 Apt. J"}, Sender = {Country = "United States", State = "SD", City ="Fargo",
Zip = 65874, Street = "Cyber Way", HousingNumber = "241 Ste J"}, ShippedDateTime = new
System.DateTime(2004, 1, 13), ReceivedDateTime = new System.DateTime(2004, 1, 15), Ware
houseId = _warehouse[0].Id },
new Shipments { NumberOfItems = 10, Recipient = {Country = "United Stat
es", State = "NY", City ="New York", Zip = 87871, Street = "Empire Ln", HousingNumber =
"2145"}, Sender = {Country = "United States", State = "ND", City ="Northern", Zip = 62
513, Street = "Nougat Ct", HousingNumber = "6589 A"}, ShippedDateTime = new DateTime(20
11, 2, 13), ReceivedDateTime = new DateTime(2011, 2, 23), WarehouseId = _warehouse[0].I
d },
new Shipments { NumberOfItems = 15, Recipient = {Country = "United Stat
es", State = "MO", City ="Bozeman", Zip = 89201, Street = "Chicken Way", HousingNumber
= "128"}, Sender = {Country = "United States", State = "SD", City ="Slates", Zip = 3152
4, Street = "Leik Circle", HousingNumber = "124"}, ShippedDateTime = new DateTime(2011,
2, 13), ReceivedDateTime = new DateTime(2011, 2, 23), WarehouseId = _warehouse[1].Id }
,
new Shipments { NumberOfItems = 3, Recipient = {Country = "United State
s", State = "OR", City ="Salem", Zip = 20152, Street = "Milky Way Ln", HousingNumber =
"6542"}, Sender = {Country = "United States", State = "CA", City ="Sharp", Zip = 98457,
Street = "Nimdoy Ct", HousingNumber = "6578"}, ShippedDateTime = new DateTime(2011, 2,
13), ReceivedDateTime = new DateTime(2011, 2, 23), WarehouseId = _warehouse[1].Id },
new Shipments { NumberOfItems = 8, Recipient = {Country = "United State
s", State = "KY", City ="Campbellsville", Zip = 42718, Street = "Williams St", HousingN
umber = "4233"}, Sender = {Country = "United States", State = "TX", City ="Mackay", Zip
= 31542, Street = "Walkens Way", HousingNumber = "5487"}, ShippedDateTime = new DateTi
me(2012, 5, 1), ReceivedDateTime = new DateTime(2012, 5, 21), WarehouseId = _warehouse[
1].Id },
new Shipments { NumberOfItems = 45, Recipient = {Country = "United Stat
es", State = "TX", City ="Honeybun", Zip = 43770, Street = "Milk and Cookies Way", Hous
ingNumber = "5487"}, Sender = {Country = "United States", State = "ID", City ="Manchest
er", Zip = 59014, Street = "Quahog St", HousingNumber = "6958"}, ShippedDateTime = new
DateTime(2001, 8, 5), ReceivedDateTime = new DateTime(2012, 3, 12), WarehouseId = _ware
house[2].Id },
new Shipments { NumberOfItems = 24, Recipient = {Country = "United Stat
es", State = "MA", City ="Middleton", Zip = 65879, Street = "One Way E. Ln", HousingNum
ber = "215"}, Sender = {Country = "United States", State = "UT", City ="Milk", Zip = 20
154, Street = "Juke Way", HousingNumber = "3654 Bldg B"}, ShippedDateTime = new DateTim
e(2009, 8, 5), ReceivedDateTime = new DateTime(2009, 9, 1), WarehouseId = _warehouse[2]
.Id },
new Shipments { NumberOfItems = 12, Recipient = {Country = "United Stat
es", State = "MN", City ="Minnetonka", Zip = 31254, Street = "Informational Way", Housi
ngNumber = "2121"}, Sender = {Country = "United States", State = "AZ", City ="Snickers"
, Zip = 35126, Street = "Kreet Ln", HousingNumber = "5487 Ste A"}, ShippedDateTime = ne
w DateTime(1995, 4, 12), ReceivedDateTime = new DateTime(1995, 4, 14), WarehouseId = _w
arehouse[2].Id }
};
_shipments.ForEach(x => context.Shipments.Add(x));
context.SaveChanges();

_warehouse[0].shipments.Add(_shipments[0]);
_warehouse[0].shipments.Add(_shipments[1]);
_warehouse[0].shipments.Add(_shipments[2]);
_warehouse[1].shipments.Add(_shipments[3]);
_warehouse[1].shipments.Add(_shipments[4]);
_warehouse[1].shipments.Add(_shipments[5]);
_warehouse[2].shipments.Add(_shipments[6]);
_warehouse[2].shipments.Add(_shipments[7]);
_warehouse[2].shipments.Add(_shipments[8]);
context.SaveChanges();
}
}
}

No w that o ur Entities are pro perly designed, we need to change o ur /Glo bal.asax file to seed o ur database using o ur
Wareho useInitializer. Mo dify the co de as sho wn:
CODE TO TYPE: /Glo bal.asax
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Http;
using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;
using System.Data.Entity;
using EF_Code_First.WarehouseData;

namespace EF_Code_First
{
// Note: For instructions on enabling IIS6 or IIS7 classic mode,
// visit http://go.microsoft.com/?LinkId=9394801

public class MvcApplication : System.Web.HttpApplication


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

WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
AuthConfig.RegisterAuth();
var context = new WarehouseContext();
context.Database.Initialize(true);
Database.SetInitializer<WarehouseContext>(new WarehouseInitializer());
}
}
}

No w, let's create a Co ntro ller fo r o ur Shipments Mo del. Right-click the Co nt ro lle rs fo lder and select Add |
Co nt ro lle r.... Name it Shipm e nt sCo nt ro lle r. Set the template to Em pt y MVC co nt ro lle r:

Mo dify the co ntro ller as sho wn:


CODE TO TYPE: /Co ntro llers/ShipmentsCo ntro ller.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using EF_Code_First.Models;
using EF_Code_First.WarehouseData;

namespace EF_Code_First.Controllers
{
public class ShipmentsController : Controller
{
WarehouseContext context = new WarehouseContext();
//
// GET: /Shipments/

public ActionResult Index()


{
return View(context.Shipments.ToList());
}

}
}

We need to add a View so we can see what's being listed. Right-click the Index metho d and cho o se Add Vie w:

Set the Scaffo ld Template to List and set the Mo del class to Shipm e nt s (EF_Co de _First .Mo de ls) as sho wn:
Entity Framewo rk creates a basic layo ut fo r us in /Vie ws/Shipm e nt s/Inde x.csht m l:
/Views/Shipments/Index.cshtml View file
@model IEnumerable<EF_Code_First.Models.Shipments>

@{
ViewBag.Title = "Index";
}

<h2>Index</h2>

<p>
@Html.ActionLink("Create New", "Create")
</p>
<table>
<tr>
<th>
@Html.DisplayNameFor(model => model.NumberOfItems)
</th>
<th>
@Html.DisplayNameFor(model => model.ShippedDateTime)
</th>
<th>
@Html.DisplayNameFor(model => model.ReceivedDateTime)
</th>
<th>
@Html.DisplayNameFor(model => model.WarehouseId)
</th>
<th></th>
</tr>

@foreach (var item in Model) {


<tr>
<td>
@Html.DisplayFor(modelItem => item.NumberOfItems)
</td>
<td>
@Html.DisplayFor(modelItem => item.ShippedDateTime)
</td>
<td>
@Html.DisplayFor(modelItem => item.ReceivedDateTime)
</td>
<td>
@Html.DisplayFor(modelItem => item.WarehouseId)
</td>
<td>
@Html.ActionLink("Edit", "Edit", new { id=item.TrackingNumber }) |
@Html.ActionLink("Details", "Details", new { id=item.TrackingNumber }) |
@Html.ActionLink("Delete", "Delete", new { id=item.TrackingNumber })
</td>
</tr>
}

</table>

and and navigate to the Shipments Index by adding /Shipm e nt s to the URL in the address bar:
If yo u enco unter an erro r message like "Canno t create file because it already exists," click the Sho w All
File s ico n at the to p o f the So lutio n Explo rer, expand the App_Dat a fo lder, expand the Ware ho use .m df
file, delete the files with the .ldf extensio n, and try again. These are lo g files used fo r tracking what
Note happens in the SQL database. In a pro ductio n enviro nment, yo u wo uld perfo rm a co mplete backup o f
yo ur data and lo g files befo re remo ving these files, but in this case, because we're creating and seeding a
new database that wo n't be used in a pro ductio n co ntext, it's o kay to delete them.

No tice there are no addresses listed. Let's fix that. Mo dify /Vie ws/Shipm e nt s/Inde x.csht m l as sho wn:
CODE TO TYPE: Adding addresses to the index view o f Shipments

@model IEnumerable<EF_Code_First.Models.Shipments>

@{
ViewBag.Title = "Index";
}

<h2>Index</h2>

<p>
@Html.ActionLink("Create New", "Create")
</p>
<table>
<tr>
<th>
@Html.DisplayNameFor(model => model.NumberOfItems)
</th>
<th>
Sender Address
</th>
<th>
@Html.DisplayNameFor(model => model.ShippedDateTime)
</th>
<th>
Recipient Address
</th>
<th>
@Html.DisplayNameFor(model => model.ReceivedDateTime)
</th>
<th>
@Html.DisplayNameFor(model => model.WarehouseId)
</th>
<th></th>
</tr>

@foreach (var item in Model) {


<tr>
<td>
@Html.DisplayFor(modelItem => item.NumberOfItems)
</td>
<td>
@Html.DisplayFor(modelItem => item.Sender.Country)<br />
@Html.DisplayFor(modelItem => item.Sender.HousingNumber) @Html.DisplayFor(m
odelItem => item.Sender.Street), @Html.DisplayFor(modelItem => item.Sender.State)<br />
@Html.DisplayFor(modelItem => item.Sender.Zip)
</td>
<td>
@Html.DisplayFor(modelItem => item.ShippedDateTime)
</td>
<td>
@Html.DisplayFor(modelItem => item.Recipient.Country)<br />
@Html.DisplayFor(modelItem => item.Recipient.HousingNumber) @Html.DisplayFo
r(modelItem => item.Recipient.Street), @Html.DisplayFor(modelItem => item.Recipient.Sta
te)<br />
@Html.DisplayFor(modelItem => item.Recipient.Zip)
</td>
<td>
@Html.DisplayFor(modelItem => item.ReceivedDateTime)
</td>
<td>
@Html.DisplayFor(modelItem => item.WarehouseId)
</td>
<td>
@Html.ActionLink("Edit", "Edit", new { id=item.TrackingNumber }) |
@Html.ActionLink("Details", "Details", new { id=item.TrackingNumber }) |
@Html.ActionLink("Delete", "Delete", new { id=item.TrackingNumber })
</td>
</tr>
}

</table>

and and navigate to the Index o f Shipments again. It no w displays all the info rmatio n we want.

We'll co ver the Database First appro ach in the Entity Data Mo del (EDM) sectio n.

Data Abstraction
In this sectio n, we'll co ver Data Abstractio n in terms o f the Repo sito ry and Unit o f Wo rk patterns. These patterns create
an abstractio n layer between the data access and business lo gic layer o f o ur applicatio ns.

The metho d we've been using to create relatio nships has been a straightfo rward metho d that go es fro m IIS to the
co ntro ller, and directly fro m the co ntro ller to the DbCo ntext, and then to the Entity Framewo rk and database. The Unit o f
Wo rk and Repo sito ry patterns add ano ther level o f abstractio n between the co ntro ller and Entity Framewo rk and
database phase.
OBSERVE: Difference between no abstractio n and Unit o f Wo rk and Repo sito ry

No Data Abstraction With Data Abstraction

------------------- ----------------------
| IIS | | IIS |
------------------- ----------------------
| |
| |
V V
------------------- ----------------------
| Controller | | Controller |
------------------- ----------------------
| |
| |
| V
| ----------------------
| | Unit of Work |
| | __________________ |
| | | Repositories | |
V | ------------------ |
------------------- | __________________ |
| DbContext | | | DbContext | |
------------------- | ------------------ |
| | |
| ----------------------
V |
------------------- |
| Entity Framework| V
|-----------------| ----------------------
| Database | | Entity Framework |
------------------- ----------------------
| Database |
----------------------

Repository
Let's put Data Abstractio n to wo rk. Create a fo lder named Abst ract io nLaye r. In this fo lder, add a class
named IShipm e nt Re po sit o ry:
Mo dify this new file as sho wn:

CODE TO TYPE: IShipmentRepo sito ry.cs


using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using EF_Code_First.Models;

namespace EF_Code_First.AbstractionLayer
{
public class interface IShipmentRepository : IDisposable
{
IEnumerable<Shipments> GetAllShipments();
Shipments GetByTrackingNumber(int trackingNumber);
void CreateNewShipment(Shipments shipment);
void RemoveShipment(int trackingNumber);
void ModifyShipment(Shipments shipment);
void saveShipment();
}
}

int e rf ace will be discussed in a future lesso n. In case yo u want a preview o f co ming
Note attractio ns, here is an interface reference.

Let's discuss this class declaratio n:

OBSERVE:
public interface IShipmentRepository : IDisposable

IShipmentRepo sito ry inherits the IDispo sable interface as a means to release allo cated services. In o ur
case, IShipmentRepo sito ry will be the DbCo ntext. We've set up o ur interface fo r the next lesso n to use the
Entity Framewo rks CRUD o peratio ns.

T ip CRUD is an acro nym fo r Create, Read, Update, Delete.

To implement o ur newly created repo sito ry, add a new class to the Abst ract io nLaye r fo lder named
Shipm e nt Re po sit o ry.

Mo dify the Shipm e nt Re po sit o ry.cs class so we can begin to use the interface we just created:
CODE TO TYPE: ShipmentRepo sito ry.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Data;
using EF_Code_First.Models;
using EF_Code_First.WarehouseData;

namespace EF_Code_First.AbstractionLayer
{
public class ShipmentRepository : IShipmentRepository, IDisposable
{
private WarehouseContext context;

public ShipmentRepository(WarehouseContext context)


{
this.context = context;
}

public IEnumerable<Shipments> GetAllShipments()


{
return context.Shipments.ToList();
}

public Shipments GetByTrackingNumber(int trackingNumber)


{
return context.Shipments.Find(trackingNumber);
}

public void CreateNewShipment(Shipments shipment)


{
context.Shipments.Add(shipment);
}

public void RemoveShipment(int trackingNumber)


{
Shipments shipment = context.Shipments.Find(trackingNumber);
context.Shipments.Remove(shipment);
}

public void ModifyShipment(Shipments shipment)


{
context.Entry(shipment).State = EntityState.Modified;
}

public void saveShipment()


{
context.SaveChanges();
}

private bool ContextDisposed = false;

protected virtual void Dispose(bool disposal)


{
if (!this.ContextDisposed)
{
if (disposal)
{
context.Dispose();
}
}
this.ContextDisposed = true;
}

public void Dispose()


{
Dispose(true);
GC.SuppressFinalize(this);
}
}
}

We've added quite a bit o f co de, and so me o f it may be a little co nfusing. Let's discuss what we've do ne with
the repo sito ry.

OBSERVE:

.
.
.
private WarehouseContext context;

public ShipmentRepository(WarehouseContext context)


.
.
.
private bool ContextDisposed = false;

protected virtual void Dispose(bool disposal)


{
if (!this.ContextDisposed)
{
if (disposal)
{
context.Dispose();
}
}
this.ContextDisposed = true;
}

public void Dispose()


{
Dispose(true);
GC.SuppressFinalize(this);
}
.
.
.

privat e Ware ho use Co nt e xt co nt e xt defines a lo cal variable to ho ld o ur database co ntext.


public Shipm e nt Re po sit o ry(Ware ho use Co nt e xt co nt e xt ) defines a Co nstructo r fo r o ur
repo sito ry that expects a Wareho useCo ntext o bject to be passed in when the repo sito ry is
instantiated.
privat e bo o l Co nt e xt Dispo se d = f alse declares a lo cal bo o lean variable to use as a flag that
indicates whether o ur co ntext has been dispo sed.
The public vo id Dispo se () functio n accepts no arguments, but makes a self-referential call to an
o verlo aded functio n pro t e ct e d virt ual vo id Dispo se (bo o l dispo sal). (This usage will beco me
clearer when we create o ur Co ntro ller.)
GC.Suppre ssFinalize (t his); tells o ur Garbage Co llecto r(GC) that o ur o bject was cleaned up
pro perly and that it do es no t need to go into the finalizer queue.

SuppressFinalize sho uld o nly be called o n classes that have finalizers. Finalizers mimic C++
T ip destructo rs, but perfo rm co mpletely different o peratio ns.

and Build the pro ject to make sure we have no erro rs.

Unit of Work
No w, we'll create o ur Unit o f Wo rk class. Befo re we can add the items that are necessary fo r this sectio n, we
need to create a new Entity and co mplex mo del.

Add a new mo del to the /Mo de ls fo lder named Pe rso n, and mo dify it as sho wn:

CODE TO TYPE: /Mo dels/Perso n.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Data.Entity;
using System.ComponentModel.DataAnnotations.Schema;

namespace EF_Code_First.Models
{
[ComplexType]
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string MiddleName { get; set; }
public string SocialSecurity { get; set; }
public string FullName
{
get
{
return FirstName + " " + MiddleName[0] + ". " + LastName;
}
}
}
}

Right-click the /Mo de ls fo lder and add a new Em plo ye e class. Add so me pro perties fo r o ur Emplo yee entity
as sho wn:

CODE TO TYPE: /Mo dels/Emplo yee.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Data.Entity;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace EF_Code_First.Models
{
public class Employee
{
public Employee() { person = new Person(); }

[Key]
[Required]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int EmployeeId { get; set; }
public string Position { get; set; }
public Person person { get; set; }
public int WarehouseId { get; set; }
public virtual Warehouse EmployeeLocation { get; set; }
}
}
OBSERVE:
[Key]
[Required]
[DatabaseGenerated(DatabaseGeneratedOption.None)]

In the co de abo ve, [Dat abase Ge ne rat e d(Dat abase Ge ne rat e dOpt io n.No ne )] tells EF we want to define
a key attribute manually fo r this entity and that the database sho uld no t do it fo r us.

Add a list o f Emplo yees to the Wareho use Entity. Mo dify /Mo de ls/Ware ho use .cs as sho wn:

CODE TO TYPE:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel.DataAnnotations;

namespace EF_Code_First.Models
{
public class Warehouse
{
public Warehouse() { WarehouseAddress = new Address(); }

public int Id { get; set; }


public string Name { get; set; }
public Address WarehouseAddress { get; set; }
public ICollection<Shipments> shipments { get; set; }
public ICollection<Employee> employees { get; set; }
}
}

Add the Emplo yee Entity to o ur /Ware ho use Dat a/Ware ho use Co nt e xt .cs file so we can create a table in
o ur database:

CODE TO TYPE:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Data.Entity;
using EF_Code_First.Models;
using System.Data.Entity.ModelConfiguration.Conventions;

namespace EF_Code_First.WarehouseData
{
public class WarehouseContext : DbContext
{
public DbSet<Warehouse> Warehouse { get; set; }
public DbSet<Shipments> Shipments { get; set; }
public DbSet<Employee> Employees { get; set; }

protected override void OnModelCreating(DbModelBuilder modelBuilder)


{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
}
}
}

Add so me initializer info rmatio n fo r o ur newly created Emplo yee entity in Ware ho use Init ialize r.cs:
CODE TO TYPE: Seeding Emplo yee Entity in Wareho useInitializer.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Data.Entity;
using EF_Code_First.Models;

namespace EF_Code_First.WarehouseData
{
public class WarehouseInitializer : DropCreateDatabaseAlways<WarehouseContex
t>
{
protected override void Seed(WarehouseContext context)
{
var _warehouse = new List<Warehouse>()
{
new Warehouse { Name = "Warehouse 1", WarehouseAddress = { Count
ry ="Japan", State = "Province B", City = "Xiangzhou", Zip = 65147, Street ="War
ehouse Way", HousingNumber = "1000"}, shipments = new List<Shipments>(), employe
es = new List<Employee>() },
new Warehouse { Name = "Warehouse 2", WarehouseAddress = { Count
ry ="United States", State = "LA", City = "Tabasco", Zip = 91542, Street = "Ware
house Way", HousingNumber = "2000"}, shipments = new List<Shipments>(), employee
s = new List<Employee>() },
new Warehouse { Name = "Warehouse 3", WarehouseAddress = { Count
ry ="Brazil", State = "Small", City = "Beach Town", Zip = 89245, Street = "Wareh
ouse Way", HousingNumber = "3000"}, shipments = new List<Shipments>(), employees
= new List<Employee>() }
};
_warehouse.ForEach(x => context.Warehouse.Add(x));
context.SaveChanges();
.
.
.

var _employees = new List<Employee>()


{
new Employee { EmployeeId = 15789, Position = "Administrator", p
erson = { FirstName ="Robert", LastName ="Mikolnag", MiddleName ="Indo", SocialS
ecurity ="012-034-0678" }, WarehouseId = _warehouse[0].Id },
new Employee { EmployeeId = 18754, Position = "Shipping Tagger",
person = { FirstName ="Melissa", LastName ="Lockie", MiddleName ="Emerald", Soc
ialSecurity ="012-067-0578" }, WarehouseId = _warehouse[0].Id },
new Employee { EmployeeId = 12458, Position = "Receiving Coordin
ator", person = { FirstName ="Shawndra", LastName ="Namind", MiddleName ="Ko", S
ocialSecurity ="012-015-0789" }, WarehouseId = _warehouse[0].Id },
new Employee { EmployeeId = 22541, Position = "Administrator", p
erson = { FirstName ="Michael", LastName ="Hilter", MiddleName ="May", SocialSec
urity ="012-023-0875" }, WarehouseId = _warehouse[1].Id },
new Employee { EmployeeId = 29875, Position = "Floor Manager", p
erson = { FirstName ="Miranda", LastName ="Miltred", MiddleName ="Liner", Social
Security ="012-098-0632" }, WarehouseId = _warehouse[1].Id },
new Employee { EmployeeId = 27898, Position = "Forklift Operator
", person = { FirstName ="Joe", LastName ="Banderfield", MiddleName ="Barry", So
cialSecurity ="012-032-0808" }, WarehouseId = _warehouse[1].Id },
new Employee { EmployeeId = 32565, Position = "Janitor", person
= { FirstName ="Bob", LastName ="Kaler", MiddleName ="Kalifa", SocialSecurity ="
012-032-0505" }, WarehouseId = _warehouse[2].Id },
new Employee { EmployeeId = 30054, Position = "Logistics Adminis
trator", person = { FirstName ="Misty", LastName ="Contreal", MiddleName ="Nancy
", SocialSecurity ="012-075-3030" }, WarehouseId = _warehouse[2].Id },
new Employee { EmployeeId = 30821, Position = "Web Developer", p
erson = { FirstName ="Carrie", LastName ="Aderly", MiddleName ="Kimto", SocialSe
curity ="012-054-7077" }, WarehouseId = _warehouse[2].Id }
};
_employees.ForEach(x => context.Employees.Add(x));
context.SaveChanges();

.
.
.

_warehouse[0].employees.Add(_employees[0]);
_warehouse[0].employees.Add(_employees[1]);
_warehouse[0].employees.Add(_employees[2]);
_warehouse[1].employees.Add(_employees[3]);
_warehouse[1].employees.Add(_employees[4]);
_warehouse[1].employees.Add(_employees[5]);
_warehouse[2].employees.Add(_employees[6]);
_warehouse[2].employees.Add(_employees[7]);
_warehouse[2].employees.Add(_employees[8]);
context.SaveChanges();
}
}
}

and Build the applicatio n, but do n't run it yet.

No w that we've established o ur Entity Framewo rk, let's begin building o ur Unit o f Wo rk mo del. We'll start by
building a Generic Repo sito ry. It will be much like the Shipment Repo sito ry, but far less dependent o n a type.

In the /Abst ract io nLaye r fo lder, create a new class named Ge ne ricRe po .cs. Add so me functio nality to o ur
newly created repo sito ry as sho wn:
CODE TO TYPE: /Abstractio nLayer/GenericRepo .cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Data;
using System.Data.Entity;
using System.Linq.Expressions;
using EF_Code_First.Models;
using EF_Code_First.WarehouseData;

namespace EF_Code_First.AbstractionLayer
{
public class GenericRepo<EntityType> where EntityType : class
{
internal WarehouseContext context;
internal DbSet<EntityType> DatabaseSet;

public GenericRepo(WarehouseContext context)


{
this.context = context;
this.DatabaseSet = context.Set<EntityType>();
}

public virtual IEnumerable<EntityType> Retrieve(


Expression<Func<EntityType, bool>> filter = null,
Func<IQueryable<EntityType>, IOrderedQueryable<EntityType>> orderBy
= null,
string includeProperties = "")
{
IQueryable<EntityType> DbQuery = DatabaseSet;

if (filter != null)
{
DbQuery = DbQuery.Where(filter);
}

foreach (var incProp in includeProperties.Split(new char[] { ',' },


StringSplitOptions.RemoveEmptyEntries))
{
DbQuery = DbQuery.Include(incProp);
}

if (orderBy != null)
{
return orderBy(DbQuery).ToList();
}
else
{
return DbQuery.ToList();
}
}

public virtual EntityType GetEntitiesById(object id)


{
return DatabaseSet.Find(id);
}

public virtual void AddEntity(EntityType entityName)


{
DatabaseSet.Add(entityName);
}

public virtual void RemoveEntityEntry(object id)


{
EntityType EntityToRemove = DatabaseSet.Find(id);
RemoveEntityEntry(EntityToRemove);
}
public virtual void RemoveEntityEntry(EntityType entity)
{
if (context.Entry(entity).State == EntityState.Detached)
{
DatabaseSet.Attach(entity);
}
DatabaseSet.Remove(entity);
}

public virtual void UpdateEntity(EntityType entity)


{
DatabaseSet.Attach(entity);
context.Entry(entity).State = EntityState.Modified;
}
}
}

Let's discuss what we've do ne in o ur new repo sito ry.

OBSERVE:
internal DbSet<EntityType> DatabaseSet;

public GenericRepo(WarehouseContext context)


{
this.context = context;
this.DatabaseSet = context.Set<EntityType>();
}

public virtual IEnumerable<EntityType> Retrieve(


Expression<Func<EntityType, bool>> filter = null,
Func<IQueryable<EntityType>, IOrderedQueryable<EntityType>> orderBy
= null,
string includeProperties = "")
.
.
.
public virtual EntityType GetEntitiesById(object id)

int e rnal DbSe t <Ent it yT ype > Dat abase Se t creates a class variable fo r the entity set fo r which
the repo sito ry is instantiated.
The Co nst ruct o r accepts o ur wareho use co ntext o bject and initializes o ur DatabaseSet variable.
The Re t rie ve metho d uses Lambda Expressio ns to allo w fo r an o ptio nal metho d to filter the
results o r o rder them by an entity pro perty. It receives a co mma-delimited set o f strings that
represent navigatio n pro perties. This metho d pro vides an eager-lo ading expressio n.
The Ge t Ent it ie sById(o bje ct id) metho d returns a single item that is retrieved using a pro perty
o f an entity.
The rest o f the functio ns perfo rm basic o peratio ns such as Insert, Delete, and Update.

So , why wo uld we want a generic repo sito ry when we co uld simply create a repo sito ry fo r every entity we
create? By creating a generic repo sito ry that is extensible and independent o f types, we reduce the amo unt o f
co de redundancy, and create a centralized area where we can make mo dificatio ns to the way we interact with
o ur info rmatio n.

No w we can create a Unit o f Wo rk class to ensure that o ur Entities share the same co ntext. In this way, the
Entities will no t get o ut o f sync and will update as we expect them to , witho ut o ur having to update each entity
manually.

In the /Abst ract io nLaye r fo lder, create a new class named Ware ho use Wo rkUnit .cs, and mo dify it as
sho wn:
CODE TO TYPE: /Abstractio nLayer/Wareho useWo rkUnit.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using EF_Code_First.Models;
using EF_Code_First.WarehouseData;

namespace EF_Code_First.AbstractionLayer
{
public class WarehouseWorkUnit : IDisposable
{
private WarehouseContext context = new WarehouseContext();
private GenericRepo<Warehouse> _WarehouseRepository_;
private GenericRepo<Employee> _EmployeeRepository_;

public GenericRepo<Warehouse> WarehouseRepository


{
get
{
if (this._WarehouseRepository_ == null)
{
this._WarehouseRepository_ = new GenericRepo<Warehouse>(cont
ext);
}
return _WarehouseRepository_;
}
}

public GenericRepo<Employee> EmployeeRepository


{
get
{
if (this._EmployeeRepository_ == null)
{
this._EmployeeRepository_ = new GenericRepo<Employee>(contex
t);
}
return _EmployeeRepository_;
}
}

public void Save()


{
context.SaveChanges();
}

private bool disposed = false;


protected virtual void Dispose(bool disposal)
{
if (!this.disposed)
{
if (disposal)
{
context.Dispose();
}
}
this.disposed = true;
}

public void Dispose()


{
context.Dispose();
GC.SuppressFinalize(this);
}
}
}
Let's discuss so me o f this co de.

/Abstractio nLayer/Wareho useWo rkUnit.cs

.
.
.

namespace EF_Code_First.AbstractionLayer
{
public class WarehouseWorkUnit : IDisposable
{
private WarehouseContext context = new WarehouseContext();
private GenericRepo<Warehouse> _WarehouseRepository_;
private GenericRepo<Employee> _EmployeeRepository_;

public GenericRepo<Warehouse> WarehouseRepository


{
get
{
if (this._WarehouseRepository_ == null)
{
this._WarehouseRepository_ = new GenericRepo<Warehouse>(cont
ext);
}
return _WarehouseRepository_;
}
}

public GenericRepo<Employee> EmployeeRepository


{
get
{
if (this._EmployeeRepository_ == null)
{
this._EmployeeRepository_ = new GenericRepo<Employee>(contex
t);
}
return _EmployeeRepository_;
}
}
.
.
.

privat e Ware ho use Co nt e xt co nt e xt = ne w Ware ho use Co nt e xt (); instantiates a new


Wareho useCo ntext o bject.
privat e Ge ne ricRe po <> creates o ur repo sito ry o bjects by using the appro priate entity in the
templated GenericRepo .
In the Ware ho use Re po sit o ry and Em plo ye e Re po sit o ry metho ds, we check to see if there is
an existing co ntext o bject; if there isn't, we instantiate a new o bject by passing in o ur co ntext o bject.

and Build the so lutio n. No w that we have a Generic Repo sito ry and a Unit o f Wo rk class, we will sto p this
lesso n here. In the next lesso n, we'll finish this pro ject. See yo u there!

Copyright © 1998-2014 O'Reilly Media, Inc.

This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.
See http://creativecommons.org/licenses/by-sa/3.0/legalcode for more information.
Object Relational Mapping: CRUD and Automatic Code
Generation, LINQ, and Entity Data Model
Lesson Objectives
In this lesso n, yo u will:

implement applicatio n views with Entity Framewo rks CRUD design and mo dify them to use different design patterns
to increase efficiency and pro ductivity.
implement data abstractio n to create a mo re ro bust applicatio n fo r real-wo rld develo pment.
review different design patterns that make co llabo rative design mo re effective and efficient.
perfo rm LINQ queries using the different syntaxes to retrieve info rmatio n fro m tables using navigatio n pro perties.
create an applicatio n fro m an existing database using ADO.NET to inco rpo rate applicatio n data fro m a pre-existing
applicatio n.

This lesso n explo res the benefits o f abstracting data acro ss an applicatio n, as well as using LINQ to create data techno lo gy
independent so lutio ns. Yo u will learn to develo p an applicatio n using Entity Framewo rks "Create, Read, Update, Delete
(CRUD)" generic design pattern, and to mo dify and extend the existing ASP.NET design pattern. Yo u will also learn to
implement an existing database into a new o r existing applicatio n.

In this co ntinuatio n o f Object Relatio nal Mapping, we will inco rpo rate CRUD ro utines and interact with these ro utines using the
Unit o f Wo rk design pattern. We'll also co ver the Database First appro ach using the ADO.NET framewo rk.

CRUD and Automatic Code Generation


Repository with CRUD
As pro mised, we'll co ntinue wo rking with the EF_Co de _First so lutio n, so go ahead and o pen that if it's no t
o pen already. Remo ve the Co ntro ller and View fo r the Shipments entity we created in the Entity Framewo rk
sectio n. Right-click the Shipments View fo lder and select De le t e :

Do the same with the /Co nt ro lle rs/Shipm e nt sCo nt ro lle r.cs file.

No w create a Shipments co ntro ller using EF's CRUD o ptio n:

Right-click the Co nt ro lle rs fo lder and cho o se Add | Co nt ro lle r...


Set the co ntro ller name to Shipm e nt sCo nt ro lle r with these o ptio ns:

T e m plat e : MVC co ntro ller with Read/Write actio ns and Views, using Entity Framewo rk
Mo de l class: Shipments (EF_Co de_First.Mo dels)
Dat a co nt e xt class: Wareho useCo ntext (EF_Co de_First.Wareho useData)
Vie ws: Razo r (CSHTML)

Note If these o ptio ns do n't appear in the dro p-do wn lists, press F6 to rebuild the applicatio n and retry.

Entity Framewo rk creates default CRUD co de fo r the Shipments co ntro ller:


OBSERVE: /Co ntro llers/ShipmentsCo ntro ller.cs default CRUD co de
.
.
.
namespace EF_Code_First.Controllers
{
public class ShipmentsController : Controller
{
private WarehouseContext db = new WarehouseContext();

//
// GET: /Shipments/

public ActionResult Index()


{
return View(db.Shipments.ToList());
}

//
// GET: /Shipments/Details/5

public ActionResult Details(int id = 0)


{
Shipments shipments = db.Shipments.Find(id);
if (shipments == null)
{
return HttpNotFound();
}
return View(shipments);
}

//
// GET: /Shipments/Create

public ActionResult Create()


{
return View();
}
.
.
.

It also creates Views fo r each functio n in the co ntro ller:


Let's create a link to o ur Shipments Index page so we can see what EF has do ne. Open
/Vie ws/Share d/_Layo ut .csht m l and add a link to o ur Shipments index page as sho wn:

CODE TO TYPE: Mo dify /Views/Shared/_Layo ut.cshtml to add link to Shipments index


.
.
.
<nav>
<ul id="menu">
<li>@Html.ActionLink("Home", "Index", "Home")</li>
<li>@Html.ActionLink("About", "About", "Home")</li>
<li>@Html.ActionLink("Contact", "Contact", "Home")</li>
<li>@Html.ActionLink("Shipments Index", "Index", "Shipments")</li>
</ul>
</nav>
.
.
.

and and navigate to the Shipments Index link; yo u see o nly a few data elements, because EF do esn't
kno w ho w to pull the extra info rmatio n we have sto red in o ur Shipments table.
We need to go back to /Ware ho use Dat a/Ware ho use Init ialize r.cs and change o ne line:

CODE TO TYPE: Changing Wareho useInitializer.cs to allo w fo r persistent info rmatio n


using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Data.Entity;
using EF_Code_First.Models;

namespace EF_Code_First.WarehouseData
{
public class WarehouseInitializer : DropCreateDatabaseAlwaysDropCreateDataba
seIfModelChanges<WarehouseContext>
{
protected override void Seed(WarehouseContext context)
{
.
.
.

Dro pCre at e Dat abase If Mo de lChange s will recreate and reseed the database only if we
Note change o ur mo del.

No w we'll add so me Views fo r o ur Wareho use mo del. Right-click the Co nt ro lle rs fo lder and select Add |
Co nt ro lle r.... Name the co ntro ller Ware ho use Co nt ro lle r and use the o ptio ns as sho wn:
No w add a link to the Wareho use index to ensure that the co ntro ller wo rks as we expect. Again, mo dify
/Vie ws/Share d/_Layo ut .csht m l and add ano ther Actio nLink:

CODE TO TYPE:

.
.
.
<nav>
<ul id="menu">
<li>@Html.ActionLink("Home", "Index", "Home")</li>
<li>@Html.ActionLink("About", "About", "Home")</li>
<li>@Html.ActionLink("Contact", "Contact", "Home")</li>
<li>@Html.ActionLink("Shipments Index", "Index", "Shipments")</li>
<li>@Html.ActionLink("Warehouse Index", "Index", "Warehouse")</li>
</ul>
</nav>
.
.
.

and and click the Ware ho use Inde x link, and yo u see a listing o f o ur wareho uses.
No w we can begin using o ur Repo sito ry. Mo dify /Co nt ro lle rs/Shipm e nt sCo nt ro lle r.cs as sho wn:
CODE TO TYPE:
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using EF_Code_First.Models;
using EF_Code_First.WarehouseData;
using EF_Code_First.AbstractionLayer;

namespace EF_Code_First.Controllers
{
public class ShipmentsController : Controller
{
private WarehouseContext db = new WarehouseContext();
private IShipmentRepository ShipmentRepository;

public ShipmentsController()
{
this.ShipmentRepository = new ShipmentRepository(new WarehouseContex
t());
}

public ShipmentsController(IShipmentRepository ShipmentRepository)


{
this.ShipmentRepository = ShipmentRepository;
}

//
// GET: /Shipments/

public ActionResult Index()


{
var shipmentsList = from s in ShipmentRepository.GetAllShipments() s
elect s;
return View(db.Shipments.ToList());
return View(shipmentsList);
}

//
// GET: /Shipments/Details/5

public ActionResult Details(int id = 0)


{
Shipments shipments = db.Shipments.Find(id);ShipmentRepository.GetBy
TrackingNumber(id);
if (shipments == null)
{
return HttpNotFound();
}
return View(shipments);
}

//
// GET: /Shipments/Create

public ActionResult Create()


{
ViewBag.WarehouseId = new SelectList(db.Warehouse, "Id", "Name");
return View();
}

//
// POST: /Shipments/Create
[HttpPost]
public ActionResult Create(Shipments shipments)
{
try
{
if (ModelState.IsValid)
{
db.Shipments.Add(shipments);
db.SaveChanges();
ShipmentRepository.CreateNewShipment(shipments);
ShipmentRepository.saveShipment();

return RedirectToAction("Index");
}
}
catch (DataException)
{
ModelState.AddModelError("", "An error occurred. We are working
hard to resolve this.");
}

ViewBag.WarehouseId = new SelectList(db.Warehouse, "Id", "Name", shi


pments.WarehouseId);
return View(shipments);
}

//
// GET: /Shipments/Edit/5

public ActionResult Edit(int id = 0)


{
Shipments shipments = db.Shipments.Find(id);ShipmentRepository.GetBy
TrackingNumber(id);
if (shipments == null)
{
return HttpNotFound();
}
return View(shipments);
}

//
// POST: /Shipments/Edit/5

[HttpPost]
public ActionResult Edit(Shipments shipments)
{
try
{
if (ModelState.IsValid)
{
db.Entry(shipments).State = EntityState.Modified;
db.SaveChanges();
ShipmentRepository.ModifyShipment(shipments);
ShipmentRepository.saveShipment();
return RedirectToAction("Index");
}
}
catch (DataException)
{
ModelState.AddModelError("", "Unable to edit shipment. The error
has been sent to the System Administrator.");
}

ViewBag.WarehouseId = new SelectList(db.Warehouse, "Id", "Name", shi


pments.WarehouseId);
return View(shipments);
}
//
// GET: /Shipments/Delete/5

public ActionResult Delete(int id = 0)


{
Shipments shipments = db.Shipments.Find(id);ShipmentRepository.GetBy
TrackingNumber(id);
if (shipments == null)
{
return HttpNotFound();
}
return View(shipments);
}

//
// POST: /Shipments/Delete/5

[HttpPost, ActionName("Delete")]
public ActionResult DeleteConfirmed(int id)
{
Shipments shipments = db.Shipments.Find(id);
Shipments shipments = ShipmentRepository.GetByTrackingNumber(id);
db.Shipments.Remove(shipments);
db.SaveChanges();
ShipmentRepository.RemoveShipment(id);
ShipmentRepository.saveShipment();
return RedirectToAction("Index");
}

protected override void Dispose(bool disposing)


{
db.Dispose();
base.Dispose(disposing);
ShipmentRepository.Dispose();
base.Dispose(disposing);
}
}
}

We replaced co de that used the DbCo ntext class with o ur repo sito ry.

OBSERVE:
private IShipmentRepository ShipmentRepository;

public ShipmentsController()
{
this.ShipmentRepository = new ShipmentRepository(new WarehouseContex
t());
}

public ShipmentsController(IShipmentRepository ShipmentRepository)


{
this.ShipmentRepository = ShipmentRepository;
}

Here's what's happening in o ur repo sito ry co de:

privat e IShipm e nt Re po sit o ry Shipm e nt Re po sit o ry; creates a lo cal ShipmentRepo sito ry
variable o f the repo sito ry interface.
Shipm e nt sCo nt ro lle r() creates a new ShipmentRepo sito ry co ntext instance rather than using
the DbCo ntext class.
Shipm e nt sCo nt ro lle r(IShipm e nt Re po sit o ry Shipm e nt Re po sit o ry) creates an o ptio nal
co nstructo r that allo ws the caller to pass in an instance o f a co ntext.

In the o ther metho ds, we replace o ur auto matically generated co de with the Shipments repo sito ry using o ur
interface.

We need to make o ne mo re change to the generated /Vie ws/Shipm e nt s/Cre at e .ht m l. By default, the Entity
Framewo rk scaffo ld (CRUD) tries to set up the wareho use ID field as a dro pdo wn list. To fully implement this,
we need to add a new metho d into the IShipmentRepo sito ry and also define it in ShipmentRepo sito ry. This
new metho d require us to pull the wareho useID fro m the shipments database. Fo r no w, we wo n't implement it
as a dro pdo wn; let's change the co de acco rdingly:

CODE TO TYPE: Mo dificatio n to /Views/Shipments/Create.cshtml


@model EF_Code_First.Models.Shipments

@{
ViewBag.Title = "Create";
}

<h2>Create</h2>

@using (Html.BeginForm()) {
@Html.ValidationSummary(true)

<fieldset>
.
.
.

<div class="editor-label">
@Html.LabelFor(model => model.WarehouseId, "WarehouseNavigation")
@Html.LabelFor(model => model.WarehouseId)
</div>
<div class="editor-field">
@Html.DropDownList("WarehouseId", String.Empty)
@Html.ValidationMessageFor(model => model.WarehouseId)
@Html.EditorFor(model => model.WarehouseId)
@Html.ValidationMessageFor(model => model.WarehouseId)
</div>

<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}

Our repo sito ry co de lo o ks like the previo us co de, but do es it wo rk? Let's check it and see.

and , navigate to the Shipments Index and try to create a new shipment. No tice there's no place to
enter the addresses o f the Sender o r Recipient, and the Index do es no t sho w any info rmatio n pertaining to
Senders o r Recipients:
We need to add this info rmatio n manually in /Vie ws/Shipm e nt s/Cre at e .csht m l and
/Vie ws/Shipm e nt s/Inde x.csht m l files:
CODE TO TYPE: Mo dificatio n to /Views/Shipments/Create.cshtml to include addresses
@model EF_Code_First.Models.Shipments

@{
ViewBag.Title = "Create";
}

<h2>Create</h2>

@using (Html.BeginForm()) {
@Html.ValidationSummary(true)

<fieldset>
<legend>Shipments</legend>

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

<div class="editor-label">
@Html.LabelFor(model => model.Sender.Country)
</div>
<div class="editor-field">
<b>Sender</b>: @Html.EditorFor(model => model.Sender.Country) <b>
Recipient</b>: @Html.EditorFor(model => model.Recipient.Country)
@Html.ValidationMessageFor(model => model.Sender.Country)
@Html.ValidationMessageFor(model => model.Recipient.Country)
</div>

<div class="editor-label">
@Html.LabelFor(model => model.Sender.State)
</div>
<div class="editor-field">
<b>Sender: </b>@Html.EditorFor(model => model.Sender.State) <b>Re
cipient</b>: @Html.EditorFor(model => model.Recipient.State)
@Html.ValidationMessageFor(model => model.Sender.State)
@Html.ValidationMessageFor(model => model.Recipient.State)
</div>

<div class="editor-label">
@Html.LabelFor(model => model.Sender.City)
</div>
<div class="editor-field">
<b>Sender: </b>@Html.EditorFor(model => model.Sender.City) <b>Re
cipient</b>: @Html.EditorFor(model => model.Recipient.City)
@Html.ValidationMessageFor(model => model.Sender.City)
@Html.ValidationMessageFor(model => model.Recipient.City)
</div>

<div class="editor-label">
@Html.LabelFor(model => model.Sender.Street)
</div>
<div class="editor-field">
<b>Sender: </b>@Html.EditorFor(model => model.Sender.Street) <b>
Recipient</b>: @Html.EditorFor(model => model.Recipient.Street)
@Html.ValidationMessageFor(model => model.Sender.Street)
@Html.ValidationMessageFor(model => model.Recipient.Street)
</div>

<div class="editor-label">
@Html.LabelFor(model => model.Sender.HousingNumber)
</div>
<div class="editor-field">
<b>Sender: </b>@Html.EditorFor(model => model.Sender.HousingNumber)
<b>Recipient</b>: @Html.EditorFor(model => model.Recipient.HousingNumber)
@Html.ValidationMessageFor(model => model.Sender.HousingNumber)
@Html.ValidationMessageFor(model => model.Recipient.HousingNumber)
</div>

<div class="editor-label">
@Html.LabelFor(model => model.Sender.Zip)
</div>
<div class="editor-field">
<b>Sender: </b>@Html.EditorFor(model => model.Sender.Zip) <b>Rec
ipient</b>: @Html.EditorFor(model => model.Recipient.Zip)
@Html.ValidationMessageFor(model => model.Sender.Zip)
@Html.ValidationMessageFor(model => model.Recipient.Zip)
</div>

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

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

<div class="editor-label">
@Html.LabelFor(model => model.WarehouseId)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.WarehouseId)
@Html.ValidationMessageFor(model => model.WarehouseId)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}

<div>
@Html.ActionLink("Back to List", "Index")
</div>

@section Scripts {
@Scripts.Render("~/bundles/jqueryval")
}

Update /Vie ws/Shipm e nt s/Inde x.csht m l to display the appro priate info rmatio n:
CODE TO TYPE:
@model IEnumerable<EF_Code_First.Models.Shipments>

@{
ViewBag.Title = "Index";
}

<h2>Index</h2>

<p>
@Html.ActionLink("Create New", "Create")
</p>
<table>
<tr>
<th>
@Html.DisplayNameFor(model => model.NumberOfItems)
</th>
<th>
Sender
</th>
<th>
@Html.DisplayNameFor(model => model.ShippedDateTime)
</th>
<th>
Recipient
</th>
<th>
@Html.DisplayNameFor(model => model.ReceivedDateTime)
</th>
<th>
@Html.DisplayNameFor(model => model.WarehouseId)
</th>
<th></th>
</tr>

@foreach (var item in Model) {


<tr>
<td>
@Html.DisplayFor(modelItem => item.NumberOfItems)
</td>
<td>
@Html.DisplayFor(modelItem => item.Sender.Country)
@Html.DisplayFor(modelItem => item.Sender.HousingNumber) @Html.Displ
ayFor(modelItem => item.Sender.Street), @Html.DisplayFor(modelItem => item.Sende
r.State),
@Html.DisplayFor(modelItem => item.Sender.Zip)
</td>
<td>
@Html.DisplayFor(modelItem => item.ShippedDateTime)
</td>
<td>
@Html.DisplayFor(modelItem => item.Recipient.Country)
@Html.DisplayFor(modelItem => item.Recipient.HousingNumber) @Html.Di
splayFor(modelItem => item.Recipient.Street), @Html.DisplayFor(modelItem => item
.Recipient.State),
@Html.DisplayFor(modelItem => item.Recipient.Zip)
</td>
<td>
@Html.DisplayFor(modelItem => item.ReceivedDateTime)
</td>
<td>
@Html.DisplayFor(modelItem => item.WarehouseId)
</td>
<td>
@Html.ActionLink("Edit", "Edit", new { id=item.TrackingNumber }) |
@Html.ActionLink("Details", "Details", new { id=item.TrackingNumber
}) |
@Html.ActionLink("Delete", "Delete", new { id=item.TrackingNumber })
</td>
</tr>
}

</table>

No w and , navigate to Shipments index and create a new shipment as sho wn:

Click Cre at e . Yo u see the newly added shipment in the list:


No w we can put o ur Shipments repo sito ry to use, and implement a repo sito ry.

Let's learn ho w to use o ur Unit o f Wo rk pattern so we can begin creating highly extensible and maintainable
applicatio ns.

Unit of Work with CRUD


Mo dify the Wareho use co ntro ller to use the Ware ho use Wo rkUnit class we created. Mo dify
/Co nt ro lle rs/Ware ho use Co nt ro lle r.cs as sho wn:
CODE TO TYPE: Implementing Wareho useWo rkUnit in /Co ntro llers/Wareho useCo ntro ller.cs

using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Data.Entity;
using System.Web;
using System.Web.Mvc;
using EF_Code_First.Models;
using EF_Code_First.WarehouseData;
using EF_Code_First.AbstractionLayer;

namespace EF_Code_First.Controllers
{
public class WarehouseController : Controller
{
private WarehouseContext db = new WarehouseContext();
private WarehouseWorkUnit WorkUnit = new WarehouseWorkUnit();
//
// GET: /Warehouse/

public ActionResult Index()


{
var warehouses = WorkUnit.WarehouseRepository.Retrieve(includeProper
ties: "shipments, employees");
return View(db.Warehouse.ToList()warehouses.ToList());
}

//
// GET: /Warehouse/Details/5

public ActionResult Details(int id = 0)


{
Warehouse warehouse = db.Warehouse.Find(id);WorkUnit.WarehouseReposi
tory.GetEntitiesById(id);
if (warehouse == null)
{
return HttpNotFound();
}
return View(warehouse);
}

//
// GET: /Warehouse/Create

public ActionResult Create()


{
return View();
}

//
// POST: /Warehouse/Create

[HttpPost]
public ActionResult Create(Warehouse warehouse)
{
if (ModelState.IsValid)
{
WorkUnit.WarehouseRepository.AddEntity(warehouse);
WorkUnit.Save();
db.Warehouse.Add(warehouse);
db.SaveChanges();
return RedirectToAction("Index");
}

return View(warehouse);
}
//
// GET: /Warehouse/Edit/5

public ActionResult Edit(int id = 0)


{
Warehouse warehouse = db.Warehouse.Find(id);WorkUnit.WarehouseReposi
tory.GetEntitiesById(id);
if (warehouse == null)
{
return HttpNotFound();
}
return View(warehouse);
}

//
// POST: /Warehouse/Edit/5

[HttpPost]
public ActionResult Edit(Warehouse warehouse)
{
if (ModelState.IsValid)
{
WorkUnit.WarehouseRepository.UpdateEntity(warehouse);
WorkUnit.Save();
db.Entry(warehouse).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(warehouse);
}

//
// GET: /Warehouse/Delete/5

public ActionResult Delete(int id = 0)


{
Warehouse warehouse = db.Warehouse.Find(id);WorkUnit.WarehouseReposi
tory.GetEntitiesById(id);
if (warehouse == null)
{
return HttpNotFound();
}
return View(warehouse);
}

//
// POST: /Warehouse/Delete/5

[HttpPost, ActionName("Delete")]
public ActionResult DeleteConfirmed(int id)
{
Warehouse warehouse = db.Warehouse.Find(id);WorkUnit.WarehouseReposi
tory.GetEntitiesById(id);
db.Warehouse.Remove(warehouse);WorkUnit.WarehouseRepository.RemoveEn
tityEntry(warehouse);
db.SaveChanges();WorkUnit.Save();
return RedirectToAction("Index");
}

protected override void Dispose(bool disposing)


{
db.Dispose();WorkUnit.Dispose();
base.Dispose(disposing);
}
}
}
and and navigate to the Ware ho use Inde x link. Everything wo rks the same way it did befo re.

We'll reinfo rce just ho w useful the Unit o f Wo rk pattern can be, by adding an emplo yee to a wareho use.

Open /Vie ws/Ware ho use /Inde x.csht m l and add two links to it:

CODE TO TYPE: Adding links to create a wareho use o r emplo yee to Index.cshtml

@model IEnumerable<EF_Code_First.Models.Warehouse>

@{
ViewBag.Title = "Index";
}

<h2>Index</h2>

<p>
@Html.ActionLink("New Employee", "Create", new { NewWarehouse = false } )
@Html.ActionLink("New Warehouse", "Create", new { NewWarehouse = true } )
@Html.ActionLink("Create New", "Create")
</p>
<table>
<tr>
<th>
@Html.DisplayNameFor(model => model.Name)
</th>
<th></th>
</tr>

@foreach (var item in Model) {


<tr>
<td>
@Html.DisplayFor(modelItem => item.Name)
</td>
<td>
@Html.ActionLink("Edit", "Edit", new { id=item.Id }) |
@Html.ActionLink("Details", "Details", new { id=item.Id }) |
@Html.ActionLink("Delete", "Delete", new { id=item.Id })
</td>
</tr>
}

</table>
Mo dify /Co nt ro lle rs/Ware ho use Co nt ro lle r as sho wn:

CODE TO TYPE: /Co ntro llers/Wareho useCo ntro ller.cs Create changes
.
.
.

//
// GET: /Warehouse/Details/5

public ActionResult Details(int id = 0)


{
Warehouse warehouse = WorkUnit.WarehouseRepository.GetEntitiesById(i
d);
return View(warehouse);
}

//
// GET: /Warehouse/Create

public ActionResult Create(bool NewWarehouse = true)


{
if (NewWarehouse)
return View();
else
{
CreateDropDownList();
return RedirectToAction("Create", "Employee");
}
}
.
.
.

//
// POST: /Warehouse/Delete/5

[HttpPost, ActionName("Delete")]
public ActionResult DeleteConfirmed(int id)
{
Warehouse warehouse = WorkUnit.WarehouseRepository.GetEntitiesById(i
d);
WorkUnit.WarehouseRepository.RemoveEntityEntry(warehouse);
WorkUnit.Save();
return RedirectToAction("Index");
}

private void CreateDropDownList(object chosenWarehouse = null)


{
var existingWarehouses = WorkUnit.WarehouseRepository.Retrieve(order
By: x => x.OrderBy(y => y.Name));
ViewBag.WarehouseDropDown = new SelectList(existingWarehouses, "Id",
"Name", chosenWarehouse);
}

protected override void Dispose(bool disposing)


{
WorkUnit.Dispose();
base.Dispose(disposing);
}
}
}

Create a Co ntro ller fo r the Emplo yee mo del. Right-click the Co ntro llers fo lder and add a new Co ntro ller.
Mo dify /Co nt ro lle rs/Em plo ye e Co nt ro lle r.cs to use o ur Unit o f Wo rk as sho wn:
CODE TO TYPE: Inco rpo rating Unit o f Wo rk into /Co ntro llers/Emplo yeeCo ntro ller.cs

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using EF_Code_First.Models;
using EF_Code_First.WarehouseData;
using EF_Code_First.AbstractionLayer;

namespace EF_Code_First.Controllers
{
public class EmployeeController : Controller
{
private WarehouseContext db = new WarehouseContext();
private WarehouseWorkUnit WorkUnit = new WarehouseWorkUnit();
//
// GET: /Employee/

public ActionResult Index()


{
var EmployeeList = WorkUnit.EmployeeRepository.Retrieve(includePrope
rties: "EmployeeLocation");
return View(EmployeeList.ToList());

var employees = db.Employees.Include(e => e.EmployeeLocation);


return View(employees.ToList());
}

//
// GET: /Employee/Details/5

public ActionResult Details(int id = 0)


{
Employee employee = db.Employees.Find(id);
if (employee == null)
{
return HttpNotFound();
}
return View(employee);
}

//
// GET: /Employee/Create

public ActionResult Create()


{

ViewBag.WarehouseId = new SelectList(db.Warehouse, "Id", "Name");


CreateDropDownLists();
return View();
}

//
// POST: /Employee/Create

[HttpPost]
public ActionResult Create(Employee employee)
{
if (ModelState.IsValid)
{
WorkUnit.EmployeeRepository.AddEntity(employee);
WorkUnit.Save();
db.Employees.Add(employee);
db.SaveChanges();
return RedirectToAction("Index");
}

ViewBag.WarehouseId = new SelectList(db.Warehouse, "Id", "Name", emp


loyee.WarehouseId);
CreateDropDownLists(employee.Position, employee.EmployeeLocation);
return View(employee);
}

//
// GET: /Employee/Edit/5

public ActionResult Edit(int id = 0)


{
Employee employee = db.Employees.Find(id);
if (employee == null)
{
return HttpNotFound();
}

ViewBag.WarehouseId = new SelectList(db.Warehouse, "Id", "Name", emp


loyee.WarehouseId);
return View(employee);
}

//
// POST: /Employee/Edit/5

[HttpPost]
public ActionResult Edit(Employee employee)
{
if (ModelState.IsValid)
{
db.Entry(employee).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}

ViewBag.WarehouseId = new SelectList(db.Warehouse, "Id", "Name", emp


loyee.WarehouseId);
return View(employee);
}

//
// GET: /Employee/Delete/5

public ActionResult Delete(int id = 0)


{
Employee employee = WorkUnit.EmployeeRepository.GetEntitiesById(id);
Employee employee = db.Employees.Find(id);
if (employee == null)
{
return HttpNotFound();
}
return View(employee);
}

//
// POST: /Employee/Delete/5

[HttpPost, ActionName("Delete")]
public ActionResult DeleteConfirmed(int id)
{
Employee employee = WorkUnit.EmployeeRepository.GetEntitiesById(id);
WorkUnit.EmployeeRepository.RemoveEntityEntry(employee);
WorkUnit.Save();
Employee employee = db.Employees.Find(id);
db.Employees.Remove(employee);
db.SaveChanges();
return RedirectToAction("Index");
}

private void CreateDropDownLists(object position = null, object chosenWa


rehouse = null)
{
var existingPositions = WorkUnit.EmployeeRepository.Retrieve(orderBy
: x => x.OrderBy(y => y.Position));
ViewBag.PositionsDropDown = new SelectList(existingPositions, "Posit
ion", "Position", position);

var existingWarehouses = WorkUnit.WarehouseRepository.Retrieve(order


By: x => x.OrderBy(y => y.Name));
ViewBag.WarehouseDropDown = new SelectList(existingWarehouses, "Id",
"Name", chosenWarehouse);
}

protected override void Dispose(bool disposing)


{
WorkUnit.Dispose();
db.Dispose();
base.Dispose(disposing);
}
}
}

We've already seen mo st o f this co de in the Wareho use co ntro ller. Let's discuss the new stuff.

OBSERVE:

private void CreateDropDownLists(object position = null, object chosenWa


rehouse = null)
{
var existingPositions = WorkUnit.EmployeeRepository.Retrieve(orderBy
: x => x.OrderBy(y => y.Position));
ViewBag.PositionsDropDown = new SelectList(existingPositions, "Position"
, "Position", position);

var existingWarehouses = WorkUnit.WarehouseRepository.Retrieve(orderBy:


x => x.OrderBy(y => y.Name));
ViewBag.WarehouseDropDown = new SelectList(existingWarehouses, "Id", "Na
me", chosenWarehouse);
}

Cre at e Dro pDo wnList s(o bje ct po sit io n = null, o bje ct cho se nWare ho use = null) is a
private functio n that takes two o ptio nal parameters. One o f them is a po sitio n, and the o ther is a
wareho use; bo th o f them are o bjects.
var e xist ingPo sit io ns = Wo rkUnit .Em plo ye e Re po sit o ry.Re t rie ve (o rde rBy: x =>
x.Orde rBy(y => y.Po sit io n)); creates a lo cal variable using o ur Emplo yeeRepo sito ry fro m
Wo rkUnit that we use as o ur IEnumerato r type in the SelectList co nstructo r.
We treat e xist ingWare ho use s like existingPo sitio ns, except that e xist ingWare ho use s ho lds a
list o f o ur wareho uses.

No w, mo dify the /Vie ws/Em plo ye e /Inde x.csht m l page to display the info rmatio n we want:
CODE TO TYPE: Emplo yee Index.cshtml file
@model IEnumerable<EF_Code_First.Models.Employee>

@{
ViewBag.Title = "Index";
}

<h2>Index</h2>

<p>
@Html.ActionLink("Create New", "Create")
</p>
<table>
<tr>
<th>
@Html.DisplayNameFor(model => model.Position)
</th>
<th>
Employee Name
</th>
<th>
Employee Location
</th>
<th>
@Html.DisplayNameFor(model => model.EmployeeLocation.Name)
</th>
<th></th>
</tr>

@foreach (var item in Model) {


<tr>
<td>
@Html.DisplayFor(modelItem => item.Position)
</td>
<td>
@Html.DisplayFor(modelItem => item.person.FullName)
</td>
<td>
@Html.DisplayFor(modelItem => item.EmployeeLocation.Name)
</td>
<td>
@Html.DisplayFor(modelItem => item.EmployeeLocation.Name)
</td>
<td>
@Html.ActionLink("Edit", "Edit", new { id=item.EmployeeId }) |
@Html.ActionLink("Details", "Details", new { id=item.EmployeeId }) |
@Html.ActionLink("Delete", "Delete", new { id=item.EmployeeId })
</td>
</tr>
}

</table>

No w add so me co de to the Create.cshtml file fo r o ur Emplo yee views:


CODE TO TYPE: Create.cshtml fo r Emplo yee

@model EF_Code_First.Models.Employee

@{
ViewBag.Title = "Create";
}

<h2>Create</h2>

@using (Html.BeginForm()) {
@Html.ValidationSummary(true)

<fieldset>
<legend>Employee</legend>
<div class="editor-label">
@Html.LabelFor(model => model.EmployeeId)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.EmployeeId)
@Html.ValidationMessageFor(model => model.EmployeeId)
</div>
<div class="editor-label">
@Html.LabelFor(model => model.person.FirstName)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.person.FirstName)
@Html.ValidationMessageFor(model => model.person.FirstName)
</div>
<div class="editor-label">
@Html.LabelFor(model => model.person.LastName)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.person.LastName)
@Html.ValidationMessageFor(model => model.person.LastName)
</div>
<div class="editor-label">
@Html.LabelFor(model => model.person.MiddleName)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.person.MiddleName)
@Html.ValidationMessageFor(model => model.person.MiddleName)
</div>
<div class="editor-label">
@Html.LabelFor(model => model.person.SocialSecurity)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.person.SocialSecurity)
@Html.ValidationMessageFor(model => model.person.SocialSecurity)
</div>
<div class="editor-label">
@Html.LabelFor(model => model.Position)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.Position)
@Html.ValidationMessageFor(model => model.Position)
@Html.DropDownListFor(model => model.Position, (SelectList)ViewBag.P
ositionsDropDown)
</div>
<div class="editor-label">
@Html.LabelFor(model => model.WarehouseId, "EmployeeLocation")
Warehouse Location
</div>
<div class="editor-field">
@Html.DropDownList("WarehouseId", String.Empty)
@Html.ValidationMessageFor(model => model.WarehouseId)
@Html.DropDownListFor(model => model.WarehouseId, (SelectList)ViewBa
g.WarehouseDropDown)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}

<div>
@Html.ActionLink("Back to List", "Index")
</div>

@section Scripts {
@Scripts.Render("~/bundles/jqueryval")
}

and , and go to the Wareho use Index. No w yo u see a list o f o ur wareho uses as well as two new links:

Click Ne w Em plo ye e to create a new emplo yee:


Click Cre at e . Yo u're redirected to the Emplo yee index page, where yo u see all o f o ur emplo yees:
Note Yo u can find additio nal references fo r the SelectList class here.

If we wanted to create a list o f po sitio ns that weren't already defined, we co uld create a new entity and sto re
o ur po sitio ns in a table in o ur database and reference that database to po pulate a po sitio n element.

LINQ to Entities
The Entity Framewo rk pro vides two metho ds o f perfo rming queries: Metho d Syntax and Query Syntax.

Que ry Synt ax: must be translated into metho d calls during co mpile time since .NET Co mmo n Language
Runtime (CLR) do es no t suppo rt this type o n its o wn. It can beco me cumberso me to write, but many find it
easier to read and understand.
Me t ho d Synt ax: native to .NET and do es no t need to be translated during co mpile time. Mo st LINQ
references are written using the Metho d syntax.

Let's see these metho ds in actio n. First, we'll create a new co ntro ller named LINQCo nt ro lle r and cho o se the Em pt y
MVC co nt ro lle r template.

Add o ne actio n fo r Metho d and o ne actio n fo r Query to the LINQCo ntro ller and so me co de to see the difference
between the Query and Metho d syntax:
CODE TO TYPE: LINQCo ntro ller.cs with Metho d and Query syntax
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using EF_Code_First.Models;
using EF_Code_First.WarehouseData;

namespace EF_Code_First.Controllers
{
public class LINQController : Controller
{
private WarehouseContext db = new WarehouseContext();

public ActionResult Query()


{
var method = (from x in db.Shipments where (x.NumberOfItems < 30 && x.Numbe
rOfItems > 10) select x).ToList();
return View(method);
}

public ActionResult Method()


{
var query = db.Shipments.Where(x => (x.NumberOfItems < 30 && x.NumberOfItem
s > 10)).ToList();
return View(query);

protected override void Dispose(bool disposing)


{
db.Dispose();
base.Dispose(disposing);
}

//
// GET: /LINQ/

public ActionResult Index()


{
return View();
}
}
}

Let's discuss the new co de.

OBSERVE:
public ActionResult Query()
{
var method = (from x in db.Shipments where (x.NumberOfItems < 30 && x.NumberOf
Items > 10) select x).ToList();
return View(method);
}

public ActionResult Method()


{
var query = db.Shipments.Where(x => (x.NumberOfItems < 30 && x.NumberOfItems >
10)).ToList();
return View(query);
}
Our Me t ho d() actio n uses the Metho d syntax to return items in the Shipments table that have between 10
and 30 items.
The Que ry() actio n uses the Query syntax and Lambda expressio ns to perfo rm the same o peratio n as the
Metho d() actio n, but with less ro o m fo r erro r.

These two statements are equivalent:

f ro m x in db.Shipm e nt s is equivalent to db.Shipm e nt s.


whe re (x.Num be rOf It e m s < 30 && x.Num be rOf It e m s > 10 ) se le ct x is equivalent to Whe re (x =>
(x.Num be rOf It e m s < 30 && x.Num be rOf It e m s > 10 ).

Create a view fo r the Metho d() actio n. Check the St ro ngly-T ype d vie w o ptio n and select the Shipm e nt s mo del as
o ur mo del class. Set the template to List :

LINQ/Metho d List EF view

@model IEnumerable<EF_Code_First.Models.Shipments>

@{
ViewBag.Title = "Method";
}

<h2>Method</h2>

<p>
@Html.ActionLink("Create New", "Create")
</p>
<table>
<tr>
<th>
@Html.DisplayNameFor(model => model.NumberOfItems)
</th>
<th>
@Html.DisplayNameFor(model => model.ShippedDateTime)
</th>
.
.
.

Do the same fo r the Query() metho d; yo u'll see the same co de and the same o utput.

Query and Metho d syntax are semantically equivalent. Ho wever, Query is no t suppo rted by the .NET CLR o n its o wn
and must be interpreted at co mpile time, while the Metho d syntax is suppo rted natively and no t translated during
co mpile.

Using the Entity relatio nships we defined earlier in the lesso n, we can retrieve multiple o r single elements o f a
database. Add a new actio n to the LINQCo ntro ller named Me t ho dMany as sho wn:
CODE TO TYPE: Metho dMany actio n in LINQCo ntro ller.cs

.
.
.
public ActionResult MethodMany(int id = 0)
{
if (id == 0)
{
var methodMany = db.Warehouse
.Include(x => x.shipments)
.Include(x => x.employees)
.ToList();
return View(methodMany);
}
else
{
var methodMany = db.Warehouse
.Where(x => x.Id == id)
.Include(x => x.shipments)
.Include(x => x.employees)
.ToList();
return View(methodMany);
}
}
.
.
.

No w, create a view fo r Me t ho dMany; select the Ware ho use (EF_Co de _First .Mo de ls) mo del and an Em pt y
template. The o nly pro perty is the wareho use name, because EF do esn't kno w ho w to access o ur o ther entities and
pro perties. Let's mo dify the view so we can retrieve all the info rmatio n we need, as well as pro vide the o ptio n to view
o nly o ne wareho use wo rth o f info rmatio n. This will allo w us to see ho w o ur Metho d syntax can filter queries by values.
Mo dify the co de as sho wn:
CODE TO TYPE: Mo difying Metho dMany.cshtml to access o ther entities

@model IEnumerable<EF_Code_First.Models.Warehouse>

@{
ViewBag.Title = "MethodMany";
}

<h2>MethodMany</h2>

<p>
@Html.ActionLink("Show All", "MethodMany", new { id = 0 })
</p>
<table>
@foreach (var item in Model)
{
<tr>
<th>@Html.ActionLink(@item.Name.ToString(), "MethodMany", new { id = item.I
d }) </th>
</tr>
foreach (var emp in item.employees)
{
<tr>
<td>@emp.Position</td>
<td>@emp.person.FullName</td>
</tr>
}
<tr style="border-bottom: 1px solid black; border-right: 1px solid black;">
@foreach (var ship in item.shipments)
{
<td><b>Recipient</b>: @ship.Recipient.City, @ship.Recipient.State<b
r />
<b>Sender</b>: @ship.Sender.City, @ship.Sender.State<br /></td>
}
</tr>
}
</table>

Let's discuss so me o f the co de.

OBSERVE:
@foreach (var item in Model)
{
<tr>
<th>@Html.ActionLink(@item.Name.ToString(), "MethodMany", new { id = item.I
d }) </th>
</tr>
foreach (var emp in item.employees)
{
<tr>
<td>@emp.Position</td>
<td>@emp.person.FullName</td>
</tr>
}
<tr style="border-bottom: 1px solid black; border-right: 1px solid black;">
@foreach (var ship in item.shipments)

The first f o re ach lo o p gives us access to the pro perties in o ur Mo del by declaring a variable named item
that gains access to o ur Wareho use entity.
In the seco nd f o re ach, we declare a new variable that is valid o nly fo r the sco pe o f this lo o p. By using o ur
previo us it e m variable, we can gain access to o ur emplo yees entity. The same o ccurs with the ne xt lo o p.

If we run o ur applicatio n and navigate to o ur Metho dMany view, we sho uld see the info rmatio n listed. When we click o n
a wareho use name, we receive o nly that wareho use's info rmatio n.
and .
We wo n't go any further into the Query syntax no w; instead let's fo cus o n to o ls that are native to the .NET framewo rk.
Here's a sample co de snippet that sho ws so me o f the elements required to get the same Entity info rmatio n as the
Metho dMany() actio n:

OBSERVE: Query syntax fo r Metho dMany actio n

.
.
.
from _warehouse in db.Warehouse
join _shipments
in db.Shipments
on _warehouse.Id equals _shipments.WarehouseId
into shipGroup
from ship in shipGroup
join _employees in db.Employees
on _warehouse.Id equals _employees.WarehouseId
into empGroup
from emp in empGroup
where _warehouse.Id == id
.
.
.

Note Yo u can find references fo r Query and Metho d Syntax here.

Entity Data Model (EDM)


No w that we've wo rked with the Co de First appro ach to the Entity Framewo rk, let's discuss the Database First
appro ach with the Entity Data Mo del.

The Co nceptual Mo del is a specific representatio n o f the way data is represented. It uses Entities and relatio nships to
mo del a relatio nship fro m o ur data, which allo ws the applicatio n to gro w witho ut scalability issues. Co nceptual
mo dels are represented in a do main-specific language; specifically, in entities and relatio nships the language is
referred to as Co nceptual Schema Definitio n Language (CSDL).

Create a new pro ject named EF_DB_First .

Once the pro ject has been created, we create o ur EDM. Right-click o n the ro o t pro ject fo lder and select Add | Ne w
It e m ....

In the menu, under Installed Templates, select Visual C# , find ADO.NET Ent it y Dat a Mo de l and enter the name
DB_First :

Click Add. In the Entity Data Mo del Wizard, select the Ge ne rat e Fro m Dat abase o ptio n since we will be using the
database fro m o ur Co de-First sectio ns:
Click Ne xt . Click Ne w Co nne ct io n to change fro m the DefaultCo nnectio n and set it to Micro so f t SQL Se rve r
Dat abase File :
Click Co nt inue and then Bro wse to navigate to and cho o se the existing Ware ho use .m df database fro m o ur
EF_Co de _First pro ject:

Click OK and then click Ne xt . When the wizard asks if yo u want to co py the database and mo dify the co nnectio n, click
Ye s. In the next windo w, enable the T able s o nly, and uncheck the Pluralize o r Singularize ge ne rat e d o bje ct
nam e s bo x:
Click Finish. Visual Studio creates a file named DB_First.edmx and within that is the DB_First.Designer.edmx file,
which displays o ur entities' pro perties:
The relatio nships are represented as 1...* (o ne-to -many), *...* ,(many-to -many), 1...1 (o ne-to -o ne), and
Note 0 ...* (zero -o r-o ne-to -many).

The ADO.NET EF has designed o ur Entities. We have entity relatio nships and a database.

Right-click in the area where the edmx file is displayed and select Add Co de Ge ne rat io n It e m ...:

Cho o se the ADO.NET Se lf -T racking Ent it y Ge ne rat o r and name it DB_First _Se lf _T racking:
Click Add. Yo u may see a Security Warning like this:

In this case, it's alright to click OK.

No tice that the framewo rk has built and added so me new files fo r us under the fo lders DB_First _Se lf _T racking.t t
and DB_First _Se lf _T racking.Co nt e xt .t t :
No w that we have all o f o ur co de, let's add a co ntro ller so we can begin to use o ur newly created self-tracking entities.
Right-click the Co nt ro lle rs fo lder and add a new co ntro ller named Ware ho use Co nt ro lle r, with the Mo del Class set
to Ware ho use and the Co ntext Class set to Ware ho use Ent it ie s (o ur mo dels are lo cated in the
DB_First _Se lf _T racking.t t fo lder).
The co de that is generated auto matically, is similar to so me o f o ur Co ntro llers fro m the Co de First sectio ns o f this
lesso n:
OBSERVE: Wareho useCo ntro ller.cs
.
.
.
public ActionResult Details(int id = 0)
{
Warehouse warehouse = db.Warehouses.Single(w => w.Id == id);
if (warehouse == null)
{
return HttpNotFound();
}
return View(warehouse);
}

//
// GET: /Warehouse/Create

public ActionResult Create()


{
return View();
}

//
// POST: /Warehouse/Create

[HttpPost]
public ActionResult Create(Warehouse warehouse)
{
if (ModelState.IsValid)
{
db.Warehouses.AddObject(warehouse);
db.SaveChanges();
return RedirectToAction("Index");
}

return View(warehouse);
}
.
.
.

and and navigate to the Wareho use Index page:

The database-first appro ach allo ws us to develo p rapidly fo r an existing system, and makes co de maintenance
straightfo rward. When yo ur co de is capable o f evo lving with an applicatio n, yo u'll be a much mo re efficient
pro grammer.
T ip Yo u can find mo re info rmatio n abo ut ADO.NET here.

Befo re yo u mo ve o n to the next lesso n, as alway, do yo ur ho mewo rk! See yo u so o n...

Copyright © 1998-2014 O'Reilly Media, Inc.

This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.
See http://creativecommons.org/licenses/by-sa/3.0/legalcode for more information.
Interfaces and Extensions
Lesson Objectives
In this lesso n yo u will:

implement interfaces to increase the ability o f co llabo rative develo pment and create applicatio ns that require no
o utside, back-end co de mo dificatio ns.
design ro bust functio ns that remo ve the requirement o f database query kno wledge.
use extensio n metho ds to effectively abstract applicatio ns to create an ad-ho c type API.
create custo m extensio ns to extend already existing functio nality o f ASP.NET and Entity Framewo rk.

After yo u co mplete this lesso n, yo u will have designed and implemented a fully functio ning web applicatio n API with data
abstractio n interfaces and user interface extensio ns, and created data extensio ns that further a web applicatio n API and
abstractio n.

Interfaces and MVC


IQueryable Interface
We'll start by creating a new ASP.NET MVC 4 Applicatio n named Int e rf ace s_Ext e nsio ns and cho o sing the
Int e rne t Applicat io n pro ject type.

Befo re we begin to create a custo m IQueryable interface, we need a database with tables in it. Open
\We b.co nf ig and add a co nnectio n string as sho wn:

CODE TO TYPE: Adding a Co nnectio n String To /Web.co nfig

.
.
.
</configSections>
<connectionStrings>
<add name="DefaultConnection" connectionString="Data Source=.\SQLEXPRESS;Ini
tial Catalog=aspnet-Interfaces_Extensions-20130624172244;Integrated Security=SSP
I" providerName="System.Data.SqlClient" />

<add name="InterfacesContext"
connectionString="Data Source=.\SQLEXPRESS;Initial Catalog=Interface;At
tachDBFilename=|DataDirectory|\Interface.mdf;Integrated Security=SSPI;User Insta
nce=true"
providerName="System.Data.SqlClient"
/>
</connectionStrings>
.
.
.

We'll need a co uple o f Mo dels so we can create so me entities. Right-click the Mo de ls fo lder and add a new
class named St ude nt s and ano ther named Co urse s.

Add so me pro perties to /Mo de ls/St ude nt s.cs and /Mo de ls/Co urse s.cs as sho wn:
CODE TO TYPE: Pro perties Fo r /Mo dels/Students.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace Interfaces_Extensions.Models
{
public class Students
{
[Key]
[Display(Name = "Student ID")]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
[Range(10000, 32000, ErrorMessage = "Invalid {0}. Must be between {2} an
d {1} digits in length.")]
public Int16 Id { get; set; }

[Display(Name="Student Name")]
[StringLength(100, ErrorMessage="Invalid length for {0}. Must be between
{2} and {1} characters.", MinimumLength = 10)]
public string Name { get; set; }

public virtual ICollection<Courses> courses { get; set; }


}
}

CODE TO TYPE: Pro perties Fo r Co urses.cs


using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace Interfaces_Extensions.Models
{
public class Courses
{
[Key]
[Display(Name="Course Number")]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
[Range(100, 999, ErrorMessage = "Invalid {0}, Courses must be numbered b
etween {1} and {2}")]
public int Id { get; set; }

[Display(Name="Course")]
[StringLength(50, ErrorMessage="Invalid {0}, {0} Must be between {2} and
{1} in length", MinimumLength=5)]
public string CourseName { get; set; }

[Display(Name="Credit Hours")]
[Required(ErrorMessage="{0} is required for all new classes.")]
[Range(0, 4, ErrorMessage = "The {0} has a range of {2} and {1}.")]
public int Credits { get; set; }

public virtual ICollection<Students> students { get; set; }


}
}

Students can have many Co urses and Co urses have many Students, so we created a many-to -many
relatio nship between o ur entities.

No w add a fo lder to ho ld o ur Database Co ntext and Initializer. Right-click the Int e rf ace s_Ext e nsio ns
pro ject and select Add | Ne w Fo lde r and name it IE_Co nt e xt .

Add a new class named Int e rf ace sCo nt e xt to this fo lder.

No w set up o ur co ntext. Mo dify /IE_Co nt e xt /Int e rf ace sCo nt e xt .cs as sho wn:

CODE TO TYPE:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Data.Entity;
using Interfaces_Extensions.Models;
using System.Data.Entity.ModelConfiguration.Conventions;

namespace Interfaces_Extensions.IE_Context
{
public class InterfacesContext : DbContext
{
public DbSet<Courses> Courses { get; set; }
public DbSet<Students> Students { get; set; }

protected override void OnModelCreating(DbModelBuilder modelBuilder)


{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
}

}
}

Create a new file to seed o ur entities. Add a class to the IE_Co nt e xt fo lder named Int e rf ace sInit ialize r.

Mo dify /IE_Co nt e xt /Int e rf ace sInit ialize r.cs file to seed o ur entities as sho wn (yo u can co py and past this
large co de blo ck if yo u like):
CODE TO TYPE:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Data.Entity;
using Interfaces_Extensions.Models;
using Interfaces_Extensions.IE_Context;

namespace Interfaces_Extensions.IE_Context
{
public class InterfacesInitializer : DropCreateDatabaseAlways<InterfacesCont
ext>
{
protected override void Seed(InterfacesContext context)
{
var _courses = new List<Courses>()
{
new Courses { Id = 222, CourseName = "Physics I", Credits = 3, s
tudents = new List<Students>() },
new Courses { Id = 101, CourseName = "Psychology", Credits = 3,
students = new List<Students>() },
new Courses { Id = 144, CourseName = "Business Philosophy", Cred
its = 3, students = new List<Students>() },
new Courses { Id = 296, CourseName = "Calculus I", Credits = 4,
students = new List<Students>() },
new Courses { Id = 442, CourseName = "Physics II", Credits = 3,
students = new List<Students>() },
new Courses { Id = 111, CourseName = "English I", Credits = 3, s
tudents = new List<Students>() },
new Courses { Id = 235, CourseName = "Artistic Expression", Cred
its = 2, students = new List<Students>() },
new Courses { Id = 599, CourseName = "CSE Ind. Study", Credits =
1, students = new List<Students>() },
new Courses { Id = 485, CourseName = "Structural Dynamics", Cred
its = 4, students = new List<Students>() },
new Courses { Id = 386, CourseName = "Bioinformatics", Credits =
4, students = new List<Students>() }
};
_courses.ForEach(x => context.Courses.Add(x));
context.SaveChanges();

var _students = new List<Students>()


{
new Students { Id = 10025, Name = "Michael Hadsworth", courses =
new List<Courses>() },
new Students { Id = 10435, Name = "Arielle Highsmith", courses =
new List<Courses>() },
new Students { Id = 11324, Name = "Daniel Hartfords", courses =
new List<Courses>() },
new Students { Id = 30982, Name = "Crystal Hingerfield", courses
= new List<Courses>() },
new Students { Id = 29051, Name = "Shawndra Kiljoy", courses = n
ew List<Courses>() },
new Students { Id = 20058, Name = "Yuri Goferfeldt", courses = n
ew List<Courses>() },
new Students { Id = 10598, Name = "Andrew Bandelsy", courses = n
ew List<Courses>() },
new Students { Id = 14568, Name = "Randolph Nomenture", courses
= new List<Courses>() },
new Students { Id = 31258, Name = "Barry Mantilok", courses = ne
w List<Courses>() },
new Students { Id = 10852, Name = "Gabriel Jones", courses = new
List<Courses>() },
new Students { Id = 30125, Name = "Caesar Juneseth", courses = n
ew List<Courses>() },
new Students { Id = 31215, Name = "Rebecca Vantry", courses = ne
w List<Courses>() }
};
_students.ForEach(x => context.Students.Add(x));
context.SaveChanges();

_courses[0].students.Add(_students[0]);
_courses[3].students.Add(_students[0]);
_courses[2].students.Add(_students[0]);
_courses[8].students.Add(_students[0]);

_courses[9].students.Add(_students[1]);
_courses[5].students.Add(_students[1]);
_courses[7].students.Add(_students[1]);
_courses[4].students.Add(_students[1]);

_courses[8].students.Add(_students[2]);
_courses[1].students.Add(_students[2]);
_courses[6].students.Add(_students[2]);
_courses[3].students.Add(_students[2]);

_courses[0].students.Add(_students[3]);
_courses[9].students.Add(_students[3]);
_courses[5].students.Add(_students[3]);
_courses[4].students.Add(_students[3]);

_courses[8].students.Add(_students[4]);
_courses[4].students.Add(_students[4]);
_courses[7].students.Add(_students[4]);
_courses[2].students.Add(_students[4]);

_courses[9].students.Add(_students[5]);
_courses[3].students.Add(_students[5]);
_courses[1].students.Add(_students[5]);
_courses[6].students.Add(_students[5]);

_courses[0].students.Add(_students[6]);
_courses[2].students.Add(_students[6]);
_courses[4].students.Add(_students[6]);
_courses[6].students.Add(_students[6]);

_courses[8].students.Add(_students[7]);
_courses[0].students.Add(_students[7]);
_courses[1].students.Add(_students[7]);
_courses[3].students.Add(_students[7]);

_courses[5].students.Add(_students[8]);
_courses[7].students.Add(_students[8]);
_courses[9].students.Add(_students[8]);
_courses[1].students.Add(_students[8]);

_courses[0].students.Add(_students[9]);
_courses[2].students.Add(_students[9]);
_courses[4].students.Add(_students[9]);
_courses[6].students.Add(_students[9]);

_courses[8].students.Add(_students[10]);
_courses[0].students.Add(_students[10]);
_courses[1].students.Add(_students[10]);
_courses[3].students.Add(_students[10]);

_courses[5].students.Add(_students[11]);
_courses[7].students.Add(_students[11]);
_courses[9].students.Add(_students[11]);
_courses[0].students.Add(_students[11]);
context.SaveChanges();
}
}
}

We want to make sure that o ur pro ject can see o ur entity co ntext and initializer. Mo dify /Glo bal.asax and add
using statements and a new metho d to the Applicatio n_Start functio n as sho wn:

CODE TO TYPE:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Http;
using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;
using Interfaces_Extensions.IE_Context;
using System.Data.Entity;

namespace Interfaces_Extensions
{
// Note: For instructions on enabling IIS6 or IIS7 classic mode,
// visit http://go.microsoft.com/?LinkId=9394801

public class MvcApplication : System.Web.HttpApplication


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

WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
AuthConfig.RegisterAuth();
Database.SetInitializer<InterfacesContext>(new InterfacesInitializer
());
}
}
}

No w add a co ntro ller fo r the Co urses entity. Right-click the Co nt ro lle rs fo lder, select Add | Co nt ro lle r. Set
the Name to Co urse sCo nt ro lle r; the Template to MVC co nt ro lle r wit h re ad/writ e act io ns and vie ws,
using Ent it y Fram e wo rk; the Mo del class to Co urse s (Int e rf ace s_Ext e nsio ns.Mo de ls); the Data
co ntext class to Int e rf ace sCo nt e xt (Int e rf ace s_Ext e nsio ns.IE_Co nt e xt ); and the Views to Razo r
(CSHT ML):
Create the St ude nt sCo nt ro lle r using the same o ptio ns as befo re, but instead o f setting the Mo del class to
Co urse s, set it to St ude nt s:

No w, let's add a co uple o f links to /Vie ws/Share d/_Layo ut .csht m l:


CODE TO TYPE:
.
.
.
<div class="float-right">
<section id="login">
@Html.Partial("_LoginPartial")
</section>
<nav>
<ul id="menu">
<li>@Html.ActionLink("Home", "Index", "Home")</li>
<li>@Html.ActionLink("About", "About", "Home")</li>
<li>@Html.ActionLink("Contact", "Contact", "Home")</li>
<li>@Html.ActionLink("Courses", "Index", "Courses")</li>
<li>@Html.ActionLink("Students", "Index", "Students")</li>
</ul>
</nav>
</div>
.
.
.

Note We wo n't be using the Abo ut o r Co ntact views.

Let's build and run o ur pro ject.

and and navigate to the Co urses and Students pages to verify that o ur database was pro perly
seeded. They'll lo o k like this:
No w add a fo lder fo r o ur custo m interfaces. Right-click the Int e rf ace s_Ext e nsio ns pro ject and cho o se
Add | Ne w Fo lde r, and name it Int e rf ace s.

In the /Int e rf ace s fo lder, add a new Class named ICo urse Re po sit o ry. This class will be o ur Co urse
Repo sito ry interface.

Mo dify /Int e rf ace s/ICo urse Re po sit o ry.cs to define so me metho ds fo r o ur new repo sito ry:
CODE TO TYPE: Metho ds fo r ICo urseRepo sito ry.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Linq.Expressions;
using Interfaces_Extensions.Models;
using Interfaces_Extensions.IE_Context;

namespace Interfaces_Extensions.Interfaces
{
public classinterface ICourseRepository : IDisposable
{
IEnumerable<Courses> GetAllCourses();
IQueryable<Courses> SearchCourses(Expression<Func<Courses, bool>> query)
;
IQueryable<Courses> CheckIfCourseExists(Expression<Func<Courses, bool>>
query, string courseValue);
IQueryable<Courses> GetCourseRange(int val_1, int val_2);
void AddCourse(Courses course);
void EditCourse(Courses course);
void DeleteCourse(int id);
Courses GetByCourseNumber(int id);
void SaveChanges();
}
}

The int e rf ace keywo rd defines a class in which all metho ds must be o verridden by an inheriting class, in this
case, the Co urseRepo sito ry.

We use the return type o f IQueryable<Co urses> o n so me o f o ur interface metho ds so that when we return o ur
o bjects we can iterate o ver the co llectio n o f items returned. This will beco me clearer when we implement
these metho ds in o ur co ntro llers.

The parameters o f o ur IQueryable return types is a Lambda expressio n, so we can pass in a query using the
Metho d syntax. This will also beco me clearer when we implement these metho ds in o ur co ntro llers.

Yo u've seen the remaining metho ds in earlier lesso ns. We take a Co urses o bject and perfo rm a basic
o peratio n o n it. We either add a new item to the table, delete an item, o r retrieve a single item.

Let's implement o ur Co urses repo sito ry so we can use o ur Co urses interface. Right-click the main pro ject
directo ry and add a new fo lder named Re po sit o rie s, and in that fo lder, add a new class named
Co urse sRe po sit o ry.

No w we'll implement o ur interface and its metho ds, discussing each piece as it's implemented:
CODE TO TYPE: Defining IQueryable<Co urses> SearchCo urses in
/Repo sito ries/Co ursesRepo sito ry.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Data;
using System.Linq.Expressions;
using Interfaces_Extensions.IE_Context;
using Interfaces_Extensions.Models;
using Interfaces_Extensions.Interfaces;

namespace Interfaces_Extensions.Repositories
{
public class CoursesRepository : ICourseRepository, IDisposable
{
private InterfacesContext context;

public CoursesRepository(InterfacesContext context)


{
this.context = context;
}

public IEnumerable<Courses> GetAllCourses()


{
return context.Courses.ToList();
}

public IQueryable<Courses> SearchCourses(Expression<Func<Courses, bool>>


query)
{
return context.Courses.Where(query);
}
}
}

Here, we declare the metho d public and accept an expressio n that uses a lambda functio n that returns a
bo o lean value. We name the parameter que ry, and que ry actually ho lds the metho d syntax that we'll use in
the functio n calls.

When we're able to implement them in o ur co ntro ller yo u'll get a better idea o f ho w these wo rk. Mo dify the
co de as sho wn:

CODE TO TYPE: Defining IQueryable<Co urses> CheckIfCo urseExists in Co ursesRepo sito ry.cs
.
.
.
public IQueryable<Courses> SearchCourses(Expression<Func<Courses, bool>> query)
{
return context.Courses.Where(query);
}

public IQueryable<Courses> CheckIfCourseExists(Expression<Func<Courses, bool>> q


uery, string courseValue)
{
return context.Courses.Where(obj => obj.CourseName.Contains(courseValue));
}
.
.
.

In this metho d, we define ano ther Expressio n, but this time we pass a string as a seco nd parameter. We
return all the Co urses with names that co ntain the string co urseValue. Mo dify yo ur co de as sho wn:
CODE TO TYPE: Defining IQueryable<Co urses> GetCo urseRange in Co ursesRepo sito ry.cs
.
.
.
public IQueryable<Courses> CheckIfCourseExists(Expression<Func<Courses, bool>> q
uery, string courseValue)
{
return context.Courses.Where(obj => obj.CourseName.Contains(courseValue));
}

public IQueryable<Courses> GetCourseRange(int val_1, int val_2)


{
return context.Courses.Where(obj => (obj.Id > val_1 && obj.Id < val_2)).AsQu
eryable();
}
.
.
.

This metho d is a little different fro m tho se we've used previo usly. We use the int value that is passed in to o ur
expressio n and the Co urses o bject to perfo rm a query. We also pass in a seco nd integer val_2 to perfo rm a
search between two values. Upo n retrieving the values fro m o ur query, we cast it as queryable so we receive
an enumerato r with o ur return type:
CODE TO TYPE: Defining remaining metho ds in Co ursesRepo sito ry.cs
.
.
.
public void AddCourse(Courses course)
{
context.Courses.Add(course);
}

public void EditCourse(Courses course)


{
context.Entry(course).State = EntityState.Modified;
}

public void DeleteCourse(int id = 0)


{
Courses course = context.Courses.Find(id);
context.Courses.Remove(course);
}

public Courses GetByCourseNumber(int id = 0)


{
Courses course = context.Courses.Find(id);
return course;
}

public void SaveChanges()


{
context.SaveChanges();
}

private bool ContextDisposed = false;

protected virtual void Dispose(bool disposal)


{
if (!this.ContextDisposed)
{
if (disposal)
{
context.Dispose();
}
}
this.ContextDisposed = true;
}

public void Dispose()


{
Dispose(true);
GC.SuppressFinalize(this);
}

}
}

No w that we have o ur interface and repo sito ry designed using the IQueryable interface, let's run the
applicatio n again.

and . If we navigate to the Co urses index page, the applicatio n wo rks the same way as it did befo re
because we haven't implemented the repo sito ry in the co ntro ller:
In o rder to be able to use the repo sito ry, mo dify /Co nt ro lle rs/Co urse Co nt ro lle r.cs as sho wn (we'll
intro duce the functio ns o ne at a time and explain them as we go ):
CODE TO TYPE: Switching Index actio n metho d o f Co urseCo ntro ller.cs o ver to use the Co urses
Repo sito ry
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using Interfaces_Extensions.Models;
using Interfaces_Extensions.IE_Context;
using Interfaces_Extensions.Repositories;
using Interfaces_Extensions.Interfaces;

namespace Interfaces_Extensions.Controllers
{
public class CoursesController : Controller
{
private InterfacesContext context = new InterfacesContext();
private ICourseRepository courseRepo;

public CoursesController()
{
this.courseRepo = new CoursesRepository(new InterfacesContext());
}

public CoursesController(ICourseRepository courseRepo)


{
this.courseRepo = courseRepo;
}

//
// GET: /Courses/

public ActionResult Index()


{
var courses = courseRepo.GetAllCourses();
return View(coursesdb.Courses.ToList());
}
.
.
.

We have replaced the o riginal Index actio n metho d with the Co urses Repo sito ry to retrieve Co urses entries:

CODE TO TYPE: Switching Details actio n metho d o f Co urseCo ntro ller.cs o ver to use the Co urses
Repo sito ry

.
.
.
public ActionResult Details(int id = 0)
{
Courses courses = courseRepo.GetByCourseNumber(id)db.Courses.Find(id);
if (courses == null)
{
return HttpNotFound();
}
return View(courses);
}
.
.
.

We replaced the default co de with the repo sito ry equivalent o f the db.Co urses.Find(id):
CODE TO TYPE: Switching Create actio n metho d o f Co urseCo ntro ller.cs o ver to use the Co urses
Repo sito ry
.
.
.
//
// GET: /Courses/Create

public ActionResult Create()


{
return View();
}

//
// POST: /Courses/Create

[HttpPost]
public ActionResult Create(Courses courses)
{
if (ModelState.IsValid)
{
db.Courses.Add(courses)courseRepo.AddCourse(courses);
db.SaveChanges()courseRepo.SaveChanges();
return RedirectToAction("Index");
}

return View(courses);
}
.
.
.

We replaced the default co de with o ur new repo sito ry-based co de:


CODE TO TYPE: Switching Edit actio n metho d o f Co urseCo ntro ller.cs o ver to use the Co urses
Repo sito ry
.
.
.
//
// GET: /Courses/Edit/5

public ActionResult Edit(int id = 0)


{
Courses courses = db.Courses.Find(id)courseRepo.GetByCourseNumber(id);
if (courses == null)
{
return HttpNotFound();
}
return View(courses);
}

//
// POST: /Courses/Edit/5

[HttpPost]
public ActionResult Edit(Courses courses)
{
if (ModelState.IsValid)
{
db.Entry(courses).State = EntityState.ModifiedcourseRepo.EditCourse(cour
ses);
db.SaveChanges()courseRepo.SaveChanges();
return RedirectToAction("Index");
}
return View(courses);
}
.
.
.

We replaced the default co de with o ur repo sito ry-based co de in the Edit actio n metho d:
CODE TO TYPE: Switching Delete actio n metho d o f Co urseCo ntro ller.cs o ver to use the Co urses
Repo sito ry
.
.
.
//
// GET: /Courses/Delete/5

public ActionResult Delete(int id = 0)


{
Courses courses = db.Courses.Find(id)courseRepo.GetByCourseNumber(id);
if (courses == null)
{
return HttpNotFound();
}
return View(courses);
}

//
// POST: /Courses/Delete/5

[HttpPost, ActionName("Delete")]
public ActionResult DeleteConfirmed(int id)
{
Courses courses = db.Courses.Find(id);
db.Courses.Remove(courses)courseRepo.DeleteCourse(id);
db.SaveChanges()courseRepo.SaveChanges();
return RedirectToAction("Index");
}

protected override void Dispose(bool disposing)


{
db.Dispose()courseRepo.Dispose();
base.Dispose(disposing);
}

Befo re we go fo rward, mo dify o ne view item to fulfill o ur Co urse entities' requirements. In the
/Vie ws/Co urse s/ fo lder, add so me co de to the Edit .csht m l and Cre at e .csht m l files:

CODE TO TYPE: Adding Co urse Number input fo r /Views/Co urses/Edit.cshtml Co urses view

.
.
.
<fieldset>
<legend>Courses</legend>
<div class="editor-label">
@Html.LabelFor(model => model.Id)
</div>
<div class="editor-field">
@Html.TextBoxFor(model => model.Id)
</div>
@Html.HiddenFor(model => model.Id)

<div class="editor-label">
@Html.LabelFor(model => model.CourseName)
</div>
.
.
.

and . No w the Co urse Number appears in the Edit view:


Enter the co urse number manually, as defined in o ur entity pro perties by mo difying
/Vie ws/Co urse s/Cre at e .csht m l view:

CODE TO TYPE: Adding Co urse Number input fo r Create.cshtml Co urses view

.
.
.
<legend>Courses</legend>

<div class="editor-label">
@Html.LabelFor(model => model.Id)
@Html.ValidationMessageFor(model => model.id)
</div>
<div class="editor-field">
@Html.TextBoxFor(model => model.Id)
</div>
<div class="editor-label">
@Html.LabelFor(model => model.CourseName)
</div>
.
.
.

and .
No w we can use o ur IQueryable o bjects. Open Co urses co ntro ller and create a new actio n metho d named
Que ryCo urse s:
CODE TO TYPE: Implementing IQueryable metho d QueryCo urses

.
.
.
// POST: /Courses/Delete/5

[HttpPost, ActionName("Delete")]
public ActionResult DeleteConfirmed(int id)
{
courseRepo.DeleteCourse(id);
courseRepo.SaveChanges();
return RedirectToAction("Index");
}

public ActionResult QueryCourses(string queryString = "")


{
if (!String.IsNullOrEmpty(queryString))
{
var courseSearch = courseRepo.SearchCourses(x => x.CourseName.ToUpper()
== queryString.ToUpper());
return View("Index", courseSearch);
}
else
{
var courses = courseRepo.GetAllCourses();
return View("Index", courses);
}
}
.
.
.

co urse Re po .Se archCo urse s(x => x.Co urse Nam e .T o Uppe r() == que rySt ring.T o Uppe r()) sends a
LINQ to Entity query to the SearchCo urses IQueryable functio n that we defined in o ur ICo ursesRepo sito ry
and Co ursesRepo sito ry. This functio n returns an IQueryable Co urses o bject.

Add an area to /Vie ws/Co urse s/Inde x.csht m l where we can search fo r a co urse:

CODE TO TYPE: Adding search bo x to Co urses Index.cshtml

@model IEnumerable<Interfaces_Extensions.Models.Courses>

@{
ViewBag.Title = "Index";
}

<h2>Index</h2>
<div>
@using(Html.BeginForm("QueryCourses", "Courses", FormMethod.Post))
{
@Html.TextBox("queryString", null, new { placeholder = "Enter Course Sea
rch" })
<input type="submit" value="Search" />
}
</div>
<p>
@Html.ActionLink("Return to Index", "Index")
@Html.ActionLink("Create New", "Create")
</p>
<table>

and and perfo rm a search to see if o ur new actio n metho d wo rks.


This search metho d is a bit strict. It requires a user to enter the exact co urse name. Our o ther IQueryable
functio n o ffers mo re flexibility. Let's create a Lo o se Que ry actio n metho d in
/Co nt ro lle rs/Co urse sCo nt ro lle r:

CODE TO TYPE:

.
.
.
public ActionResult QueryCourses(string queryString = "")
{
if (!String.IsNullOrEmpty(queryString))
{
var courseSearch = courseRepo.SearchCourses(x => x.CourseName.ToUpper()
== queryString.ToUpper());
return View("Index", courseSearch);
}
else
{
var courses = courseRepo.GetAllCourses();
return View("Index", courses);
}
}

public ActionResult LooseQuery(string queryString = "")


{
if (!String.IsNullOrEmpty(queryString))
{
var courseSearch = courseRepo.CheckIfCourseExists(x => x.CourseName.ToUp
per() == queryString.ToUpper(), queryString);
return View("Index", courseSearch);
}
else
{
var courses = courseRepo.GetAllCourses();
return View("Index", courses);
}
}
.
.
.

co urse Re po .Che ckIf Co urse Exist s(x => x.Co urse Nam e .T o Uppe r() == que rySt ring.T o Uppe r(),
que rySt ring) implements the same query as befo re, but we pass queryString to the functio n call as defined
in o ur ICo urseRepo sito ry and Co ursesRepo sito ry. No w if a co urse co ntains any part o f the string we
submitted as the seco ndary parameter, it will be displayed.

OBSERVE: ICo urseRepo sito ry and Co ursesRepo sito ry definitio n o f CheckIfCo urseExists
IQueryable<Courses> CheckIfCourseExists(Expression<Func<Courses, bool>> query, s
tring courseValue);
.
.
.
public IQueryable<Courses> CheckIfCourseExists(Expression<Func<Courses, bool>> q
uery, string courseValue)
{
return context.Courses.Where(obj => obj.CourseName.Contains(courseValue));
}

Update the search bo x in /Vie ws/Co urse s/Inde x.csht m l to reference o ur new, mo re ro bust search query:

CODE TO TYPE:
.
.
.
<h2>Index</h2>
<div>
@using(Html.BeginForm("QueryCoursesLooseQuery", "Courses", FormMethod.Post))
{
@Html.TextBox("queryString", null, new { placeholder = "Enter Course Sea
rch" })
<input type="submit" value="Search" />
}
</div>
.
.
.

and and try the new search functio n:

Our new query returns co urses that co ntain a full o r partial match o f the string entered in the search bo x.

No w implement the Ge t Co urse Range functio n by passing in a query and a seco ndary integer to co mpare.
In /Co nt ro lle rs/Co urse sCo nt ro lle r.cs, create a new actio n metho d named Ge t Range as sho wn:
CODE TO TYPE: Implementing GetCo urseRange in the GetRange() actio n metho d

.
.
.
public ActionResult GetRange(string val_1 = "", string val_2 = "")
{
int val_a, val_b;
bool tryParse = int.TryParse(val_1, out val_a);
if (tryParse && val_a > 99 && val_a < 1000)
{
tryParse = int.TryParse(val_2, out val_b);
if (tryParse && val_b > 99 && val_b < 1000)
{
if (val_a > val_b)
{
int temp = val_a;
val_a = val_b;
val_b = temp;
}
var CourseRange = courseRepo.GetCourseRange(val_a, val_b).ToList();
return View("Index", CourseRange);
}
else
{
ViewBag.Error = "Second input value is invalid. Must be an integer:
" + val_2;
return View("Index", courseRepo.GetAllCourses());
}
}
else
{
ViewBag.Error = "First input value is invalid. Must be a valid integer:
" + val_1;
return View("Index", courseRepo.GetAllCourses());
}
}
.
.
.

OBSERVE:
courseRepo.GetCourseRange(val_a, val_b).ToList();

co urse Re po .Ge t Co urse Range takes two integers rather than a query. This metho d lets us have mo re
co ntro l o ver the way a user interacts with o ur interfaces. Instead o f letting users create their o wn syntax, they
are restricted to passing o nly the values required to co mplete the o peratio n. Then we call T o List () to create
an item o ver which we can iterate.

Mo dify /Vie ws/Co urse s/Inde x.csht m l again so we can pass in a range o f values to o ur actio n metho d.

Note We pass in strings and then parse them there fo r impro ved erro r checking and greater flexibility.

Mo dify yo ur co de as sho wn:


CODE TO TYPE: Adding search bo xes to Index.cshtml to allo w a user to search a co urse range
<h2>Index</h2>

<div class="error">
@if (!String.IsNullOrEmpty(ViewBag.Error))
{
@ViewBag.Error
}
</div>
<div>
@using(Html.BeginForm("LooseQuery", "Courses", FormMethod.Post))
{
@Html.TextBox("queryString", null, new { placeholder = "Enter Course Sea
rch" })
<input type="submit" value="Search" />
}
</div>
<div>
@using (Html.BeginForm("GetRange", "Courses", FormMethod.Post))
{
<text><b>Search by course range: </b></text>@Html.TextBox("val_1", null,
new { @style = "width:30px;" })<text> <b>TO</b>
</text>@Html.TextBox("val_2", null, new { @style = "width:30px;" })
<input type="submit" value="Search Courses" />
}
</div>
.
.
.

and and enter bo th valid and invalid values to verify that o ur erro r checking and query wo rk as
expected:
Invalid input in bo x 1:
Invalid input in bo x 2:
Valid input results:
Other Queryable interfaces can be used the same way. The Interface queryable type o bjects can all be
extended. They have been created to be flexible.

T ip Fo r a listing o f queryable metho ds and their arguments, see MSDN Queryable Metho ds.

Extension Methods
Extensio n metho ds give us the freedo m to create functio ns and o verride different elements o f ASP.NET C#'s built-in
o bjects. We can create o ur o wn HTML Helper metho ds, Expressio ns, Queries, and so o n.

Create a new fo lder named Ext e nsio ns to ho ld the extensio n metho ds we want to build.

Our So lutio n Explo rer lo o ks like this no w:


Create an HtmlHelper extensio n. We'll create an Actio nLink that displays links that are highlighted fo r emphasis.

Mo ve the HTML extensio ns into a separate fo lder to keep them o rganized. In the /Ext e nsio ns fo lder, add a new fo lder
named Ht m lExt e nsio ns.

In the /Ht m lExt e nsio ns fo lder, add a class named Ht m lHe lpe rExt e nsio ns.

No w, add the required using statements and an HtmlHelper extensio n metho d that will o utline the current page in the
navigatio n menu. Mo dify /Ext e nsio ns/HT MLExt e nsio ns/Ht m lHe lpe rExt e nsio ns.cs as sho wn:
CODE TO TYPE: Required using statements fo r /Extensio ns/HTMLExtensio ns/HtmlHelperExtensio ns.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Linq.Expressions;
using System.Web.Mvc;
using System.Web.Mvc.Html;
using System.Web.Routing;

namespace Interfaces_Extensions.Extensions.HtmlExtensions
{
public static class HtmlHelperExtensions
{
public static MvcHtmlString isCurrentPageActionLink(this HtmlHelper html, strin
g text, string action, string controller)
{
var _action_ = html.ViewContext.RouteData.GetRequiredString("action");
var _controller_ = html.ViewContext.RouteData.GetRequiredString("controller
");

if (action == _action_ && controller == _controller_)


{
TagBuilder anchorTag = new TagBuilder("a");
anchorTag.Attributes["href"] = "#";
anchorTag.AddCssClass("current-link");
anchorTag.SetInnerText(text);
return MvcHtmlString.Create(anchorTag.ToString(TagRenderMode.Normal));
}
else
{
return html.ActionLink(text, action, controller);
}
}
}
}

In o ur isCurre nt Page Act io nLink extensio n metho d, we pass in arguments just as if we were creating an actual
Ht m l.Act io nLink, because the Actio nLink metho d is a functio n o f MvcHtmlString.

Let's discuss o ur metho d:

OBSERVE:

public static MvcHtmlString isCurrentPageActionLink(this HtmlHelper html, strin


g text, string action, string controller)
{
var _action_ = html.ViewContext.RouteData.GetRequiredString("action");
var _controller_ = html.ViewContext.RouteData.GetRequiredString("controller
");

if (action == _action_ && controller == _controller_)


{
TagBuilder anchorTag = new TagBuilder("a");
anchorTag.Attributes["href"] = "#";
anchorTag.AddCssClass("current-link");
anchorTag.SetInnerText(text);
return MvcHtmlString.Create(anchorTag.ToString(TagRenderMode.Normal));
}
else
{
return html.ActionLink(text, action, controller);
}
}
MvcHt m lSt ring returns an HTML enco ded string that sho uld no t be enco ded again.
ht m l.Vie wCo nt e xt .Ro ut e Dat a.Ge t Re quire dSt ring(st ring) retrieves a string fro m a set o f ro ute data
fro m the ViewCo ntext class. (ViewCo ntext is respo nsible fo r encapsulating info rmatio n related to views).
T agBuilde r ancho rT ag = ne w T agBuilde r(" a" ); creates an ancho r tag fo r o ur links so they are enco ded
pro perly. We use TagBuilder to create HTML tags.
ancho rT ag.At t ribut e s[" hre f " ] = " # " asso ciates the hre f attribute (o r any link pro perty) with o ur
ancho rTag, so instead o f allo wing it to link back to itself, we render it as an empty link so we can't click the
link, rerender the view, and relo ad all relevant data.
ancho rT ag.AddCssClass(" curre nt -link" ); assigns a CSS class to the ancho rTag element so that its
style is different fro m that o f o ther links.
MvcHt m lSt ring.Cre at e (ancho rT ag.T o St ring(T agRe nde rMo de .No rm al)) creates an MvcHtmlString
o bject and sets the T agRe nde rMo de to no rmal. TagRenderMo de is an enumerato r type o bject.

Here's a list o f TagRenderMo de o ptio ns and values:


No rm al renders "<tag></tag>" (with attributes if defined)
Note St art T ag renders "<tag>" (with attributes if defined)
EndT ag renders "</tag>"
Se lf Clo sing renders "<tag />"

No w that we have an HtmlExtensio n, we'll add a bit o f CSS so o ur curre nt -link pro perty do es so mething.

To add CSS, mo dify /Co nt e nt /sit e .css as sho wn:

CODE TO TYPE: Adding definitio n o f current-link to /Co ntent/site.css

/* Custom CSS for HtmlHelperExtension */


a.current-link
{
border:1px solid Red;
}

/*______End CSS for HtmlHelperExtension________*/

html {
background-color: #e2e2e2;
margin: 0;
padding: 0;
}
.
.
.

We add a red bo rder to the link fo r the view we are currently using. No w we need to change the Actio nLinks in the
_Layo ut .csht m l file so we kno w where we are:

CODE TO TYPE: Adding o ur isCurrentPageActio nLink() to _Layo ut.cshtml

.
.
.
<nav>
<ul id="menu">
<li>@Html.isCurrentPageActionLink("Home", "Index", "Home")</li>
<li>@Html.isCurrentPageActionLink("Courses", "Index", "Courses")</li>
<li>@Html.isCurrentPageActionLink("Students", "Index", "Students")</li>
</ul>
</nav>
.
.
.

Finally, add the HtmlHelperExtensio n namespace to We b.co nf ig—no t the main Web.co nfig—located in the Views
fo lder:
CODE TO TYPE: Adding the namespace to /Views/Web.co nfig

.
.
.
<system.web.webPages.razor>
<host factoryType="System.Web.Mvc.MvcWebRazorHostFactory, System.Web.Mvc, Version=4
.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
<pages pageBaseType="System.Web.Mvc.WebViewPage">
<namespaces>
<add namespace="System.Web.Mvc" />
<add namespace="System.Web.Mvc.Ajax" />
<add namespace="System.Web.Mvc.Html" />
<add namespace="System.Web.Optimization"/>
<add namespace="System.Web.Routing" />
<add namespace="Interfaces_Extensions.Extensions.HtmlExtensions" />
</namespaces>
</pages>
</system.web.webPages.razor>
.
.
.

Run the applicatio n to see the new additio n.

and , then navigate the main links and o bserve ho w they differ fro m previo us runs.

T ip If the changes do no t take effect, restart the sandbo x enviro nment.

Note MSDN has a listing o f all metho ds that have extensio ns and are available to the Entity Framewo rk.

Create a new fo lder in the Ext e nsio ns fo lder named DisplayNam e Fo rExt e nsio ns and add a new class to it named
DisplayNam e Fo rExt e nsio nHe lpe r.

Mo dify this new file as sho wn (create metho ds named DisplayNam e Fo rExt e nsio n, Ge t DisplayNam e , and
ge t Pro pe rt yNam e ):
CODE TO TYPE: Defining the three metho ds necessary fo r the DisplayNameFo rExtensio n metho d
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Linq.Expressions;
using System.Web.Mvc;
using System.Web.Mvc.Html;
using System.Web.Routing;

namespace Interfaces_Extensions.Extensions.DisplayNameForExtensions
{
public static class DisplayNameForExtensionHelper
{
public static MvcHtmlString DisplayNameForExtension<ModelType, PropertyType>(th
is HtmlHelper<IEnumerable<ModelType>> html, Expression<Func<ModelType, PropertyType>> q
uery)
{
var expression = GetDisplayName(html, query);
return getPropertyName<ModelType>(expression);
}

public static string GetDisplayName<ModelType, ClassType, PropertyType>(HtmlHel


per<ModelType> html, Expression<Func<ClassType, PropertyType>> query)
{
var expression = ExpressionHelper.GetExpressionText(query);
expression = html.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(ex
pression);
return expression;
}

public static MvcHtmlString getPropertyName<ClassType>(string property)


{
var metadata = ModelMetadataProviders.Current.GetMetadataForProperty(() =>
Activator.CreateInstance<ClassType>(), typeof(ClassType), property);
return new MvcHtmlString(metadata.DisplayName ?? typeof(ClassType).Name);
}
}
}

Our new extensio n metho d perfo rms the metho d calls like this:

1. DisplayNameFo rExtensio n()


2. GetDisplayName()
3. getPro pertyName()

getPro pertyName() returns either metadata.DisplayName o r typeo f(ClassType).Name. Let's discuss this new syntax:

OBSERVE:

public static string GetDisplayName<ModelType, ClassType, PropertyType>(HtmlHel


per<ModelType> html, Expression<Func<ClassType, PropertyType>> query)
{
var expression = ExpressionHelper.GetExpressionText(query);
expression = html.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(ex
pression);
return expression;
}

public static MvcHtmlString getPropertyName<ClassType>(string property)


{
var metadata = ModelMetadataProviders.Current.GetMetadataForProperty(() =>
Activator.CreateInstance<ClassType>(), typeof(ClassType), property);
return new MvcHtmlString(metadata.DisplayName ?? typeof(ClassType).Name);
}
Expre ssio nHe lpe r.Ge t Expre ssio nT e xt (que ry) returns the name o f the mo del fro m the lambda
expressio n that is passed into the metho d.
ht m l.Vie wCo nt e xt .Vie wDat a.T e m plat e Inf o .Ge t FullHt m lFie ldNam e (e xpre ssio n) retrieves a fully
qualified name fro m a field using an HTML attribute.
Mo de lMe t adat aPro vide rs.Curre nt .Ge t Me t adat aFo rPro pe rt y(() =>
Act ivat o r.Cre at e Inst ance <ClassT ype >(), t ype o f (ClassT ype ), pro pe rt y) retrieves the metadata fo r
o ur ClassType pro perty and returns the value in pro pe rt y. It requires a mo delAccesso r (() =>
Act ivat o r.Cre at e Inst ance <ClassT ype >()), a type o f co ntainer (t ype o f (ClassT ype )), and the name o f
a pro perty (pro pe rt y).

Let's put o ur new extensio n metho d to wo rk in the /Vie ws/St ude nt s/Inde x.csht m l file. Open that file and replace the
o ld DisplayNameFo r with o ur metho d DisplayNameFo rExtensio n:

CODE TO TYPE: Replacing DisplayNameFo r with DisplayNameFo rExtensio n in /Views/Students/Index.cshtml


.
.
.
<tr>
<th>
@Html.DisplayNameForDisplayNameForExtension(model => model.Name)
</th>
<th></th>
</tr>

@foreach (var item in Model) {


.
.
.

Add the namespace to /Vie ws/We b.co nf ig:

CODE TO TYPE: Adding namespace fo r DisplayNameFo rExtensio n to /Views/Web.co nfig

.
.
.
<pages pageBaseType="System.Web.Mvc.WebViewPage">
<namespaces>
<add namespace="System.Web.Mvc" />
<add namespace="System.Web.Mvc.Ajax" />
<add namespace="System.Web.Mvc.Html" />
<add namespace="System.Web.Optimization"/>
<add namespace="System.Web.Routing" />
<add namespace="Interfaces_Extensions.Extensions.HtmlExtensions" />
<add namespace="Interfaces_Extensions.Extensions.DisplayNameForExtensions" />
</namespaces>
</pages>
.
.
.

Run the applicatio n to see the result o f all o f yo ur hard wo rk.

and and navigate to the Students index page.


It wo rks just as planned. It displays all the students' names and no thing has changed o n the fro nt end.

Filtering Using Extension Methods


No w implement an Interface and repo sito ry fo r the Students entity. Create a new class in the /Int e rf ace s fo lder named
ISt ude nt sRe po sit o ry.

Mo dify the file as sho wn to implement the repo sito ry with the interface already defined:
CODE TO TYPE: Defining /Interfaces/IStudentsRepo sito ry.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Linq.Expressions;
using Interfaces_Extensions.Models;
using Interfaces_Extensions.IE_Context;
namespace Interfaces_Extensions.Interfaces
{
public classinterface IStudentsRepository : IDisposable
{
IEnumerable<Students> getStudents();
IQueryable<Students> OrderByName(char letter);
IQueryable<Students> Filter(string filterParams = "");
}
}

We have an Interface, no w we need to create a repo sito ry in the /Re po sit o rie s fo lder named St ude nt sRe po sit o ry.

To begin to implement o ur interface fo r the repo sito ry and define o ur interface metho ds, mo dify
/Re po sit o rie s/St ude nt sRe po sit o ry.cs as sho wn:
CODE TO TYPE: Defining /Repo sito ries/StudentRepo sito ry Interface metho ds

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Data;
using System.Linq.Expressions;
using Interfaces_Extensions.IE_Context;
using Interfaces_Extensions.Models;
using Interfaces_Extensions.Interfaces;

namespace Interfaces_Extensions.Repositories
{
public class StudentsRepository : IStudentsRepository, IDisposable
{
private InterfacesContext context;

public StudentsRepository(InterfacesContext context)


{
this.context = context;
}

public IEnumerable<Students> getStudents()


{
return context.Students.ToList();
}

public IQueryable<Students> OrderByName(char letter)


{

var list = getStudents();


return list.OrderByDescending(x => x.Name).AsQueryable();
}

public IQueryable<Students> Filter(string filterParams = "")


{
var list = getStudents();
return context.Students.Where(x => x.Name.Contains(filterParams));
}

private bool ContextDisposed = false;

protected virtual void Dispose(bool disposal)


{
if (!this.ContextDisposed)
{
if (disposal)
{
context.Dispose();
}
}
this.ContextDisposed = true;
}

public void Dispose()


{
Dispose(true);
GC.SuppressFinalize(this);
}
}
}
OBSERVE: getStudents() metho d

public IEnumerable<Students> getStudents()


{
return context.Students.ToList();
}

public IQueryable<Students> OrderByName(char letter)


{
var list = getStudents();
return list.OrderByDescending(x => x.Name[0] == letter).AsQueryable();
}

public IQueryable<Students> Filter(string filterParams = "")


{
return context.Students.Where(x => x.Name.Contains(filterParams));
}

The ge t St ude nt s() metho d returns a list o f students fro m o ur Students table.

Fo r the Orde rByNam e () metho d we are required to pass a letter fro m o ur co ntro ller that will be used to mo ve any
student name that start with that letter.

Our Filt e r() implementatio n accepts a string as a parameter and will filter the names depending o n whether the name
co ntains the string passed in to the functio n.

No w that we have set up o ur Student repo sito ry and interface, add a search bo x and a dro p-do wn list so we can use
the o rdering and filtering functio ns we just created. Mo dify /Vie ws/St ude nt s/Inde x.csht m l as sho wn:
CODE TO TYPE: Students Index.cshtml adding dro pdo wn list and search bo x
@model IEnumerable<Interfaces_Extensions.Models.Students>
@{
ViewBag.Title = "Index";
}
<h2>Index</h2>

<p>
@Html.ActionLink("Create New", "Create")

@using (Html.BeginForm("Filters", "Students", FormMethod.Post))


{
@Html.TextBox("filterParams", null, new{ placeholder = "Enter filter terms" })
<input type="submit" value="Filter!" />
}
@using (Html.BeginForm("OrderByName", "Students", FormMethod.Post))
{
<div>
Sort by student First Names <select name="letter">
@for (char c = 'A'; c <= 'Z'; c++ )
{
<option>@c</option>
}
</select>
<input type="submit" value="Sort" />
</div>
}

</p>
<table>
<tr>
<th>
@Html.DisplayNameForExtension(model => model.Name)
</th>
<th></th>
</tr>

@foreach (var item in Model)


{
<tr>
<td>
@Html.DisplayFor(modelItem => item.Name)
</td>
<td>
@Html.ActionLink("Edit", "Edit", new { id = item.Id }) |
@Html.ActionLink("Details", "Details", new { id = item.Id }) |
@Html.ActionLink("Delete", "Delete", new { id = item.Id })
</td>
</tr>
}
</table>

Let's discuss the dro pdo wn menu co de:


OBSERVE: Dro pdo wn List fo r Students Index.cshtml

@using (Html.BeginForm("OrderByName", "Students", FormMethod.Post))


{
<div>
Sort by student First Names <select name="letter">
@for (char c = 'A'; c <= 'Z'; c++ )
{
<option>@c</option>
}
</select>
<input type="submit" value="Sort" />
</div>
}

T ip Dynamic dro pdo wn lists are available using the Razo r syntax. See Dro pDo wnListFo r() - MSDN.

Here, we use basic HTML to create a <se le ct ></se le ct > item; then we po pulate o ur dro pdo wn list explicitly, using a
f o r lo o p and charact e rs as t he range .

No w we need to mo dify the St ude nt sCo nt ro lle r to implement o ur repo sito ry. Since we wo n't be using any o f the
Entity Framewo rk-created actio n metho ds (Add, Edit, and Delete), we'll delete them. We'll keep o nly the Index and
Dispo se metho ds:
CODE TO TYPE: Adding/Remo ving co de fro m StudentsCo ntro ller.cs to implement repo sito ry
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using Interfaces_Extensions.Models;
using Interfaces_Extensions.IE_Context;
using Interfaces_Extensions.Repositories;
using Interfaces_Extensions.Interfaces;

namespace Interfaces_Extensions.Controllers
{
public class StudentsController : Controller
{
private InterfacesContext db = new InterfacesContext();
private IStudentsRepository studRepo;

public StudentsController()
{
this.studRepo = new StudentsRepository(new InterfacesContext());
}

public StudentsController(IStudentsRepository studRepo)


{
this.studRepo = studRepo;
}

public ActionResult Filters(string filterParams = "")


{
return View(studRepo.Filter(filterParams));
}

public ActionResult OrderByName(char letter)


{
ViewBag.letter = letter;
return View(studRepo.OrderByName(letter));
}

//
// GET: /Students/

public ActionResult Index()


{
return View(db.Students.ToList()studRepo.getStudents());
}

//
// GET: /Students/Details/5

public ActionResult Details(short id = 0)


{
Students students = db.Students.Find(id);
if (students == null)
{
return HttpNotFound();
}
return View(students);
}

//
// GET: /Students/Create

public ActionResult Create()


{
return View();
}

//
// POST: /Students/Create

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

return View(students);
}

//
// GET: /Students/Edit/5

public ActionResult Edit(short id = 0)


{
Students students = db.Students.Find(id);
if (students == null)
{
return HttpNotFound();
}
return View(students);
}

//
// POST: /Students/Edit/5

[HttpPost]
public ActionResult Edit(Students students)
{
if (ModelState.IsValid)
{
db.Entry(students).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(students);
}

//
// GET: /Students/Delete/5

public ActionResult Delete(short id = 0)


{
Students students = db.Students.Find(id);
if (students == null)
{
return HttpNotFound();
}
return View(students);
}

//
// POST: /Students/Delete/5

[HttpPost, ActionName("Delete")]
public ActionResult DeleteConfirmed(short id)
{
Students students = db.Students.Find(id);
db.Students.Remove(students);
db.SaveChanges();
return RedirectToAction("Index");
}

protected override void Dispose(bool disposing)


{
dbstudRepo.Dispose();
base.Dispose(disposing);
}
}
}

Add a View fo r o ur Filt e rs and Orde rByNam e actio n metho ds. Right-click the actio n metho ds and select Add | Vie w:

Mo dify /Vie ws/St ude nt s/Filt e rs.csht m l as sho wn:


CODE TO TYPE: /Views/Students/Filters.cshtml
@model IEnumerable<Interfaces_Extensions.Models.Students>

@{
ViewBag.Title = "Filters";
}

<h2>Filters</h2>

<p>
@Html.ActionLink("Create NewBack to Index", "CreateIndex")
</p>
<table>
<tr>
<th>
@Html.DisplayNameFor(model => model.Name)
</th>
<th></th>
</tr>

@foreach (var item in Model) {


<tr>
<td>
@Html.DisplayFor(modelItem => item.Name)
</td>
<td>
@Html.ActionLink("Edit", "Edit", new { id=item.Id }) |
@Html.ActionLink("Details", "Details", new { id=item.Id }) |
@Html.ActionLink("Delete", "Delete", new { id=item.Id })
</td>
</tr>
}

</table>
Perfo rm the same changes to /Vie ws/St ude nt s/Orde rByNam e .csht m l:

CODE TO TYPE: Views/Students/OrderByName

@model IEnumerable<Interfaces_Extensions.Models.Students>

@{
ViewBag.Title = "OrderByName";
}

<h2>OrderByName</h2>

<p>
@Html.ActionLink("Create NewBack to Index", "CreateIndex")
</p>
<table>
<tr>
<th>
@Html.DisplayNameFor(model => model.Name)
</th>
<th></th>
</tr>

@foreach (var item in Model) {


<tr>
@if (item.Name[0] == ViewBag.letter)
{
<td style="color:#03c108;">
@Html.DisplayFor(modelItem => item.Name)
</td>
}
else
{
<td>
@Html.DisplayFor(modelItem => item.Name)
</td>
}
<td>
@Html.ActionLink("Edit", "Edit", new { id=item.Id }) |
@Html.ActionLink("Details", "Details", new { id=item.Id }) |
@Html.ActionLink("Delete", "Delete", new { id=item.Id })
</td>
</tr>
}

</table>

No w that we have o ur co ntro ller assembled, let's see it in actio n:

and and navigate to the Students index page and test o ur filtering extensio ns:
No w let's make o ur repo sito ry use an extensio n metho d so that o rdering and filtering are independent o f an interface.

Create a new fo lder in /Ext e nsio ns named IQue ryable Ext e nsio ns. Then, in
/Ext e nsio ns/IQue ryable Ext e nsio ns, create a class named IQue ryable Filt e rs.

Mo dify the new filter file as sho wn:


CODE TO TYPE: Adding co de to /Extensio ns/IQueryableExtensio ns/IQueryableFilters.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Linq.Expressions;
using System.Web.Mvc;
using System.Web.Mvc.Html;
using System.Web.Routing;
using System.Reflection;
using Interfaces_Extensions.Models;

namespace Interfaces_Extensions.Extensions.IQueryableExtensions
{
public static class IQueryableFilter
{
public static IQueryable<Students> filterByName(this IQueryable<Students> stude
nt, string name)
{
return student.Where(x => x.Name.Contains(name));
}

public static IQueryable<Students> filterByLetter(this IQueryable<Students> stu


dent, char letter)
{
return student.OrderByDescending(x => x.Name[0] == letter);
}

public static IQueryable<Courses> checkIfCourseExists(this IQueryable<Courses>


course, string courseName)
{
return course.Where(x => x.CourseName.ToUpper().Contains(courseName.ToUpper
()));
}

public static IQueryable<Courses> courseRange(this IQueryable<Courses> course,


int val_a, int val_b)
{
return course.Where(x => (x.Id > val_a && x.Id < val_b));
}
}
}

OBSERVE:

public static IQueryable<Courses> courseRange(this IQueryable<Courses> course, int val_


a, int val_b)

The t his keywo rd refers to the instance o f the current class co ntext.

The t his keywo rd is used to access pro perties o f a class within a Co nstructo r, Instance Metho ds, and
Note Instance Accesso rs.

The co de is similar to the Co ursesRepo sito ry and StudentsRepo sito ry files we implemented earlier:
OBSERVE: /Repo sito ries/Co ursesRepo sito ry

.
.
.
public IQueryable<Courses> CheckIfCourseExists(Expression<Func<Courses, bool>> query, s
tring courseValue)
{
return context.Courses.Where(obj => obj.CourseName.Contains(courseValue));
}

public IQueryable<Courses> GetCourseRange(int val_1, int val_2)


{
return context.Courses.Where(obj => (obj.Id > val_1 && obj.Id < val_2)).AsQueryable
();
}
.
.
.

OBSERVE: /Repo sito ries/StudentsRepo sito ry

.
.
.
public IQueryable<Students> OrderByName(char letter)
{

var list = getStudents();


return list.OrderByDescending(x => x.Name[0] == letter).AsQueryable();
}

public IQueryable<Students> Filter(string filterParams = "")


{
var list = getStudents();
return context.Students.Where(x => x.Name.Contains(filterParams));*/
}
.
.
.

No w, o pen /Vie ws/We b.co nf ig and add the new namespace to it:
CODE TO TYPE: Adding IQueryableExtensio ns namespace to /Views/Web.co nfig
.
.
.
<system.web.webPages.razor>
<host factoryType="System.Web.Mvc.MvcWebRazorHostFactory, System.Web.Mvc, Version=4
.0.0.0, Culture=neutral, PublicKeyToken=XXXXXXXXXXXXXXX" />
<pages pageBaseType="System.Web.Mvc.WebViewPage">
<namespaces>
<add namespace="System.Web.Mvc" />
<add namespace="System.Web.Mvc.Ajax" />
<add namespace="System.Web.Mvc.Html" />
<add namespace="System.Web.Optimization"/>
<add namespace="System.Web.Routing" />
<add namespace="Interfaces_Extensions.Extensions.DisplayNameForExtensions" />
<add namespace="Interfaces_Extensions.Extensions.HtmlExtensions"/>
<add namespace="Interfaces_Extensions.Extensions.IQueryableExtensions"/>
</namespaces>
</pages>
</system.web.webPages.razor>
.
.
.

Add the f ilt e rByNam e and f ilt e rByLe t t e r metho ds to /Re po sit o rie s/St ude nt sRe po sit o ry.cs:

CODE TO TYPE: adding IQueryableExtensio n metho ds to /Repo sito ries/StudentsRepo sito ry.cs

.
.
.
using System.Linq.Expressions;
using Interfaces_Extensions.IE_Context;
using Interfaces_Extensions.Models;
using Interfaces_Extensions.Interfaces;
using Interfaces_Extensions.Extensions.IQueryableExtensions;
.
.
.
public IQueryable<Students> OrderByName(char letter)
{

var list = getStudents().AsQueryable();


return list.OrderByDescending(x => x.Name[0] == letter).AsQueryable();list.filterBy
Letter(letter);
}

public IQueryable<Students> Filter(string filterParams = "")


{
var list = getStudents().AsQueryable();
return context.Students.Where(x => x.Name.Contains(filterParams));list.filterByName
(filterParams);
}

Add che ckIf Co urse Exist s and co urse Range metho ds to /Re po sit o rie s/Co urse sRe po sit o ry.cs file:
CODE TO TYPE: adding IQueryableExtensio n metho ds to /Repo sito ries/Co ursesRepo sito ry.cs
.
.
.
using System.Linq.Expressions;
using Interfaces_Extensions.IE_Context;
using Interfaces_Extensions.Models;
using Interfaces_Extensions.Interfaces;
using Interfaces_Extensions.Extensions.IQueryableExtensions;
.
.
.
public IQueryable<Courses> CheckIfCourseExists(Expression<Func<Courses, bool>> query, s
tring courseValue)
{
return context.Courses.Where(obj => obj.CourseName.Contains(courseValue));context.C
ourses.checkIfCourseExists(courseValue);
}

public IQueryable<Courses> GetCourseRange(int val_1, int val_2)


{
return context.Courses.Where(obj => (obj.Id > val_1 && obj.Id < val_2)).AsQueryable
();context.Courses.courseRange(val_1, val_2);
}
.
.
.

Make similar changes to the calls to the CheckIfCo urseExists metho d in Co urse sCo nt ro lle r.cs and
ICo urse Re po sit o ry.cs.

and and navigate to the Co urses and Students pages to verify that it wo rks as it did befo re:
We've created an element o f abstractio n by implementing o ur IQueryable filtering extensio ns. Rather than require o ur
repo sito ries to implement the interfaces, we can create a separate sectio n where o ur filtering and searching is self-
co ntained.

By using Interfaces and Extensio n metho ds, an API (Applicatio n Pro grammer Interface) can interact with o ther
pro grams.

MSDN pro vides excellent do cumentatio n o n the IQueryable generalized interface that can be mo dified to fit
T ip specific yo ur needs.

Phew! That was a lo ng lesso n. I'm glad yo u're sticking with it. Practice what yo u've learned here in yo ur ho mewo rk befo re yo u
mo ve o n.

Copyright © 1998-2014 O'Reilly Media, Inc.

This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.
See http://creativecommons.org/licenses/by-sa/3.0/legalcode for more information.
Web Services
Lesson Objectives
In this lesso n yo u will:

find and use web services with yo ur C# applicatio ns.

What is a Web Service?


A web service is so ftware written and deplo yed to act as a so ftware service using the internet. As yo u learn to create a
web service, yo u'll enco unter a wide variety o f standards, pro to co ls, styles, and so o n. Essentially, web services are
separated into two camps: SOAP and REST. We will co ver each o f these standards in depth, but first we'll create a web
service to facilitate o ur discussio n.

Any so ftware that runs o ver a netwo rk using any co mmunicatio n pro to co l and standard, co uld be co nsidered a web
service. Fo r o ur purpo ses, a web service is a so ftware service accessible thro ugh the internet using a standard
pro to co l such as HTTP, that exchanges info rmatio n using a standard fo rmat such as JSON o r XML. Web services are
pro gramming language-independent, but we'll be using C# and MVC.

Web services can be co mplete applicatio ns, but mo re co mmo nly they are so ftware co mpo nents that serve a specific
ro le. Many bro wser-based so ftware applicatio ns (web applicatio ns), interact with o ne o r mo re individual web services,
altho ugh many deskto p, mo bile, tablet, and o ther applicatio n types also use web services.

A web service is almo st always self-co ntained and self-describing. In o ther wo rds, yo u can access the full range o f
features o f an individual web service witho ut requiring o ther web services, and yo u can determine which features are
available by querying the web service. In fact, an entire gro up o f vendo rs exists using Service Oriented Architecture
(SOA) as a guiding co ncept fo r their pro ducts. Go o gle has mo st o f their pro ducts created using the SOA co ncept, and
have expo sed numero us web services fo r anyo ne to use. Yo u can find free web services by searching o nline fo r f re e
we b se rvice s.

Okay, eno ugh talk. Let's create a C# MVC web service pro ject!

Our First Web Service


Select File | Ne w Pro je ct , and in the New Pro ject dialo g bo x, select Visual C# | We b under Installed Templates, then
ASP.NET MVC 4 We b Applicat io n. Change the pro ject Name to MVCSt ude nt We bAPI, click OK, select We b API
fro m the New ASP.NET MVC 4 Pro ject dialo g bo x, and click OK. Run the pro ject, and yo u'll see this page:

That screen lo o ks like a typical MVC web applicatio n—what's different? When we examine the Co ntro llers fo lder in the
So lutio n Explo rer, we find two co ntro llers: Ho meCo ntro ller and ValuesCo ntro ller. When we o pen the
Ho meCo ntro ller.cs, we find a typical co ntro ller that returns a view, undo ubtedly where the default page came fro m.
When we o pen the ValuesCo ntro ller, we see metho ds that are designed fo r use as a web service. The metho ds listed
belo w are pro vided by default, altho ugh yo ur listing may be slightly different:

OBSERVE: ValuesCo ntro ller.cs


public class ValuesController : ApiController
{
// GET api/values
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}

// GET api/values/5
public string Get(int id)
{
return "value";
}

// POST api/values
public void Post([FromBody]string value)
{
}

// PUT api/values/5
public void Put(int id, [FromBody]string value)
{
}

// DELETE api/values/5
public void Delete(int id)
{
}
}

These metho ds may appear similar to no rmal MVC co ntro ller and view metho ds; the co ntro ller inherits fro m
ApiCo nt ro lle r. Classes derived fro m this class do no t return views. Familiar syntax co mments preceding each
metho d. This API syntax no t o nly describes the bro wser path, but the required HTTP verbs.

GET is used to send info rmatio n as part o f the URI and sho uld be repeatable witho ut any side effects.
POST is used to send individual gro ups o f info rmatio n that do es have side effects. Each POST results in a
separate server respo nse. POST is typically used with HTML fo rm co ntro l submissio ns.
DELET E is used to request the remo val o f data and is no t repeatable.
PUT is used to request mo dificatio n o r insertio n o f data, typically to a single o bject, and is repeatable.

Did yo u no tice we used the acro nym URI rather than URL? URI is the acro nym fo r Unifo rm Reso urce
Identifier. Only when the co mplete specificatio n is included to identify the reso urce do es a URI beco me a
Note URL, which requires the pro to co l and the do main. Witho ut this info rmatio n, a URL path is really just a
URI.

When we wo rked with JavaScript and Ajax, we were able to specify the HTTP verb, which makes using Ajax with web
services co nvenient.

Let's try the GET verb and URI pattern. Return to the bro wser o r rerun the applicatio n, then add /api/value s/ to the URL.

Depending o n yo ur bro wser, yo u will either see o utput, o r be pro mpted to do wnlo ad the results as a file. If yo u were
pro mpted to save as a file, save the HTTP GET results as a text file, and then o pen the file using a text edito r like
No tepad:
OBSERVE: GET URL With Numerical
<ArrayOfstring><string>value1</string><string>value2</string></ArrayOfstring>

Ho w did o ur MVC applicatio n use the ro ute to reso lve to the co rrect metho d? We've seen ro uting in previo us lesso ns,
and o ur MVC Web API applicatio n has the same default ro uting that we saw earlier in the
/App_St art /Ro ut e Co nf ig.cs file, but fo r Web API ro uting, MVC uses the /App_St art /We bApiCo nf ig.cs file ro uting
instead as sho wn:

OBSERVE: WebApiCo nfig.cs


config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);

Each HTTP request is ro uted to a co ntro ller by co nsulting the ro uting table. Fro m the ro uting declaratio n in
WebApiCo nfig.cs, we can see that the ro ute is api/{co nt ro lle r} /{id} .

Yo u can use the {co nt ro lle r} and {id} placeho lders to match against the inco ming URI and {id} if yo u like. These are
the general ro uting rules used in an MVC Web API pro ject:

{co ntro ller} is matched against the co ntro ller name.


The HTTP verb (also kno wn as HTTP request metho d) is matched against all o r the first part o f the co ntro ller
metho d names.
{id}, if present, is matched to a metho d parameter named id.
Query parameters are matched to metho d parameter names where po ssible.

Note Query parameters typically fo llo w the questio n mark (?) symbo l in a URI.

Metho d names do no t have to be an exact match; the MVC ro uting will attempt to find the clo sest match.

Go ahead and try the GET verb fo llo wed by a number, by adding /api/value s/5 to the URL.

Yo ur results will lo o k so mething like this:

OBSERVE: GET URL With Numerical


<string>value</string>

No t bad fo r a first web service pro ject. We'll create ano ther custo m web pro ject sho rtly, but first let's discuss web
services in general, and the different metho ds available.

Web Services: SOAP and REST


Histo rically, the mo st co mmo n web service has been SOAP (Simple Object Access Pro to co l). To day mo st o nline web
service pro viders co ntinue to o ffer this web service specificatio n, and many o thers o ffer o nly this o ptio n. Since the
inceptio n o f SOAP (aro und 19 9 8 ), ano ther web service architecture has emerged kno wn as REST (Representatio nal
State Transfer). Each o f these web service pro to co ls o ffers unique features, but discussio n o f tho se is beyo nd the
sco pe o f this lesso n. Fo r no w, we'll summarize a co uple o f po ints abo ut each pro to co l:

SOAP:

uses a variety o f co mmunicatio n pro to co ls, but HTTP has beco me mo re co mmo n.
requires SOAP standards, where a SOAP message must include an XML-based envelo pe fo r identificatio n,
and an XML-based bo dy that specifies service and respo nse specifics.
requires and pro vides an XML-based WSDL (Web Services Descriptio n Language) co nstruct that describes
the services o ffered and mechanism fo r usage.
typically uses XML-based respo nse fo rmat, altho ugh o ther fo rmats are suppo rted.
REST :

typically uses HTTP, but many o ther pro to co ls are po ssible.


uses a URL syntax fo r co mplete access to the web service.
the URI syntax is intended to be self-explanato ry.
typically uses JSON as a data transpo rt, altho ugh XML is also heavily used, and can suppo rt any valid
HTTP-co mpatible media type.
syntax used by MVC Web API.

The sample MVC Web API pro ject emplo y the REST web service pro to co l, so let's mo dify it to add capabilities.

Adding REST Methods


In the MVCStudentWebAPI pro ject, add a student mo del, a new co ntro ller to manage a web services API fo r students
with appro priate messages, a "hard-co ded" array to represent o ur "database," and then we'll query o ur database
using Ajax.

Right-click o n the /Mo de ls fo lder in the So lutio n Explo rer, select Add | Class, change the class Name to St ude nt ,
and click Add. Mo dify /Mo de ls/St ude nt .cs as sho wn:

CODE TO TYPE: /Mo dels/Student.cs


.
.
.
namespace MVCStudentWebAPI.Models
{
public class Student
{
public string Name { get; set; }
public string ID { get; set; }
}
}

No w add the co ntro ller.

Right-click the Co nt ro lle rs fo lder, select Add | Co nt ro lle r, change the co ntro ller name to St ude nt Co nt ro lle r,
select the Em pt y API Co nt ro lle r template, and click Add. Mo dify /Co nt ro lle rs/St ude nt Co nt ro lle r.cs as sho wn:
CODE TO TYPE: /Co ntro llers/StudentCo ntro ller.cs
.
.
.
using MVCStudentWebAPI.Models;
.
.
.
public class StudentController : ApiController
{
Student[] students = new Student[]
{
new Student { Name = "Tom Thumb", ID="ID001" },
new Student { Name = "Bob Robertson", ID="ID002" },
new Student { Name = "Alice Wonders", ID="ID003" },
new Student { Name = "Sarah Smith", ID="ID004" },
new Student { Name = "April Showers", ID="ID005" },
};

// GET /api/student
public IEnumerable<Student> GetAllStudents()
{
return students;
}

// GET /api/student/id
public HttpResponseMessage GetStudentByID(string id)
{
HttpResponseMessage response;
var student = students.FirstOrDefault(s => s.ID == id);
if (student == null)
{
response = new HttpResponseMessage(HttpStatusCode.NotFound)
{
Content = new StringContent(string.Format("No student with ID = {0}", i
d)),
ReasonPhrase = "Student ID Not Found"
};
}
else
{
response = Request.CreateResponse(HttpStatusCode.OK, student);
response.Content.Headers.Expires = new DateTimeOffset(DateTime.Now.AddSecon
ds(300));
}
return response;
}
}
.
.
.

and . Augment the URL like befo re, but fo llo w the API URI syntax as given in the co mments. Adding
/api/st ude nt returns all o f the students, while /api/st ude nt /ID0 0 1 returns an individual student. A request fo r an
invalid ID will return an erro r message.

The MVC Web API by default will return XML, but yo u can specify which type o f data yo u want, just as we did in the
JavaScript lesso n when using jQuery and Ajax. Let's implement co de in o ur view that will return JSON data instead o f
XML. We'll use the sho rtcut jQuery Ajax getJSON call.

Mo dify /Vie ws/Ho m e /Inde x.csht m l. Delete all o f the Razo r and HTML co de in the file, and retype the co de as sho wn:
CODE TO TYPE: /Views/Ho me/Index.cshtml
<header>
<div class="content-wrapper">
<div class="float-left">
<p class="site-title">
<a href="~/">ASP.NET MVC Web API</a></p>
</div>
</div>
</header>
<div id="body">
<section class="content-wrapper main-content clear-fix">
<div>
<h2>All Students</h2>
<ul id="students" />
</div>
<div>
<h2>Search by Student ID</h2>
<input type="text" id="studentID" size="5" />
<input type="submit" value="Search" onclick="studentSearch();" />
<p id="student" />
</div>
</section>
</div>

@section scripts {
<script type="text/javascript">
var apiURI = "api/student";

$(document).ready(function () {
// Send an Ajax request
$.getJSON(apiURI)
.done(function (data) {
// On success, 'data' contains a list of students
$.each(data, function (key, student) {
// Add a list item for the student.
$('<li>', { text: formatStudent(student) }).appendTo($('#students')
);
});
});
});

function formatStudent(student) {
return student.Name + ': ' + student.ID;
}

function studentSearch() {
var id = $('#studentID').val();
$.getJSON(apiURI + '/' + id)
.done(function (data) {
$('#student').text(formatStudent(data));
})
.fail(function (jqXHR, textStatus, err) {
$('#student').text('Error: ' + jqXHR.responseText);
});
}
</script>
}

and . Yo u see this web page:


After the page lo ads, the first call to getJSON gets all o f the students. Using the Se arch text field and butto n, yo u can
search fo r a valid student ID, o r generate an erro r message by searching fo r an invalid student ID.

Accessing Public Web Services


We can access public web services, but any service available fo r free o nline co uld disappear, so we need to select o ne
we think will be aro und fo r awhile. Let's try a web service fro m Go o gle that returns time zo ne info rmatio n.

One o f the benefits o f a web service is that o ften yo u can access the service co mpletely thro ugh a web bro wser. Enter
the URL ht t ps://m aps.go o gle apis.co m /m aps/api/t im e zo ne /jso n?lo cat io n=39 .6 0 34 810 ,-
119 .6 8225 10 &t im e st am p=133116 120 0 &se nso r=t rue in a web bro wser, and yo u'll see o utput similar to this:

OBSERVE: Go o gle TimeZo ne URL and JSON Result


{
"dstOffset" : 0.0,
"rawOffset" : -28800.0,
"status" : "OK",
"timeZoneId" : "America/Los_Angeles",
"timeZoneName" : "Pacific Standard Time"
}

Just as we were able to interact with the web services we created o urselves, we can make calls to o ther web services,
as lo ng as we understand ho w to use them. Fo r the Go o gle TimeZo ne API, yo u can refer to the suppo rt page at The
Go o gle Time Zo ne API. Let's mo dify o ur student pro ject to display the timeZo neId fro m a call to this web service.
Mo dify /Vie ws/Ho m e /Inde x.csht m l as sho wn:
CODE TO TYPE: /Views/Ho me/Index.cshtml
.
.
.
<div>
<h2>All Students</h2>
<p id="tz" />
<ul id="students" />
</div>
.
.
.
$(document).ready(function () {
// Send an Ajax request
$.getJSON(apiURI)
.done(function (data) {
// On success, 'data' contains a list of students
$.each(data, function (key, student) {
// Add a list item for the student.
$('<li>', { text: formatStudent(student) }).appendTo($('#students'));
});
});
$.getJSON('https://maps.googleapis.com/maps/api/timezone/json?location=39.6034810,-
119.6822510&timestamp=1331161200&sensor=true')
.done(function (data) {
$('#tz').text(data.timeZoneId);
});
});
.
.
.

and . Yo u see the time zo ne info rmatio n Am e rica/Lo s_Ange le s fo r the lo catio n.

Yo u can also make the web service call fro m within C# co de and no t jQuery. When making a web service call fro m
co de, yo u have to co nsider the type o f web service yo u want to call and adhere to the requirements o f that web service.
Fo r a REST call using GET (which is what the Go o gle Time Zo ne call uses), we need to do a web request fro m within
C#.

Mo dify /Co nt ro lle rs/Ho m e Co nt ro lle r.cs as sho wn:


CODE TO TYPE: /Co ntro llers/Ho meCo ntro ller.cs
.
.
.
using System.Net;
using System.Xml;
.
.
.
public ActionResult Index()
{
string url = "https://maps.googleapis.com/maps/api/timezone/xml?location=39.6034810
,-119.6822510&timestamp=1331161200&sensor=true";
HttpWebRequest request = WebRequest.Create(url) as HttpWebRequest;
using (HttpWebResponse response = request.GetResponse() as HttpWebResponse)
using (XmlReader xmlReader = XmlReader.Create(response.GetResponseStream()))
{
xmlReader.ReadToFollowing("time_zone_id");
ViewBag.TimeZone = xmlReader.ReadElementContentAsString();
}

return View();
}
.
.
.

No w, mo dify /Vie ws/Ho m e /Inde x.csht m l as sho wn:

CODE TO TYPE: /Views/Ho me/Index.cshtml


<div>
<h2>All Students</h2>
Internal web service read time zone id: @ViewBag.TimeZone
<p id="tz" />
<ul id="students" />
</div>

and . Yo u no w see a new line that sho ws the time zo ne ID as retrieved in co de and passed to the view using
ViewBag.

Web Service Legacy and Windows Communication Foundation (WCF)


To be co mplete, we wo uld like to demo nstrate using pure SOAP fro m C#, but if yo u search o nline fo r SOAP-based
web services, mo st o f them are no lo nger available. This is a testament to the gro wing po pularity o f REST and the
decline o f SOAP. Despite the prevalence o f REST-based web services, SOAP still exists, but finding an available SOAP
service that will co ntinue to be available after these lesso ns are published might be pro blematic, so we will create o ur
o wn SOAP service.

The Micro so ft Visual Studio with .NET arrived in 20 0 2; the features o f that pro duct have undergo ne a number o f
changes, including web services. Previo usly, Studio suppo rted creating an ASP.NET Web Service, but this template is
no lo nger available as a primary template. Ho wever, we can still create these co mpo nents by adding them directly to
o ur pro ject. We do that because we want to create a quick SOAP-co mpatible co mpo nent we can use to demo nstrate
WCF. WCF is a Micro so ft framewo rk that can be used to create SOA style o f so ftware applicatio ns, and suppo rts the
SOAP WSDL as well as REST; data can be exchanged in a variety o f fo rmats, including XML and JSON. We co uld
create a SOAP service using WCF, but the co mplexity o f such an implementatio n is beyo nd the sco pe o f this lesso n.
The o lder Web Service co mpo nents are still co mpletely valid, and o ften used when an implementatio n do es no t need
the co mplexity o f WCF.

Right-click the MVCSt ude nt We bAPI pro ject name in the So lutio n Explo rer, select Add | Ne w It e m , select We b fo r
the Template catego ry, find and select We b Se rvice in the list, change the web service Name to
St ude nt We bSe rvice .asm x, and click Add. The Studio Co de Edito r will o pen with the StudentWebService.asmx
co de.
No w add a reference to o ur pro ject to suppo rt the web service.

Right-click the /Re f e re nce s fo lder in the So lutio n Explo rer, select Add Re f e re nce , and in the Add Reference dialo g
bo x, select the .NET tab, lo cate Syst e m .We b.Ext e nsio ns in the list, as sho wn:

Click OK. Mo dify St ude nt We bSe rvice .asm x as sho wn:


CODE TO TYPE: StudentWebService.asmx
.
.
.
using System.Web.Script.Services;
using System.Xml;
using System.Xml.Serialization;
using System.IO;
using System.Text;
using MVCStudentWebAPI.Models;
.
.
.
// [System.Web.Script.Services.ScriptService]
public class StudentWebService : System.Web.Services.WebService
{
Student[] students = new Student[]
{
new Student { Name = "Tom Thumb", ID="ID001" },
new Student { Name = "Bob Robertson", ID="ID002" },
new Student { Name = "Alice Wonders", ID="ID003" },
new Student { Name = "Sarah Smith", ID="ID004" },
new Student { Name = "April Showers", ID="ID005" },
};

[WebMethod]
[ScriptMethod(ResponseFormat = ResponseFormat.Json, UseHttpGet = true)]
public string HelloWorld()
{
return "Hello World";
}

[WebMethod(Description="Returns all students")]


public string GetAllStudents()
{
return ObjectToXML(students);
}

private string ObjectToXML(object obj)


{
StringBuilder strXML = new StringBuilder();
try
{
Type objectType = obj.GetType();
XmlSerializer xmlSerializer = new XmlSerializer(objectType);
MemoryStream memoryStream = new MemoryStream();
try
{
using (XmlTextWriter xmlTextWriter = new XmlTextWriter(memoryStream, En
coding.UTF8) { Formatting = Formatting.Indented })
{
xmlSerializer.Serialize(xmlTextWriter, obj);
memoryStream = (MemoryStream)xmlTextWriter.BaseStream;
strXML.Append(new UTF8Encoding().GetString(memoryStream.ToArray()))
;
}
}
finally
{
memoryStream.Dispose();
}
}
catch (Exception ex)
{
strXML.Clear();
strXML.Append("<Error>" + ex.Message + "</Error>\n");
}
return strXML.ToString();
}
}
.
.
.

The changes we made allo w o ur co de to suppo rt calling the metho ds and serializing the student data as an XML
string.

No w, mo dify /Vie ws/Ho m e /Inde x.csht m l to demo nstrate calling the StudentWebService.asmx Hello Wo rld metho d:

CODE TO TYPE: /Views/Ho me/Index.cshtml


.
.
.
<div>
<h2>Web Service</h2>
<p id="webServiceHelloWorld" />
</div>
.
.
.
$(document).ready(function () {
// Send an Ajax request
$.getJSON(apiURI)
.done(function (data) {
// On success, 'data' contains a list of students
$.each(data, function (key, student) {
// Add a list item for the student.
$('<li>', { text: formatStudent(student) }).appendTo($('#students')
);
});
});
$.getJSON('https://maps.googleapis.com/maps/api/timezone/json?location=39.6
034810,-119.6822510&timestamp=1331161200&sensor=true')
.done(function (data) {
$('#tz').text(data.timeZoneId);
});
});

$.ajax({
type: "GET",
contentType: "application/json; charset=utf-8",
url: "StudentWebService.asmx/HelloWorld",
data: '{ }',
dataType: "json",
success: function (data) {
$('#webServiceHelloWorld').text(data.d);
},
error: function (data) {
$('#webServiceHelloWorld').text(data.d);
}
});

and ; yo u see the text He llo Wo rld.

Remember, we created the web service to demo nstrate SOAP using WCF. No w let's demo nstrate that the web service
do es actually suppo rt SOAP. Run the pro ject again, but this time, add /St ude nt We bSe rvice .asm x to the URL.

Yo u see so mething like this:


ASP.NET Web Services generate these pages to help test yo ur web service. Read thro ugh the text, then click o n either
o f the web service metho ds. Yo u'll see a page that discusses the SOAP request and respo nse fo r these web services;
yo u can execute, o r "invo ke" them:
View the WSDL by appending ?wsdl to the URL, resulting in an XML listing that describes each available metho d.

Finally, create a way to call to wo rk with a SOAP web service using WCF. Altho ugh there are a variety o f techniques that
might be used to interact with a SOAP web service using WCF, the technique we'll use is to add a Service Reference,
effectively creating a stro ngly typed o bject we can use in o ur co de.

Right-click the /Re f e re nce s fo lder in the So lutio n Explo rer and select Add Se rvice Re f e re nce . In the Add Service
Reference dialo g, select the Disco ve r | Se rvice s in So lut io n dro pdo wn to have Studio find the StudentWebService
within the pro ject. Once it is disco vered, yo u can use the Services sectio n o f the dialo g bo x to examine the available

services. Change the Namespace to St ude nt We bSe rvice Re f e re nce and click OK. .

When yo u add a service reference, a new fo lder named Se rvice Re f e re nce s is added to yo ur pro ject with the
StudentWebServiceReference. Changes are made to the /Web.Co nfig file to suppo rt this reference, so yo u can use this
reference to access o ur SOAP-suppo rting web service.

The pro cess o f adding a Service Reference to wrap a SOAP web service is referred to as creating a pro xy.

Yo u can remo ve the service reference by deleting it fro m the pro ject. Deleting the service reference will also remo ve the
/Web.Co nfig changes.

Mo dify /Co nt ro lle rs/St ude nt Co nt ro lle r.cs as sho wn:


CODE TO TYPE: /Co ntro llers/StudentCo ntro ller.cs

.
.
.
// POST /api/student
[HttpPost]
public string GetWCFStudents()
{
StudentWebServiceReference.StudentWebServiceSoapClient ws = new StudentWebServiceRe
ference.StudentWebServiceSoapClient();
return ws.GetAllStudents();
}
.
.
.

This co de may lo o k familiar, except that we've deco rated the metho d call with the HttpPo st deco ratio n to ensure this
metho d can o nly be called when using the POST verb. Why? Because MVC will apply ro uting to o ur URI syntax, and we
already have o ne GET metho d, so to avo id co mplicating this pro ject by changing the ro uting tables, we can specify the
unused POST verb. Using the newly created StudentWebServiceReference, we're able to call o ur web service using
SOAP witho ut wo rrying abo ut the SOAP details.

To co mplete the pro cess and test o ur changes, mo dify /Vie ws/Ho m e /Inde x.csht m l as sho wn:

CODE TO TYPE: /Views/Ho me/Index.cshtml

.
.
.
<div>
<h2>WCF Service</h2>
<textarea rows="20" cols="80" id="wcfStudents"></textarea>
</div>
.
.
.
$.ajax({
type: "POST",
contentType: "application/json; charset=utf-8",
url: apiURI,
data: '{ }',
dataType: "text",
success: function (data) {
document.getElementById('wcfStudents').value = data;
},
error: function (data) {
document.getElementById('wcfStudents').value = data;
}
});
.
.
.

and , and no w yo u'll see the returned XML fro m the WCF mapped web service using SOAP.

Yo u kno w quite a bit abo ut web services. Practice using that kno wledge and I'll see yo u so o n!

Copyright © 1998-2014 O'Reilly Media, Inc.

This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.
See http://creativecommons.org/licenses/by-sa/3.0/legalcode for more information.
Final Project
Lesson Objectives
In this final lesso n and pro ject, yo u will:

co mbine many o f the techniques yo u've learned thro ugho ut this co urse and previo us C# co urses to create a
co mprehensive dynamic MVC web applicatio n that will include unit tests and a database.
create a website that includes public and private webpage co ntent, and emplo y ASP.NET security.
inco rpo rate UI Design, Class Diagram, and Use Cases.
plan a co mplete pro ject and include the develo pment o f Use Cases.

Project Functional Specifications


This list summarizes the functio nal specificatio ns and requirements fo r yo ur final co urse pro ject:

Create a web applicatio n called OST We bBlo g.


The web applicatio n will allo w multiple users to add blo g po sts to a blo g website.
Each blo g user ("blo gger") will register with the website, selecting their username and passwo rd.
Only registered users may add blo gs.
Each blo g po st will co nsist o f a title, main co ntent, and a time/date stamp that reco rds when the blo g was
created.
A main page will list each blo gger by their username, will allo w selectio n to view an individual blo g, and
display the current to tal o f blo g po stings per blo gger.
Blo g po stings are o nly managed, including editing and deletio n, by the registered blo gger who added the
co ntent.
Display o f blo g po stings will be in reverse chro no lo gical o rder by the last po sted time/date stamp.
No n-registered readers sho uld be able to add co mments to individual blo g po stings.
Fo r a blo g po sting, the title and main co ntent are required, so yo u must use C# mo del attribute validatio n.
The applicatio n must be implemented using MVC and Razo r, and include Unit Testing.
Yo u must use the Entity Framewo rk to create yo ur class mo dels. Yo u may use either the Database First,
Mo del First, o r Co de First wo rkflo w.

In additio n to the required features, yo u may co nsider adding any o f these features:

Add a flag to allo w blo g po sts to be private o r public.


Administrato r review and flagging o f questio nable po stings and co mments.
Catego rizing blo g po stings.
Multiple so rt views.

Planning the Project


As with o ther pro jects, yo u need to plan ho w yo u will co mplete each o f the necessary tasks fo r this pro ject, which
includes Use Cases, UI Design, the Entity Data Mo del and database Data Mo del, Co ding, Unit Tests, and Testing.
We've pro vided the Functio nal Specificatio ns, listing o ut the requirements o f the applicatio n, and included a few
o ptio nal features, but yo u can add o ther features so lo ng as the minimum functio nality is pro vided. Pro ceed thro ugh
each o f the catego ries belo w, pro ducing the needed co ntent as part o f yo ur final pro ject.

Use Cases
Co nsider ho w yo ur website applicatio n will be used, and then list each o f the Use Cases. Yo u may find this task easier
if yo u create a list o f each o f the MVC Views, then list the capabilities o r functio nality pro vided by each View. Fo r
example:

Ind ex View
Blo g selectio n fro m co mplete list o f blo ggers.
Registratio n using blo g registratio n link.
Blo gger lo gin using blo g lo gin entry bo xes.
Navigatio n to o ther sectio ns o f website.

UI Design
A majo r co mpo nent o f any applicatio n is the user interface (UI). Fo r develo pment, creating an initial o r pro to type UI will
help yo u o rganize yo ur applicatio n, and pro vide a visual reminder o f the features yo u need to implement. Yo u must
pro vide an initial UI Design fo r yo ur pro ject, ho wever, with MVC the UI is o ften co nstructed using class data mo dels, so
yo u will need to iterate thro ugh this step, and the Entity Data Mo del step, to co mplete the initial UI Design.

There is a sample UI Design fo r the Index View belo w (user lo gged in, and user lo gged o ut). Altho ugh the layo ut and
co lo rs are based o n the MVC Internet Applicatio n template, yo u may set up yo ur pro ject layo ut and co lo rs ho wever
yo u'd like. We elected to use the Internet Applicatio n template to include ASP.NET registratio n and security.
Data Modeling: Entity Data Model
When creating MVC applicatio ns, classes serve as the mo dels to ho ld o ur applicatio n data, back up o ur UI co ntro ls,
and enable MVC and the Entity Framewo rk to pro vide data-typed co nstructs that simplify the develo pment pro cess.
Yo u must use o ne o f the Entity Framewo rk wo rkflo ws. Yo ur final data mo del must include appro priate asso ciatio ns
between yo ur classes. Here's o ne po ssible data mo del fo r this applicatio n:
Data Modeling: Database T ables
An advantage o f MVC is its ability to persist mo dels to permanent sto rage, such as a database. A variety o f techniques
are available to create database tables. If yo u elect to fo llo w the Mo del First wo rkflo w, yo u may want to use the Entity
Framewo rk Entity Data Mo del Designer to generate yo ur tables.

Software Development and Unit T est Development


After yo u've created yo ur Entity Data Mo del and generated the individual data mo del classes, yo u will pro ceed to add
co de and View co ntent. We stro ngly reco mmend that yo u co de a set o f use cases and View co ntent, and add the
necessary functio nality to yo ur class mo dels. As yo u add such functio nality, add the unit tests that are required to test
yo ur class functio nality tho ro ughly. Unit tests are required fo r the pro ject, and befo re submissio n, all o f yo ur unit tests
sho uld pass with no erro rs.

T esting and Completion


As part o f the pro ject develo pment pro cess, yo u sho uld iterate thro ugh use case, View, and functio nality, adding unit
tests as appro priate. After yo u've finished adding the functio nality fo r a use case, and as yo u add additio nal use cases,
perfo rm integrated testing to ensure that yo ur applicatio n wo rks as designed. Do no t wait to perfo rm integrated testing
until the end o f all use cases, but instead perfo rm testing incrementally as the applicatio n is develo ped.

Make sure yo ur final pro ject co mpiles witho ut erro rs, meets all o f the functio nal requirements and specificatio ns, and
includes unit tests fo r all o f the class functio nality.

Once yo u've co mpleted this final pro ject, yo u've co mpleted the co urse! Co ngratulatio ns!

Copyright © 1998-2014 O'Reilly Media, Inc.


This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.
See http://creativecommons.org/licenses/by-sa/3.0/legalcode for more information.

Das könnte Ihnen auch gefallen