To The Development of Web Applications (Core) MVC: Razvan Alexandru Mezei
To The Development of Web Applications (Core) MVC: Razvan Alexandru Mezei
Introduction
to the Development
of Web Applications
Using ASP .Net
(Core) MVC
Synthesis Lectures on Computer Science
The series publishes short books on general computer science topics that will appeal to
advanced students, researchers, and practitioners in a variety of areas within computer
science.
Razvan Alexandru Mezei
Introduction
to the Development
of Web Applications Using
ASP .Net (Core) MVC
Razvan Alexandru Mezei
Department of Computer Science
Hal and Inge Marcus School of Engineering
Saint Martin’s University
Lacey, WA, USA
© The Editor(s) (if applicable) and The Author(s), under exclusive license to Springer Nature
Switzerland AG 2024
This work is subject to copyright. All rights are solely and exclusively licensed by the Publisher, whether the whole
or part of the material is concerned, specifically the rights of translation, reprinting, reuse of illustrations, recitation,
broadcasting, reproduction on microfilms or in any other physical way, and transmission or information storage
and retrieval, electronic adaptation, computer software, or by similar or dissimilar methodology now known or
hereafter developed.
The use of general descriptive names, registered names, trademarks, service marks, etc. in this publication does
not imply, even in the absence of a specific statement, that such names are exempt from the relevant protective
laws and regulations and therefore free for general use.
The publisher, the authors, and the editors are safe to assume that the advice and information in this book are
believed to be true and accurate at the date of publication. Neither the publisher nor the authors or the editors give
a warranty, expressed or implied, with respect to the material contained herein or for any errors or omissions that
may have been made. The publisher remains neutral with regard to jurisdictional claims in published maps and
institutional affiliations.
This Springer imprint is published by the registered company Springer Nature Switzerland AG
The registered company address is: Gewerbestrasse 11, 6330 Cham, Switzerland
I would like to dedicate this work to my fiancée
Adriana Cheteles for her great support and
encouragement and to my Ph.D. advisor,
Prof. George Anastassiou, whose mentorship
and guidance opened my professional path.
Razvan Alexandru Mezei
Preface
vii
Contents
1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
2 Prepare the Development Environment . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
2.1 Choose a Web Browser . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
2.2 Install Visual Studio Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
2.3 Install Visual Studio . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
2.4 Install DB Browser for SQLite . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
2.5 Miscellaneous/Optional . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
2.5.1 Show File Name Extensions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
2.6 Microsoft SQL Server . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
2.6.1 Sample Data Generators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
3 Brief Introduction to Html . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
3.1 Let’s Create Our First HTML Page . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
3.2 Add Titles, Paragraphs and Headings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
3.3 Add a Second Webpage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
3.4 Add Links and White Spaces to Our Pages . . . . . . . . . . . . . . . . . . . . . . . . 13
3.5 Add Images and White Spaces to Our Pages . . . . . . . . . . . . . . . . . . . . . . 15
3.6 Tables and Buttons . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
3.7 A Few Other HTML Elements We’ll Use Later . . . . . . . . . . . . . . . . . . . . 19
3.7.1 Label and Select Elements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
3.7.2 Input Elements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
3.8 Form and More on Input Elements and Attributes . . . . . . . . . . . . . . . . . . 21
3.9 GET Versus POST Request, the Action and the Method Attributes . . . 25
4 Brief Introduction to CSS, Javascript, and Bootstrap . . . . . . . . . . . . . . . . . . . 29
4.1 Motivation for Using CSS and JavaScript . . . . . . . . . . . . . . . . . . . . . . . . . 29
4.2 Our First CSS Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
4.3 Introduction to CSS Syntax . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
4.4 CSS Selectors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
4.5 Conflicting CSS Specifications . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
ix
x Contents
References . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 311
Introduction
1
Fig. 1.1 A screenshot of how the final application will look like in a browser
topics related to web applications development using ASP.Net Core MVC such as rout-
ing, middleware pipeline, services and dependency injection, models, views, view models,
controllers and actions, Razor syntax, model binding, HTML and tag helpers, model val-
idation, layouts, entity framework core, connection strings, identity, authentication, and
simple authorization.
In Chap. 6, we build a web application and add to it new functionality for the remaining
chapters of the book. By the end of the book, our web application will store its data in a
database, will allow us to create user accounts, and include login and logout functionality.
We’ll mostly use an SQLite database but will also demonstrate how to use a Microsoft
SQL Server database.
Figure 1.1 shows how the final application will look like.
The two main sources used for most topics covered in this book and extensively used
during class presentations are as follows:
. W3Schools Online Web Tutorials ([1])—a great resource for learning about client-
side development (and much more!), in particular, used introducing HTML, CSS,
JavaScript, and Bootstrap 5.
. Get started with ASP.NET Core MVC ([2])—a great resource for learning about ASP.Net
Core MVC.
In our course, as part of the course assignments, we asked our students to create their own
personal projects. Each week, as we covered new concepts and demonstrated in class how
they can be applied to our class project, students were asked to apply the same concepts
to their own personal project. It has been documented that individualized assignments
can help keep students motivated and engaged throughout the course, give students a
1 Introduction 3
chance to express their own interests, as well as “deter cheating or blindly copying from
other students” ([3]). Overall, we were quite impressed with the range of ideas for web
applications that our students came up with when they created their own projects. Based
on the course evaluations, most students not only enjoyed the class materials, but they also
felt more confident and more prepared to apply for software development positions. Some
also felt they gained a better understanding of coding and debugging skills in general.
We conclude this chapter with the following statement. This work is not meant to
be a complete/comprehensive resource on C# nor ASP.Net (for that please see the list
of references), but a one-semester simplified introduction to web development using the
ASP.Net (Core) framework and the MVC architectural pattern. There are many great
topics and features that we could not include in a one-semester course, so a comprehensive
approach is beyond the scope of this book.
Prepare the Development Environment
2
Before we start developing our web applications, we first need to set up our development
environment. Here is a list of tools we’ll be using throughout this book:
Let’s go over each one of these tools and provide some guidance on how to install them
on your computers, and what packages to add.
One important reason why web applications are so popular is the fact that most desktop
computers, laptop computers, tables, and mobile phones that are connected to the Internet
will also have a browser installed on them. If you already have a web browser (you
probably do!) then use that one. Otherwise, you can install one from the Internet.
In this book, we will use the Google Chrome browser for no particular reasons other
than the fact that is it a very popular one, and already have this web browser installed. To
download Google Chrome, one can use the following source https://www.google.com/chr
ome/. Other popular browsers are Mozilla Firefox, Microsoft Edge, and Opera. Choose
your favorite one.
The languages “spoken” by virtually all modern web browsers are HTML, CSS, and
JavaScript. For this reason, web applications make use of these “client-side” languages.
If your clients already have a modern web browser installed, they don’t need to install
anything else in order to run JavaScript, CSS, and HTML.
Why do we care? While we can have control over our servers and what applications
they have installed on them, making assumptions about the clients’ systems is not an easy
task. Any wrong assumption and we can severely limit our clients’ pool. But if our client
side of the application uses HTML, CSS, and/or JavaScript, the chances that our code can
run on our clients’ machines are very high (all they need is a modern web browser). This
is not the case if we want our application to use, say Java, or C#. What would our clients
need in order to be able to run Java applications? What about C# applications?
To write JavaScript, HTML, and CSS code, we can use any text editor program. One can
use any popular text editor such as Vim, Notepad, Notepad++, and Atom. In this book,
we will use Visual Studio Code (VS Code). VS Code is a very popular editor that works
on Windows, Mac OS, and Linux machines. To download it and then install it, one can
go to the following page on the Microsoft’s webpage https://visualstudio.microsoft.com/.
Once downloaded, run the installation file (you will need to agree with the license
agreement terms) and feel free to use the default settings (press the Next> button twice
then press the Install button).
We will only use Visual Studio Code for the next two chapters. Then, we will use
Visual Studio for the remaining of this book. Depending on the operating system on your
machine you may use it for the entire book (see below for more details).
Once we start programming in C# (see Chap. 5) and for the remaining part of the book
we will be using Visual Studio. To download it and then install it, one can go to the
following page on the Microsoft’s webpage https://visualstudio.microsoft.com/.
In here you will find that there are two versions of Visual Studio, one that works on
Windows machines, and one that works on Mac OS systems.
Important note: Unfortunately, at the time of writing this book, Visual Studio is not
available for Linux machines. If you’re using a Linux distribution, you should use Visual
Studio Code instead. Note that the Visual Studio Code does not come to include the C#
compiler nor the .Net SDK, so you’ll need to install them separately. We recommend our
readers to check [1] or [2] for guidance on how to use Visual Studio Code for developing
ASP.Net Core MVC applications.
Install the version that corresponds to your machine’s operating system (note: if you
already have Visual Studio 2022 or above installed on your machine, then you can skip
2.4 Install DB Browser for SQLite 7
this step and move to the next one). For the slides shown in this book, I will use the one
for Windows. The Community edition is free and we will use this version (Visual Studio
Community 2022) in our book.
Important note: In order to use .Net 6 (or above), you must get Visual Studio 2022 (or
above). For example, if you already have Visual Studio 2019 installed on your machine,
you won’t be able to use that for .Net 6.0: you’ll need to install Visual Studio 2022. You
can have multiple versions of Visual Studio installed on your machine—if you already
have Visual Studio 2019, you do not have to uninstall it, you can keep it and install Visual
Studio 2022 along with Visual Studio 2019.
Once you run the installation application, the Visual Studio Installer will open up
(note: if you already have Visual Studio installed on your machine, then open Visual
Studio Installer and continue with this step). Make sure to select the ASP.NET and web
development workload.
On the Individual components tab, make sure the .Net 6.0 Runtime is selected (also
select LINQ to SQL tools which are down the page, under Code Tools).
Then click on the Install button. When the installation finishes, open the Visual Studio
application and click on the Create a new project button. Then, in the search box type
MVC and make sure to have the option of creating an ASP.NET Core Web App (Model-
View-Controller).
If you do, then you’re all set. Otherwise, make sure to follow the Visual Studio Installer
step described above. If you want, we recommend you to continue and follow the steps
shown on the webpage: https://learn.microsoft.com/en-us/aspnet/core/tutorials/first-mvc-
app/start-mvc to create your first ASP.Net Core MVC application and make sure it runs
on your machine.
One last installation (we’ll only use this starting with Chap. 11) is a free open-source
application that allows us to interact with SQLite database files. We will use it to check
our database values. To download this application, go to https://sqlitebrowser.org/dl/ and
download the version that corresponds to your system. For my system, that is DB Browser
for SQLite—Standard installer for 64-bit Windows. Once downloaded, run the installation
file (you will need to agree with the license agreement terms) and feel free to use the
default settings (press the Next button twice then press the Install button).
Once the installation finishes, open the application to make sure you can run it on your
machine.
8 2 Prepare the Development Environment
2.5 Miscellaneous/Optional
We recommend that you set your operating system to show file extensions. On my Win-
dows 11 machine, I would open any folder and go to Views > Show > File name
extensions (make sure it’s checked).
In this book, we will include an optional section that shows you how to use Microsoft SQL
Server instead of SQLite. If you want to install the Microsoft SQL Server Management
Studio. Here is a link for it: https://aka.ms/ssmsfullsetup.
In Chap. 11, we’ll go over details on how to install the SQL Server Express 2019
LocalDB database, using Visual Studio Installer.
We will occasionally need to create some names as sample data. In class, we typically
like to ask our students to give us some ideas. In this book, we used a name generator
instead, namely we used https://commentpicker.com/fake-name-generator.php to help us
create some random names.
Brief Introduction to Html
3
The purpose of this chapter is to introduce some fundamental HTML concepts. We’ll use
this knowledge quite a bit in subsequent chapters, especially when we create Razor views
and layouts. One great resource that we often used in the class as a source of information
as well as for quick demonstrations is the following: [1].
HTML stands for Hyper Text Markup Language and it is used to describe the structure
of a Webpage. Using various HTML elements, we can tell a web browser how to display
different parts of a given content. HTML files typically have the extension .html or .htm.
The way the author likes to think of the HTML is as follows: we have content that
we would like to display in a browser. Using HTML (tags), we can let a browser know
how to display various pieces of this content. Some parts will show up as a paragraph,
others as tables, others as headings, and so on. So, in general, we use HTML to describe
the structure of the page. Note that for styling (that describes whether the contents should
show up left aligned or centered, bold or italic, red or green, etc.) we will use CSS (which
is the subject of the next chapter).
Open the Visual Studio Code application. First, go to File > Open Folder and create a new
folder for our HTML files.
We named our folder HTML FILES. Then, inside that folder, let’s create our first
HTML file. We named our file firstwebpage.html.
In this file, let’s add the following contents (do not include the line numbers on the
left side, we included them for easier reference):
Fig. 3.1 A screenshot of how the H1 and P elements will show in a browser
30>#FQEV[RG jvon@
40>JVON@
50" >JGCF@
60" >VKVNG@Vkvng<"qwt"hktuv"ygdrcig>1VKVNG@
70" >1JGCF@
80" >DQF[@
90" >J3@Fkurnc{"vjku"cu"c"jgcfkpi>1J3@
:0" >R@Fkurnc{"vjku"cu"c"rctcitcrj0>1R@
;0" >R@Fkurnc{"vjku"cu"cpqvjgt"rctcitcrj0>1R@
320" >1DQF[@
330>1JVON@
Save the changes and open the firstwebpage.html file in a browser. Here (Fig. 3.1) is
what we got in our browser:
Now let’s go over each line of the HTML code above.
The first line <!DOCTYPE html> is a declaration that specifies that the file is an
HTML5 document (HTML5 is the latest version of HTML). We’ll use this in all our
HTML files.
In line 2, <HTML> represents a tag (in this case, the HTML tag). This tag has a
corresponding end tag in line 11 </HTML>. The tags and all content between them
together represent an element. In this case, lines 2–11 represent the root element of the
page. This element typically contains two nested elements: the HEAD element and the
BODY element.
The <HEAD> element (represented by the start tag: <HEAD> from line 3, the end tag:
</HEAD> from line 5, and the contents between these two tags) typically contains meta
information about a page (we’ll see more about this soon). One such meta information is
represented by the <TITLE> element, which can be seen displayed in a browser, in the
page’s tab (top part of the page).
The <BODY> element (represented by the start tag: <BODY> from line 6, the end tag:
</BODY> from line 10, and the contents between these two tags) defines the body of
the page, which contains all the visible parts of the page, such as headings, paragraphs,
3.2 Add Titles, Paragraphs and Headings 11
images, tables, and links. An example of such content is represented by the <P> element,
which defines a paragraph.
We close this section with a few remarks:
. HTML is NOT case sensitive. That means that we could have used either of the fol-
lowing to represent the HTML tag: <HTML>, <html>, or even <HtMl>. This applies
to all tags.
. HTML elements can be nested (they often are). As seen above, we have the <P>
element nested inside (it is a part of) the <BODY> element.
. There are HTML elements (for example, <BR>) that have no content. Such elements
are called empty elements and they have no end tag.
. If no title is provided for a webpage, a browser will typically use the filename as
its title. This is not always very elegant. So please make sure to always provide a
<TITLE> element for your webpages. Also, the contents of the title are used by
search engines [2], so it is important to provide a meaningful title.
. “HTML largely ignores whitespace” (see more details in [3]).
First, let’s change the title of the webpage so it displays: Info for <Put Your
Name>.
Then, remove the contents of the <BODY> element and replace them with the
following:
Before we show you the solution, here is what your page should look like (see Fig. 3.2):
After you make the changes to your HTML file inside Visual Studio Code, make sure
to refresh your web browser, so you see the changes. Try to generate the same webpage,
then look at the solution below:
12 3 Brief Introduction to Html
>#FQEV[RG jvon@
>jvon@
>jgcf@
>vkvng@Kphq"hqt"Tc|xcp"Og|gk>1vkvng@
>1jgcf@
>dqf{@
>j3@Uvwfgpv"Kphqtocvkqp>1j3@
>j4@Tc|xcp"Og|gk>1j4@
>r@Nqtgo"kruwo"fqnqt"ukv"cogv"eqpugevgvwt."cfkrkukekpi"gnkv0"Fgngpkvk"
xqnwrvcvwo"gzegrvwtk"vgorqtc"gkwu"korgfkv"ewo"pgoq"pguekwpv"vgorqtg"oczkog"eqtrqtku"
gttqt"pqp"rgturkekcvku"pgeguukvcvkdwu"gv."gctwo"ncdqtg"pkjkn"xgnA"Swkdwufco0">1r@
>r@Nqtgo"kruwo"fqnqt"ukv"cogv"eqpugevgvwt"cfkrkukekpi"gnkv0"Hceknku."swkuswco"
pwoswco#"Ncdqtwo"okpkoc"swk"tgrgnncv"tgrwfkcpfcg"fgngpkvk"cv"fqnqtwo"ukpv"kpxgpvqtg."
tgtwo"swkfgo"pcoA"Ocipk"swku"oqnnkvkc"ceewucpvkwo"pqp"xgtq0>1r@
>r@Nqtgo"kruwo"fqnqt"ukv"cogv"eqpugevgvwt"cfkrkukekpi"gnkv0"Pgeguukvcvkdwu."
kf"swkuswco"xgnkv"ceewucowu"qopku"fqnqtgu"oqfk."kwuvq"kpekfwpv"vgorqtg"ocipco"cnkswkf"
pgswg"fwekowu"gwo"eqpugevgvwt."uwpv"hcegtg"rgthgtgpfku"ctejkvgevq"xgtkvcvku0>1r@
>1dqf{@
>1jvon@
Note: Our page may not look very pretty just yet. For that, we’ll add styling (CSS)
during our next chapter. So please have patience. Again, the purpose of HTML is to
describe the structure of a Webpage.
Above, we used H1 and H2 elements. These are used to define HTML headings,
with H1 being the most important, H2 less important, and so on. You should not skip
levels: this means, your page should not contain a H2 element, if the H1 is not included
anywhere on the page.
3.4 Add Links and White Spaces to Our Pages 13
Fig. 3.3 Shows how the page should be displayed in a browser. Currently, it only contains one H1
element
Let’s add a second webpage. On the left side of Visual Studio Code (see the screen-
shot below) click on the + New File... icon and add a new file with the name
secondwebpage.html.
To this file, add the following lines:
30>#FQEV[RG jvon@
40>JVON@
50" >JGCF@
60" >VKVNG@Vkvng<"qwt"ugeqpf"ygdrcig>1VKVNG@
70" >1JGCF@
80" >DQF[@
90" >J3@Fkurnc{"vjku"cu"cpqvjgt"jgcfkpi>1J3@
:0" >1DQF[@
;0>1JVON@
Open this file in a browser and check that it looks like the screenshot below (Fig. 3.3).
To this second page, let’s add three links: one that links to www.w3schools.com, one that
links to www.stmartin.edu, and one that links to our first webpage created above.
After line 7 (shown above), after the <H1> element, add the following five lines (notice
there are two empty lines between the second and third <A> elements):
>C jtgh?$jvvru<11yyy0y5uejqqnu0eqo1$@Iq"vq"Y5Uejqqnu>1C@
>C jtgh?$jvvru<11yyy0uvoctvkp0gfw1$@Iq"vq"Uv0"Octvkp"Wpkxgtukv{>1C@
>C jtgh?$hktuvygdrcig0jvon$@Iq"vq"vjg"hktuv"rcig>1C@
14 3 Brief Introduction to Html
Fig. 3.4 Shows the page displayed in a browser. It contains an H1 element and three A elements
Then go to your browser, refresh your page, and check how the second page looks like
(see Fig. 3.4).
Is this what you expected? Let’s talk about these results.
In general, we use the <A> elements to add links (anchors) in our HTML pages.
The content of these elements (the text between the <A> and </A> tags) is the text that
shows up in as the link. These elements made use of attributes to specify where the links
should point to. In particular, we used href="https://www.w3schools.com/" to point to
the w3schools’ website.
Attributes, in general, are used to provide additional information
needed for HTML elements. They usually come in pairs that look like
attributename="attributevalue" (above we used: href="https://www.
w3schools.com/") and are specified in the start tag of an element. As we will see below,
one can use multiple attributes in one element.
The whitespaces (space, tabs, newline, and empty lines) are mostly ignored by the
browser. If you want your links to
. show on different lines, use the tag: <BR> BR stands for break);
. show just spaces away from each other, use the following:
Let’s replace the lines above with the following, then refresh your page in the browser
and check the results:
As an exercise, please add the same three links to the first webpage. Have them dis-
played on the same line, with five spaces between each other, and modify the third link
to point to your secondwebpage.html.
Here is what you should get (see Fig. 3.5).
To the first page, let’s add an image, right after the first paragraph. For this, we will make
use of the <IMG> element (note: it only has a start tag, there is no end tag for it) and
use multiple attributes.
We’ll use the SRC attribute to specify which image to load into the browser. This can
use a relative path (for images on local machine for example) or an absolute path (typically
used to link to images stored somewhere online, but you can also use it for images found
on your local machines). Add the following line after the first <P> element:
Now reload your HTML file in the browser to see the changes. The image may be
displayed too large. Therefore, let’s add another attribute to specify a desired height and/or
width. We’ll add the HEIGHT="300" attribute.
Lastly, let’s add one more attribute, the TITLE="This is me, resting"
attribute, which will provide a tooltip for our image:
Fig. 3.6 Shows how the page should show up in a browser after we embedded an image. Note the
tooltip text shown as the mouse hovers over the image (this was set by the TITLE attribute of the
IMG element)
3.6 Tables and Buttons 17
Fig. 3.7 This is the same as the above figure, but the underlying image was removed. Note that the
text “This is me, resting” is still included/displayed in the page
We should also add the alt attribute in case the clients seeing our page are using
a screen reader, have a poor Internet connection, or the image has not been found (this
attribute is also helping make the page more accessible):
Here (see Fig. 3.7) is how this will look if we remove our image (so that it is not found
by the browser).
Next, let’s add a table and a few buttons to a webpage. For this, let’s make changes to
our webpage: secondwebpage.html. In here, let’s change the TITLE and the H1 elements
so they both say Current enrollment for Razvan Mezei.
Before we create our own table, we encourage you to visit the following page to see an
example of table https://www.w3schools.com/tags/tryit.asp?filename=tryhtml_tbody_css.
That example also contains CSS, which you can ignore for now. You should note the
following:
18 3 Brief Introduction to Html
Please get comfortable with this code. We’ll use this again when we learn about ASP .Net
Core MVC—Views.
After the <H1> element, let’s add our own <TABLE> element. We’ll create
a table that contains the courses our person is enrolled in. For example, let’s
assume Razvan Mezei is enrolled in the following courses (CSC200: Object
Oriented Programming, CSC495: ST ASP .Net Core MVC, and CSC340:
Data Structures and Algorithms). Let’s also provide some imaginary links for
them (for now they should just point to https://www.stmartin.edu/).
Here is how the output should look like. What HTML code can be added to accomplish
this (Fig. 3.8)?
Here is a possible solution (note that we also added two <BR> elements at the end of
the table):
>VCDNG@
>VJGCF@
>VT@
>VJ@Eqwtug"KF>1VJ@
>VJ@Eqwtug"pcog>1VJ@
>VJ@Eqwtug"nkpm>1VJ@
>1VT@
>1VJGCF@
>VDQF[@
>VT@
>VF@EUE422>1VF@
>VF@Qdlgev"Qtkgpvgf"Rtqitcookpi>1VF@
>VF@>c jtgh?$jvvru<11yyy0uvoctvkp0gfw1$@Eqwtug"nkpm>1c@>1VF@
>1VT@
>VT@
>VF@EUE562>1VF@
>VF@Fcvc"Uvtwevwtgu"cpf"Cniqtkvjou>1VF@
>VF@>c jtgh?$jvvru<11yyy0uvoctvkp0gfw1$@Eqwtug"nkpm>1c@>1VF@
>1VT@
>VT@
>VF@EUE6;7>1VF@
>VF@UV"CUR"0Pgv"Eqtg"OXE>1VF@
>VF@>c jtgh?$jvvru<11yyy0uvoctvkp0gfw1$@Eqwtug"nkpm>1c@>1VF@
>1VT@
>1VDQF[@
>1VCDNG@
>DT@
>DT@
3.7 A Few Other HTML Elements We’ll Use Later 19
Fig. 3.8 Shows how an HTML table will look like in a browser
The table isn’t the prettiest it could be. We’ll make it look better once we see what
CSS is. Please have patience.
The following HTML elements will show up later in the course, so we thought it would
be useful to briefly introduce them in here.
Below our table let’s add a <LABEL> element, which can be used to display a text.
Labels can provide very useful information to screen readers (see [4]) which will read out
loud the label when a user clicks on an associated element.
>NCDGN@ Ejqqug"c"oclqt">1NCDGN@
Let us associate this with a <SELECT> element (see [5]), which can be used to provide
the user with a dropdown list of options to choose from.
>UGNGEV kf?$oclqtu$@
>QRVKQP xcnwg?$Ctv$@Ctv>1QRVKQP@
>QRVKQP xcnwg?$Ocvj$@Ocvjgocvkeu>1QRVKQP@
>QRVKQP xcnwg?$EU$@Eqorwvgt"Uekgpeg>1QRVKQP@
>QRVKQP xcnwg?$Wpfgekfgf$@Wpfgekfgf>1QRVKQP@
>1UGNGEV@
20 3 Brief Introduction to Html
Fig. 3.9 Shows the same table as seen in Fig. 3.8, but with a newly added dropdown list
To associate a <LABEL> element with a <SELECT> you need to set a unique identifier
in the <SELECT> element (we chose id=“majors”), then in the <LABEL> tag add the
attribute for=“majors”.
Here is the outcome (Fig. 3.9).
Labels also work (can be associated via the for attribute) with <INPUT> elements. There
are many types of <INPUT> elements: color, password, date, number, text, and submit,
to name a few (see more in here [4]). Let’s briefly see them by example. Below the
<SELECT> element of the secondwebpage.html file, add the following lines::
Reload the webpage in the browser to see the changes. Then, try to interact with
these <INPUT> elements. They should look similar to (Fig. 3.10).
We encourage you to interact with each of these <INPUT> elements.
3.8 Form and More on Input Elements and Attributes 21
Fig. 3.10 Shows several labels and input elements of various types (including password, text, color,
and file)
For this part, let’s create a third webpage, call it register.html, and add the following
HTML code in it as a starting point:
>#FQEV[RG jvon@
>JVON@
>JGCF@
>VKVNG@Tgikuvgt"c"pgy"ceeqwpv>1VKVNG@
>1JGCF@
>DQF[@
>J3@Tgikuvgt"c"pgy"ceeqwpv>1J3@
>1DQF[@
>1JVON@
There will be times when we want to allow the user to submit multiple values at once.
For example, when a user logs in, we want to collect the user’s username and password
and submit them together. Similarly, when we want to allow a user to register a new
account, we typically want to collect multiple information, such as username, password,
email address, and maybe a phone number.
A <FORM> element can be used to collect user information (typically from multiple
<INPUT> elements). See more in [6]. Below the <H1> element from register.html, add
a <FORM> element. If you refresh your browser, you will see that the <FORM> element
by itself is not visible; it’s a container for other elements that we’ll add below.
Let’s add a few <INPUT> elements to collect the user’s information (also add
<LABEL> elements, so the user knows what’s expected in the form). Here is how
our register.html file looks like after adding four <INPUT> elements nested inside the
<FORM> element:
22 3 Brief Introduction to Html
>#FQEV[RG jvon@
>JVON@
>JGCF@
>VKVNG@Tgikuvgt"c"pgy"ceeqwpv>1VKVNG@
>1JGCF@
>DQF[@
>J3@Tgikuvgt"c"pgy"ceeqwpv>1J3@
>HQTO@
>NCDGN hqt?$rcuuy$@Rcuuyqtf<">1NCDGN@ >KPRWV v{rg?$rcuuyqtf$ kf?$rcuuy$@ >DT@
>NCDGN hqt?$wugt$@Wugtpcog<">1NCDGN@ >KPRWV v{rg?$vgzv$ kf?$wugt$@ >DT@
>NCDGN hqt?$gockn$@Gockn"cfftguu<">1NCDGN@ >KPRWV v{rg?$gockn$ kf?$gockn$@">DT@
>NCDGN hqt?$rjqpg$@Rjqpg"pwodgt<">1NCDGN@ >KPRWV v{rg?$vgn$ kf?$rjqpg$@ >DT@
>1HQTO@
>1DQF[@
>1JVON@
Refresh the browser to see the results. It should look similar to (Fig. 3.11).
Note: If you click on any <LABEL> element, the associated <INPUT> element gets
focus. Try it!
To be able to send all information entered by the user in these fields inside one
<FORM> element, we can add the following:
. An <INPUT> element with the attribute: type=“submit”. This will show up as a button!
. Each <INPUT> element whose value you want to be sent to the server must use a
name attribute.
Fig. 3.11 Shows a H1 element and a form containing four labels and input elements
3.8 Form and More on Input Elements and Attributes 23
>HQTO@
>NCDGN hqt?$rcuuy$@Rcuuyqtf<">1NCDGN@ >KPRWV v{rg?$rcuuyqtf$ kf?$rcuuy$ pcog?$rcuuyqtf$@ >DT@
>NCDGN hqt?$wugt$@Wugtpcog<">1NCDGN@ >KPRWV v{rg?$vgzv$ kf?$wugt$ pcog?$wugtpcog$@ >DT@
>NCDGN hqt?$gockn$@Gockn"cfftguu<">1NCDGN@ >KPRWV v{rg?$gockn$ kf?$gockn$ pcog?$gockn$@ >DT@
>NCDGN hqt?$rjqpg$@Rjqpg"pwodgt<">1NCDGN@ >KPRWV v{rg?$vgn$ kf?$rjqpg$ pcog?$rjqpg$@ >DT@
>DT@
>KPRWV v{rg?$uwdokv$ xcnwg?$TGIKUVGT"PGY"WUGT$@
>1HQTO@
Note: The <INPUT type="submit" value="REGISTER NEW USER"> provided us with a button,
ready to submit all named values (see Fig. 3.12).
Important note: The name attribute used in each of our <INPUT> elements can be
used by the server (when processing the request, we’re sending to the server). If you
omitted this for an <INPUT> element, the value of that <INPUT> element will not be
sent at all to the server.
Note: We already mentioned this (using different words) but would like to emphasize
the for attribute of a <LABEL> element must be equal to the id attribute of the associated
<INPUT> element in order to bind them together.
Next, before the submit button, let’s add a checkbox (and an associated label):
>NCDGN hqt?$xgvgtcp$@Ctg"{qw"c"xgvgtcpA">1NCDGN@
>KPRWV v{rg?$ejgemdqz$ kf?$xgvgtcp$ pcog?$kuXgvgtcp$@ >DT@
Fig. 3.12 Shows the same elements as seen in Fig. 3.11, but with a newly added input element
(displayed as a button)
24 3 Brief Introduction to Html
Fig. 3.13 To Fig. 3.12 we now added a label and an input element of type checkbox
. Use the value attribute to specify a default value for an <INPUT> element.
. Use the placeholder attribute to specify a hint for an <INPUT> element. When the
user clicks on the element, the placeholder text/hint disappears.
. Use the required attribute to specify that an <INPUT> element must contain a value
before the <FORM> is submitted (and the values sent to the server).
. There are many more attributes that can be used with <INPUT> elements. Here is one
source to check: [7].
Let’s see some examples. Change the <INPUT> elements above to match the code below:
>HQTO@
>NCDGN hqt?$rcuuy$@Rcuuyqtf<">1NCDGN@
>KPRWV v{rg?$rcuuyqtf$ kf?$rcuuy$ pcog?$rcuuyqtf$ tgswktgf@
>DT@
>NCDGN hqt?$wugt$@Wugtpcog<">1NCDGN@
>KPRWV v{rg?$vgzv$ kf?$wugt$ pcog?$wugtpcog$ rncegjqnfgt?$ejqqug"c"wugtpcog$@
>DT@
>NCDGN hqt?$gockn$@Gockn"cfftguu<">1NCDGN@
>KPRWV v{rg?$gockn$ kf?$gockn$ pcog?$gockn$@
>DT@
>NCDGN hqt?$rjqpg$@Rjqpg"pwodgt<">1NCDGN@
>KPRWV v{rg?$vgn$ kf?$rjqpg$ pcog?$rjqpg$ xcnwg?$222/222/2222$@
>DT@
>NCDGN hqt?$xgvgtcp$@Ctg"{qw"c"xgvgtcpA">1NCDGN@
>KPRWV v{rg?$ejgemdqz$ kf?$xgvgtcp$ pcog?$kuXgvgtcp$@
>DT@
>DT@
>KPRWV v{rg?$uwdokv$ xcnwg?$TGIKUVGT"PGY"WUGT$@
>1HQTO@
3.9 GET Versus POST Request, the Action and the Method Attributes 25
Fig. 3.14 If the user attempt to submit a form without a text in the password field (which was set
as required), then an error (“Please fill out this field”) will be displayed
3.9 GET Versus POST Request, the Action and the Method
Attributes
There are just a few more things to know about the <FORM> elements (we’ll make use
of these in future chapters!). We’ll talk next about the action attribute and the method
attribute for <FORM> elements.
We use the action attribute of the <FORM> element to specify where to send your
request. Who/which code on the server side should process your request? Here is an
example of how to use this attribute. Change the <FORM> tag so it matches the code
below:
>HQTO cevkqp?$1tgikuvgt0rjr$@
In the browser, if you fill out the form, and click on the SUBMIT button, you will
probably get an error message (we did not set up the server side yet—please have
patience).
26 3 Brief Introduction to Html
The next attribute we want to discuss about is the method. We review it in more details
in a future chapter and make extensive use of it, but we would like to briefly introduce it
in here.
GET and POST are two (there are several others, and you can also create your own)
HTTP verbs. We can use either of them when we send requests to servers. But the
difference between them is very important.
. Use the GET method to inform the browser to send the request data as part of the
URL.
o This is very useful if we want to allow users to bookmark searches so that it contains
field values.
o An example: https://www.amazon.com/s?k=lenovo+laptops.
o The data is appended to the URL request, after the ? (the portion of the URL that
comes after the ? is called a query string).
o There are limitations on how much data can be sent using GET requests, in particular
one can only send text data.
. Use the POST method to inform the browser to send the request data as part of the
request body, not part of the URL.
o This is useful if you are sending sensitive information that should not be book-
marked.
o An example: when you login, you probably don’t want your password to show up
in the browser’s history.
o Imagine seeing this in a browser: https://www.amazon.com/s?username=alex&pas
sword=Password123.
o Another example: you can’t send a file in the URL portion of the request. So, if you
want to upload files to a server, you will want to use the POST method.
o There are fewer limitations on what data you can send via this method. You are not
limited to text data, you can also send binary (for example, images, pdfs, etc.).
Let’s see this in practice. For this part, please set the action attribute to “#” (this will send
the request back to the current page):
>HQTO cevkqp?$%$@
3.9 GET Versus POST Request, the Action and the Method Attributes 27
Then fill in some values in the form, click on the submit button, and observe the URL.
Notice that the URL contains the following query string (the part of the URL that
comes after the question mark):
?password=secretpassword&username=admin&email=admin%40admin.com&phone=
000-000-0000&isVeteran=on#
The query string contains name=value pairs, where name is the attribute we used for
our <INPUT> elements, and value is the actual value entered in the <INPUT> element.
Now change the attribute to method=“post” and submit the same data as before (make
sure to first remove the query string from the URL). You will see that there is no query
string being sent this time:
If you want, check out the following two examples from W3Schools:
We’ll see more about GET versus POST in future chapters, but we wanted to quickly
introduce them in here.
There are many more HTML elements (and topics in general) to cover, but for the
purposes of this book, and what we’ll need when we cover the ASP.Net (Core) MVC,
we believe we covered sufficient material. We’ll learn more HTML elements as we need
them. In the meantime, feel free to explore the following great resource [1].
Brief Introduction to CSS, Javascript,
and Bootstrap
4
In the previous chapter, we’ve seen how one can use HTML (Hypertext Markup Lan-
guage) to define the structure of a webpage. When we have information to display in a
browser, we make use of various HTML tags/elements to tell the browser that certain
parts of this information represent various headings, while others represent paragraphs,
tables, hyperlinks, images, and so on.
In this chapter, we will make use of CSS (Cascading Style Sheets) which can help us
specify styling for our webpages. For example, we can specify if we want our headings to
be colored in blue, display our paragraphs as centered/left/right aligned, and many other
styling options.
Lastly, we will also make use of JavaScript to make our webpages more interactive.
We can program responses to various events. For example, when a user clicks on a button,
what should the webpage do?
Using CSS and JavaScript (or better yet, a library such as Bootstrap) we’ll be able
to make the basic HTML table (and other elements) from our secondwebpage.html look
much better. Right now, the table with no style looks like (Fig. 4.1).
By the end of this chapter, the very same table (seen in Fig. 4.1), but with styling
added, will look like (Fig. 4.2).
We hope this sparked your interest. Before we dive in, we would like to mention that
this chapter is only meant to be an introduction to CSS and JavaScript. Enough for what
we’ll need for the other chapters in this book, those that focus on the ASP .Net Core
MVC. But there is much more out there to learn, and we encourage you to explore it.
Some useful resources (and sources of inspiration for this book) are as follows:
Fig. 4.2 Shows the same table as shown in Fig. 4.1, but with styling (in particular, Bootstrap) added
Important note: Just like HTML, CSS is NOT case sensitive. But JavaScript IS case
sensitive. So be very careful with this.
Let’s start with an example. Let’s say that we would like our firstwebpage.html to have:
For this, we’ll make use of CSS. One way to add CSS to a webpage is to add a <STYLE>
element nested inside the <HEAD> element of the page. Add the following CSS code to
your firstwebpage.html source file:
4.3 Introduction to CSS Syntax 31
<STYLE>
BODY{background-color:lightblue;}
P{background-color: lightgray;}
</STYLE>
Let’s see how the CSS code above styled our page. You are given below a screenshot
without (Fig. 4.3) and one with (Fig. 4.4) the above given CSS code (which we’ll explain
below).
Now let’s talk about the CSS syntax. To add CSS, we can use the <STYLE> element
(we’ll see other ways below). The CSS syntax typically follows the following format:
The selector determines where to apply the styling specifications between the curly
braces ({…}). Inside these curly braces, we can include one or more property-value
pairs, separated by semicolons (;). Inside each property-value pair, we use the colon (:).
Spacing is largely ignored by CSS, so we make use of it to make our code easier to read.
For example,
P{background-color: lightgray;}
In here we used:
. the (element) selector P (meaning the styling specifications inside the curly braces
{…} will be applied to all paragraph <P> elements of the page), and
. the property-value pair: background-color:lightgray (meaning we want
these paragraphs to have a gray background color).
J3}eqnqt<itggp="vgzv/cnkip<"egpvgt=Ä
32 4 Brief Introduction to CSS, Javascript, and Bootstrap
Fig. 4.3 (Left) Shows the firstwebpage.html file displayed in a browser, before adding CSS styling
In here we used.
. another element selector: H1 (meaning this is applied to all heading H1 elements from
the page), and
. multiple property-value pairs:
o color:green (meaning we want the H1 elements to have a green text color) and
o text-align:center (meaning we want the H1 elements to have a centered text
alignment).
4.4 CSS Selectors 33
Fig. 4.4 (Right) Shows the firstwebpage.html file displayed in a browser, after adding CSS styling
There are many types of CSS selectors, but in this book, we’ll only focus on a few of
them, namely the element selectors, the id selectors, and the class selectors.
In the example given above, we used the following element selectors: BODY, H1,
and P. They are called element selectors because they specify to which HTML ele-
ments should the styling specifications apply. The example P{background-color:
lightgray;} applies the given styling to all paragraph elements.
34 4 Brief Introduction to CSS, Javascript, and Bootstrap
What if we only want to apply certain styling to only one of the paragraphs? To
apply CSS styling to only one element (paragraph, table, heading1, etc.), we can use id
selectors. For this, we can follow the two steps shown as follows:
. Then use an id selector in our CSS specifications (make sure to use #).
o For example, we can add the following CSS specification inside the <STYLE>
element:
#thisisspecial{background-color: rgb(222,157,210);}
The result is that we uniquely identified an element (one paragraph in our example)
and applied certain CSS styling to it. In our example, the paragraph will have a pink
background (see Fig. 4.5).
Id selectors should only be applied to one/unique element in a webpage. If we want to
apply certain styling to multiple elements, we can make use of the class selectors. Class
selectors can be applied to multiple elements of the same type (say multiple paragraphs)
or of different types (say a table and a paragraph). To use class selectors, we again use
two steps:
. Then use a class selector in our CSS specifications (make sure to use a.).
o For example, we can add the following CSS specification inside the <STYLE>
element:
.myclass{color:blue; text-align: center;}
4.4 CSS Selectors 35
Fig. 4.5 Shows the result of applying specific CSS styling to an element (first paragraph) selected
using an ID selector
Looking at the outcome below (Fig. 4.6), can you guess which elements were applied the
class selector defined above?
We give below the entire code for firstwebpage.html:
36 4 Brief Introduction to CSS, Javascript, and Bootstrap
Fig. 4.6 This is similar to Fig. 4.5, but certain elements were applied styling via class selectors. Can
you figure out which elements?
4.5 Conflicting CSS Specifications 37
>#FQEV[RG jvon@
>JVON@
>JGCF@
>VKVNG@Kphq"hqt"Tc|xcp"Og|gk>1VKVNG@
>UV[NG@
DQF[}dcemitqwpf/eqnqt<nkijvdnwg=Ä
J3}eqnqt<itggp="vgzv/cnkip<"egpvgt=Ä
R}dcemitqwpf/eqnqt<"nkijvitc{=Ä
%vjkukuurgekcn}dcemitqwpf/eqnqt<"tid*444.379.432+=Ä
0o{encuu}eqnqt<dnwg="vgzv/cnkip<"egpvgt=Ä
>1UV[NG@
>1JGCF@
>DQF[@
>J3 @Uvwfgpv"Kphqtocvkqp>1J3@
>J4 ENCUU?$o{encuu$@Tc|xcp"Og|gk>1J4@
Important observation: From the example above, you should note that we can apply
multiple class and/or id selectors to the same element.
A related and important question may be what happens if two selectors have conflicting
specifications? For example, one chooses a color of red, while the other chooses blue.
Which one wins?
The answer is more complex (see the links below for a more in-depth explanation),
but here is a simplification that is sufficient for this book:
38 4 Brief Introduction to CSS, Javascript, and Bootstrap
. The most specific wins, regardless of the order in which they are specified.
In particular, id selector > class selector > element selector.
. For the same specificity, the last one wins.
We direct the reader to check out the following two resources that will go into more
depth:
There are other types of selectors, for example, universal selectors, grouping selectors,
child selectors, descendant selectors, attribute selectors, pseudo-selectors, and so on. We
won’t go over them in this book, but we do want to encourage you to research about them
as you need them.
To provoke your interest, add the following line to your CSS specifications (add it
right before the </STYLE> tag):
R<<hktuv/ngvvgt}eqnqt<tgf="hqpv/ygkijv<"dqnf=Ä
ugngevqt"}rtqrgtv{3<xcnwg3="rtqrgtv{4<xcnwg4="Ӏ
and covered some of the most important CSS selectors and included some important
property-value pairs to use in our CSS code. We focused primarily on understanding the
CSS syntax. Below we will introduce a few more property-value pairs we can make use
of in later chapters. Since we skipped many useful CSS properties (that we may not use in
this course), we refer the readers to check out [1], a resource we often use in our in-class
demonstrations.
4.7 A Few More Examples of Property-Value Pairs for CSS 39
Fig. 4.7 This screenshot shows how the H1 element will be displayed after adding the text color
styling described above
As seen above, to set the color of a given text, one can use the color property. Similarly,
to set the background color for a given text, one can use the background-color
property.
For example, let’s add CSS specifications for H1 elements so the H1 selector looks as
shown below:
J3}eqnqt<itggp="dcemitqwpf/eqnqt<"cnkegdnwg="vgzv/cnkip<"egpvgt=Ä
The H1 element from our firstwebpage.html now looks as follows (see Fig. 4.7).
Besides specifying colors by name (for example, background-color:
aliceblue;) one can also use
Use the following tool to help you pick a great color for your design: https://www.w3s
chools.com/colors/colors_picker.asp.
To specify the horizontal text alignment, one can use the text-alignment property
and make use of the following values: center, left, right.
40 4 Brief Introduction to CSS, Javascript, and Bootstrap
J3}eqnqt<itggp="vgzv/cnkip<"egpvgt=Ä
On your own, modify the value of text-align property to make use of other values.
To choose a font for our text, we can make use of the font-family property. As you
start typing.
IntelliSense from Visual Studio Code has some suggestions ready to use, for example,
hqpv/hcokn{<)Htcpmnkp"Iqvjke"Ogfkwo).")Ctkcn"Pcttqy)."Ctkcn."ucpu/ugtkh
. If a font name includes spaces, then you need to make use of quotes around that name.
. The order is important: the browser will use the first available font from the list (or
use a default font if none from the given list are available). So, start with the one you
prefer, but provide “fallback” options afterward.
o We encourage you to read more about this (including the “fallback” system) at [2].
You may want to try the following font-family: Papyrus, Helvetica, sans-serif; see if you like
this better (Fig. 4.9).
To use italic text, we can specify the font-style property. For bold, use the
font-weight property.
4.7 A Few More Examples of Property-Value Pairs for CSS 41
Fig. 4.8 A font styling has been added to the BODY element (via the BODY element selector)
To specify text size, one can use the font-size property. There are multiple ways
to specify a value for the font-size property, including the following:
. Use viewport width to get text whose size will depend on the browser window.
42 4 Brief Introduction to CSS, Javascript, and Bootstrap
Fig. 4.9 This is similar to Fig. 4.8, but a different font-family is being used
4.8 The Box Model and the Developer Tools 43
Add the following to your CSS code and see what happens if you resize the width of the
webpage:
J4}hqpv/uk|g<32xy=Ä
Challenge:
. Add the following to your CSS code and try to find out what it does (hint: select some
text inside the browser):
<<ugngevkqp }"dcemitqwpf/eqnqt<"nkijvitggp="Ä
Each HTML element is considered a box. That means that each element has the following:
One can view this in a browser, by pressing the F12 key, which will open the Developer
Tools. There are lots of great things to say about the Dev. Tools, but we will probably not
make much use of it in this book, due to time constraints. We strongly encourage you to
find out more (on your own) about the Developer Tools.
Using the Developer Tools, one can select an HTML element (under the Elements tab)
and inspect or temporarily modify various values of your webpage (do not worry, your
changes will not affect/change the underlying webpage!).
Under Styles tab (see the bottom left side of the Dev tools page), one can find the box
model of the selected element. Currently, this is similar to the following (see Fig. 4.10).
44 4 Brief Introduction to CSS, Javascript, and Bootstrap
Let’s change various values of the padding, border, and margin. Double click on the
“-” to give them some values. For example (see Fig. 4.11),
will change the appearance of the corresponding HTML element (a paragraph, <P>
element, for us) into (Fig. 4.12).
Note: Changes made inside the Dev tools are not permanent. Let’s make such changes
inside our CSS code defined inside the firstwebpage.html file.
Fig. 4.10 Shows the box model. Notice how the content is in the middle. Then, comes the padding.
Then the border, and finally the margin
Fig. 4.11 Shows the box model seen in Fig. 4.10, but some of the values (for margin and padding)
have been changed
4.10 Ways to Add CSS 45
Fig. 4.12 Shows how the P element will look once we change some of the box-model values (for
margin and padding)
r }
dcemitqwpf/eqnqt<"nkijv{gnnqy=
ykfvj<"92'=
dqtfgt<"37rz uqnkf itggp=
rcffkpi<"72rz=
octikp<"42rz=
Ä
<DIV> elements can be used to define a division, or better named a section, in an HTML
document. We will use it as a container of elements (nest various elements inside a <DIV>
element and style it using CSS).
4.10.1 Internal
There are multiple ways of applying CSS to our webpages. What we have seen so far
is what’s called internal CSS. For internal CSS, one adds a <STYLE> element in the
<HEAD> element of a webpage and adds the desired CSS code in there.
4.10.2 In-line
Another way to add CSS is called in-line CSS. For in-line CSS, one would add a style
attribute directly inside the desired HTML element. For example,
Fig. 4.13 This is how the firstwebpage.html file will look after applying all the styling described
above
4.10 Ways to Add CSS 47
4.10.3 External
Probably the best way to apply CSS to your webpages is by using external .css files. This
way, one could reuse the same one (or more) file across multiple webpages. If changes
need to be made, you only need to modify one file (and affect all webpages that use that
.css file). To move our CSS code to external CSS files, we’ll need to:
. create a file, with extension .css. In this file, we put the code that is inside the
<STYLE> element (not including the <STYLE> tags);
. to apply the CSS code from the file created above, we then need to link it inside the
webpage (see below).
Let’s modify our webpage, so it uses an external CSS file. Then, let’s link this file to all
three webpages we created so far.
Step 1: Move all the CSS code between (not including) the <STYLE> tags into a
newly created file, let’s name it personal.css, and (for simplicity) put it inside the same
directory as your webpages. Here are the contents we added to this file:
DQF[}dcemitqwpf/eqnqt<nkijvdnwg="hqpv/hcokn{<)Htcpmnkp"Iqvjke"Ogfkwo).")Ctkcn"Pcttqy)."Ctkcn."ucpu/
ugtkhÄ
J3}"eqnqt<itggp="
dcemitqwpf/eqnqt<"cnkegdnwg="
vgzv/cnkip<"egpvgt="Ä
1,"R}dcemitqwpf/eqnqt<"nkijvitc{=Ä",1
%vjkukuurgekcn}dcemitqwpf/eqnqt<"tid*444.379.432+=Ä
0o{encuu}eqnqt<dnwg="vgzv/cnkip<"egpvgt=Ä
R<<hktuv/ngvvgt}eqnqt<tgf="hqpv/ygkijv<"dqnf=Ä
J4}hqpv/uk|g<32xy=Ä
<<ugngevkqp"}"dcemitqwpf/eqnqt<"nkijvitggp="Ä
r"}
dcemitqwpf/eqnqt<"nkijv{gnnqy=
ykfvj<"92'=
dqtfgt<"37rz"uqnkf"itggp=
rcffkpi<"72rz=
octikp<"42rz=
Note: Refresh your webpage in the browser, and check that your styling is now gone.
Step 2: Link this external file into the firstwebpage.html (add the line below in place
of the <STYLE> element).
48 4 Brief Introduction to CSS, Javascript, and Bootstrap
Copy the link above to all other webpages and see how the CSS file we just created
above affects them.
Note: In any given webpage, one can make use of any or all three ways to add CSS
seen above.
We hope that we have convinced you that styling is a great way to improve the look and
feel of a webpage. But there is more to it. We’ve seen above how you can link a .css file
and use it to multiple webpages. In particular, one can use the same .css file for an entire
website, helping create a consistent look and feel. More importantly, you can use .css files
created by others. There are free and open-source .css files that you can just learn how to
use them and utilize in your web application.
One such file can be found at https://cdn.jsdelivr.net/npm/[email protected]/dist/css/boo
tstrap.min.css. This is part of the Bootstrap 5 framework, “which is the most popular
HTML, CSS, and JavaScript framework for creating responsive, mobile-first websites”
([3]). Bootstrap 5 is free and open source.
To link this .css file to our webpages, we just need to add.
>NKPM jtgh?$jvvru<11efp0lufgnkxt0pgv1pro1dqqvuvtcrB704051fkuv1euu1dqqvuvtcr0okp0euu$
tgn?$uv{ngujggv$@
Add the following two <LINK> elements to all three webpages we created so far, and
add them right after the <TITLE> element:
>NKPM jtgh?$jvvru<11efp0lufgnkxt0pgv1pro1dqqvuvtcrB704051fkuv1euu1dqqvuvtcr0okp0euu$
tgn?$uv{ngujggv$@
>NKPM tgn?$uv{ngujggv$ jtgh?$rgtuqpcn0euu$@
As of right now, after we linked these .css files into the secondwebpage.html file, our
table looks like this (see Fig. 4.14).
Next, let’s make use of the CSS classes defined in Bootstrap 5. For more details, please
refer to [3].
4.11 First Encounter with Bootstrap 49
Fig. 4.14 Shows the table from the secondwebpage.html file, after the external CSS styling was
applied to this file
Tables are something we see quite often on webpages. So, it should not come as a surprise
to find that there are CSS libraries that can help us make our tables look more professional.
Bootstrap 5 contains CSS class definitions that are very useful for tables. One such class
is the .table class. Let’s use it with our table. To the <TABLE> element, add the .table
class (replace <TABLE> with <TABLE class="table">). Our table will now look a
little better (Fig. 4.15).
There are more classes defined in Bootstrap 5. In particular, we’ll use the fol-
lowing (please add this to the <TABLE> element): class="table table-dark
table-hover". Here is the outcome (Fig. 4.16).
Fig. 4.15 Shows the same table as the one from Fig. 4.14, after we added the .table class selector
(defined by Bootstrap 5)
Fig. 4.16 Shows the same table as the one from Fig. 4.14, after we added the CSS selector
class="table table-dark table-hover" (defined in Bootstrap 5)
50 4 Brief Introduction to CSS, Javascript, and Bootstrap
Using Bootstrap 5 we can also make our <A> elements/links look much better. In partic-
ular, we can make the links shown above to look like buttons. Also, there are different
colors we can use for our buttons, for example,
These classes must be used in conjunction with the .btn class. Read more about this in
[4]. In there, you’ll find other classes that can be used, for example, ones that allow you
to change the size, the outline, and so on.
In our secondwebpage.html, we added the classes mentioned above to make our page
look better. For example, we replaced:
>VF@>c jtgh?$jvvru<11yyy0uvoctvkp0gfw1$@Eqwtug"nkpm>1c@>1VF@
Fig. 4.17 Shows the same table as the one from Fig. 4.16, after we added CSS selectors such as
class="btn btn-success" and class="btn btn-warning" (defined in Bootstrap 5) to
the A elements in the table
4.11 First Encounter with Bootstrap 51
Fig. 4.18 This image is left as an exercise for the readers to figure out what styling to add, in order
to get their pages to look like this one
One last improvement we’ll add to our webpages. For each webpage, add all its contents
inside a <DIV> element with the CSS class of class="container-fluid p-5".
This will provide some padding around our webpages. Here is how our webpage looks
right now (see Fig. 4.19).
There is much more about Bootstrap, but we won’t use more details right now (except
for the following). Besides the sources we recommended above that should be great for
learning Bootstrap 5 in more details, we want to encourage you to directly open the link
used above in a web browser. You should see the actual CSS code we made use of above:
jvvru<11efp0lufgnkxt0pgv1pro1dqqvuvtcrB704051fkuv1euu1dqqvuvtcr0okp0euu
52 4 Brief Introduction to CSS, Javascript, and Bootstrap
Fig. 4.19 This figure is similar to Fig. 4.18, but it has more padding added because of the styling
described above
If you remove the.min from the link (min stands for minified), you can see a more
reader friendly version of the same source code, but this version now contains comments
and more spacing, making it easier for a developer to read it:
jvvru<11efp0lufgnkxt0pgv1pro1dqqvuvtcrB704051fkuv1euu1dqqvuvtcr0euu
We want to finish this part of the chapter that focuses on CSS with the following example.
We would like the three buttons at the bottom of the webpage to be nicely centered. For
this, we can put them inside a <DIV> element and apply CSS to this element.
4.11 First Encounter with Bootstrap 53
>FKX encuu?$EgpvgtVjqugNkpmu$@
>C jtgh?$jvvru<11yyy0y5uejqqnu0eqo1$ encuu?$dvp"dvp/rtkoct{$@Iq"vq"Y5Uejqqnu>1C@
>C jtgh?$jvvru<11yyy0uvoctvkp0gfw1$ encuu?$dvp"dvp/rtkoct{$@Iq"vq"Uv0"Octvkp"Wpkxgtukv{>1C@
>C jtgh?$hktuvygdrcig0jvon$ encuu?$dvp"dvp/rtkoct{$@Iq"vq"vjg"hktuv"rcig>1C@
>1FKX@
0EgpvgtVjqugNkpmu}
vgzv/cnkip<"egpvgt=
Ä
Now the page has nicely aligned buttons at the bottom of the page (Fig. 4.20).
For completeness, and for you to be able to verify your work, we provide here the
entire source code for secondwebpage.html:
>#FQEV[RG jvon@
>JVON@
>JGCF@
>VKVNG@Ewttgpv"gptqnnogpv"hqt"Tc|xcp"Og|gk>1VKVNG@
>NKPM jtgh?$jvvru<11efp0lufgnkxt0pgv1pro1dqqvuvtcrB704051fkuv1euu1dqqvuvtcr0okp0euu$ tgn?$uv{ngujggv$@
>NKPM tgn?$uv{ngujggv$ jtgh?$rgtuqpcn0euu$@
>1JGCF@
>DQF[@
>FKX encuu?$eqpvckpgt/hnwkf"r/7$@
>J3@Ewttgpv"gptqnnogpv"hqt"Tc|xcp"Og|gk>1J3@
>VCDNG encuu?$vcdng"vcdng/fctm"vcdng/jqxgt$@
>VJGCF@
>VT@
>VJ@Eqwtug"KF>1VJ@
>VJ@Eqwtug"pcog>1VJ@
>VJ@Eqwtug"nkpm>1VJ@
>1VT@
>1VJGCF@
>VDQF[@
>VT@
>VF@EUE422>1VF@
>VF@Qdlgev"Qtkgpvgf"Rtqitcookpi>1VF@
>VF@>c jtgh?$jvvru<11yyy0uvoctvkp0gfw1$ encuu?$dvp"dvp/uweeguu$ @Eqwtug"nkpm>1c@>1VF@
>1VT@
>VT@
>VF@EUE562>1VF@
>VF@Fcvc"Uvtwevwtgu"cpf"Cniqtkvjou>1VF@
>VF@>c jtgh?$jvvru<11yyy0uvoctvkp0gfw1$ encuu?$dvp"dvp/yctpkpi$@Eqwtug"nkpm>1c@>1VF@
>1VT@
54 4 Brief Introduction to CSS, Javascript, and Bootstrap
>VT@
>VF@EUE6;7>1VF@
>VF@UV"CUR"0Pgv"Eqtg"OXE>1VF@
>VF@>c jtgh?$jvvru<11yyy0uvoctvkp0gfw1$ encuu?$dvp"dvp/fcpigt$@Eqwtug"nkpm>1c@>1VF@
>1VT@
>1VDQF[@
>1VCDNG@
>DT@
>DT@
>NCDGN@ Ejqqug"c"oclqt">1NCDGN@
>UGNGEV kf?$oclqtu$@
>QRVKQP xcnwg?$Ctv$@Ctv>1QRVKQP@
>QRVKQP xcnwg?$Ocvj$@Ocvjgocvkeu>1QRVKQP@
>QRVKQP xcnwg?$EU$@Eqorwvgt"Uekgpeg>1QRVKQP@
>QRVKQP xcnwg?$Wpfgekfgf$@Wpfgekfgf>1QRVKQP@
>1UGNGEV@
>DT@
>DT@
>NCDGN hqt?$rcuuy$@Rcuuyqtf<">1NCDGN@ >KPRWV v{rg?$rcuuyqtf$ kf?$rcuuy$@ >DT@
>NCDGN hqt?$wugt$@Wugtpcog<">1NCDGN@ >KPRWV v{rg?$vgzv$ kf?$wugt$@ >DT@
>NCDGN hqt?$eqnqt$@Hcxqtkvg"eqnqt<">1NCDGN@ >KPRWV v{rg?$eqnqt$ kf?$eqnqt$@ >DT@
>NCDGN hqt?$ex$@Wrnqcf"{qwt"EX<">1NCDGN@ >KPRWV v{rg?$hkng$ kf?$ex$@ >DT@
>NCDGN hqt?$if$@Gzrgevgf"itcfwcvkqp"fcvg<">1NCDGN@ >KPRWV v{rg?$fcvg$ kf?$if$@ >DT@
>DT@
>DT@
>FKX encuu?$EgpvgtVjqugNkpmu$@
>C jtgh?$jvvru<11yyy0y5uejqqnu0eqo1$ encuu?$dvp"dvp/rtkoct{$@Iq"vq"Y5Uejqqnu>1C@
>C jtgh?$jvvru<11yyy0uvoctvkp0gfw1$ encuu?$dvp"dvp/rtkoct{$@Iq"vq"Uv0"Octvkp"Wpkxgtukv{>1C@
>C jtgh?$hktuvygdrcig0jvon$ encuu?$dvp"dvp/rtkoct{$@Iq"vq"vjg"hktuv"rcig>1C@
>1FKX@
>1FKX@
>1DQF[@
>1JVON@
To program the behavior of our webpages, and make them more dynamic and more
interactive, we can use JavaScript. Although we won’t directly write a lot of JavaScript
code in our book, we can use it to perform computations, create conditional and repetitive
actions, change contents in pages and elements, apply CSS styling dynamically, validate
forms, alert users, and (most importantly for this book) respond to various events (such
as mouse move events, mouse click events, and so on).
NOTE: There are a lot of great details to learn about JavaScript, but we’ll only focus
on the parts that will be useful for our upcoming chapters. We only include this very brief
introduction to JavaScript in order to have a more complete discussion about client-side
technologies. To learn more about JavaScript, we invite the reader to look into [5].
VERY IMPORTANT: JavaScript IS case sensitive. So please be very careful.
4.12 Introduction to JavaScript 55
Fig. 4.20 This figure is similar to Fig. 4.19, but the buttons at the bottom of the page are now
centered
This leads to an interesting situation: we can combine HTML, CSS, and JavaScript
into our webpages, but some languages (HTML, CSS) are not case sensitive, while others
(JavaScript) are. So please be extra careful. We’ll see (below) examples on how to use all
three languages together.
To add JavaScript to a webpage, we can use external .js files, or we can embed
JavaScript directly inside our webpages, using the <SCRIPT> element. For simplicity,
we’ll first embed them inside our webpages, then we’ll see how to link external .js files
into a webpage.
One can embed the <SCRIPT> element inside the <HEAD> element. For better per-
formance reasons, many sources recommend adding the <SCRIPT> element right before
the end tag of the <BODY> element.
56 4 Brief Introduction to CSS, Javascript, and Bootstrap
Just like in other languages (such as C# and Java), a JavaScript consists of a list of
instructions called statements. These statements are run by the browser and optionally
end with a semicolon.
To define blocks of code (statements that are meant to be run together), we make use
of curly braces.
For example,
>UETKRV@
xct eqwpv ?"2=
eqwpv ?"eqwpv -"3=
>1UETKRV@
JavaScript has keywords, which are reserved words that cannot be used as identifiers
(for example, they cannot be used as variable names).
To declare variables, one can use the var keyword or the let keyword. There are
very important differences between the two, but we’ll skip them because we won’t make
much use of them in this book.
Lastly, to use comments, one can use any of the following (just like in C# or Java):
For example,
>UETKRV@
xct eqwpv ?"2=" 11vjku"ku"c"ukping"nkpg"eqoogpv
eqwpv ?"eqwpv -"3="1,vjku"ku"c"ownvk/nkpg"eqoogpv,1
>1UETKRV@
We’ll make some use of JavaScript function, so please look into them more carefully. A
function is essentially a named and reusable block of code, designed to do a given task.
Here is an example of function definition:
4.15 Add JavaScript to Our Webpages 57
hwpevkqp Ocz*pwo3."pwo4+
}
ngv cpuygt ?"pwo3=
kh*pwo4@pwo3+
}
cpuygt ?"pwo4=
Ä
tgvwtp cpuygt=
Ä
fqewogpv0vkvng ?"Ocz*6."4244+=
In the example above, please note the keyword function being used when defining a
function. Most of this code should be very familiar to you (if you’ve taken an introductory
course in C# or Java). Can you answer the following questions?
We’ll learn more about fqewogpv0vkvng below (see the Document Object Model -
DOM).
In the register.html let’s add the following HTML and JavaScript code, right after the
</FORM> tag:
>DT@
>DT@
>DWVVQP qpenkem?$VqItggpDcemitqwpf*+$@Itggp"dcemitqwpf>1DWVVQP@
>DWVVQP qpenkem?$VqTgfDcemitqwpf*+$@Tgf"dcemitqwpf>1DWVVQP@
>UETKRV@
hwpevkqp VqItggpDcemitqwpf*+}
fqewogpv0dqf{0uv{ng0dcemitqwpfEqnqt?$ITGGP$=
Ä
hwpevkqp VqTgfDcemitqwpf*+}
fqewogpv0dqf{0uv{ng0dcemitqwpfEqnqt?$TGF$=
Ä
>1UETKRV@
58 4 Brief Introduction to CSS, Javascript, and Bootstrap
Inside the <SCRIPT> element we defined two functions. One that would change the
background color of the page’s BODY into GREEN, and the other that would change it to
RED.
Just defining two JavaScript methods won’t do much, we also need to call these
methods in order to carry on the task they were programmed to do. For this, we added
two buttons and programmed them so that when you click on a button, one of the
functions will be called.
When you refresh your webpage inside a browser, it should look like (Fig. 4.21).
Next, if you click on the Green background button, you should get (Fig. 4.22).
Lastly, by clicking on the Red background button, you should obtain (Fig. 4.23).
When a webpage is loaded into a browser, the browser creates an object, called the Doc-
ument Object Model, for the webpage ([6]). Using this object (which has a logical tree
structure), one can manipulate the webpage, for example,
fqewogpv0dqf{0uv{ng0dcemitqwpfEqnqt?$TGF$=
4.16 Introduction to the Document Object Model (DOM) 59
Fig. 4.22 The webpage has a green background after the user clicks on the “Green background”
button
Fig. 4.23 The webpage has a red background after the user clicks on the “Red background” button
This code would change the background color (which is styling/CSS) of the <BODY>
element to RED.
Similarly, the example below would change the TITLE element to have the value 2022
(which is the value returned by Max(4, 2022)):
fqewogpv0vkvng ?"Ocz*6."4244+=
60 4 Brief Introduction to CSS, Javascript, and Bootstrap
We can use the DOM object, document, to search for elements that have various char-
acteristics. For example (there are many more than the ones shown below, see more in [7]),
o Example:
document.getElementById("myCSSid").innerHTML = "Paragraph contents changed!";
o Example:
document.getElementById("myCSSid").src = "OlympiaLogo.jpg";
o Example:
document.getElementById("myCSSid").style.color = 'red';
There are various events that JavaScript can capture and therefore we can program a
response (called event handler) when they occur. Some examples of events:
4.18 An Example: Toggle Between Dark/Light Mode 61
The typical way to associate JavaScript response (event handler) with a specific event is
as follows:
There are several ways to associate events and handler; see [5] for more examples.
Below, let’s see an example that demonstrates some of these concepts. We’ll see more as
we go through this book.
In the register.html replace all the code between the </FORM> tag and </BODY> tag
with the following code:
>DT@
>DT@
>DWVVQP qpenkem?$ VqiingNkijvCpfFctm*+@Vqiing"nkijv1fctm"oqfg>1DWVVQP@
>UETKRV@
hwpevkqp VqiingNkijvCpfFctm*+}
fqewogpv0dqf{0encuuNkuv0vqiing*$o{/fctm/oqfg$+=
Ä
>1UETKRV@
Also, add the following CSS code inside the <HEAD> element:
>UV[NG@
0o{/fctm/oqfg}
dcemitqwpf/eqnqt<"dncem=
eqnqt<yjkvg=
Ä
>1UV[NG@
Here is how one can add a button that will take you back to the previously visited
webpage, using the browser’s history:
We’ll make use of it when we start developing our ASP .Net Core MVC application.
What you should note in here is the following (see more [9]):
4.20 External JavaScript 63
Fig. 4.25 (Right) The webpage has a black background after the user clicks on the “Toggle
light/dark mode” button
If you prefer the <BUTTON> element, you can use the following instead:
Lastly, let’s move our JavaScript code into an external file and link that file to our web-
page. For this, create a file with the extension .js (we used myScript.js) and move all
contents of the <SCRIPT> element (not including the <SCRIPT> tags!) into this file.
Then, replace the <SCRIPT> tags with the following:
>UETKRV ute?$o{Uetkrv0lu$@>1UETKRV@
That’s it. Now, you can reuse this script in other/multiple webpages if you so desire.
64 4 Brief Introduction to CSS, Javascript, and Bootstrap
To learn Bootstrap in more depth, we recommend the following two sources: [10] and
[11]. In here we would like to add a little more introduction to Bootstrap 5. First, you
should note that Bootstrap 5 contains not only CSS but also JavaScript code (see below
for more details).
Bootstrap 5 “is the most popular HTML, CSS, and JavaScript framework for creating
responsive, mobile-first websites. […] is completely free to download and use!” ([10]).
In particular, it allows us to quickly create responsive webpages without “reinventing the
wheel”. By responsive we mean the layout automatically adapts/responds to the device’s
layout. As an example, as we’ll see below, if the browser’s window is too narrow, the
navbar collapses the menu options (to create a more user-friendly environment). We get
this by making use of pre-built (i.e., developed into Bootstrap) CSS styles and JavaScript
functionality for buttons, tables, navbars, and so on.
There are multiple ways to include Bootstrap in our projects. One can:
. use various package managers (such as npm, yarn, NuGet, and so on),
. download and use it as your own .css and .js files, or
. (the way we’ll use it in this book) using a Content Delivery Network (CDN).
Content Delivery Networks (CDNs) are a set of servers located around the World that
can hold copies of some content and deliver this content to users much faster (since these
globally distributed servers may be closer to your customers than your web application’s
host server). There are multiple CDNs that host the Bootstrap 5 files, and you can use
either one of them. We’ll give below a couple of options.
Note: We include a <LINK> element for the .css file and a <SCRIPT> element for
the .js file!
JSDELIVR.NET.
. >NKPM jtgh?$jvvru<11efp0lufgnkxt0pgv1pro1dqqvuvtcrB704051fkuv1euu1dqqvuvtcr0okp0euu$
tgn?$uv{ngujggv$@
. >UETKRV
ute?$jvvru<11efp0lufgnkxt0pgv1pro1dqqvuvtcrB704051fkuv1lu1dqqvuvtcr0dwpfng0okp0lu$@>1UETKRV@
4.25 Bootstrap 5 navbar 65
Cloudflare
>NKPM tgn?$uv{ngujggv$ jtgh?$jvvru<11efplu0enqwfhnctg0eqo1clcz1nkdu1vykvvgt/
. dqqvuvtcr1704051euu1dqqvuvtcr/itkf0okp0euu$ kpvgitkv{?$ujc734/
LSmuM58YfTgmXtxfzP{X5D2S3jwsdVmKSPd|3fneHXiP{pGOTn2H:QUsQIfXrrNWFKxuQgljt1Y7N5I1d5L
-:y??$ etquuqtkikp?$cpqp{oqwu$ tghgttgtrqnke{?$pq/tghgttgt$ 1@
>UETKRV ute?$jvvru<11efplu0enqwfhnctg0eqo1clcz1nkdu1vykvvgt/
. dqqvuvtcr1704051lu1dqqvuvtcr0okp0lu$ kpvgitkv{?$ujc734/
31Tx\VeEFGWl[1E{rkO|-kssvcqShCKVoPUL[39O{r6Ou7ofzRU7WX9kQhf\qzeIj|HdQo8upvVMLrrlxwj
i6i??$ etquuqtkikp?$cpqp{oqwu$ tghgttgtrqnke{?$pq/tghgttgt$@>1UETKRV@
We’ve seen already above that you can actually look into and read the CSS source files
for Bootstrap 5 files. Similarly, you can read the JavaScript source files. In a browser,
open the minified version (which you should link in your webpages):
jvvru<11efp0lufgnkxt0pgv1pro1dqqvuvtcrB704051fkuv1lu1dqqvuvtcr0dwpfng0okp0lu
Then, go to the non-minified (developer-friendly) version which you can use for
debugging purposes or for studying it. For this remove the .min from the URL:
jvvru<11efp0lufgnkxt0pgv1pro1dqqvuvtcrB704051fkuv1lu1dqqvuvtcr0dwpfng0lu
The last example we’ll see in here (we’ll see other examples in the upcoming chapters)
is the navbar (navigation bar). A great resource for this is the following: [12]. In that
resource, you’ll find many more options.
Here is what we would like to get: menu options (links) to show up at the top of the
page (Fig. 4.26).
In here, we should see that we have a <NAV> element, which contains all other ele-
ments. This is our navbar. To get a responsive collapsible menu, we use the following
CSS class: navbar-expand-sm. To choose a color for our navbar, we used the classes:
bg-dark navbar-dark.
Then, directly inside the <NAV>, we used a <DIV> element as a container for all
other elements. In particular, we can choose between a CSS class of container (which
provides a responsive fixed width container) and a CSS class of container-fluid
(which provides a full width container, which spans the entire width of the viewport).
Then, each of the links (each an <A> element using the css class nav-link) are essen-
tially list items (see the <LI> element) using the css class nav-item, in an unordered list
(see the <UL> element) which is nested inside the <DIV> element mentioned above. For
the <UL> element, we used the css class navbar-nav.
Note: Replacing the line
>FKX encuu?$eqpvckpgt$@
with
you can get a collapsible menu (see Fig. 4.28) that looks like this (when the window
width is narrow enough):
Then, clicking on the upper left button (with three horizontal lines on it), a menu opens
(Fig. 4.29).
There is much more to say about responsive design, and about Bootstrap, but because
of the focus of this book, we won’t go into more detail.
To help you get a sense of what you can accomplish with Bootstrap, and give you
some design ideas, we recommend you check out the following link for a set of themes
build using this framework: [13].
Fig. 4.28 Shows the same menu as seen in Fig. 4.27, but the menu is not collapsed (see the icon
with three horizontal lines)
68 4 Brief Introduction to CSS, Javascript, and Bootstrap
Fig. 4.29 Shows the same collapsed menu shown in Fig. 4.28, but it is now opened
Some C# Fundamentals
5
In this book, we will assume that you have some fundamental knowledge of an object-
oriented programming language such as C#, Java, or C++ . Below we’ll introduce some
concepts that we’ll need throughout this book. We’ll introduce other concepts later in the
book, but this chapter should provide us with some C# knowledge baseline. Feel free to
skip this chapter (or only read selected sections) if you are already proficient in C#.
Let’s create the “Hello, World!” application using a console application in C#. This is the
only console application we’ll create in the entire book. Also, from here on we’ll stop
using Visual Studio Code and we’ll exclusively use Visual Studio.
If you cannot install Visual Studio on your machine (for example, you are using a
Linux distribution), then you should continue to use Visual Studio Code (but this edition
of the book only focuses on Visual Studio).
For this, open the Visual Studio application. There, click on Create a new project
button, then click on the Console App button. Then, in the next window, in the Configure
your new project window, enter a project a name, and select a location, then click on the
Next button.
At the next step, in the Additional Information window, make sure to choose .Net 6.0.
For this demo, make sure to also check the Do not use top-level statements. We’ll explain
this later in this chapter, but for now make sure it is checked and then click Next.
You should get the following code in the Program.cs file (we’ll explain the code
below):
pcogurceg JgnnqYqtnf
}
kpvgtpcn encuu Rtqitco
}
uvcvke xqkf Ockp*uvtkpi]_"ctiu+
}
Eqpuqng0YtkvgNkpg*$Jgnnq."Yqtnf#$+=
Ä
Ä
Ä
. Run without debugging (press Ctrl + F5—or click on the dark green “play” button).
o Faster, but breakpoints will be omitted (we’ll explain breakpoints later in this
chapter).
. Run with debugging (press F5—or click on the light green “play” button)
o May be a little slower, this option is better for debugging (breakpoints will not be
omitted).
Let’s run this application without debugging. It should display “Hello, World!”.
Hello, World!
If you create another project and follow the same steps as in the previous section, except
that this time, in the Additional Information window, you leave Do not use top-level
statements unchecked, you will get the following code in Program.cs:
11"Ugg"jvvru<11cmc0ou1pgy/eqpuqng/vgorncvg"hqt"oqtg"kphqtocvkqp
Eqpuqng0YtkvgNkpg*$Jgnnq."Yqtnf#$+=
If you run the application, you should get the same result as in the previous section:
Hello, World!
5.3 Namespaces, Using Directive, and Global Using Directive 71
What is happening is as follows. If any one source file (in our case the file named
Program.cs) contains statements outside of a namespace declaration, the compiler will
wrap them inside a Main method, inside a class, and inside a namespace. See more
about this in [1]. Our ASP .Net Core MVC applications that we’ll create in the following
chapters will make use of this.
“Starting in C# 9, you don’t have to explicitly include a Main method in a console
application project. Instead, you can use the top-level statements feature to minimize the
code you have to write. In this case, the compiler generates a class and Main method
entry point for the application” ([2]).
Important note: C# is case sensitive. That is if , IF , and iF are all considered
different. So please be careful.
5.3.1 Namespaces
The first building blocks for C# applications are namespaces. When we write code, we
organize it into namespaces. Namespaces are containers of code. In them one can cre-
ate other namespaces, classes, enumerations, and so on. To create a namespace, we use
the namespace keyword, we give it a name, and then, within curly braces, we add its
corresponding contents.
For example, to define our first namespace, named FirstNamespace we write.
pcogurceg JgnnqYqtnf
}
000"cff"eqfg"kp"jgtg"000
Ä
Starting with C# version 10, the above code can also be written as follows:
pcogurceg JgnnqYqtnf=
000"cff"eqfg"kp"jgtg"000
created in the second namespace is unknown to the code in the first namespace (you will
get a compiler error if you try to run this code):
pcogurceg JgnnqYqtnf
}
kpvgtpcn encuu Rtqitco
}
uvcvke xqkf Ockp*uvtkpi]_"ctiu+
}
O{Encuu"qdl"?"pgy O{Encuu*+=
Eqpuqng0YtkvgNkpg*$Jgnnq."Yqtnf#$+=
Ä
Ä
Ä
pcogurceg O{UgeqpfPcogurceg
}
encuu O{Encuu
}
Ä
Ä
When you want to use code from different namespaces, you will have two options.
Either use the full class name (that is <namespace name> . <class name>) as below:
pcogurceg JgnnqYqtnf
}
kpvgtpcn encuu Rtqitco
}
uvcvke xqkf Ockp*uvtkpi]_"ctiu+
}
O{UgeqpfPcogurceg0O{Encuu"qdl"?"pgy O{UgeqpfPcogurceg0O{Encuu*+=
Eqpuqng0YtkvgNkpg*$Jgnnq."Yqtnf#$+=
Ä
Ä
Ä
pcogurceg O{UgeqpfPcogurceg
}
encuu O{Encuu
}
Ä
Ä
Ä
Ä
5.3 Namespaces, Using Directive, and Global Using Directive 73
There is more to say about namespaces (for example, what happens if you have two
classes with the same name declared in two namespaces and want to use both of them in
a source file?) but since we won’t use them in this book, we skipped them.
The Console class was created in a namespace called System, which is different than the
current namespace. Yet, for the Console class, we did not need to use (but could have
used) a directive such as
wukpi U{uvgo=
The reason for this is implicit using directives. Based on the type of project you’re
creating, a set of using directives is automatically added to your code by the C# compiler.
In particular, for a Console Application, the compiler already added the following using
directives:
wukpi U{uvgo=
wukpi U{uvgo0KQ=
wukpi U{uvgo0Eqnngevkqpu0Igpgtke=
wukpi U{uvgo0Nkps=
wukpi U{uvgo0Pgv0Jvvr=
wukpi U{uvgo0Vjtgcfkpi=
wukpi U{uvgo0Vjtgcfkpi0Vcumu=
The using directives only have effect in the file they are declared. If you want a using
directive to import a namespace for your entire application, not only the file in which it
was declared, use a global using directive instead. It is the same as before, but it uses
the keyword global:
Now, you won’t have to add a using MySecondNamespace; anywhere else in your
project. See more in [4].
74 5 Some C# Fundamentals
5.4 Comments
Comments are pieces of text embedded in our source code that will get ignored by the
compiler. Similarly, to JavaScript, we can have single-line and multi-line comments.
. Single-line comments start with // and continue until the end of that line. Anything
that we include in that line, following //, will be ignored by the compiler.
. Multi-line comments start with /* and continue, possibly on multiple lines, until the
first */. Anything that we include in between /* and */ will be ignored by the compiler.
Example:
For the remainder of this chapter, let’s focus on the example that uses top-level statements,
and build code in it. Here is the starting point (in the Program.cs file):
11"Ugg"jvvru<11cmc0ou1pgy/eqpuqng/vgorncvg"hqt"oqtg"kphqtocvkqp
Eqpuqng0YtkvgNkpg*$Jgnnq."Yqtnf#$+=
In C# all variables must have a declared type. C# is a strongly typed language, so the
compiler will make sure (will enforce that) you only use variables in the context in which
it makes sense for their declared type.
Some C# data types (see more in [6]) we’ll make use of later in this book:
For example, we let’s create the following variables and assign them some initial values
(delete all other codes from Program.cs and add the following):
5.5 Existing Data Types 75
kpv [gctuQhGzrgtkgpeg"?"34=
fqwdng Ucnct{"?"322222="1,qpg"ecp"cnuq"wug"vjg"Fgekocn"v{rg"jgtg",1
dqqn KuXgvgtcp"?"hcnug=
uvtkpi HwnnPcog"?"$Tc|xcp"C0"Og|gk$=
FcvgVkog"JktkpiFcvg"?"FcvgVkog0Rctug*$2:1371423:":<22<22"CO$+=
FcvgVkog"EwttgpvFcvgVkog"?"FcvgVkog0Pqy= 11fkurnc{u"vjg"ewttgpv"fcvg"cpf"vkog
Then, to display to the console the values of each variable, use the following
Console.WriteLine statements:
Eqpuqng0YtkvgNkpg*[gctuQhGzrgtkgpeg+=
Eqpuqng0YtkvgNkpg*Ucnct{+=
Eqpuqng0YtkvgNkpg*KuXgvgtcp+=
Eqpuqng0YtkvgNkpg*HwnnPcog+=
Eqpuqng0YtkvgNkpg*JktkpiFcvg+=
Eqpuqng0YtkvgNkpg*EwttgpvFcvgVkog+=
12
1000000
False
Razvan A. Mezei
8/15/2018 8:00:00 AM
12/10/2022 7:37:30 PM
Because C# is a strongly typed language, once you declare a variable, you can only
assign it values of compatible types. For example, the following results in a compilation
error because string is not a compatible type to be used for integers:
[gctuQhGzrgtkgpeg"?"$vygnxg$=
A related topic here is to use the keyword var when you declare and initialize a
variable, and let the compiler figure out what type to use for the declaration of that
variable. This is called type inference or implicit typing. For example, one can replace.
kpv [gctuQhGzrgtkgpeg"?"34=
with
76 5 Some C# Fundamentals
xct [gctuQhGzrgtkgpeg=
[gctuQhGzrgtkgpeg"?"34=
This is because in the line var YearsOfExperience; the compiler does not have a value
to use in order to decide what time should be assigned for this variable.
We’ll make use of the following several times throughout our book. To build a string that
combines text and variable values, one can use string interpolation. For this, you need
to add the $ right before the string, then inside the string use {} to embed expressions
(for example, variables). Here is an example:
Eqpuqng0YtkvgNkpg*&${gctu"qh"gzrgtkgpeg<"}[gctuQhGzrgtkgpegÄ$+=
Eqpuqng0YtkvgNkpg*&$hwnn"pcog<"}HwnnPcogÄ."ewttgpv"ucnct{<"}Ucnct{Ä$+=
Eqpuqng0YtkvgNkpg*&$jktkpi"fcvg<"}JktkpiFcvgÄ."ewttgpv"fcvg<"}EwttgpvFcvgVkogÄ$+=
years of experience: 12
Inside {} one can also use format specifiers. For example, add :c to display the salary
as currency, or :dd/MM/yyyy to specify a date format for the output:
Eqpuqng0YtkvgNkpg*&${gctu"qh"gzrgtkgpeg<"}[gctuQhGzrgtkgpegÄ$+=
Eqpuqng0YtkvgNkpg*&$hwnn"pcog<"}HwnnPcogÄ."ewttgpv"ucnct{<"}Ucnct{<eÄ$+=
Eqpuqng0YtkvgNkpg*&$jktkpi"fcvg<"}JktkpiFcvgÄ."ewttgpv"fcvg<"}EwttgpvFcvgVkog<ff1OO1{{{{Ä$+=
5.7 Enumerations 77
We obtained:
years of experience: 12
5.7 Enumerations
Above we’ve seen some existing data types from C#. But we can also create custom
types. Two ways to create custom types are enumerations and classes. In here we’ll see
enumerations.
To create an enumerated type (enumeration) we use the keyword enum, then use a
name of our choice, and then in {} add the desired values. For example,
Let’s move this into its own source file. One way to do this is to select this code,
right-click on it, and select the context menu option: Quick Actions and Refactorings ….
Then click on Move type to FacultyLevel.cs and press the enter key:
In the Solution Explorer window, you should see a new file added to your project. If
you double click on that new file (FacultyLevel.cs), you will see that your code has been
moved in there.
Then, we can create variables of this newly created type/enumeration:
Hcewnv{Ngxgn"EwttgpvNgxgn"?"Hcewnv{Ngxgn0CUUKUVCPV=
and use it, just like we used the other variables. For example,
Eqpuqng0YtkvgNkpg*&$Hcewnv{"ngxgn<"}EwttgpvNgxgnÄ$+=
78 5 Some C# Fundamentals
5.8 Classes
Another way to build custom types is by defining/creating classes. This is a very important
topic, and we’ll use it extensively in this book.
Let’s consider the following “type” which currently does not exist in C#. We would
like to work with a type named Instructor. Each instructor should have a name, a hiring
date, and so on (we’ll worry about this in the next section).
If you add the following line of code to your program: Instructor myself; you’ll get a
compilation error, similar to the one below:
CS0246: The type or namespace name ‘Instructor’ could not be found (are you
missing a using directive or an assembly reference?)
One way to create new types was by defining new enumerations (seen in the previous
section). Another way is by defining new classes. To create a new class, we use the class
keyword, followed by a chosen name, and then by {}.
If we add the following code, the above-mentioned error goes away (because now we
have a type named Instructor):
encuu Kpuvtwevqt
}
We can add this code inside Program.cs, but let’s be a little organized. Let’s create a
new file for it, in the same project. In the Solution Explorer window (if it is not already
opened in Visual Studio, then go to View > Solution Explorer to open it), right-click on
the project name and choose Add > Class… Then enter a name for the new class, (please
enter Instructor.cs) and click on the Add button. You should see the newly created file in
the Solution Explorer. It should contain the following lines of code:
namespace HelloWorld2
{
internal class Instructor
{
}
}
Before we continue, make sure to delete the Instructor class definition from Pro-
gram.cs. We do not need to have it defined in two files. Notice that now, the Instructor
type is unknown in our Program.cs file. At the beginning of the Program.cs we need to
5.9 References and Objects 79
add the import directive for the namespace that contains the Instructor class definition.
For the example in the screenshot above, that is,
wukpi JgnnqYqtnf4=
we’ll see more about classes in the next few sections below and use them extensively in
this book.
Now that we defined a class called Instructor, we can create variables of that type. For
example,
Kpuvtwevqt"o{ugnh=
In this example, the variable myself is what we call a reference (more on it below).
To create a new instance of a class (also called object), we use the new keyword. For
example,
pgy Kpuvtwevqt*+=
The line shown above will create a new instance of type Instructor. In order to be able
to access it, we need some type of handle, a reference, which allows us to access it. In
the example below:
Kpuvtwevqt"o{ugnh"?"pgy Kpuvtwevqt*+=
Note: Version 10 of C# (and beyond) also allows the code above to be written as.
Kpuvtwevqt"o{ugnh"?"pgy*+=
80 5 Some C# Fundamentals
References are a little special. In the sense that they themselves do not hold the object,
they just hold a reference to the object. In particular, in the code below:
Kpuvtwevqt"o{ugnh"?"pgy Kpuvtwevqt*+=
Kpuvtwevqt"o{ugnh4"?"o{ugnh=
only one object is constructed, and we have two references (myself and myself2), both
pointing/referring to the same object (see Fig. 5.1).
Important to remember:
. We use classes to define new types and new blueprints for objects.
. Objects are particular instances of classes.
For example, we can create a new class Car. Then, from it, we can build multiple
instances (also called objects), for example,
Ect"o{Ect"?"pgy Ect*+=
Ect"{qwtEct"?"pgy Ect*+=
Above we created two instances of the Car class (so we have two objects) and we
also have two references (myCar and yourCar) that we can use to reference/access those
objects.
So far, we created some new classes, but they don’t do much. Let’s add more to them. In
here we’ll add fields. We make use of fields to add characteristics to our classes. Fields
(non-static fields) are also called instance variables.
5.11 Dot Notation 81
encuu Kpuvtwevqt
}
11hkgnfu
rwdnke uvtkpi Pcog=
rwdnke FcvgVkog"JktkpiFcvg=
rwdnke Hcewnv{Ngxgn"Hcewnv{Ngxgn= 11eqphwukpiA"ngv)u"fkuewuu vjku
rwdnke dqqn KuVgpwtgf=
Ä
Now that we declared these instance variables in the Instructor class, all instances of
the Instructor class will have a HiringDate, a FacultyLevel, and whether or not they are
tenured (IsTenured).
Note: In the line public FacultyLevel FacultyLevel; the second word is the type of the
instance variable, while the third is the name of the instance variable. The compiler is
able to know which one is which from the context it is used. We’ll talk about the public
access modifier below.
Let’s see one more example. When you think of products that you buy online, what
characteristics do they all have? For example, each product has a name, has a description,
has a price, maybe a manufacturing date, a weight (needed for its shipping), and so on.
Let’s create a new class. This time, let’s use the name Product (yes, the .cs part is optional,
Visual Studio will automatically add this to the newly created file). Here is an example::
encuu Rtqfwev
}
rwdnke uvtkpi RtqfwevPcog=
rwdnke uvtkpi Fguetkrvkqp=
rwdnke fqwdng Rtkeg=
rwdnke FcvgVkog"OcpwhcevwtkpiFcvg=
rwdnke fqwdng Ygkijv=
Ä
To access the various characteristics of an object (to access its fields) we use the so-called
dot notation. Namely, we use a dot after the reference name followed by the name of the
instance variable we would like to access.
82 5 Some C# Fundamentals
Note: We can use the dot notation with other members of a class, not just with fields
(as seen below).
For example,
Kpuvtwevqt"kpuvtwevqt3"?"pgy Kpuvtwevqt*+=11c"pgy"kpuvcpeg"ku"etgcvgf
kpuvtwevqt30Pcog"?"$Tc|xcp"C0"Og|gk$="11tgcf"kv"cu"vjg"Pcog"qh"kpuvtwevqt3"ku"000
kpuvtwevqt30JktkpiFcvg"?"FcvgVkog0Rctug*$2:1371423:":<22<22"CO$+=
kpuvtwevqt30KuVgpwtgf"?"hcnug=
kpuvtwevqt30Hcewnv{Ngxgn"?"Hcewnv{Ngxgn0CUUKUVCPV=
It is important to understand that each instance has its own copy of instance variables.
If we create a second instance, then the Name fields of instance1 and instance2 are not
shared. This is why they are called instance variables.
Kpuvtwevqt"kpuvtwevqt4"?"pgy Kpuvtwevqt*+=11c"pgy"kpuvcpeg"ku"etgcvgf
kpuvtwevqt40Pcog"?"$Uw|cppg"Dctvqp$="11tgcf"kv"cu"vjg"Pcog"qh"kpuvtwevqt4"ku"000
kpuvtwevqt40Hcewnv{Ngxgn"?"Hcewnv{Ngxgn0CUUQEKCVG=
We obtained (note how each instance (each Instructor in here) has their own instance
fields (their own Name,…)):
On your own, create one more object, this time an instance of the Product class created
above. As soon as you type in the. after the instance name, a tool in Visual Studio, called
IntelliSense, will help us choose one of the available fields (and several other options that
will be explained later; hint: inheritance).
5.12 Methods
Above, we’ve seen how we can use non-static fields to store characteristics (or instance
variables). To program behavior and actions that each instance can perform, we can make
use of methods. Think of methods as a named set of statements that we can call when
needed (in particular, we can easily reuse this set of statements).
5.12 Methods 83
Let’s define a couple of methods in our Instructor class. We would like to be able to
change the name of an instructor. To define such a method, we can use code similar to
the one below (we give the entire class, to help you make sure your code is complete):
11ogvjqfu
rwdnke xqkf EjcpigPcog*uvtkpi pgyPcog+
}
Pcog"?"pgyPcog=
Ä
Above, we defined two (instance) methods. One named ChangeName, and another
named YearsSinceHired. You should note that these methods have access to the instance
variables of the class, so these do not need to be passed as arguments/parameters for the
method.
To make use of these methods, we can again use the dot notation. Since these are meth-
ods, we will not only use their names when we call them, but also must use parentheses,
and if they have required parameters, we need to pass arguments for these parameters.
Let’s add this line in Program.cs.
kpuvtwevqt30EjcpigPcog*$Cngz Og|gk$+=
Eqpuqng0YtkvgNkpg*&$Kpuvtwevqt"}kpuvtwevqt30PcogÄ yqtmgf"jgtg"hqt"
}kpuvtwevqt30[gctuUkpegJktgf*+Ä {gctu$+=
Then compile and run the application. You should get an output similar to
The this keyword inside a class represents the current instance. In particular, the
ChangeName method above could be rewritten as.
In the code shown above, the keyword this is not necessary because from the given
context (a method inside a class) it was clear that Name represents an instance variable
(or a field/property).
The code shown below, however, contains a logical error. Can you spot it?
Probably the intent is to use the value of the parameter Name (right side of =) to set
the value of the instance variable Name (left side of =). But that’s not what’s happening.
Inside the ChangeName method, the method’s parameter Name will hide the instance field
Name and hence the assignment just sets a value to itself without affecting the instance
variable name. In order to disambiguate which one is which, you can use the this keyword
to specify that the Name from the left side of the assignment operator (=) refers to the
instance variable Name, not the parameter Name:
Access modifiers allow us to control what parts of the application have access to our code.
We can apply access modifiers to namespaces, classes, fields, methods, properties, and
others. There are six access modifiers but the most used ones in our book are the following:
. public: the member that has this access modifier can be accessed by any other code.
. private: the member that has this access modifier can only be accessed by code in the
same class (or struct).
5.15 Properties 85
To learn more about them (protected and internal in particular), we recommend the
following [7, 8].
5.15 Properties
Properties are kind of a mix between fields and methods. They are very important,
especially for upcoming chapters.
Let’s first see the need for properties. Let’s look at the following example:
encuu Rtqfwev
}
rwdnke fqwdng Rtkeg=
Ä
What parts of our code have access to the Price field? Since this field is public, any
part of your program (inside or outside of the Product class) has access to it. Any part of
your program can read it, or even modify it.
Challenge: What if you want to allow code outside the Product class to only read the
Price field, but not change it? How can you create such a restriction? How can we provide
such controlled access to our fields?
If you change the access modifier of the Price field to private, then your field is
only accessible inside the Product class, and nowhere else. This only partially solves
the challenge, but not completely.
One solution: One way to solve the above challenge (if you’ve used Java, you’ve
probably seen this) is as follows:
. Then create public methods that allow us to read the field from the outside of the
Product class (such a method is called a getter or an accessor) and do not create any
public method that allows us to change it:
This solves our challenge proposed above. Here is how the code put together looks like:
encuu Rtqfwev
}
rtkxcvg fqwdng Rtkeg=
From outside on the Product class, we cannot change the price, but we can read it via
the getPrice method (which is publicly accessible). Inside the Main method, code to read
the price of a product would look something like.
Rtqfwev"ncrvqr"?"pgy Rtqfwev*+=
11Eqpuqng0YtkvgNkpg*ncrvqr0Rtkeg+=11GTTQT<"Rtkeg"ku"rtkxcvg
Eqpuqng0YtkvgNkpg*ncrvqr0igvRtkeg*++=""11wug"vjg"igvRtkeg"ogvjqf"kpuvgcf
A similar discussion can be applied to the case where you want to allow users to
modify a field, without being able to read the existing value. We could create a public
method that allows us to change the field (such a method would be called a setter or
mutator). It would look something like.
Another/A better solution: C# has a more elegant way to solve this challenge (and
many related ones), by making use of Properties. Here, we’ll only give a simplified
description of properties since we won’t use their full potential, but we do encourage
you to read more information on properties here [9]. We will replace the Price field
declaration and the definition of the accessor with the following one line (that completely
solves the above given challenge):
Let’s test our code in Main. Notice how we use the dot notation on the property:
Rtqfwev"ncrvqr"?"pgy Rtqfwev*+=
11ncrvqr0Rtkeg"?"32;;0;;="=11GTTQT<"Rtkeg"ugvvgt"ku"rtkxcvg
Eqpuqng0YtkvgNkpg*ncrvqr0Rtkeg+=""11dwv"vjg"igvvgt"ku"rwdnke
5.16 Constructors 87
From the code given above, we can see that we are able to read the value of Price, but
not change it in Main.
If you want to allow Main to only change the value of Price but not read it, you will
use the following property declaration instead:
Read the source mentioned above ([9]) to also learn about computed properties, vali-
dation, and many other capabilities that properties have. We will make extensive use of
properties, but we will probably just use them in the following form:
5.16 Constructors
Constructors are special methods that are used when creating new objects/instances.
They look like regular methods but their name must match the name of the class in which
they are defined, and they do not have a return type. If (and only if!) you define a class
and do not include a constructor definition, one will automatically be created and added
for you by the compiler. We typically use constructors to set the initial state/values for
an object.
Constructors that have no parameters are called default constructors. In the line below,
when we create a new object of type Product, we are calling the default constructor
defined in the Product class:
Rtqfwev"ncrvqr"?"pgy Rtqfwev*+=
Since we did not define an explicit default constructor, one was added for us
automatically. Check out the output of
Eqpuqng0YtkvgNkpg*ncrvqr0Rtkeg+="11kv"fkurnc{u"2"*fghcwnv"xcnwg"hqt"fqwdng+
rwdnke Rtqfwev*+
}
Rtkeg"?"320;;=
OcpwhcevwtkpiFcvg"?"FcvgVkog0Pqy=
Ä
88 5 Some C# Fundamentals
Run again your code and see how the output changes, because we start with a different
initial value for Price:
Eqpuqng0YtkvgNkpg*ncrvqr0Rtkeg+="11kv"pqy"fkurnc{u"320;;"*fghcwnv"eqpuvtwevqt"ugv"vjku"xcnwg+
We can have multiple methods with the same name (as long as their parameters have
different types or a different number of parameters)—this is called method overloading.
Similarly, we can have multiple constructors. Constructors that have parameters are called
non-default constructors.
Let’s see an example—add a second constructor to the Product class.
Rtqfwev"fgumvqr"?"pgy Rtqfwev*:;;089+=11kv"ecnnu"vjg"pqp/fghcwnv"eqpuvtwevqt"
Eqpuqng0YtkvgNkpg*fgumvqr0Rtkeg+="11kv"fkurnc{u":;;089
Rtqfwev"ncrvqr"?"pgy Rtqfwev*+=11kv"ecnnu"vjg"fghcwnv"eqpuvtwevqt"
Eqpuqng0YtkvgNkpg*ncrvqr0Rtkeg+="11kv"fkurnc{u"320;;
We will assume that you understand and can work with if statements and various forms
of loops (for, while, foreach). Below we will give a few brief examples but if you need
more resources, we recommend the following [10, 11].
Let’s write an example where we create a list of Instructors. Then, we check if the list
is empty. If it is empty, we display “There are no instructors in the list!”. Otherwise, we
display the name of each instructor.
At the end of the Program.cs file add the following code:
5.18 Conditionals, Loops, and Lists 89
11ngv)u"etgcvg"c"nkuv"qh"kpuvtwevqtu
Nkuv>Kpuvtwevqt@"EUFgrctvogpv?"pgy Nkuv>Kpuvtwevqt@*+=
EUFgrctvogpv0Cff*pgy Kpuvtwevqt"}"Pcog"?"$Fcokcp"Tqnhuqp$."Hcewnv{Ngxgn"?"
Hcewnv{Ngxgn0CUUKUVCPV."KuVgpwtgf"?"hcnug Ä+=
EUFgrctvogpv0Cff*pgy Kpuvtwevqt"}"Pcog"?"$Guvgnn"Iqvvnkgd$."Hcewnv{Ngxgn"?"
Hcewnv{Ngxgn0CUUQEKCVG."KuVgpwtgf"?"vtwg Ä+=
EUFgrctvogpv0Cff*pgy Kpuvtwevqt"}"Pcog"?"$Nkppgc"Mqgnrkp$."Hcewnv{Ngxgn"?
Hcewnv{Ngxgn0HWNNRTQHGUUQT."KuVgpwtgf"?"vtwg Ä+=
EUFgrctvogpv0Cff*pgy Kpuvtwevqt"}"Pcog"?"$Pcppkg"Nkvvng$."Hcewnv{Ngxgn"?"
Hcewnv{Ngxgn0HWNNRTQHGUUQT."KuVgpwtgf"?"hcnug Ä+=
kh*EUFgrctvogpv0Eqwpv"??"2+"11ejgem"kh"vjg"nkuv"ku"gorv{
}"""11hqt"cp"gorv{"nkuv fkurnc{"c"oguucigu
Eqpuqng0YtkvgNkpg*$Vjgtg"ctg"pq"kpuvtwevqtu"kp"vjg"nkuv#$+=
Ä
gnug
}"""11hqt"c"pqp/gorv{"nkuv fkurnc{"vjg"pcog"qh"gcej"kpuvtwevqt
hqt*kpv k?2="k>"EUFgrctvogpv0Eqwpv="k--+
}
Eqpuqng0YtkvgNkpg*EUFgrctvogpv]k_0Pcog+=
Ä
Ä
Note: In class we typically ask students to help us provide some sample data. This is
more fun this way and it provides another opportunity for students’ engagement.
Running this code, we get the following displayed:
Damian Rolfson
Estell Gottlieb
Linnea Koelpin
Nannie Little
Let’s now rewrite the above code so we make use of the var keyword and use foreach
instead of the for loop. We’ll get the same output.
11ngv)u"etgcvg"c"nkuv"qh"kpuvtwevqtu
xct EUFgrctvogpv?"pgy Nkuv>Kpuvtwevqt@*+=
EUFgrctvogpv0Cff*pgy Kpuvtwevqt"}"Pcog"?"$Fcokcp"Tqnhuqp$."Hcewnv{Ngxgn"?"
Hcewnv{Ngxgn0CUUKUVCPV."KuVgpwtgf"?"hcnug Ä+=
EUFgrctvogpv0Cff*pgy Kpuvtwevqt"}"Pcog"?"$Guvgnn"Iqvvnkgd$."Hcewnv{Ngxgn"?"
Hcewnv{Ngxgn0CUUQEKCVG."KuVgpwtgf"?"vtwg Ä+=
EUFgrctvogpv0Cff*pgy Kpuvtwevqt"}"Pcog"?"$Nkppgc"Mqgnrkp$."Hcewnv{Ngxgn"?"
Hcewnv{Ngxgn0HWNNRTQHGUUQT."KuVgpwtgf"?"vtwg Ä+=
EUFgrctvogpv0Cff*pgy Kpuvtwevqt"}"Pcog"?"$Pcppkg"Nkvvng$."Hcewnv{Ngxgn"?"
Hcewnv{Ngxgn0HWNNRTQHGUUQT."KuVgpwtgf"?"hcnug Ä+=
kh*EUFgrctvogpv0Eqwpv"??"2+"11ejgem"kh"vjg"nkuv"ku"gorv{
}"""11hqt"cp"gorv{"nkuv fkurnc{"c"oguucig
Eqpuqng0YtkvgNkpg*$Vjgtg"ctg"pq"kpuvtwevqtu"kp"vjg"nkuv#$+=
Ä
gnug
}"""11hqt"c"pqp/gorv{"nkuv fkurnc{"vjg"pcog"qh"gcej"kpuvtwevqt
hqtgcej*xct"kpuvtwevqt"kp EUFgrctvogpv+
}
Eqpuqng0YtkvgNkpg*kpuvtwevqt0Pcog+=
Ä
Ä
90 5 Some C# Fundamentals
This section is not meant to teach you generic programming. Instead, we want to make
you aware of it. Generic programming, in particular generic collections, are used quite a
bit in .Net applications. We will also make use of them in our book.
Collections are classes that allow you to group and manage multiple related objects.
Some examples of collections are array lists, linked lists, stacks, queues, and so on.
There are collection classes developed in the System.Collections namespace that store
everything as instances of class Object. Example of classes defined in here: ArrayList,
(there are no linked lists!), Stack, Queue, and others (see more in [12]). You should not use
them in new development, and consider them obsolete. Better choices are the following.
There are collection classes developed in the System.Collections.Generic namespace—
these are generic classes, and users of these classes get to specify what types of objects
they want to store in these collections. Examples of classes defined in here: List<T>,
LinkedList<T>, Stack<T>, Queue<T>, and others (see more in [13]).
In the previous section, we made use of such generic class List.
11ngv)u"etgcvg"c"nkuv"qh"kpuvtwevqtu
Nkuv>Kpuvtwevqt@"EUFgrctvogpv?"pgy Nkuv>Kpuvtwevqt@*+=
The class name is List, and in < > we specify the type of elements we want to store
in our collection/list. In the example above, we used Instructor.
Let’s see another example. If we want to create a generic array list of whole numbers,
we can use code similar to the following:
Nkuv>kpv@"itcfgu"?"pgy Nkuv>kpv@*+="11etgcvg"c"nkuv"qh"kpvgigtu
itcfgu0Cff*322+=
itcfgu0Cff*;2+=
itcfgu0Cff*;:+=
itcfgu0Cff*87+=
5.20 Inheritance
Inheritance is a very important topic; we’ll make use of it throughout this book. We’ll
only cover here what we need to know for this book, but please consider learning more
about it on your own. Here is a resource we recommend [14]:
5.20 Inheritance 91
Inheritance is the process of creating new classes by extending existing ones. The
new class is called a child class or derived class and the existing/original class is called
the parent class or base class. Below we give a quick example. For simplicity (and
demonstration purposes), we’ll overuse the public access modifier, but in a real context,
you should question if that is the right access modifier to use.
For our example, let’s create a very simple class called User. For it, let’s create a new
file, called User.cs and put the code in this file. What characteristics does each User have?
What can each User do? The User class will be our base class.
A simple implementation for this class would be the following:
Supposes now that your application needs to create more specialized Users. For
example, assume that we need
. Professors (these are Users, in the sense they need to be able to login/logout, but also
have characteristics such as whether or not they are tenured, maybe a hiring date) and
. Students (these are also Users, they too need to be able to login/logout, but also have
characteristics such as major and admission date).
Note: All fields, properties, and methods from the base class will be inherited in the
derived class. Whether or not the derived class has access to all of them depends on the
access modifiers being used. In particular, a Professor has a UserName, a Password, and
can Login and Logout:
Rtqhguuqt"rtqh"?"pgy Rtqhguuqt*+=
rtqh0WugtPcog"?"$tog|gk$=
rtqh0Rcuuyqtf"?"$Rcuuyqtf345#$=
rtqh0Nqikp*+=
rtqh0Nqiqwv*+=
rtqh0KuVgpwtgf?"vtwg=
Then, in the Program.cs file we could use code such as the one below:
Uvwfgpv"uv"?"pgy Uvwfgpv*+=""""""11etgcvgu"c"pgy"kpuvcpeg"qh"Uvwfgpv
uv0WugtPcog"?"$tc|xcp0og|gk$=""""11rtqrgtv{"kpjgtkvgf"htqo"Wugt
uv0Rcuuyqtf?"$Rcuuyqtf346#$="""""11rtqrgtv{"kpjgtkvgf"htqo"Wugt
uv0CffokuukqpFcvg"?"FcvgVkog0Pqy=11rtqrgtv{"fghkpgf"kp"vjg"Uvwfgpv"encuu
uv0Nqikp*+=
uv0Nqiqwv*+=
Something important happens with the constructors. To see this, let’s add default
constructors in both the User class and in the Professor class:
5.21 The base Keyword and the Constructors 93
rwdnke Rtqhguuqt*+11fghcwnv"eqpuvtwevqt
}
Eqpuqng0YtkvgNkpg*$Jgnnq"htqo"vjg"Rtqhguuqt"fghcwnv"eqpuvtwevqt$+=
Ä
And
rwdnke Wugt*+11fghcwnv"eqpuvtwevqt
}
Eqpuqng0YtkvgNkpg*$Jgnnq"htqo"vjg"Wugt"fghcwnv"eqpuvtwevqt$+=
Ä
To double-check your code, your Professor class should now look similar to (Fig. 5.2).
Then, in Program.cs, remove all codes (except for the using directive) and add the
following:
Rtqhguuqt"rtqh"?"pgy Rtqhguuqt*+=
What you should see is that the constructor for our Professor class, automatically
called the constructor for the User class (which is the base class for our Professor).
Fig. 5.2 Shows the Professor class that extends the User class. It contains two properties, IsTenured
and HiringDate, and a default constructor
94 5 Some C# Fundamentals
and
Now, change the line in Program.cs so it looks similar to the line below (essentially
calling the non-default constructor from the Professor class):
Rtqhguuqt"rtqh"?"pgy Rtqhguuqt*4245+=
Run the code and check out the results. You should obtain the following:
You should notice that the non-default constructor from the Professor class was called.
This should not be a surprise since we passed a value to the constructor when we cre-
ated the new instance of Professor class. But the important part to observe is that this
constructor in turn called the default constructor from the base class (User class in here).
That means, all constructors, by default, will call the base constructor from the base
class. We can change that behavior by explicitly calling the base constructor of our choice
(default or non-default). This is where we’ll make use of the base keyword.
Currently our non-default Professor constructor looks like
This is equivalent to
which is why the constructor calls the default constructor from the base class.
If we want to call the non-default constructor from the base class, we can pass
parameters to the base() call, as follows:
Now, if we run again the program, we’ll see that our non-default constructor from
Professor class called the non-default constructor from its base class (the User class).
To recap, all constructors (default or non-default) from a class, by default, call the
base class’s default constructor. If we want a constructor to call a non-default constructor
from the base class, we add the call to base() and pass parameters needed for that base
non-default class constructor.
Methods do not have the same cascading effect as constructors, but we can build one
using the base keyword again. We’ll skip this part since we won’t use it in our book.
5.22 Interfaces
Although we’ll make extensive use of interfaces, in this book we will only define very
few interfaces. As such we’ll only briefly introduce them in here.
IMPORTANT: As a convention, interface names always start with a capital I (see
examples below).
96 5 Some C# Fundamentals
Let’s start with an example of interface that is included in the System library, namely
IComparable.
If we want to sort integers, we can do so as long as we know how to compare each
pair of numbers. Similarly, if we want to compare instances of let’s say Professor class,
again we can do so, as long as we have a way to compare every two professors. The
essence of what we are trying to suggest here is that sometimes we want to expect similar
functionalities from unrelated classes. If they are not related, inheritance is probably not
the way to go (you should not use inheritance for unrelated classes). In this case, the
interfaces will be the appropriate choice.
In particular, the generic class List, which can store elements of any given type, can
also sort them, as long as the type used in the List implements the IComparable interface.
kpvgthceg KRtkpvcdng
}
xqkf RtkpvVqEqpuqng*+=
Ä
Important to notice: We did not include an access modifier (this is explained below).
Next, let us implement this interface. For a class to implement an interface, we need to.
. declare this by using the : (just like inheritance, which is why it’s important to use the
I in the interface name!);
. implement each member from the interface in our class.
5.23 How to Use an Interface 97
Let’s see this by example. Let’s modify the Professor class so it now implements the
IPrintable interface.
Since we already have an inheritance declaration (: is already in there) we only need
to add a comma after User and add the name of the interface we intend to implement:
Then, to implement the specified interface(s), we need to define all members from the
interface declaration. In particular, we’ll need to define a void PrintToConsole(); method.
Important to note: All members from the interface must be added as public members
in the class (regardless of whether or not the interface uses the public access modifier
when it declares those members). So, we’ll need to define a public void PrintToConsole();
method.
What you put in this method it’s up to you, the interface just cares that you have a
definition for each member of the interface. Let’s add the following method definition in
the Professor class:
Note: One class can only inherit from one base class, but it can implement multiple
interfaces.
Important note: One can have multiple completely unrelated classes, all implementing
the same interface. How can this be useful? That’s what we’ll see below.
Let’s start with the following example. If we define the method below:
xqkf Jcrr{Ogvjqf*Wugt"wut+
}
11vq"fq000
Ä
what can type of parameters can you pass to this method? The answer is you can pass in
any instance of the User class, or any class derived from User (in particular, you can pass
in an instance of a Professor).
98 5 Some C# Fundamentals
Can you pass in an integer? Can you run HappyMethod(2024)? The answer is no,
because 2024 is not an instance of User (or any derived class from User).
Interfaces provide us with more flexibility. Let’s create a method, which uses an inter-
face as a parameter type. For example, add the following to the end of the Program.cs
file:
xqkf Jcrr{Ogvjqf*KRtkpvcdng"qdl+
}
qdl0RtkpvVqEqpuqng*+=
Ä
What can you pass to such a method? The answer is an instance of any class (or derived
class) that implements the IPrintable interface. In particular, if you have two unrelated
classes, say Professor and Classroom, both implementing the IPrintable interface, then
you could pass an instance of either one to the HappyMethod. That’s quite some flexibility.
See more about interfaces in here [15].
This is a very brief introduction to lambda expressions. They are very convenient when
we want to pass certain information to methods. See more in [16].
Lambda expressions are nice short ways to create so-called anonymous functions.
Lambda expressions make use of the lambda operator =>(read it as: “goes to” or
“becomes”) and have two forms:
If we have exactly one parameter, then the parentheses around it are not required. The
following examples are equivalent:
Let’s see some practical examples. Earlier, we created the following list:
hqtgcej*xct"kpuvt"kp EUFgrctvogpv+
}
Eqpuqng0YtkvgNkpg*&$Pcog<"}kpuvt0PcogÄ."Ngxgn<"}kpuvt0Hcewnv{NgxgnÄ."Vgpwtgf<"}kpuvt0KuVgpwtgfÄ $+=
Ä
And obtain
which is the order in which we added each instructor into our list.
What if we would like to sort this list? Before the foreach statement add the following
statement:
EUFgrctvogpv0Uqtv*+=
Run again your code. Did it work? The output should contain a rather error message,
including.
What this means is that the Sort method did not know how to compare two instances
of Instructor.
We now have some solutions. One is to implement the IComparable interface inside
the Instructor class.
Another, which we’ll see below, is to provide a lambda expression to the Sort method,
an expression that is essentially a comparison method that can be used by Sort when
sorting our list.
100 5 Some C# Fundamentals
EUFgrctvogpv0Uqtv**kpuvt3."kpuvt4+"?@"Uvtkpi0Eqorctg*kpuvt30Pcog."kpuvt40Pcog+"+=
Above note that we provided the Sort method a lambda expression, an anonymous
method that can be used to compare every two instructors. Running the code above, you
should now get the list sorted by Name:
Similarly, we can sort by Tenure. In this example, we will use the CompareTo method
that is included for Boolean values. Replace the statement above with.
EUFgrctvogpv0Uqtv**kpuvt3."kpuvt4+"?@"kpuvt30KuVgpwtgf0EqorctgVq*kpuvt40KuVgpwtgf+"+=
See how flexible the Sort method was? Allowing us to use lambda expressions, we
were able to quickly specify how to sort our list. Above we’ve seen how to sort the list
by name, or by tenure.
5.25 LINQ
This is another very brief introduction. To read more about LINQ, we recommend the
following [17].
5.25 LINQ 101
hqtgcej*xct"kpuvt"kp EUFgrctvogpv0Yjgtg*kpuv"?@"kpuv0Pcog0Eqpvckpu*$pp$+++
}
Eqpuqng0YtkvgNkpg*&$Pcog<"}kpuvt0PcogÄ."Ngxgn<"}kpuvt0Hcewnv{NgxgnÄ."Vgpwtgf<"}kpuvt0KuVgpwtgfÄ $+=
Ä
We were able to narrow down the list to only elements that match a specific condition
(given as a lambda expression):
We can then use another operation on the results of Where. One could, for example,
use another Where.
And obtain:
will yield:
We mentioned earlier that C# is a strongly typed language. That means that if a variable
is, let’s say, declared as an int, then you can only assign compatible values to it.
In particular, if you declare number as an int, then you cannot assign a string value or
even a null value. You would get a compiling error:
kpv pwodgt=
pwodgt"?"32="11QM
pwodgt"?"$vyq$="11gttqt"/ ytqpi"v{rg
pwodgt"?"pwnn="11gttqt"/ ytqpi"v{rg
When dealing with web applications, it is possible the user may fill out all form fields.
How would you treat that?
One is to use nullable types. “A nullable value type T? represents all values of its
underlying value type T and an additional null value” (see more in [18]).
If you put a ? next to the int type, you obtain int? which is a nullable type, namely it
represents all integers and the null value. In the previous example, we now get
kpvA"pwodgt=
pwodgt"?"32="11QM
pwodgt"?"$vyq$="11gttqt"/ uvknn"ytqpi"v{rg
pwodgt"?"pwnn="11QM"pqy
Another example: bool represents the values true and false. Therefore, bool? represents
the values: true, false, null.
Important: When you work with nullable types you should check for null values.
There is a lot more to learn about this topic, but we’ll skip the rest. We recommend
you to also check out the following [19]. Also, we won’t use the following but we want
to challenge you to find out what is the difference between each of the following:
kpv]_"xcnwgu=
kpvA]_"xcnwgu=
kpv]_A xcnwgu=
kpvA]_A xcnwgu=
To reopen a Visual Studio project (in particular an ASP .Net Core MVC project), you
should locate and double click on the .sln file. This is typically in the same directory as
the project, or one level up.
5.28 Other Resources for Learning C# 103
We have seen (many times) learners trying to open a project by opening the .cs (the
C#) file, which may (typically) also open in Visual Studio. But once the file opens, there
is no compile button. Instead you should open the .sln file (a text-based file). See more
about this file here [20].
This chapter is not meant to provide a comprehensive C# tutorial. For that, we recommend
the following:
. This chapter applies to ASP .Net Core web applications, regardless of whether or not
they use the MVC pattern.
. We’ll start our project in this chapter. Then, until the end of the book, we’ll add to it
a little more in every chapter.
First, let’s cover some background knowledge. In the previous chapter, we saw Console
Applications, where we interacted with our application using a console window:
Hello, World!
© The Author(s), under exclusive license to Springer Nature Switzerland AG 2024 105
R. A. Mezei, Introduction to the Development of Web Applications Using
ASP .Net (Core) MVC, Synthesis Lectures on Computer Science,
https://doi.org/10.1007/978-3-031-30626-6_6
106 6 Middleware, Services, Intro to Dependency Injection
Fig. 6.1 Shows the welcome page of our web application in a web browser
The current version of .Net 6 is a unified development platform and now (for this version)
.Net and .Net Core can be used interchangeably. With .Net 6.0 (and beyond) you may
sometimes see the word Core included (for example, ASP .Net Core) to emphasize this
framework is cross-platform, but there is no need for it anymore (so for .Net 6.0 and
beyond, ASP .Net and ASP .Net Core are equivalent names). You may want to read more
about this in [46].
Now you know what ASP .Net (Core) means. What about the MVC? Model-View-
Controller (MVC) is an architectural pattern, one that we’ll introduce below and use for
all subsequent chapters of this book.
In this book, we’ll cover the ASP .Net (Core) MVC, but there are other frameworks
that also use this pattern, for example, Spring MVC (which is a Java framework for
building web applications).
6.2 An Introduction to the MVC Pattern 107
. For example (think about a web application such as Amazon, Canvas, Moodle,
…), we could have the following model classes: Course, User, Student, Instructor,
Administrator, Product, Seller, Buyer, and so on.
Fig. 6.2 Shows the main components of an MVC web application. In particular, the client side uses
a browser (HTML, CSS, and JavaScript), then on the server side we have the middleware pipeline,
controllers, models, and views, and lastly, the data may be stored in a database
108 6 Middleware, Services, Intro to Dependency Injection
. These are the typical C# classes we’ve seen in the previous chapter (plus other things
we’ll see later—these files will have extension .cs).
The views will make up the user interface. We’ll present content to users via views (more
accurately, we’ll use views to build webpages that ultimately will get displayed in a user’s
browser). We’ll see more about this later.
. For example (think about a web application such as Amazon, Canvas, Moodle, …), we
can have a view that will be used to display a list of all courses taken by a student, or a
list of all laptops available to purchase. We could use another view to build a page that
allows our users to change their password, and yet another view to add a new laptop
to sell it online.
. These are files that will combine HTML and CSS with C# (these files will have
extension .cshtml).
The controllers will handle the user interaction. We’ll give some examples below, but
they will be better understood once we start using them in our project.
. For example (think about a web application such as Amazon, Canvas, Moodle, …).
What happens when you click on a button? Or click on a link? Or load the first
welcome page? In each of these, your requests will (eventually) be sent to a Controller
(more specifically to an Action from that Controller). In many cases, the Controller
will create an instance of a Model, then pass it to a View to build a page that will
eventually show up in the user’s browser.
. These are C# classes derived from the Microsoft.AspNetCore.Mvc.Controller class
(these files will have extension .cs).
Confused? Do not worry! We’ll cover these concepts in more depth in the next few
chapters. This section is only meant to provide a quick introduction to what MVC pattern
entails.
If you don’t have time to review this section, you can skip it. In here we want to clarify a
little more the concepts included above, and also give you a very quick tour into a simple
MVC example.
Let’s create an ASP .Net Core MVC web application. Open Visual Studio, then click
on the Create a new project button.
Then, in the Create a new project window, enter the word MVC in the search bar, then
select ASP .NET Core Web App (Model-View-Controller), and click the Next button.
6.3 A Quick Dive into an MVC Example (Optional) 109
Very important:
. Make sure to choose the option that uses C#, not some other programming language!
If you look carefully, there are other languages available too (for example, F#).
. A common mistake we’ve seen in class is that students would choose ASP .NET Core
Web App. If you look carefully at its description, you’ll note that it uses Razor Pages
instead. Please do NOT use that option for this book.
Next, choose a name and location for your project. We called ours DiveIntoMVC, then
click on the Next button.
In the last step, in the Additional information window, you can choose a .Net framework
to work with. In this book, we’ll always go with .Net 6.0 (Long Term Support). Also,
uncheck the Configure for HTTPS. We won’t use it in this example. Then click on the
Create button.
Once the new project is ready to run, either press Ctrl + F5, or go to Debug > Start
Without Debugging, or click on the light green play button located around the center of
the top menu options.
This will start, in a browser, your newly created ASP .Net Core MVC web application
(note the URL: localhost:5096).
You should note that the opened webpage has a navbar at the top of the page. As
you click on those menu options or on any of the links from this webpage (Fig. 6.3), the
(local) server responds (see Fig. 6.4) with another webpage (with URL: localhost:5096/
Home/Privacy).
To know what goes into this project, check out the Solution Explorer window. There are
several files and folders showing up in there. In particular, you should note the following:
"profiles": {
"ASPBookProject": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "http://localhost:5096",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
If you open the site.css file found under wwwroot > css: you should find very familiar
code (CSS styling).
html {
font-size: 14px;
}
html {
position: relative;
min-height: 100%;
}
body {
margin-bottom: 60px;
}
112 6 Middleware, Services, Intro to Dependency Injection
Fig. 6.5 Shows the Welcome page in a browser, after adding the CSS code, shown above, to the
site.css file
background-color:lightblue;
Then, either rerun the application. Did you see any changes?
Very important: Often, when you make changes to your CSS files, you may not see
them show up in the browser. The reason is as follows: the browsers may cache the CSS
file and if you reload the page, it will reuse an already downloaded CSS file. The solution/
workaround: press Ctrl + F5 in your browser to tell your browser to reload everything,
including the CSS file. The outcome is shown in Fig. 6.5 (at the URL: localhost:5096).
The next stop is the Layout.cshtml. In here you should note two things as follows:
. It defines the navbar we see on our pages (similar to the one we’ve seen in Chap. 4).
. It links to the site.css file we’ve seen above.
There are many things to show, but we’ll learn about them in more details in the upcoming
chapter. If you have time and a keen interest, look around on your own. In particular,
please explore the Models, Views, and Controllers folders.
If you are using Visual Studio Mar, or Visual Studio Code, check out the links below
to help you create your first MVC application:
. https://github.com/dotnet/AspNetCore.Docs/blob/main/aspnetcore/tutorials/first-mvc-
app/start-mvc.md
. https://docs.microsoft.com/en-us/aspnet/core/tutorials/first-mvc-app/start-mvc?view=
aspnetcore-6.0&tabs=visual-studio.
6.4 Let’s Start Our ASP .Net Core Application Project in Here 113
6.4 Let’s Start Our ASP .Net Core Application Project in Here
Open Visual Studio and Create a new project. Make sure to choose the ASP .NET Core
Empty template!
Then, choose a name for your project. We called ours ASPBookProject. Then click on
the Next button.
We’ll then choose .Net 6.0(Long Term Support) for the framework, and
make sure to uncheck the Configure for HTTPS. Then click the Create button.
Now run your application and make sure it opens in a browser (for us, the URL will
use another random port: localhost:5125)—it should look similar to Fig. 6.6.
Before we move on to the next section, let us compare what we got here (where we
created an Empty web application) against what we got in the previous section (where we
created an MVC application). In this book, we will continue with our empty application
and will build our way up to an MVC application.
The files in the project (seen from the Solution Explorer window) are far fewer than before
(check it out!).
Also, the Program.cs file contains the following starting point/initial code:
app.Run();
The MVC web application has many more initial files (check out the Solution Explorer
window!). In particular, you should note the wwwroot folder (containing other subfolders
and CSS and JS files), the Models folder, the Controllers folders, the View folder, and
many others. Also, note that the Program.cs file contains the following code:
var builder = WebApplication.CreateBuilder(args);
app.UseAuthorization();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.Run();
Just like we’ve seen in the previous chapter when we covered Console Applications, the
entry point to a C# application is the Main method. Since we made use of top-level
statements in our project, (in particular in the Program.cs file), the Main method is
automatically created by the compiler for us in any one (and only one) file where we use
top-level statements. For us, this is the Program.cs file.
As a consequence, we can look at Program.cs as the entry point to our web applica-
tion (but really, Main is the real entry point, Program.cs can be renamed (for example,
EntryPoint.cs) and our web application would still work).
In the Program.cs file we’ll configure services and create the middleware pipeline
for our web application. These two topics are introduced below, but they will make much
more sense as we dive deeper into this book. Services in particular will make much more
sense later when we get to see the need they solve and see how services work with the
other parts of the application.
6.6 The Middleware Pipeline 115
Think of the middleware pipeline as the entry point into your web application. All HTTP
requests pass through this point. In here, you can decide how to respond to your requests.
In the middleware pipeline, one can add various middleware components that (see more
details in [48])
. route certain requests to the appropriate controllers (we’ll cover this in the next
chapter);
. add authentication and authorization support;
. log all HTTP requests and responses;
. provide support for static files;
. provide support for caching responses;
. provide support for managing user sessions;
. and many others.
As we’ll see below, each middleware component is responsible for invoking the next
component in the pipeline, or not (in which case we say that the component is short-
circuiting the pipeline, making this component a terminal middleware).
Before we see some examples, we should note the following. For every HTTP request
received by our web application, the ASP .Net Core platform will create a Request object
(that contains information regarding the request received from the client) and a Response
object (that contains information about the response being sent back to the client)—see
Fig. 6.7. Each middleware component can inspect the Request object and modify the
Response as needed. We get access to these objects (and others) via a variable of type
HttpContext (see the example below).
Let’s first go over the existing code in the Program.cs file, in our ASP .Net Core
application example:
//set up the basic features of the ASP.NET Core platform
var builder = WebApplication.CreateBuilder(args);
app.Run();
116 6 Middleware, Services, Intro to Dependency Injection
Fig. 6.7 Is similar to Fig. 6.2, it shows how HTTP requests and responses are being used for the
interaction between a client and a server
If you run this application, you’ll get the following response in a browser (URL:
localhost:5125): Hello World!
The port number shown in your URL is probably different. If it is different, you should
change it, so we all use the same port number. First, double click on the launchSet-
tings.json file (inside Solution Explorer window look inside the Properties folder) to
open it. Then, make sure to change the port number to match ours (5125):
"profiles": {
"DiveIntoMVC": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "http://localhost:5125",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
6.6 The Middleware Pipeline 117
Alternatively, you can leave the default value generated for your port number as is, but
please make sure to use that number (instead of the one we use in this book: 5125) in all
examples shown below.
Next, we’ll introduce some simple middleware components, but please keep in mind that
we’ll see more of them (and more useful ones for this book) in future sections and
chapters.
One way to build the middleware pipeline is by making use of request delegates.
These can be configured using the Run, Use, and Map methods. Let’s quickly introduce
them, then see some examples. Note: we won’t make much use of the Use, Run, and Map
middleware after this chapter—we just use them to introduce other concepts.
The Use middleware allows a parameter, next, which is a reference to the next middle-
ware in the pipeline. One can chain multiple request delegates using next.Invoke()
(to proceed to the next middleware in the pipeline). If a Use middleware does not call
next.Invoke() then it is said that this component is short-circuiting the pipeline.
The Run middleware is similar to the Use middleware but it does not have the next
parameter, hence it cannot call a next middleware component. Because of this, Run is a
terminal middleware and should be placed at the very bottom of the middleware pipeline.
The Map middleware branches the request pipeline based on matches of the given
request path. We’ll skip this in our book.
Important note: As we will see below,
Let’s create an example of middleware pipeline based on the Use and Run defined above.
First, replace the line containing app.MapGet with the following:
crr0Twp*cu{pe"eqpvgzv"?@"}"cyckv"eqpvgzv0Tgurqpug0YtkvgCu{pe*$Jgnnq"htqo"Twp"okffngyctg$+="Ä+="
app.Run();
We used a simple middleware component. Whatever the HTTP request is, our web
application responds with Hello from Run middleware.
Try each of the following requests:
http://localhost:5125/
http://localhost:5125/Ada
http://localhost:5125/Turing/Alan
You should get the same response in each case: Hello from Run middleware.
Let’s change this a little bit, so we make use of the Request object. Replace the line.
crr0Twp*cu{pe"eqpvgzv"?@"}"cyckv"eqpvgzv0Tgurqpug0YtkvgCu{pe*$Jgnnq"htqo"Twp"okffngyctg$+="Ä+=
that was added earlier with the following and recompile your project (or use the Hot
reload button and refresh your browser):
crr0Twp*cu{pe"eqpvgzv"?@"}""
""""cyckv"eqpvgzv0Tgurqpug0YtkvgCu{pe*&$[qw"jcxg"tgswguvgf"}eqpvgzv0Tgswguv0RcvjÄ$+=""
Ä+="
Now, if you use the links above, you should get different responses depending on the
HTTP request used.
For example,
http://localhost:5125/ will give you the following response: You have
requested/.
Similarly, http://localhost:5125/Turing/Alan will return: You have requested /
Turing/Alan.
Using Microsoft IntelliSense, you should be able to play with the line of code we just
added in Visual Studio and find more details about the context, Request, Response,
etc.
If you hover your mouse over the text context, you’ll find out it represents
(references) an object of type: HttpContext.
If you put a dot right after context, you’ll quickly find out what methods and
properties it has. In particular, you should note the following properties: Connection,
Request, Response, Session, and others.
Similarly, if you put a dot right after Request, you’ll find out that you can get access
to a lot of information regarding the HTTP request. In particular, you can find information
6.6 The Middleware Pipeline 119
regarding the Body of the request the Path (see the example above), the Method (get
vs. post), the QueryString, and so on.
You should note that in the example above, we are returning back to the user what we
build inside the context.Response object.
Now let’s add a second middleware component. Add the following code, right before the
very last line (before app.Run();)
app.Run(async context => {
await context.Response.WriteAsync($"The second Run");
});
Rebuild your project and open the following in your browser (this will send an HTTP
request to your web application):
http://localhost:5125/Turing/Alan
Here is the output: You have requested /Turing/Alan.
Why didn’t our newly added code run? The answer is Run is a terminal middleware,
so it won’t invoke the next middleware component. To chain multiple middleware com-
ponents, we’ll make use of Use. Remember to always put this request delegate (Run)
last in your middleware pipeline.
Note: Look at the last line in Program.cs. What is its role? Again, make use of Intel-
liSense, good documentation is very helpful in this case. Hover your mouse over the two
app.Run calls shown below. What do you notice?
You should note that Run is an overloaded method. We can use it with a request
delegate argument (the first of the two screenshots), but you can also use it with no
arguments.
The first call is used to add a middleware delegate to the request pipeline. We use
the second call, so the application doesn’t shut down too early (since we’re dealing with
asynchronous calls). Make sure to not delete this last line by mistake.
To chain multiple request delegates, we can utilize Use. Modify your Program.cs file so
it looks like the code below:
120 6 Middleware, Services, Intro to Dependency Injection
Fig. 6.8 Shows the output (in a browser) from the middleware components described above
app.Run();
What is the output? More importantly, in which order? Let’s use the following: http://
localhost:5125/Turing/Alan.
We obtained (see Fig. 6.8).
In particular, we should note that next.Invoke(); was used to call the second
component.
Very important: The order in which middleware components are declared is very
important. In particular, what would happen if we switched the order of Run and Use, as
shown below? Why?
app.Run(async (context) =>
{
await context.Response.WriteAsync("Hello from Run\n");
});
If time, check out the following. What does it do? What URLs would unlock the
secret?
6.6 The Middleware Pipeline 121
if(context.Request.Path.ToString().Contains("SECRET"))
await next.Invoke();
await context.Response.WriteAsync("Hello from Use end\n");
});
We won’t be using the Use and Run in the upcoming chapters of this book. We
only use them in here to give you some sense of what the middleware pipeline is and
understand a little bit about what an HTTP Request is. In the next section, we’ll introduce
another middleware component, this one is very important, and we’ll use this middleware
component until the end of the book. Please make sure to understand it.
ASP .NET Core comes with many middleware components ready for use. We will see
some of them in this chapter, and others later in this book. To learn more about them,
check out the table shown in [48]. Here is a list of some middleware components we’ll
see in this book:
Files that do not change at runtime are called static files. These files are not dynamically
generated or modified when the user interacts with our web application, so we call them
static. The following are some examples of static files: CSS files, (some) HTML files,
JavaScript files, and some images and videos (company logo, company intro). For now,
focus on their functionality; we’ll see them more in depth as we go through the next
chapters. To learn more, check out [49].
We typically store static files in the project’s web root directory (this is just a folder
named wwwroot created directly in the root of the project). We can store them elsewhere,
but we won’t do that in this book.
To create a web root directory, in the Solution Explorer window, right-click on the
project’s name (ASPBookProject) and select Add > New Folder. There, type in the name
wwwroot. Once you press the enter key, you should notice that Visual Studio is using a
special icon for this folder, which should suggest this is an important folder.
In this folder/directory, one can add files and subdirectories. Let’s add a few images
in there. First, in the Solution Explorer window, right-click on the wwwroot folder, and
select Add > New Folder. Choose a name for this new folder (we chose images). Then,
inside images, add a few images (one can use drag and drop to copy images in this
subdirectory).
By default, files from wwwroot are NOT accessible from the client side. To give your
clients access to these files, you can use the UseStaticFiles middleware component, as
seen in the example below.
Let’s change the Program.cs to match the following contents:
6.7 Static Files Middleware 123
11ugvu"wr"vjg"dcuke"hgcvwtgu"qh"vjg"CUR0PGV"Eqtg"rncvhqto"
xct"dwknfgt"?"YgdCrrnkecvkqp0EtgcvgDwknfgt*ctiu+="
"
11ugv"wr"okffngyctg"eqorqpgpvu0"
xct"crr"?"dwknfgt0Dwknf*+="
"
crr0WugUvcvkeHkngu*+="11pggfgf"vq"ikxg"ceeguu"vq"hkngu"kp"yyytqqv"
"
crr0Twp*cu{pe"*eqpvgzv+"?@"
}"
""""cyckv"eqpvgzv0Tgurqpug0YtkvgCu{pe*$Ygneqog"vq"qwt"ygd"crrnkecvkqp$+="
Ä+="
"
"
crr0Twp*+="
To access static files, you need to use paths relative to the web root. For example, if we
wish to access the file: wwwroot/images/image01.JPG, we will use http://localh
ost:5125/images/image01.JPG (see Fig. 6.9).
You should note the following:
. The order of the middleware components is very important. Since we used the
UseStaticFiles middleware component before the Run, we were able to obtain
the static file (in our case image01.JPG).
. The UseStaticFiles is a terminal middleware, so the Run component was not
run.
Fig. 6.9 Shows a static file (in here an image), displayed in a browser. Note the URL to this image
contains the relative path of this image inside the web root
124 6 Middleware, Services, Intro to Dependency Injection
. But UseStaticFiles middleware will only run if the requested path matches a
file inside the wwwroot (with the same path!). Otherwise, the next middleware will be
called, in our case the Run
– If you use the URL: localhost:/5125/images/image011.JPG, you should get
the following text displayed inside the browser: Welcome to our web
application.
Very important: We can (and later will) call UseStaticFiles before calling
UseAuthentication. This allows access to static files even to users who are not
yet logged in. Therefore, all files under wwwroot are publicly accessible, so be careful
not to store any sensitive files in there.
If you have time and interest, check out and learn about the Directory browsing section
in [49] which we skipped.
To serve a default webpage from wwwroot without the need to provide the filename in
the request URL, we can
We won’t spend too much time on this default webpage because we’ll use another default
page when we work with the MVC pattern. So please don’t spend too much time on this
webpage.
As an example, we’ll copy the firstwebpage.html into the wwwroot folder for our ASP
.Net Core project. Once in there, rename it as index.html. Double-checked that you copied
the file inside wwwroot, not inside the images folder!
Then, add the following line right before app.UseStaticFiles();
That’s it. Rebuild your web application and run it again (see Fig. 6.10). Here is what
we got (URL: localhost:5125).
Let’s now also copy the CSS file we created earlier. Let’s put it inside the css subfolder
(create it!) inside wwwroot. Then open the index.html page and update the link path from
Fig. 6.10 Shows the default page (index.html) being set for our application. Note how some ele-
ments are not properly displayed (in particular the CSS and the embedded image need to be updated)
Note: You can drag and drop the .css file from the Solution Explorer into your
index.html file: Visual Studio will automatically paste the corresponding link element.
Let’s also update the image. Inside the index.html, we also need to update the
path to the image used in it (currently, this is: <IMG SRC ="image01.JPG" …).
One easy way to do this is as follows. Delete the text from the quotes shown
in the image, then press Ctrl + Space to get IntelliSense support. We used:
<IMG SRC ="/images/image01.JPG" .
With these changes, we are now able to make our web application display a default
page that makes use of the following static resources: a CSS file and an image file (see
Fig. 6.11).
Please comment out (or delete) the following CSS code from the www > css >
personal.css file:
126 6 Middleware, Services, Intro to Dependency Injection
Fig. 6.11 This is the same as Fig. 6.10, but with the image and CSS contents properly set
6.7 Static Files Middleware 127
p {
background-color: lightyellow;
width: 70%;
border: 15px solid green;
padding: 50px;
margin: 20px;
}
With this change, the page (see Fig. 6.12) now looks a little better:
Fig. 6.12 This is the same as Fig. 6.11, after changing the personal.css file as discussed above
128 6 Middleware, Services, Intro to Dependency Injection
Services are very important, and they will make more sense later when we get to solve
real problems with them. In here we want to give a brief introduction to services since
they are also using the Program.cs file. Feel free to skip this section if you wish and be
sure to revisit this section again when you encounter services.
Services are essentially classes that can be reused easily in multiple locations without
having to worry about instantiating them and their various dependencies.
One example of a service that we’ll see later will be the class that will be responsible
for connecting to a database. We won’t use multiple instances of such an object (we only
need one object connecting to the database), but we may need access to (we’ll essentially
reuse the same one instance of) this class from various parts of the web application. We’ll
get access services (to these instances) using a process called dependency injection.
This is facilitated via a technique known as Dependency Injection. The Dependency
Injection is (a factory) responsible for creating instances of the dependencies when they
are needed and disposing of them when they are no longer needed. To register a service
with the Dependency Injection, we’ll make use of the builder.Services (we’ll see
an example below). Read more about Dependency Injection in here [50].
Let’s see an example (inspired by the example in [51]). We’ll see more useful services
in the upcoming chapters, this is just to demonstrate the steps involved in creating and
using a service.
Any class that implements any interface can act as a service. Let’s create a sample
interface and a class that implements it (you can place them anywhere in your project,
we created a Services folder and put them in there):
rwdnke"kpvgthceg"KO{HktuvUgtxkeg"
}"
""""uvtkpi"O{Ogvjqf*+="
Ä"
"
"
rwdnke"encuu"O{HktuvUgtxkeg"<"KO{HktuvUgtxkeg"
}"
""""rwdnke"uvtkpi"O{Ogvjqf*+"
""""}"
""""""""tgvwtp"&$jcuj"qh"ewttgpv"kpuvceg"qh"o{"ugtxkeg<"}vjku0IgvJcujEqfg*+Ä$="
""""Ä"
Ä"
6.8 Introduction to Services (Optional) 129
The class above is not yet a service. To make it a service, you’ll need to register it as
a service (in Program.cs). One way to register it as a service is as follows (more details
below):
builder.Services.AddSingleton<IMyFirstService,MyFirstService>();
Make sure to add this line before the Build method is called, namely before the line:
Now we have a service. This code above will add the service to the dependency injec-
tion container. We won’t have to worry about creating an instance of the MyFirstService.
The dependency injection will manage its instance.
To use this newly created service, we need one last step. We’ll need to inject the service
where we want to use/access it. For completeness of this example, below we’ll see how
we can inject a service in a middleware component. But in later chapters, we’ll see how
easily (easier than this example) they can be injected into controllers and views.
Change the middleware component.
crr0Twp*cu{pe"*eqpvgzv+"?@"
}"
""""cyckv"eqpvgzv0Tgurqpug0YtkvgCu{pe*$Ygneqog"vq"qwt"ygd"crrnkecvkqp$+="
Ä+=""
crr0Twp*cu{pe"*eqpvgzv+"?@"
}"
""""xct"o{Ugtxkeg"?"crr0Ugtxkegu0IgvTgswktgfUgtxkeg>KO{HktuvUgtxkeg@*+="
""""cyckv"eqpvgzv0Tgurqpug0YtkvgCu{pe*o{Ugtxkeg0O{Ogvjqf*++="
Ä+="
You should note that all display the same hash value, an indication that there is
(probably) only one object used for both requests.
AddSingleton is used when you want to create one instance of the service for
the web application’s lifetime. So as long as you don’t restart your web application, all
requests will make use of the same one instance of the service.
An alternative to AddSingleton is AddTransient. Use AddTransient if you
want the dependency injection to create a new instance of the service every single time
the service is injected (in particular, every time you click on a link, a button, or refresh
a page). To test this, replace AddSingleton with AddTransient in your code and
run again your web application as seen above. Here is what we obtained:
Our journey into the ASP .Net Core MVC development starts here. In this chapter, we’ll
focus on routing, but we’ll also see a first example of controllers and other related topics.
In particular, we’ll see two types of routing, conventional routing and attribute rout-
ing. Routing gives us the developers complete control over the URLs used in our web
application. In particular, this could be helpful for search engine optimization (SEO).
Important note: As we noted at the beginning of the book, the MVC design pattern
emphasizes separation of concerns, by considering three major components: models,
views, and controllers. We’ll get a good understanding of these components as we go
through this book, but please have patience until we finish this and the next two chapters.
Some concepts (such as routing and models) should make complete sense in this chapter,
while others (such as actions, controllers, and views) are only introduced in here, but
will make more sense as we go through this and the next two chapters. We’ll see several
examples, and by the end of Chap. 9, you should have a good grasp of the MVC design
pattern.
Because we’re introducing several new concepts, some in more details in the following
chapters, this chapter may be a little confusing at first. It will get better once we cover
more details in the subsequent chapters.
Before we proceed, let’s clean up our project a little bit. In Program.cs delete all lines of
code except for the following:
© The Author(s), under exclusive license to Springer Nature Switzerland AG 2024 131
R. A. Mezei, Introduction to the Development of Web Applications Using
ASP .Net (Core) MVC, Synthesis Lectures on Computer Science,
https://doi.org/10.1007/978-3-031-30626-6_7
132 7 Routing, Models, and Controllers
xct dwknfgt"?"YgdCrrnkecvkqp0EtgcvgDwknfgt*ctiu+=
11ugv"wr"okffngyctg"eqorqpgpvu0
xct crr"?"dwknfgt0Dwknf*+=
crr0WugUvcvkeHkngu*+="11pggfgf"vq"ikxg"ceeguu"vq"hkngu"kp"yyytqqv
crr0Twp*+=
Optionally, you can also remove the following file and folder which we won’t use
anymore in this book:
If you recompile and run your code, you should get the HTTP ERROR 404. This should
be expected, we do not have anything in the middleware pipeline to answer the HTTP
requests from clients.
Let’s have a very brief review of some essential MVC concepts. We’ll go into more details
in this and the next two chapters.
The models are the classes that represent the various types of objects managed by the
web application. These objects represent the state of the application.
. For example (think about a web application such as Amazon, Canvas, Moodle,
…), we could have the following model classes: Course, User, Student, Instructor,
Administrator, Product, Seller, Buyer, and so on.
The views will make up the user interface. We’ll present content to users via views (more
accurately, we’ll use views to build webpages that ultimately will get displayed in a user’s
browser). We’ll see more about this later.
. For example (think about a web application such as Amazon, Canvas, Moodle, …),
we can have a view that will be used to display a list of all courses taken by a student,
or a list of all laptops available to buy. We could use another view to build a page that
allows our users to change their passwords.
7.2 Some Essential MVC Concepts and the HTTP Request Lifecycle 133
The controllers will handle the user interaction. We’ll see them below.
. For example (think about a web application such as Amazon, Canvas, Moodle, …).
What happens when you click on a button? Or click on a link? Or load the first
welcome page? In each of these, your requests will (eventually) be sent to a Controller
(more specifically to an Action from that Controller). In many cases, the Controller
will create an instance of a Model, then pass it to a View to build a page that will
eventually show up in the user’s browser.
Now, let’s see the request lifecycle (see Fig. 7.1). This is just an introduction, so you get
some sense of what we’re dealing with. We’ll get into more details below. What happens
when a user requests a page (either types in a URL, or clicks on a link or button):
Step 1: The user sends an HTTP request.
Fig. 7.1 Shows the main components of an MVC web application. In particular, the client side uses
a browser (HTML, CSS, and JavaScript), then on the server side we have the middleware pipeline,
controllers, models, and views, and lastly, the data may be stored in a database
134 7 Routing, Models, and Controllers
. The URL routing determines which controller & action will handle the HTTP
request.
. If we assume the default routing, presented below, the InstructorController will be
instantiated.
. If we assume the default routing, a Show action will be called by the controller.
. A model binder helps us determine the values passed to the action as parameters (e.g.,
1).
. If needed, the action may create a new instance of a model class. In our example an
Instructor object.
. Typically, the action will pass the model object to a view to display the requested
results.
Step 4:
. We’ll make use of views to produce the output that is sent back to the client’s browser.
By the end of this and the next two chapters, you should completely understand the steps
described above. For now, they are just meant to provide a map of what we’re dealing
with.
As we will see below, routing is one middleware component that will send requests (route
them) to actions in controllers. Since we didn’t yet see what actions and controllers are,
think of routing as the middleware component that will send incoming HTTP requests
(only those that follow a specific format) to the MVC part of our web application.
Routing gives us, the developers, full control over the format of the URLs in our web
application. It lets us describe what URL paths will be matched to what actions. As we
will see later (when we cover tag helpers and HTML helpers), routing is also used when
generating URLs for various links and buttons—if we change the routing, the (tag and
html) helpers will generate different links that conform to our prescribed routing.
7.3 Introduction to Routing 135
To add the MVC framework to our ASP .Net Core web application, we’ll need the
following two lines of code:
dwknfgt0Ugtxkegu0CffEqpvtqnngtuYkvjXkgyu*+="11cffu"ugtxkegu"pggfgf"hqt"eqpvtqnngtu"
crr0WugTqwvkpi*+=""11cffu"tqwvg"ocvejkpi"vq"vjg"okffngyctg"rkrgnkpg
xct crr"?"dwknfgt0Dwknf*+=11ugv"wr"okffngycvg"eqorqpgpvu
crr0WugUvcvkeHkngu*+="11pggfgf"vq"ikxg"ceeguu"vq"hkngu"kp"yyytqqv
crr0WugTqwvkpi*+="11cffu"tqwvg"ocvejkpi"vq"vjg"okffngyctg"rkrgnkpg
crr0Twp*+=
Let’s make sense of this default routing. In here, we have the following:
. A name that must be present (and distinct from other route names), but it is not used
in the routing itself.
. A pattern that describes what requests it needs to match.
– We can have multiple routes. Above is just one, we’ll see more below.
– HTTP requests that do not match this pattern will be ignored by this route.
– In our program so far, if a request does not match the route, we’ll get the HTTP
404 error.
The pattern above has three segments (each segment is described in a pair of {})
separated by /.
This will make more sense once we add a controller and see it in action. Please be patient.
It will make complete sense soon.
With the default routing described above:
. The link: http://localhost:5125/ will send our HTTP request to the controller Home-
Controller, action Index, and there is no specified Id.
. The link: http://localhost:5125/Home will send our HTTP request to the controller
HomeController, action Index, and there is no specified Id.
. The link: http://localhost:5125/Instructor will send our HTTP request to the controller
InstructorController, action Index, and there is no specified Id.
. The link: http://localhost:5125/Home/SecondAction will send our HTTP request to the
controller HomeController, action SecondAction, and there is no specified Id.
. The link: http://localhost:5125/Instructor/ListAll will send our HTTP request to the
controller InstructorController, action ListAll, and there is no specified Id.
. The link: http://localhost:5125/Instructor/Display/10 will send our HTTP request to the
controller InstructorController, action Display, and the Id = 10 (we should use an
Id parameter for the Display action).
. The link: http://localhost:5125/Instructor/Display/10/20 does not match the route
above. The default routing uses up to three segments, but we sent four.
For the name, type Controllers. To create a new controller, right-click on the
Controllers folder then select Add > Controller …
In the Add New Scaffolded Item window that opens, select MVC Controller—Empty
(we’ll talk about the other options later) then click on the Add button. In the new window
that opens, for the name field, enter HomeController.cs (make sure to have the correct
spelling!).
Congratulations. You just created your first controller!
To test this code, change the Index method (it’s called an action) to match the
following:
public IActionResult Index()
{
return Content("Hello World from Index action, HomeController!");
}
Now we can finally run our web application again. Run your application. You should
get (URL: localhost:5125).
For example, when working with Instructor data (model), what can we do? We
can add a new instructor, delete an existing instructor, edit an existing instructor, show
details/display an instructor, and maybe list all instructors. These are (related) actions that
we can put together in one controller class, in here called InstructorController.
Similarly, for User data (model), we have an AccountController that allows us
(by means of actions) to register a new user account, login a user, or logout a user.
IMPORTANT: All controller classes must reside in the project’s root-level Controllers
folder.
We’ll see much more on actions soon; this chapter merely introduces actions and
controllers.
Inside the HomeController class, let’s add a second action. This time we’ll add a
very simple public method:
Using the default routing discussed above, how can you call this action?
The following URLs:
http://localhost:5125/
http://localhost:5125/Home/
http://localhost:5125/Home/Index
They all call the Index action from the HomeController.
To call our SecondAction method, we need to use.
http://localhost:5125/Home/SecondAction
We obtained the following in the browsing window: (0)^2 = 0.
Or better yet we can also pass a value for the id:
http://localhost:5125/Home/SecondAction/4
We obtained the following in the browsing window: (4)^2 = 16.
Note: If you don’t pass an Id, and one is needed by the action, the model binder will
use the default value (for integers that is 0).
Another note: One can also use query strings to pass values. The model binder is clever
enough to use them (we’ll see model binder later):
http://localhost:5125/Home/SecondAction?id=10.
We obtained the following in the browsing window: (10)^2 = 100.
7.4 Add a Model, a Controller, and Views 139
What happens when you use any of the following URLs? Why?
http://localhost:5125/Home/SecondAction/4/5
http://localhost:5125/SecondAction/
http://localhost:5125/Home/SecondAction?number=10.
In here we’ll add a more meaningful example of controller and actions. We’ll also add a
model as well as some views.
Let’s start with creating a model class, let’s say a Student class.
First, create a new folder named Models in the root of the project (use the Solution
Explorer window). Then, inside this folder, add a new one called Student.
When you think of a student, what characteristics would each student have? Below is
just a set of characteristics we’ll use for this demonstration, but feel free to add more. We
will add the following (properties) in our model class:
Above, we tried to include multiple types for our properties, such as integers, strings,
Booleans, and an enumeration.
That’s pretty much it for a model. Easy, right? We’ll add more to this soon, but for
now, you should feel quite comfortable defining model classes. They are just POCO
(plain old CLR object) classes that you have seen also in Chap. 5, where we reviewed
some fundamental concepts in C#.
140 7 Routing, Models, and Controllers
We’ve seen above how we can add a controller class. Let’s add a more meaningful one
in here. This one will work (in conjunction) with the model class created above.
Let’s first decide what actions we would like to be able to perform on this model. Let’s
say we would like to.
We put all these actions into one class, a controller class, and we’ll name it
StudentController. It is typical for controller classes that work with a model, say
called XYZ, to be named XYZController. To create our controller, right-click on the
Controllers folder (inside the Solution Explorer window) and select Add > Controller ….
And just like we’ve seen of the first controller, select MVC Controller - Empty, then
name it StudentController.cs. Please double-check your spelling for the name of this class.
In this class, delete the default Index action, and add the following rather simple
actions:
public IActionResult Index()
{
return Content("Student Controller\nTO DO: display a list of student in here");
}
Above, make sure to add the appropriate using directive for the Student class.
using ASPBookProject.Models;
One way to pass data from an action to a view is by making use of the dynamic object
ViewBag. ViewBag is a dynamic wrapper of the ViewData dictionary, so we could
use either one, but in this book, we’ll only use the ViewBag.
One way to add data to the ViewBag is to use the dot notation. Since it is a dynamic
object, you’ll get no IntelliSense support so just be careful on the names you’re using. In
the example above, we used ViewBag.student. Instead of student, you could use
any identifier of your choice.
Later we’ll see better alternatives to ViewBag, (we’ll use strongly typed views) but
this is simpler to use for now.
The simplest way to add a view for an action is to right-click anywhere inside the action
(for us, this is inside the ShowDetails method) and select Add View …:
Make sure to add a view for the ShowDetails action, not the Index action. Then,
in the Add New Scaffolded Item window that opens, select Razor View (not Razor View—
Empty!), and click on the Add button.
Then, just confirm that the view’s name matches the action name, ShowDetails,
and uncheck all the options (we’ll learn about them later). Then click on the Add button.
You should note that Visual Studio created a new folder, called Views. Inside it, it
created a subfolder named Student (from the name StudentController), and inside
it, you have a new file, for the newly created view (check this out using the Solution
Explorer window).
Once the view file is created, you should see the following starting code in it:
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>ShowDetails</title>
</head>
<body>
</body>
</html>
142 7 Routing, Models, and Controllers
Does this look familiar? Except for the first three lines (which you can ignore for now),
what you have there is pretty much the basic HTML template we used in the first few
chapters of this book. You should feel in a familiar territory.
Let’s add some code to this view, so it displays the student that was passed via
the ViewBag object. Change the contents of the ShowDetails.cshtml file to match the
following (we’ll see views in more depth in the next chapter!):
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Showing Details of a Students</title>
</head>
<body>
<h1>Showing details of student: @ViewBag.student.LastName, @ViewBag.student.FirstName</h1>
<p>Major: @ViewBag.student.Major</p>
<p>Is veteran: @ViewBag.student.IsVeteran</p>
<p>GPA: @ViewBag.student.GPA </p>
<p>Admission date: @ViewBag.student.AdmissionDate</p>
</body>
</html>
Compile and run the project. The landing page (based on the default constructor) should
be the Index action of the HomeController. The browsing window should display:
Either of the following links should give you the same result as above:
http://localhost:5125
http://localhost:5125/Home
http://localhost:5125/Home/Index
Then, use the following URL: http://localhost:5125/Student
Note: The Student in the URL refers to the StudentController, not the
Student model.
Now let’s call the Index action of the StudentController class. What URL
would you use?
Any of the following should work (with the default routing we have set up so far):
http://localhost:5125/Student
http://localhost:5125/Student/Index
We obtained:
7.5 Various Action Result Types 143
Fig. 7.2 Shows the result of calling the ShowDetails action (we see the view displayed in a browser)
when id is set to 20, or no value is provided in the URL request
Student Controller
TO DO: display a list of students in here
Now let’s call the ShowDetails action. Run both of the following URLs. Can you
explain the results in each case (see Fig. 7.2)?
http://localhost:5125/Student/ShowDetails
http://localhost:5125/Student/ShowDetails/20
And for http://localhost:5125/Student/ShowDetails/10 (see Fig. 7.3).
Our web application is very simple, but this example helped us get a quick view of
models, views, and controllers. We’ll see them in more depth in the upcoming chapters.
And starting with Chap. 11, we’ll grab/load our data from a database.
their return type, in both cases, was declared as IActionResult. Let’s explain this in
more depth.
Our actions can have various return types. Here is a list of common ones (although we
will mostly focus on ViewResult for the remainder of this book).
All these types enumerated here are implementing the IActionResult interface. If you
hold the Ctrl key and while doing so, also click on IActionResult, Visual Studio will
open the IActionResult.cs that contains the following definition of this interface:
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Threading.Tasks;
namespace Microsoft.AspNetCore.Mvc
{
/// <summary>
/// Defines a contract that represents the result of an action method.
/// </summary>
public interface IActionResult
{
/// <summary>
/// Executes the result operation of the action method asynchronously. This method is
called by MVC to process
/// the result of an action method.
/// </summary>
/// <param name="context">The context in which the result is executed. The context
information includes
/// information about the action that was executed and request information.</param>
/// <returns>A task that represents the asynchronous execute operation.</returns>
Task ExecuteResultAsync(ActionContext context);
}
}
In our project, all actions are declaring this return type instead of the class type they
are returning. That is, we use.
7.6 Conventional Versus Attribute Routing 145
{
return Content("Student Controller\nTO DO: display a list of student in here");
}
which is simpler than using different return types for each action:
public ContentResult Index()
{
return Content("Student Controller\nTO DO: display a list of student in here");
}
On your own, you may want to add the following actions to the StudentController
class and test them:
public IActionResult GoToGoole()
{
return Redirect("https://www.google.com/");
}
For the remaining chapters of this book, we will mostly use the default routing; for this
reason, we’ll keep this section short. But we thought it would be good for you to know a
little more about routing, to better understand it. In here we’ll compare the conventional
routing against the attribute routing. Check out the main source for this chapter [54].
146 7 Routing, Models, and Controllers
In this code, we used the MapControllerRoute to add one single route. One can
add more than one route (see the example below). Defining routes this way, in one central
location, the Program.cs file is called conventional routing.
We already explained the route given above, but now that we have seen a few exam-
ples, this should make more sense. Let’s review it once more. The route uses three path
segments, separated by /, and described in the pattern parameter. It has the following:
When can add more than one route. In this case, each route has a higher priority for
matching than the subsequent ones. Therefore, the order in which routes are declared is
very important. If we have URLs that match multiple routes, the first route matching our
URL will be used.
Note: There are many ways to declare routes, we’ll just give here one more route.
Immediately after the line UseRouting already in the Program.cs (so right before
the default route) let’s add the following:
app.MapControllerRoute( //adds a second route
name: "secondroute",
pattern: "Display/{id?}",
defaults: new { controller = "Student", action= "ShowDetails" });
The name parameter is required that must be distinct from the names of the other
routes, but it has no impact on URL matching. It is only used internally when URLs are
generated.
Next, let’s explain the pattern parameter given above, then test it. This pattern
has two parts:
7.6 Conventional Versus Attribute Routing 147
Fig. 7.4 Shows the result of the HTTP request for the ShowDetails action using “secondroute” route
. Display—since this is not inside {}, URLs must contain the exact word (case
insensitive) to match the route.
. {id?}—since this contains {}, the value will represent an id. This segment is
optional because we have ?.
To match this route, your HTTP requests must look like this /Display or
/Display/somevalue. For example,
http://localhost:5125/Display
http://localhost:5125/Display/10
http://localhost:5125/Display/20
Which code (controller and action) will handle these requests? The defaults
parameter sets the request to be handled by the ShowDetails action from
StudentController.
In particular, note that the following HTTP request, which is handled by different
routes, will yield the same results (see Figs. 7.4 and 7.5):
http://localhost:5125/Display/10
http://localhost:5125/Student/ShowDetails/10.
One can use a “catch-all” route, but we will later use a friendly error page instead.
Therefore, for the purposes of this book, you may not want to add it to your routes. We
put it here just for completeness (it should be added last in the list of routes):
app.MapControllerRoute( //adds a catch-all route
name: "catch-all",
pattern: "{*anything}",
defaults: new { controller = "Home", action = "Index" });
Fig. 7.5 Shows the same result as Fig. 7.4, but using a different (the default) route
After this chapter, we will not be using attribute routing. We only include them here for
completeness, and because we found them very easy to use, you may use them if you
go beyond the concepts covered in this book and learn about Web API. The reason why
we don’t recommend them for large MVC applications is that such routes are distributed
across multiple (controller) files, and hence they can quickly get out of control—especially
if you have multiple teams doing development of various controller classes.
Here is a good comparison between conventional routing and attribute routing: “The
conventional default route handles routes more succinctly. However, attribute routing
allows and requires precise control of which route templates apply to each action” [54].
Let’s see some examples (see more in [54]). We’ll add attribute routes to the
StudentController class.
If we run our web application, right now the HomeController, Index action is the
default page (see Fig. 7.6):
Let’s add the following attribute to our Index action: [Route("")] . Our action now
looks as follows:
[Route("")]
public IActionResult Index()
{
return Content("Student Controller\nTO DO: display a list of student in here");
}
Fig. 7.6 Shows the default page for our application is currently coming from the Index action in
HomeController
7.6 Conventional Versus Attribute Routing 149
Fig. 7.7 Shows the default page for our application is now coming from the Index action in Stu-
dentController
Rebuild and run your web application. This made the Index action of the
StudentController the default page look similar to Fig. 7.7.
Let’s add a second and a third route to the same action. These are essentially alternative
routes (one can use either route to call our action):
[Route("")]
[Route("second")]
[Route("third/fourth")]
public IActionResult Index()
{
return Content("Student Controller\nTO DO: display a list of student in here");
}
Now, we can access this page using any of the following URLs:
http://localhost:5125/
http://localhost:5125/second
http://localhost:5125/third/fourth.
IMPORTANT: Following HTTP requests will not get routed to our action from
Student controller by the default routing:
http://localhost:5125/third/
http://localhost:5125/Student/Index (we’ll explain this one in the next subsection).
One can use token replacement for action and controller names. For example, we can
add the following attribute routes to our actions inside StudentController:
public class StudentController : Controller
{
[Route("TestMe/[controller]/[action]")]
public IActionResult Index()
{
return Content("Student Controller\nTO DO: display a list of student in here");
}
[Route("TestMe/[controller]/[action]/{id?}")]
public IActionResult ShowDetails(int id)
{
// ...
In these attribute routes we added above, the class name will be used for
[controller], and the action name for [action]. In particular, to access these
actions, we’ll need to use URLs as the ones below (see Figs. 7.8 and 7.9):
http://localhost:5125/TestMe/Student/Index
http://localhost:5125/TestMe/Student/ShowDetails/10.
150 7 Routing, Models, and Controllers
Fig. 7.8 Shows how the attribute routing can be used to call on the Index action from StudentCon-
troller
Fig. 7.9 Shows how the attribute routing can be used to call on the ShowDetails action from Stu-
dentController
Note that the routes have a lot of repeated code. We can improve our code by applying
the repetitive part of the route at the class level, so we don’t have to copy and paste it
for each action. Here is how that code would look like (the links above would work the
same):
[Route("TestMe/[controller]/[action]")]
public class StudentController : Controller
{
[Route("")]
public IActionResult Index()
{
return Content("Student Controller\nTO DO: display a list of student in here");
}
[Route("{id?}")]
public IActionResult ShowDetails(int id)
{
// ...
There is much more to say about attribute routing, but we won’t be using it in our
book, so we’ll skip the other details. Check out [54] for more examples.
We’ve seen above that we can use both conventional and attribute routing in the same
project.
7.6 Conventional Versus Attribute Routing 151
Fig. 7.10 Shows you that requesting the Index action from StudentController will not give you
the expected results. This is because, in this example, we mixed attribute routing with conventional
routing for the same Controller class
When should we use either one? “It’s typical to use conventional routes for controllers
serving HTML pages for browsers, and attribute routing for controllers serving REST
APIs” [54].
IMPORTANT: In the same web application one can use both attribute and conventional
routing. But they should not both apply to the same controller class. “Actions that define
attribute routes cannot be reached through the conventional routes” [54].
In particular (see Fig. 7.10), http://localhost:5125/Student/Index will not give you the
Index action from the StudentController.
For the remaining part of this book, we’ll only use conventional routing.
More on Controllers and Views, Introduction
to Razor Syntax
8
In this chapter, we’ll create a new model, and a new controller, and learn more about
views. In particular, we’ll introduce in here the Razor syntax and some tag helpers. By
the end of this chapter, you should have a good understanding of controllers and be fairly
comfortable with views. We’ll see views in more depth in the next chapter.
Before we continue with this chapter, let’s simplify our code. Let’s remove all attribute
routes from our StudentController class. If you wish, you may also remove all
conventional routes except for the default route.
We make these changes so it becomes easier to debug our project in case we get any
errors along the way. You may choose to disregard this, which is fine. Here is how our
Program.cs file looks like after these changes:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews(); //adds services needed for controllers
app.Run();
Next, let’s quickly review the main MVC concepts (you should become more familiar
with these as we go through this and the next chapter).
© The Author(s), under exclusive license to Springer Nature Switzerland AG 2024 153
R. A. Mezei, Introduction to the Development of Web Applications Using
ASP .Net (Core) MVC, Synthesis Lectures on Computer Science,
https://doi.org/10.1007/978-3-031-30626-6_8
154 8 More on Controllers and Views, Introduction to Razor Syntax
The models are the classes that represent the various types of objects managed by the
web application.
. We’ll use views to build webpages that ultimately will get displayed in a user’s browser.
. Typically, views will display our model data.
. For example (think about a web application such as Amazon, Canvas, Moodle, …),
we can have a view that will be used to display a list of all courses taken by a student,
or a list of all laptops available to buy. We could use another view to build a page that
allows our users to change their passwords.
. For example (think about a web application such as Amazon, Canvas, Moodle, …).
What happens when you click on a button? Or click on a link? Or load the first
welcome page? In each of these, your requests will (eventually) be sent to a Controller
(more specifically to an Action from that Controller).
. In many cases, the Controller will create an instance of a Model, then pass it to a View
to build a page that will eventually show up in the user’s browser.
Now let’s see again the HTTP request lifecycle (see Fig. 8.1). You should have a much
better grasp of it since we covered routing in the last chapter. What happens when a user
requests a page (either types in a URL, or clicks on a link or button)? Below, let’s assume
the default routing has been set up.
8.2 Some Essential MVC Concepts and the HTTP Request Lifecycle (Revisited) 155
Fig. 8.1 Shows various components of an MVC web application. In particular, note the middleware
pipeline, routing, static files, controllers, models, and views
. The URL routing determines which controller & action will handle the HTTP
request.
. In our case, the StudentController will be instantiated.
Step 4:
. We’ll make use of views to produce the output that is sent back to the client’s browser.
Notes:
In here we’ll add another model, controller, and corresponding views. But this time we’ll
go in more depth with the MVC. In particular, we’ll focus more on views, and we’ll
introduce the Razor syntax (which essentially allows us to embed C# code inside views).
Let’s start by adding a new model. For this example, we’ll create a new class called
Instructor. Make sure to add this new class inside the Models folder (in the Solution
Explorer window, right-click on the Models folder then select Add > Class…).
Next, we need to choose what characteristics to add to this class. In our example below,
we added the following:
Feel free to add other properties too. The ones we added above should be sufficient to
demonstrate the topics we want to cover in this book.
Here is the code we added to Instructor.cs:
public enum Ranks { Adjunct, Instructor, AssistantProfessor, AssociateProfessor,
}; FullProfessor
We hope that by now you feel very comfortable with creating model classes. We’ll see
more exciting things about them later, but what we have so far should be sufficient for
now.
namespace ASPBookProject.Controllers
{
public class InstructorController : Controller
{
public IActionResult Index()
{
return View();
}
}
}
Inside the InstructorController class (we’ll add this right before the action
method), let’s create a List of Instructors with some hard-coded data. Make sure to add
the necessary using statement. Here is an example of what we added (feel free to add
more data):
List<Instructor> InstructorsList = new List<Instructor>()
{
new Instructor() {InstructorId = 100,
FirstName = "Maegan", LastName = "Borer",
IsTenured=false, HiringDate=DateTime.Parse("2018-08-15"),
Rank = Ranks.AssistantProfessor},
Note: In class we typically ask students to help us provide some sample data. This is
more fun this way and it provides another opportunity for students’ engagement. Here we
used an online sample name generator (see, for example [55]).
IMPORTANT: Above that several actions have the same return View(); state-
ment. As we will see below, they will actually return different views/results. In here we
make use of the convention over configuration. In particular,
. the return View(); statement from Edit action will return the Edit view;
. the return View(); statement from Add action will return the Add view.
The view files used are Razor view files (also called Razor-based view templates). These
files have extension .cshtml and will contain both C# and HTML code. The Razor engine
will use the C# and HTML code from a view to render the corresponding HTML content
(HTML response) that will be sent back to the client who made the request (and will
ultimately be displayed in a browser). To read more about views, see also [56].
On a side note, the above actions should remind you of the CRUD operations, often
seen in a Database course. CRUD stands for the following (see more here: [57]):
160 8 More on Controllers and Views, Introduction to Razor Syntax
// GET: TestController/Details/5
public ActionResult Details(int id)
{
return View();
}
// GET: TestController/Create
public ActionResult Create()
{
return View();
}
// POST: TestController/Create
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(IFormCollection collection)
{
try
{
return RedirectToAction(nameof(Index));
}
catch
{
return View();
}
}
// GET: TestController/Edit/5
public ActionResult Edit(int id)
{
return View();
8.4 The Index Action and View 161
// POST: TestController/Edit/5
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(int id, IFormCollection collection)
{
try
{
return RedirectToAction(nameof(Index));
}
catch
{
return View();
}
}
// GET: TestController/Delete/5
public ActionResult Delete(int id)
{
return View();
}
// POST: TestController/Delete/5
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Delete(int id, IFormCollection collection)
{
try
{
return RedirectToAction(nameof(Index));
}
catch
{
return View();
}
}
}
This section will introduce many new and important concepts. We’ll revisit them, in
subsequent sections and chapters.
We would like to define this action to be used for requests that will ultimately display (in
the view) a list of instructors. For teaching/demonstration purposes, we’ll actually use a
table instead of a list.
162 8 More on Controllers and Views, Introduction to Razor Syntax
As seen in the previous chapter, the easiest way to add a corresponding view is to
right-click anywhere in the action and select Add View …. Then select the Razor View
option (for this example do not select Razor View—Empty option):
Make sure the name matches the action name (Index for us) and make sure all
Options are unchecked. Then click on the Add button.
IMPORTANT: You should note where this newly created view was added—it was
added inside the Instructor folder, which is inside the Views folder (see the Solution
Explorer windows).
All views are under the Views folder, inside a subfolder that matches the controller’s
name.
. All views for the InstructorController will be created under the Views folder,
Instructor subfolder.
. All views for the StudentController will be created under the Views folder,
Student subfolder.
Here are the contents of the Index view that were added automatically by the View
template:
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Index</title>
</head>
<body>
</body>
</html>
To see this view, run the application and use the following URL in a browser (see
Fig. 8.2):
http://localhost:5125/Instructor/Index
8.4 The Index Action and View 163
Fig. 8.2 Shows the Index view from InstructorController displayed in a browser
When you use the URL above and press enter to send an HTTP request to the server,
based on the default routing (which we set up in our project), your request will be sent
to the InstructorController, Index action. Based on the code above, the action
returns the Index view, which contains <h1> TO DO: add a list/table of
instructors </h1>.
Before we add more code to this view, let’s introduce two important topics.
In the previous chapter, we saw how to use the dynamic object ViewBag to pass infor-
mation from an action to its view. In here we’ll see a better (when appropriate) approach,
namely strongly typed views.
A view can be
. strongly typed—if it has a @model declaration at the top of the view page.
– The @model will declare the type of object this view works with.
– A view can work with one instance of a model: @model
ASPBookProject.Models.Instructor
– A view can work with a collection of instances of a model: @model
IEnumerable <ASPBookProject.Models.Instructor>
In this case, we’ll use @foreach to iterate through the collection
– Important: You cannot have more than one @model directive in a view!
. dynamically typed—if it does not have a @model declaration at the top of the view’s
page.
– We use this type if the view does not work with any model or
– We use this type if the view needs to work with more than one model.
– Important: Before using the model inside the view, you’ll need to check it is not
null!
Going back to our example, we would like to pass the InstructorsList to the view
to display it. For this, we do the following:
. Inside the Index action, make sure to pass this as a parameter to the View method.
– Replace return View(); with return View(InstructorsList);
164 8 More on Controllers and Views, Introduction to Razor Syntax
. Inside the Index view, at the top of the page, we need to add the @model directive
(this makes the view strongly typed):
– @model IEnumerable <ASPBookProject.Models.Instructor>
Here is how the Index action (from InstructorController) looks after the
change:
public IActionResult Index()
{
return View(InstructorsList);
}
In the Index view, you can delete (if you wish—it will make no difference, we’ll
explain this later).
@{
Layout = null;
}
Here is how our Index view looks after the change specified above:
@model IEnumerable<ASPBookProject.Models.Instructor>
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Index</title>
</head>
<body>
<h1>TO DO: add a list/table of instructors</h1>
</body>
</html>
Now we would like to add code to our Index view, so it displays information about
all our instructors. As a starting point, let’s add the following table inside the body of the
Index view, right after the <H1> element:
<TABLE>
<THEAD>
<TR>
<TH>Course ID</TH>
<TH>Course name</TH>
<TH>Course link</TH>
</TR>
</THEAD>
<TBODY>
<TR>
<TD>CSC200</TD>
<TD>Object Oriented Programming</TD>
<TD><a href="https://www.stmartin.edu/">Course link</a></TD>
</TR>
<TR>
<TD>CSC340</TD>
<TD>Data Structures and Algorithms</TD>
<TD><a href="https://www.stmartin.edu/">Course link</a></TD>
</TR>
<TR>
<TD>CSC495</TD>
<TD>ST ASP .Net Core MVC</TD>
<TD><a href="https://www.stmartin.edu/">Course link</a></TD>
</TR>
</TBODY>
</TABLE>
Then rebuild your web application and run it. It should display the following default
page, similar to Fig. 8.3 (URL: localhost:5125/Instructor/Index):
Next, let’s modify this table to use the following table headers: First name, Last name,
and Rank. For this, replace the lines:
Fig. 8.3 Shows the Index view from InstructorController displayed in a browser, now containing a
table
166 8 More on Controllers and Views, Introduction to Razor Syntax
<TH>Course ID</TH>
<TH>Course name</TH>
<TH>Course link</TH>
with
<TH>First name</TH>
<TH>Last name</TH>
<TH>Rank</TH>
To display all values from the list of instructors, we’ll need to use C#. This is where
Razor comes in handy.
“Razor is a markup syntax for embedding .NET based code into webpages” (see more
in [58]). Therefore, using Razor syntax, we can embed C# code in our (Razor) views.
Then, a mechanism called Razor Engine will go through the view and run the C# and
HTML code, giving us only HTML content which is what we send as a response to the
client’s request.
Razor uses the @ symbol to transition from HTML to C#. For the transition from C#
back to HTML there is no symbol, Razor will (typically correctly) infer where this needs
to be done. These are called implicit Razor expressions and an example of it is the
following:
@DateTime.Now
When the implicit expressions aren’t correctly interpreting a Razor expression, we can
use explicit Razor expressions by making use of parentheses @(), for example,
@(DateTime.Now - TimeSpan.FromDays(7)).
For Razor code block we use curly braces, @{}. We’ll see examples below. Just like
C#, Razor supports.
. Conditionals: @if, else if, else, and @switch. For example (can you guess
what it does?),
@if (User.Identity.IsAuthenticated) //if the user is logged in
{
<li class="nav-item">
<a class="nav-link" asp-action="Logout" asp-controller="Account">Logout</a>
</li>
}
else
{
<li class="nav-item">
<a class="nav-link" asp-action="Login" asp-controller="Account">Login</a>
</li>
}
. Looping: @for, @foreach, @while, and @do while. We’ll see an example of this
below.
. Comments: @* … *@.
. C# comments (// and /*…*/) are also supported.
8.4 The Index Action and View 167
We’ll see more examples as we go through this book. Now let’s use this information to
build our table of instructors for the Index view. Go to the Index.cshtml file. Inside the
<TBODY> element we have three <TR> elements (one for each row). We would like to
replace those three rows with code that generates one row for each instructor from the
InstructorsList.
Some general notes:
. The Index view is strongly typed: it has the following @model directive:
. @model IEnumerable<ASPBookProject.Models.Instructor>.
. The InstructorsList was passed from the action to this view, and inside the
view, we refer to this list as Model.
. IMPORTANT: We use uppercase M in our view, except for the @model directive
where we use lowercase m.
. The Model represents the list, and we can use the dot notation to access its values.
Now, replace the <TBODY> element (and all its contents) with the following.
<TBODY>
@foreach (var instructor in Model)
{
<TR>
<TD>@instructor.FirstName </TD>
<TD>@instructor.LastName</TD>
<TD>@instructor.Rank</TD>
</TR>
}
</TBODY>
<h1>All instructors</h1>
Then rebuild your application and run it, check out the results (URL: localhost:5125)—
they should look similar to Fig. 8.4.
We’ll see several more examples (involving the Razor syntax) below, so please be
patient.
We would like to conclude this part with the following two brief examples.
If you want an action to use/return a view with the same name (for Index action,
to use the Index view), we just used return View(); and later we used return
View(InstructorsList);
168 8 More on Controllers and Views, Introduction to Razor Syntax
In both cases, we did not have to specify to use the “Index” view, the compiler just
knew to use that view. This is an example of convention over configuration.
If instead we want an action (say DisplayAll) to use a view with a different name
(say Index), then we must pass the name as the first argument to the View() method
call. Here is an example (see both actions side by side):
public IActionResult Index()
{
return View(InstructorsList); //will use the Index.cshtml view
}
Fig. 8.5 Same as the image in Fig. 8.4, but we now used a different action to request it (a different
URL)—the view is the same. The URL after the request stayed the same as the URL used in the
request
8.5 The ShowDetails Action and View 169
Fig. 8.6 Same as the image in Fig. 8.4, but we now used a different action to request it (a different
URL)—the view is the same. The URL after the request has changed (was redirected) to a URL
different from the URL used in the request
IMPORTANT: Please note the URL, it shows the action being called is DisplayAll,
although the view used is Index.
The second example is the following. Modify the ShowAll action so its body is as
shown below:
public IActionResult ShowAll()
{
return RedirectToAction("Index", InstructorsList);
}
Below we’ll get to revisit some of the concepts we covered above (namely the
Razor syntax and strongly typed views), then we’ll introduce a few new ones. For the
ShowDetails action, we would like to create a view that nicely displays information
related to one instance of our model (one instance of Instructor).
What should we expect from this action? What should this action do? It should allow us
to provide an instructor id, search (normally in a database) for the instructor that matches
this id, and as a response, display the instructor.
170 8 More on Controllers and Views, Introduction to Razor Syntax
kh *kpuvt"#?"pwnn+"11ycu"cp"kpuvtwevqt"hqwpfA
tgvwtp Xkgy*kpuvt+=
11kh"pq"kpuvtwevqt"ycu"hqwpf"000
tgvwtp PqvHqwpf*+=
Ä
Above, we first used the FirstOrDefault method to search for an instructor whose
InstructorId equals id. Note the lambda expression we used to essentially tell the
FirstOrDefault method how to search for our instructor. It’s a short and elegant
statement, but as we’ll see later, we’ll pretty much use the same code to search in a
database (via the Entity Framework Core) instead of a List, so please spend some extra
time if needed to understand this statement. The returned value will either be a reference
to an instance of Instructor (if one was found for the provided id) or the null
reference (if none was found).
If we found an/the Instructor, we’ll pass that to a view to prepare the client-side
code to be displayed in a browser. Otherwise, we’ll return the NotFound view that is
part of the ASP .Net (we did not implement this ourselves). We’ll see it below when we
test our ShowDetails view.
Next, we’ll implement the ShowDetails view for the ShowDetails action.
We need to create a view for the ShowDetails action. Right-click anywhere in this
action as select Add View …, then follow the same steps as seen above to create a view.
8.5 The ShowDetails Action and View 171
Since the action is passing an instance of Instructor to this view, we should make
our view a strongly typed view by using the model directive:
@model ASPBookProject.Models.Instructor
Note: When using the @model directive, we needed to use the full class name that
has the form: namespace.classname.
Boqfgn"CURDqqmRtqlgev0Oqfgnu0Kpuvtwevqt
@model IEnumerable<ASPBookProject.Models.Instructor>
with
@using ASPBookProject.Models
@model IEnumerable<Instructor>
@using ASPBookProject.Models
Then, you can remove this directive from the Index and ShowDetails view files. You
won’t need to add this directive to any views you create from now on in this project.
172 8 More on Controllers and Views, Introduction to Razor Syntax
More specifically, the Index view should have no @using directive, only the following
directive:
@model IEnumerable<Instructor>
and similarly, for the ShowDetails view, it should only have the directive:
@model Instructor
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Instructor @Model.LastName Details</title>
</head>
<body>
<h1>Instructor @Model.LastName details</h1>
<p>First name: @Model.FirstName</p>
<p>Last name: @Model.LastName</p>
<p>Is tenured: @Model.IsTenured</p>
<p>Academic rank: @Model.Rank</p>
<p>Hiring date: @Model.HiringDate</p>
</body>
</html>
Note the use of Razor syntax (we used @) to embed our C# code inside HTML. To
test it (see Fig. 8.7), use the following two URLs:
http://localhost:5125/Instructor/ShowDetails/200
http://localhost:5125/Instructor/ShowDetails/20
You should get the HTTP ERROR 404—This localhost page can’t be found.
Note: We could have checked for a null reference inside the view (instead of checking
for it in the action) and provided a more friendly output via the ShowDetails view, but
we won’t need this. Later we’ll provide links for existing instructors and a friendly error
page for HTTP error codes.
8.6 A First Look at Tag Helpers and HTML Helpers 173
In this section, we would like to add links to our views, so we provide an easier navigation
for our users. One way to do this is by making use of HTML helpers and/or tag helpers.
They both use the project’s routing to generate links; on your own, we challenge you
to test this by modifying the routes and see how the links created by these helpers are
changed accordingly!
HTML helpers are essentially C# functions that return (or build) HTML code. Here is
an example:
The result of the HTML helper above: it will show up in a browser as click for details.
The HTML code generated by the above HTML helper, using default routing, is:
Instead of HTML helpers one can use tag helpers. Tag helpers “enable server-side code
to participate in creating and rendering HTML elements in Razor files” [59]. Tag helpers
look very similar to HTML code, and in the author’s opinion, they are easier to read as
developers. In this book, we will mostly focus on tag helpers, but we will occasionally
also make use of HTML helpers.
We give below the tag helper equivalent to the HTML helper given above:
The output and HTML code generated is identical to what the HTML helper gave us.
Note how natural this tag helper is. Since we need to create a link, we used the <A>
element (just like we did in Chap. 3, when we covered HTML). The content of this
element (“click for details”) is the text that appears as a link (pretty much what
we’ve seen in Chap. 3). But, thanks to tag helpers, the <A> element has two “attributes”
(which are not HTML, these are part of the tag helpers) we used:
Very important: To use tag helpers, you will need to add the following directive:
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
either
Before you proceed, please add this directive into your ViewImports.cshtml. Here are its
contents now:
@using ASPBookProject.Models
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
We will see a lot more about tag helpers in the next sections and chapters. But if you’re
eager to learn more now, we recommend the following Ref. [59].
8.6 A First Look at Tag Helpers and HTML Helpers 175
8.6.3 Add Links to the Index View Using Tag Helpers and HTML
Helpers
Let’s make use of the helpers introduced above to add links to the table displayed by
Index view of Student controller. To the table defined in Index.cshtml, add two more
<TH> elements:
<TH>Details (HTML helper)</TH>
<TH>Details (tag helper)</TH>
Here is how the <TABLE> element looks like with the changes above:
<TABLE>
<THEAD>
<TR>
<TH>First name</TH>
<TH>Last name</TH>
<TH>Rank</TH>
<TH>Details (HTML helper)</TH>
<TH>Details (tag helper)</TH>
</TR>
</THEAD>
<TBODY>
@foreach (var instructor in Model)
{
<TR>
<TD>@instructor.FirstName </TD>
<TD>@instructor.LastName</TD>
<TD>@instructor.Rank</TD>
<TD>@Html.ActionLink("details", "ShowDetails", new{[email protected]})</TD>
<TD><a asp-action="ShowDetails" asp-route-id="@instructor.InstructorId"> details</a> </TD>
</TR>
}
</TBODY>
</TABLE>
Above we used the helpers as seen in the previous subsection, but instead of hardcoding
the value 100, we used (via the dot notation) the InstructorId property for each
instructor included in the table. If you rebuild and run your project, you should get a
table (URL: localhost:5125), similar to the one in Fig. 8.8:
If you click on any of those links, you will get to the ShowDetails
page (see Fig. 8.9) of the corresponding instructor. For example (URL: local-
host:5125/Instructor/ShowDetails/200),
We will make our web application prettier in a future chapter. For now, focus on the
functional part of the application.
176 8 More on Controllers and Views, Introduction to Razor Syntax
Fig. 8.8 Shows the Index view for InstructorController displays a table of instructors. Each row
in this table contains links to the ShowDetails actions. Note how each link contains the ID of the
highlighted Instructor (in our example: 200)
Fig. 8.9 Shows the ShowDetails view, displayed in a browser, that we obtained by clicking on a link
from the Index page
To keep you motivated, we will do one small detour and make our table prettier using
Bootstrap 5 tables. If you review the section where we covered Bootstrap 5 tables, then
the following should be very familiar to you.
To use Bootstrap5 in our Index.cshtml view, we add the following links inside the
<HEAD> element, let’s say before the <TITLE> element:
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
8.6 A First Look at Tag Helpers and HTML Helpers 177
Fig. 8.10 This is the same page (Index) as the one shown in Fig. 8.9, but after adding the Bootstrap
library and corresponding classes (as described above)
Then, add the following CSS classes to the <TABLE> element: class="table
table-dark table-hover". It should look like
Now, run again your web application. The table should look like the one in Fig. 8.10:
For now, please have patience, we’ll make our web application look prettier when we
get to introduce layouts (in Chap. 12). This way we’ll be more efficient because we will
minimize redundant work (otherwise we would duplicate code in multiple places).
We finish this section with one more example of a tag helper and an html helper used to
generate links. Add the following lines right before the end tag of the <BODY> element
inside the ShowDetails view:
<a asp-action="Index">go to Index</a>
@Html.ActionLink("go to Index", "Index")
Now, running your web application and clicking on a link to show the details of any
instructor, you should see two identical links, one created using a tag helper and one using
an HTML helper. Clicking on any of them will take you to Index.
For example, for the link with URL: localhost:5125/Instructor/ShowDetails/200 we
obtained Fig. 8.11:
In the next chapter, we’ll develop the actions and views for the following operations:
Add, Edit, and Delete.
178 8 More on Controllers and Views, Introduction to Razor Syntax
Fig. 8.11 Shows the ShowDetails view, as seen in Fig. 8.9, but with two hyperlinks added (as
described above)
More on Views, Data Annotations
9
In this chapter, we’ll develop the actions and views for the following operations: Add,
Edit, and Delete. Along the way, we’ll also introduce Data Annotations, the Model
Binder, Model Validation, and some HTTP Verb Attributes.
IMPORTANT: Before we proceed, make sure your model classes use properties, not
fields! Otherwise, you may get unexpected errors or behaviors.
We’ll start with some simple examples of data annotations that probably won’t seem to
be very useful, but as we introduce more data annotations, you’ll find them very useful
and very powerful. They are well worth your time and we’ll make extensive use of them,
so please invest your time in understanding them.
Data annotations “are used to define metadata for ASP.NET MVC and ASP.NET data
controls” (see [60]). In particular, HTML helpers and tag helpers work very nicely with
Data Annotations (as we will see below).
First, let’s modify the ShowDetails view. In the <BODY> element, replace the following
lines:
>r@Hktuv"pcog<"BOqfgn0HktuvPcog>1r@
>r@Ncuv"pcog<"BOqfgn0NcuvPcog>1r@
>r@Ku"vgpwtgf<"BOqfgn0KuVgpwtgf>1r@
>r@Cecfgoke"tcpm<"BOqfgn0Tcpm>1r@
>r@Jktkpi"fcvg<"BOqfgn0JktkpiFcvg>1r@
© The Author(s), under exclusive license to Springer Nature Switzerland AG 2024 179
R. A. Mezei, Introduction to the Development of Web Applications Using
ASP .Net (Core) MVC, Synthesis Lectures on Computer Science,
https://doi.org/10.1007/978-3-031-30626-6_9
180 9 More on Views, Data Annotations
with
>r@>ncdgn cur/hqt?$BOqfgn0HktuvPcog$@>1ncdgn@<"BOqfgn0HktuvPcog>1r@
>r@>ncdgn cur/hqt?$BOqfgn0NcuvPcog$@>1ncdgn@<"BOqfgn0NcuvPcog>1r@
>r@>ncdgn cur/hqt?$BOqfgn0KuVgpwtgf$@>1ncdgn@<"BOqfgn0KuVgpwtgf>1r@
>r@>ncdgn cur/hqt?$BOqfgn0Tcpm$@>1ncdgn@<"BOqfgn0Tcpm>1r@
>r@>ncdgn cur/hqt?$BOqfgn0JktkpiFcvg$@>1ncdgn@<"BOqfgn0JktkpiFcvg>1r@
If we rebuild and run our project, and go to the ShowDetails page for any of our
instructors, we’ll see something similar to Fig. 9.1 (URL: localhost:5125/Instructor/
ShowDetails/200).
This doesn’t look like an improvement (yet!), especially since the labels displayed
do not contain spaces between words. For example, it now shows “FirstName” instead
of “First Name”. How can we fix this? We cannot have spaces in variable names, for
example, the following property name is not valid (see Fig. 9.2).
To fix the spacing in the displayed property name, we will use data annotations. They
are very powerful, and we’ll soon see why. But for now, let’s use an easy data annotation,
namely the display data annotation. If you go to the Instructor class definition, we
Fig. 9.1 This looks similar to Fig. 8.11, for now. To build it, we now used tag helpers
Fig. 9.2 Shows how creating a property with name containing spaces (“First Name”) will lead to
compilation errors
9.1 Introduction to Data Annotations 181
can add display data annotations to each of our properties, so instead of the property
names, we can display any text we want. For this, change the code in Instructor.cs from.
to
To use data annotations, we also need to include the following using directive:
Notes:
. One can add multiple data annotations for the same property, and
. some properties may have no data annotations.
A data annotation applies to the very next property that follows the data annotation.
With these changes, now the ShowDetails looks a little better (see Fig. 9.3).
What did we get out of this section? The most important part for now is that we
briefly introduced some display data annotations. Also, we have seen how we can use tag
helpers to display the name of the properties in our views, and how using display data
annotations, we can instead display any text we wish in place of property names. This
is very powerful in case we later want to change how a property is being displayed—we
only need to change the value in the display data annotation, without having to change the
name of the property, which means the code will still compile and work, but the displayed
value will be different. Just imagine changing a property from a class that is being used
in many files—you would need to change all those files; using data annotations, we did
not change any property name, we just changed how they can be displayed.
182 9 More on Views, Data Annotations
Fig. 9.3 Shows the same view as in Fig. 9.1, and it also uses tag helpers. Using data annotations,
however, we were able to override what is being displayed for each property (for example, we
displayed “Academic Rank” for the property named Rank)
In the above example, you may notice that the enum value of “AssociateProfessor”
isn’t very nice looking. We can use similar display data annotations for our enumerated
type too. In Instuctor.cs replace.
rwdnke gpwo Tcpmu }"Cflwpev."Kpuvtwevqt."CuukuvcpvRtqhguuqt."CuuqekcvgRtqhguuqt."HwnnRtqhguuqt"
Ä=
with
rwdnke gpwo Tcpmu }"Cflwpev."
Kpuvtwevqt."
]Fkurnc{*Pcog"?"$Cuukuvcpv"Rtqhguuqt$+_"CuukuvcpvRtqhguuqt."
]Fkurnc{*Pcog"?"$Cuuqekcvg"Rtqhguuqt$+_"CuuqekcvgRtqhguuqt."
]Fkurnc{*Pcog"?"$Hwnn"Rtqhguuqt$+_"HwnnRtqhguuqt"
Ä=
IMPORTANT: To see annotations being displayed we cannot just use the property
name (as in @Model.FirstName), we need to use tag helpers/html helpers to make
use of these display data annotations. Above we used a tag helper with the <label>
element to display property names. Below we’ll make changes (we can add HTML helpers
or tag helpers) to display property values too.
For our example, please replace the lines in ShowDetails.cshtml:
>r@>ncdgn cur/hqt?$BOqfgn0HktuvPcog$@>1ncdgn@<"BOqfgn0HktuvPcog>1r@
>r@>ncdgn cur/hqt?$BOqfgn0NcuvPcog$@>1ncdgn@<"BOqfgn0NcuvPcog>1r@
>r@>ncdgn cur/hqt?$BOqfgn0KuVgpwtgf$@>1ncdgn@<"BOqfgn0KuVgpwtgf>1r@
>r@>ncdgn cur/hqt?$BOqfgn0Tcpm$@>1ncdgn@<"BOqfgn0Tcpm>1r@
>r@>ncdgn cur/hqt?$BOqfgn0JktkpiFcvg$@>1ncdgn@<"BOqfgn0JktkpiFcvg>1r@
9.1 Introduction to Data Annotations 183
with
>r@>ncdgn cur/hqt?$BOqfgn0HktuvPcog$@>1ncdgn@<""BJvon0Fkurnc{Hqt*o"?@"o0HktuvPcog+>1r@
>r@>ncdgn cur/hqt?$BOqfgn0NcuvPcog$@>1ncdgn@<""BJvon0Fkurnc{Hqt*o"?@"o0NcuvPcog+>1r@
>r@>ncdgn cur/hqt?$BOqfgn0KuVgpwtgf$@>1ncdgn@<"BJvon0Fkurnc{Hqt*o"?@"o0KuVgpwtgf+>1r@
>r@>ncdgn cur/hqt?$BOqfgn0Tcpm$@>1ncdgn@<"BJvon0Fkurnc{Hqt*o"?@"o0Tcpm+>1r@
>r@>ncdgn cur/hqt?$BOqfgn0JktkpiFcvg$@>1ncdgn@<"BJvon0Fkurnc{Hqt*o"?@"o0JktkpiFcvg+>1r@
Using these helpers, we ensured that the values displayed will make use of the Data
Annotations in our code. Above we used the DisplayFor Html helper. In order to use
this HTML helper, we provided it with a lambda expression specifying which property we
wanted to display. Rebuild and rerun your application, and see that the displayed enum
value has been fixed (see Fig. 9.4).
You should also note that the html helper (and similarly for tag helpers) made use of
the fact that IsTenured was declared as a Boolean property. Because of it, it displayed
its value as a checkbox (checked for true, unchecked for false). We’ll see more of
this in the next sections.
One last fix to do in here. In terms of hiring date, no one really records the time, just
the date. So we should only display the date portion and omit the time portion of our
DateTime property: HiringDate. To accomplish this, go to the Instructor.cs file, and
add the following data annotation right before the HiringDate property:
]FcvcV{rg*FcvcV{rg0Fcvg+_
Fig. 9.4 Shows the same view as in Fig. 9.3 but now it is making use of the HTML helper Display-
For. In particular, note how a Boolean property such as IsTenured was displayed as a checkbox
184 9 More on Views, Data Annotations
Fig. 9.5 Shows the same view as in Fig. 9.4 but now the DateTime properly, named HiringDate,
only shows a Date (no Time)
]Fkurnc{*Pcog"?"$Jktkpi"fcvg$+_
]FcvcV{rg*FcvcV{rg0Fcvg+_
rwdnke FcvgVkog"JktkpiFcvg"}"igv="ugv="Ä
Rebuild and run your application. Here (see Fig. 9.5) is what we obtained now (much
better!).
Let’s update the Index view so that it uses tag helpers and the display data annotations
as seen above. Also, we should use an if statement to check if the list is empty (this is
different than null!). When the list is empty, we should display some specific text, such
as “No instructors found!”. To do this, add the following code to Index.cshtml and move
the <TABLE> element inside the if block (Note: to use C#, we used Razor syntax):
Bkh*Oqfgn0Eqwpv*+@2+
}
11rwv"vjg"vcdng"gngogpv"kp"jgtg
Ä
gnug
}
>j4@Pq"kpuvtwevqtu"hqwpf# >1j4@
Ä
Next, we would like to use tag helpers similar to what we have seen in the previous
subsection, to use the data annotations for the names of the table’s columns.
9.1 Introduction to Data Annotations 185
Challenge: Now the model reference is not pointing to one instance of Instructor,
but a list of Instructors, so our syntax will be a little different. We give below the entire
Index.cshtml file so you can also check your work:
Boqfgn"KGpwogtcdng>Kpuvtwevqt@
B}
Nc{qwv"?"pwnn=
Ä
>#FQEV[RG jvon@
>jvon@
>jgcf@
>ogvc pcog?$xkgyrqtv$ eqpvgpv?$ykfvj?fgxkeg/ykfvj$ 1@
>nkpm jtgh?$jvvru<11efp0lufgnkxt0pgv1pro1dqqvuvtcrB704051fkuv1euu1dqqvuvtcr0okp0euu$ tgn?$uv{ngujggv$@
>uetkrv ute?$jvvru<11efp0lufgnkxt0pgv1pro1dqqvuvtcrB704051fkuv1lu1dqqvuvtcr0dwpfng0okp0lu$@>1uetkrv@
>vkvng@Kpfgz>1vkvng@
>1jgcf@
>dqf{@
>j3@Cnn"kpuvtwevqtu>1j3@
Bkh*Oqfgn0Eqwpv*+@2+
}
>VCDNG encuu?$vcdng"vcdng/fctm"vcdng/jqxgt$@
>VJGCF@
>VT@
>VJ@>ncdgn cur/hqt?$Hktuv*+0HktuvPcog$@>1ncdgn@>1VJ@
>VJ@>ncdgn cur/hqt?$Hktuv*+0NcuvPcog$@>1ncdgn@>1VJ@
>VJ@>ncdgn cur/hqt?$Hktuv*+0Tcpm$@>1ncdgn@>1VJ@
>VJ@Fgvcknu"*JVON"jgnrgt+>1VJ@
>VJ@Fgvcknu"*vci"jgnrgt+>1VJ@
>1VT@
>1VJGCF@
>VDQF[@
Bhqtgcej *xct kpuvtwevqt"kp Oqfgn+
}
>VT@
>VF@Bkpuvtwevqt0HktuvPcog">1VF@
>VF@Bkpuvtwevqt0NcuvPcog>1VF@
>VF@Bkpuvtwevqt0Tcpm>1VF@
>VF@BJvon0CevkqpNkpm*$fgvcknu$."$UjqyFgvcknu$."pgy}kf?Bkpuvtwevqt0KpuvtwevqtKfÄ+>1VF@
>VF@>c cur/cevkqp?$UjqyFgvcknu$ cur/tqwvg/kf?$Bkpuvtwevqt0KpuvtwevqtKf$@fgvcknu>1c@>1VF@
>1VT@
Ä
>1VDQF[@
>1VCDNG@
Ä
gnug
}
>j4@Pq"kpuvtwevqtu"hqwpf#>1j4@
Ä
>1dqf{@
>1jvon@
As an exercise, we want to let you figure out how to use the HTML helpers seen in
the previous subsection (namely DisplayFor) so the Academic rank will make use of the
data annotations set in Instructor.cs. Your table should look similar to Fig. 9.6.
Here is one solution:
Fig. 9.6 Shows the same view as in Fig. 8.6 but it should now be using HTLM helpers
186 9 More on Views, Data Annotations
>VF@BJvon0Fkurnc{Hqt*o"?@"kpuvtwevqt0HktuvPcog+">1VF@
>VF@BJvon0Fkurnc{Hqt*o"?@"kpuvtwevqt0NcuvPcog+>1VF@
>VF@BJvon0Fkurnc{Hqt*o"?@"kpuvtwevqt0Tcpm+>1VF@
This section is very important as it introduces several new important concepts, and it may
look a little more challenging than it actually is. Most concepts will be reviewed again
in the next section and you will probably have a much better understanding then. Please
have patience.
As we will see below, the Add operation is actually a two-step process and we’ll need
to create two Add actions:
. one for the GET operation that will be used to send a request for a form to fill out,
and
. one for the POST operation that we will use to send all data from the form to the
server.
Let’s start easy. First, in the InstructorController, let’s add an Add action (if you
already have this, do not add it again!), as shown below.
rwdnke KCevkqpTguwnv"Cff*+
}
tgvwtp Xkgy*+=
Ä
Note: To access this action easily, we next add a link to this action in the Index view
(this is what we first see when we run our application). In the Index.cshtml, right before
the </BODY> tag, add the following link to our Add action (it’s a tag helper!):
Now, let’s run the application. You should get a new link at the end of the Index page
(see Fig. 9.7).
If you click on that link, you will get an error message (“InvalidOperationException:
The view ‘Add’ was not found”.), which should make sense, since we did not yet create
a view for the Add action (try it!).
Now let’s add a view. Then ask yourself, what do you expect to get when you click on
that link? That’s what we’ll put in the Add view. Just like we’ve seen earlier, make sure
the view name matched the action name, and no options are selected.
9.2 The Add Action and View 187
Fig. 9.7 Shows the same view as in Fig. 9.6 but it should include a new link, Add a new instructor,
that would point to the Add action of InstructorController
Now, if we try again to click on the “Add a new instructor” link, we should get this
newly added view, which is empty.
Let’s add contents to this view.
You should note that in the above we made use of the tag helpers to spec-
ify where should the data/request be sent to (namely the Add action from
InstructorController) when the user clicks on the submit button (the button will
display the text: Create Instructor). Here, in Fig. 9.8 is what we get so far if you
click on the “Add a new instructor” link (URL: localhost:5125/Instructor/Add).
We’ll explain the reason why we set the method=“post” below.
188 9 More on Views, Data Annotations
Fig. 9.8 Shows the Add view. It contains an H1 element and a form that only contains the submit
button
Before we continue, we need to make our view strongly typed. This is because the
form will work with an instance of Instructor, and also to get Model Binder support (see
below). Make sure to add the following at the beginning of the Add.cshtml file:
Boqfgn"Kpuvtwevqt
In particular, you should note that we used tag helpers for both:
. To display the names of the properties (via data annotations), we used the following
tag helper of the form:
– >ncdgn cur/hqt?$Rtqrgtv{Pcog$@>1ncdgn@
. To create an input field for the properties (again via data annotations), we used
– >kprwv cur/hqt?$Rtqrgtv{Pcog$ 1@
Running your application again, the form now (see Fig. 9.9) looks much better (do not
bother fixing InstructorId, we’ll remove it in Chap. 11).
Because we used tag helpers, Razor engine was able to figure out that.
Fig. 9.9 This is the same as Fig. 9.8, but the form now includes several labels and input elements
(as described above)
Can you see how powerful tag helpers and data annotations are?
>kprwv cur/hqt?$Tcpm$ 1@
with
Run again your application and check that you obtained a result similar to Fig. 9.10.
For completeness, we provide you below all the contents of the Add view:
190 9 More on Views, Data Annotations
Fig. 9.10 Is similar to Fig. 9.9, but now a dropdown menu was added for the Academic rank option.
Note that the first option is automatically selected
@model Instructor
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Create a new Instructor</title>
</head>
<body>
<h1>Create a new Instructor</h1>
<form asp-action="Add" asp-controller="Instructor" method="post">
<label asp-for="InstructorId"></label>
<input asp-for="InstructorId" />
<br>
<label asp-for="FirstName"></label>
<input asp-for="FirstName" />
<br>
<label asp-for="LastName"></label>
<input asp-for="LastName" />
<br>
<label asp-for="IsTenured"></label>
<input asp-for="IsTenured" />
<br>
<label asp-for="HiringDate"></label>
<input asp-for="HiringDate" />
<br>
<label asp-for="Rank"></label>
<select asp-for="Rank" asp-items="@Html.GetEnumSelectList(typeof(Ranks))"></select>
<br>
<br />
<input type="submit" value="Create Instructor" />
</form>
</body>
</html>
9.2 The Add Action and View 191
One more fix for our dropdown list: We would like to not have Adjunct selected by
default. To fix this, we’ll add the following content for the <SELECT> element above:
<option value="">Select</option>
with:
<select asp-for="Rank" asp-items="@Html.GetEnumSelectList(typeof(Ranks))">
<option value="">Select</option>
</select>
then your Rank selector will not have a default value selected (see Fig. 9.11).
Rebuild and run again the application and fill in data into the form included in the
Add view. What happens when you click on the Create Instructor (the submit) button?
Per our code above, the submit button will send your data, as a POST request, to the
Add action (as of right now that is the same action as we created above). We would like
it to go to another action. For reason we’ll see later (when we do validation) we would
like our second action to also be called Add. Let’s create a second action, this one with
a parameter of type Instructor (our model class).
Fig. 9.11 Is similar to Fig. 9.10, but note that a default value is not preselected for the Academic
rank
192 9 More on Views, Data Annotations
This code compiles, because method overloading is allowed, but it will introduce a
challenge for the routing. Run your application again and click on the “Add a new instruc-
tor …” link. You will get the following error: AmbiguousMatchException: The
request matched multiple endpoints.
To fix this, we can add the following HTTP VERB Attributes:
[HttpGet]—for the first Add action and
[HttpPost]—for the second Add action.
You should now have
[HttpGet]
public IActionResult Add()
{
return View();
}
[HttpPost]
public IActionResult Add(Instructor newInstructor)
{
return View();
}
These attributes essentially restrict what type of requests their respective actions will
respond to.
Using [HttpPost] for an action, we are restricting that action to only respond to POST
requests.
. Typically, when you click on a link or type in a URL in the browser, you will make a
GET request.
. Typically, when you click on submit button for a form, or upload a file to send to the
server, you will make a POST request.
Using these attributes will take care of the ambiguity that the routing complained about.
The code should not compile without any errors.
[HttpPost]
public IActionResult Add(Instructor newInstructor)
{
InstructorsList.Add(newInstructor); //add the new instructor to our list
return View("Index", InstructorsList); //temporary fix - do not refresh the page!!!
}
Very important: The Add method above has one parameter of type Instructor.
We can choose any parameter type we need for an action, but in this case, we chose
Instructor because we have a very helpful mechanism, called the model binding,
which is helping us behind the scenes. Namely, when this second Add action is called,
the model binding will.
. look to find the information needed for our action—in our case, we need an instance
of Instructor;
. look at the information sent to the server—in our case, it will look into the data sent
by the form (and other sources too); and finally
. with the values entered in the form create an instance of the Instructor and pass
it along to our Add action.
We’ll see Model Binding again below, and it will make more sense in there. This is merely
an introduction.
Let’s test our code and see the steps involved to add a new instructor. Let’s explain
this step by step:
. First, we click on the Add a new instructor link from the Index page.
. This will send an HTTP GET request to the first Add action which in turn returns a
view that contains a form for the user to interact with (see Fig. 9.12).
. The user can fill in the form as desired (see Fig. 9.13).
And when they click on the submit button (Create Instructor), a second HTTP
request (a POST request because the form included the following in the <FROM> tag:
method="post") is being sent, and this one will go to the second Add action (because
the second one is the one that responds to POST request).
That second action will add the new Instructor object created by the model binding
and send this action to the InstructorsList and then returns the Index view, which
displays (see Fig. 9.14) the instructors (note the URL: localhost:5125/Instructor/Add).
We used return View instead of return RedirectToAction because this
did not involve a new request being sent to the server. The bad part is that the URL (look
in the screenshot above) will be localhost:5125/Instructor/Add. We did this “temporary
fix” because our data is not persistent. As soon as you go to localhost:5125 (or navigate
via any links on the page), you will lose this newly added instructor (see Fig. 9.15).
We’ll deal with data persistency later.
194 9 More on Views, Data Annotations
Fig. 9.12 The Add view for adding a new Instructor. It contains an H1 element, a form with multiple
labels, input elements, and a submit button
Fig. 9.13 This is the same view as the one seen in Fig. 9.13, but it contains some user entered values
9.2 The Add Action and View 195
Fig. 9.14 Shows the Index view for the InstructorController. It displays a table containing a list of
instructors’ details
Fig. 9.15 Shows the same information as shown in Fig. 9.14, but the newly added row is not there
anymore (it wasn’t saved)
In the example seen in the previous section, the model binding system was able to collect
various data from our form and turn them into an instance of the Instructor needed
for the Add action.
In general, the model binding system can retrieve data from various sources, in this
order:
. Form fields.
. Route data.
. Query string parameters.
. Uploaded files.
The model binding system can provide this data to controllers and even convert the
various string data to.Net types.
To learn more about model binding in ASP .Net Core, check out the following source
[61].
196 9 More on Views, Data Annotations
We finish this subsection with one more example. Suppose you have an action that
looks like.
. Convert the string 70 which is given as part of the HTTP request into the integer 70
needed for the action.
. Convert the string true which is given as part of the HTTP request into the Boolean
true needed for the action.
. It will even match the ISPRIORITY given in the HTTP request to isPriority needed
for the action.
We’ve briefly seen GET and POST (called HTTP verbs) earlier. They are both used to
send client information to a web server and are the most common. But there are some
important differences between them.
In class, we like to demonstrate the examples shown in:
. https://www.w3schools.com/html/tryit.asp?filename=tryhtml_form_get
. https://www.w3schools.com/html/tryit.asp?filename=tryhtml_form_post
GET requests
. can be cached;
. can be seen in the browser history;
. can be bookmarked;
. should not be used when dealing with sensitive data (such as username and password);
. have length restrictions;
. data is visible in the URL!;
. only ASCII characters are allowed for the data;
– we typically use them to send request for forms;
– when clicking on web links, we send GET requests.
9.2 The Add Action and View 197
POST requests
There are other HTTP verbs, such as PUT, HEAD, DELETE, and PATH, and you can
even define your own. But we won’t make use of them in this book. To read more about
them, check out the following resource [13].
Above, we’ve seen that Add was a two-step process. We had the following:
. Step 1: Add—the GET request: This was used to send a request to the server and in
return get a view that contains a form so the user can type in their data.
– This is when the user clicks a link (and later a button).
. Step 2: Add—the POST request: This was used to send the data entered into the form
to the server.
– This is when the user clicks on the submit button.
. Step 1: Login—the GET request: This sends a request to the server to get a form so
the user can type in their username and password.
. Step 2: Login—the POST request: When the user clicks on the submit button, the data
is sent to the server for processing (verify the correct login credentials and send back
some authentication cookies).
. Step 1: Edit—the GET request: This sends a request to the server to get a form loaded
with existing data.
. Step 2: Edit—the POST request: When the user clicks on the submit button, the data
is sent to the server for processing (save changes to the server).
198 9 More on Views, Data Annotations
In this section, we’ll implement the Edit action. As discussed above, we’ll have two
Edit actions:
. One for the GET request that will return a form loaded with existing data, and
. one for the POST request that will allow the user to save changes to the database.
For simplicity, we’ll assume that only some fields are editable. In particular, we’ll assume
the InstructorId is not editable.
We start with the following. We need an action that allows us to specify an Id. With this
Id in hand, we’ll search in the InstructorsList (later we’ll search in a database)
to find an instance of Instructor whose InstructorId matches the given Id.
If such an Instructor was found, send it to the view to prepopulate a form (to
allow the user to change its values).
If such an Instructor was not found, use the NotFound method.
Since we will have two actions with the same name, we will again use the [HttpGet]
attribute which will limit this action to only respond to GET requests. Replace the
following in InstructorController.cs file:
public IActionResult Edit(int id)
{
return View();
}
with
[HttpGet]
public IActionResult Edit(int id)
{
//we should look for the instance that has the given Id
// ... later we'll search in the database
Instructor? instr = InstructorsList.FirstOrDefault(inst => inst.InstructorId == id);
To make it easier for us to test the Edit functionality, let’s add an Edit link to each entry
in our table from the Index view.
9.3 The Edit Action and View 199
Fig. 9.16 Is similar to Fig. 9.15, but a new column (the Edit) is being added. This column contains
links that can be used to call the Edit action for each row/Instructor. In particular, note that these
links contain the corresponding ID for each instructor (in our example, we can see the ID 300 being
used)
<TH>Edit </TH>
When you run your application, you should now have new links, edit links, one for
each row in the table from the Index view (see Fig. 9.16).
IMPORTANT: As you hover your mouse over an “edit this” link, make sure the link
shown in the lower left part of the webpage has a link that includes the InstructorId.
In our example, we see that 300 is included in localhost:5125/Instructor/Edit/300 .
To pass this Id to our edit this link, we include the following in the tag helper
used above (where id is the third segment used in our modified default routing set in
Program.cs):
asp-route-id="@instructor.InstructorId"
If you click on any of the edit links above, you will see an error. This is because we
did not yet implement the Edit view for the Edit action. Let’s implement it next.
Anywhere in the Edit (the GET) action, right-click and select Add View …. Follow the
steps similar as above to create a view with the name Edit. Since this view will work
with an instance of the Instructor class, we’ll make this view strongly typed by
adding the following at the very beginning of the Edit.cshtml file:
200 9 More on Views, Data Annotations
@model Instructor
Next, inside the <BODY> element we’ll add an <H1> element and a <FORM> element
containing just some select fields from Instructor class. Namely, we omitted the
FirstName field, assuming we don’t want to allow this to be editable:
<h1>Edit an instructor profile</h1>
<form asp-action="Edit" method="post">
<input asp-for="@Model.InstructorId" type="hidden" />
@*needed so the InstructorId is sent to the Edit(POST)*@
<label asp-for="LastName"></label>
<input asp-for="LastName" />
<br>
<label asp-for="IsTenured"></label>
<input asp-for="IsTenured" />
<br>
<label asp-for="HiringDate"></label>
<input asp-for="HiringDate" />
<br>
<label asp-for="Rank"></label>
<select asp-for="Rank" asp-items="@Html.GetEnumSelectList(typeof(Ranks))"></select>
<br>
<br />
<input type="submit" value="Save changes" />
<input type="button" onclick="history.back()" value="Cancel">
</form>
Above, we included a “go back” button that will act as a Cancel button:
Fig. 9.18 Displays the Edit view for InstructorController. It contains an H1 element and a form that
is prepopulated with the current Instructor data
The way we wrote our code, the Edit action will search for the Instructor from
the InstructorsList that has InstructorId equal to the provided Id. Once
found, it will pass that to the Edit view to display it in a form. And this form (loaded
with the values of the Instructor found above) is what we’re getting next, in Fig. 9.18
(note the URL: localhost:5125/Instructor/Edit/200).
The Cancel will take you back to the previous page.
The Save changes button will send a POST request to the Edit action (which we’ll
implement next).
Next, we will implement the Edit action that will respond to POST requests from the
form above. For this we will create a method/action in InstructorController.cs that looks
like:
[HttpPost]
public IActionResult Edit(Instructor instructorChanges)
{
//to do ...
}
You should note that we again will make use of the model binding system, by using a
parameter of type Instructor. The model binding system will search for values needed
to create an instance on Instructor, in particular it will collect the values from the
form (seen in the Edit view) and create an instance of Instructor and pass it to the
instructorChanges parameter.
202 9 More on Views, Data Annotations
The effect is that the instructorChanges parameter contains the values in the
form at the time the user clicks on the submit button.
Next, we need to use these values to change the Instructor in the
InstructorsList to match these new values. For this, use the code below to replace
the //to do … comment inside the Edit (the POST!) action:
//find the instructor from InstructorList
// who has the same InstructorId as the changes.InstructorId
Instructor? instr = InstructorsList.FirstOrDefault(instr => instr.InstructorId ==
instructorChanges.InstructorId);
if (instr != null) //if found, change the values in InstructorsList to match the changes
{
instr.LastName = instructorChanges.LastName;
instr.IsTenured = instructorChanges.IsTenured;
instr.HiringDate = instructorChanges.HiringDate;
instr.Rank = instructorChanges.Rank;
}
return View("Index", InstructorsList); //temporary fix - do not refresh the page!!!
If we change some or all values from the form (for example, to look like the ones
below in Fig. 9.19).
And click on the Save changes (the form’s submit button), this will send a POST
request to the Edit action to save these changes. In our example, we got the following
(Fig. 9.20).
It looks like we successfully changed the values from the InstructorsList.
The actions are working as expected, but there is a problem. Every time you click on a
link (or a button), you are sending a new HTTP request to the server. This in turn means
that we are creating a new instance of the controller, so all our changes (add or edit, for
example) are lost. Our data is not persistent! Test it, click on http://localhost:5125/—all
changes are lost!
Fig. 9.19 Is similar to Fig. 9.18, but it has some values changed
9.3 The Edit Action and View 203
Fig. 9.20 Shows the Index view that contains a table with Instructors information. In particular, it
appears that the Edit operation was successful
One fix for this is to use a database, which will see in a future chapter. Until then,
a “temporary fix” would be to use a service. This “temporary fix” is not very useful in
practice, but we want to use it as a reason for us to show you how to create a service—for
teaching purposes, we think this “temporary fix” is very useful even if it may not make
complete sense.
As mentioned above, this “temporary fix” isn’t very useful in practice, but it will give us
a chance to talk about services and dependency injection. We’ll see these topics again in
future chapters, so we wanted to use this section as an introduction to what services are
and how to use dependency injection.
As of right now, our hard-coded data for instructors is essentially put in the con-
structor of the controller. That’s a problem because each time a new HTTP request is
received, a new instance of the controller is created, meaning that all changes done to the
InstructorsList are lost. One “temporary fix” is to create a service and make that
service only be created once for the lifetime of the web application. That will ensure that
our data in InstructorsList will “survive” multiple HTTP requests. As long as we
do not restart the web application, the data in InstructorsList will appear to be
persistent. In Chap. 11, we’ll create real persistency by making use of a database.
There are three steps involved when creating new services.
There you have many options, including a class and an interface (choose both,
one by one). We’ll call our interface IMyFakeDataService, and our class
MyFakeDataService.
For the interface, for this example, we only need one property, let’s call it
InstructorsList. Here is a sample code:
using ASPBookProject.Models;
namespace ASPBookProject.Services
{
public interface IMyFakeDataService
{
List<Instructor> InstructorsList { get; }
}
}
For the class, we will make it implement the interface defined above, and in the con-
structor for this class we’ll add our hard-coded data for instructors (copy and paste code
from the InstructorController class):
using ASPBookProject.Models;
namespace ASPBookProject.Services
{
public class MyFakeDataService : IMyFakeDataService
{
public List<Instructor> InstructorsList { get; }
public MyFakeDataService()//constructor
{
InstructorsList = new List<Instructor>()
{
new Instructor() {InstructorId = 100,
FirstName = "Maegan", LastName = "Borer",
IsTenured=false, HiringDate=DateTime.Parse("2018-08-15"),
Rank = Ranks.AssistantProfessor},
We must add this line before we call the builder.Build(); method (so the first
part of the Program.cs file will look similar to):
using ASPBookProject.Services;
That’s it. Now we have a service. The dependency injection will take care of creating a
new instance of this class when needed. And since we used the AddSingleton method,
only one instance will be created as long as the web application is not restarted.
Part 2: Add a parameter to the constructor that will be used to set the field defined
above. The “magic” of dependency injection is that the Dependency Injection will
automatically set the value for your parameter when the constructor is being called:
206 9 More on Views, Data Annotations
namespace ASPBookProject.Controllers
{
public class InstructorController : Controller
{
private readonly IMyFakeDataService _fakeData;
[HttpGet]
public IActionResult Add()
...
Some “Cosmetics”
Now that we have some “temporary fix” for our persistency problem, replace the
two lines that have the code (from the Add and Edit—POST actions defined in
InstructorController.cs):
With
return RedirectToAction("Index");
This will make the URL look nicer after we finish adding/editing an instructor. The
URL will be the one for the Index action. Also, the two actions have a little cleaner
code.
Test your code. Now you should be able to add multiple new instructors, perform
multiple changes, and see them all in the Index view. As long as you don’t restart the
application, these changes seem to be persistent.
Finally, we got to the last CRUD operation, the delete. This operation can be done in one
step or two. Below we’ll follow the previously mentioned two-step process.
We need an action that allows us to specify an Id. With this Id in hand, we’ll search
in our InstructorsList (later we’ll search in our database) to find an instance of
Instructor whose InstructorId matches the given Id.
. If such an Instructor was found, send it to the view to prepare the information to
be displayed in a browser.
. If such an Instructor was not found, use the NotFound method.
We will again use the [HttpGet] attribute which will limit this action to only respond
to GET requests. Make sure your InstructorController.cs file contains the following action:
[HttpGet]
}
208 9 More on Views, Data Annotations
To make it easier for us to test the Delete functionality, let’s add a Delete link to each
entry in our table from the Index view.
Add a new <TH> entry:
<TH>Delete </TH>
Let’s add a view for our Delete (the GET) action. We’ll make this view strongly typed
by adding the following at the very beginning of the Delete.cshtml file:
@model Instructor
The view should ask for a confirmation (“Are you sure …?”) and contain two buttons,
one for yes, delete, and one for no, cancel. In order to send a POST request from the
button, we’ll put it inside a form. As above, we must include a hidden field containing
the ID so this value can be sent to the server. Here is a possible <BODY> element for our
view.
Next, inside the <BODY> element we’ll add an <H1> element and a <FORM> element
containing just some select fields from Instructor class. Namely, we omitted the
FirstName field, assuming we don’t want to allow this to be editable:
<body>
<h1>Are you sure you want to delete this instructor?</h1>
<form asp-action="DeleteConfirmed" method="post">
<input asp-for="InstructorId" type="hidden" />
<p><label asp-for="@Model.FirstName"></label>: @Html.DisplayFor(m => m.FirstName)</p>
<p><label asp-for="@Model.LastName"></label>: @Html.DisplayFor(m => m.LastName)</p>
<p><label asp-for="@Model.Rank"></label>: @Html.DisplayFor(m => m.Rank)</p>
<br />
<input type="submit" value="YES, delete" />
<input type="button" onclick="history.back()" value="NO, cancel">
</form>
</body>
Notice again how we included the hidden field containing the InstructorId. This
is needed for the DeleteConfirmed action (the POST). The model binding will find it and
use it if the called action (DeleteConfirmed) needs it.
9.4 The Delete Action and View 209
Next, we will implement the DeleteConfirmed action that will respond to POST
requests from the form above.
First, let us explain the name. The second Delete action does not need an entire
Instructor parameter, and it only needs an int parameter (the Id). The problem is
that in C# we cannot have two Delete methods both having one int parameter. It
doesn’t matter we have one marked as HttpGet and the other HttpPost.
There are two solutions:
. either use a different parameter type (for example, we could use Instructor type—just
like we did for Edit and Add)
. or use different action names (we chose in here to do this—named our second action
DeleteConfirmed).
Let’s test this. When you run the web application, you should get the following table
(see Fig. 9.21).
Fig. 9.21 Is similar to the table shown in Fig. 9.20, but a new column (the Delete column) was
added. This column contains links that can be used to send Delete requests for each of the Instructors
in the list. Note that each link contains the ID of the currently selected instructor (in the screenshot
above, the ID = 300 is being shown)
210 9 More on Views, Data Annotations
If you click on any delete this link, you will send a GET request to the Delete
action. This action returns a view (see Fig. 9.22) that displays the following (URL:
localhost:5125/Instructor/Delete/300).
If you click on the NO, cancel button, you’ll be taken back to the previous page.
If you click on the YES, delete button, you’ll send a POST request to the
DeleteConfirmed action, because this is what we used in the tag helper above:
<form asp-action="DeleteConfirmed" method="post">
Fig. 9.22 Shows the Delete view displayed in a browser. In particular, this displays information
about the current instructor to be deleted, and it includes two buttons
Fig. 9.23 Shows the Index view, displayed in a browser, with one entry/row removed from the table
Fig. 9.24 Shows the Index view, displayed in a browser, when all rows are deleted from the table. In
particular, note that the table is not displayed (since it’s empty). Instead, the “No instructors found!”
is being displayed.
9.4 The Delete Action and View 211
This action will delete the selected Instructor. Now the table looks like (see Fig. 9.23).
On your own, go ahead and delete all instructors. You should get the following
(Fig. 9.24).
Which part of the code is responsible for creating this outcome?
Model Validation
10
Before we continue, let’s add a few more properties to the Instructor class. This will
allow us to better demonstrate some concepts. Let’s add the following properties and data
annotations to Instructor.cs file:
[Display(Name = "Office phone number")]
public String? PhoneNumber{ get; set; }
Fig. 10.1 Shows the Add view for InstructorController. It contains a form, and several input fields
have values in it
If you run your application and go to add a new Instructor, you should note the
following (see Fig. 10.1):
You can even submit an empty form (multiple times too!), as shown in Fig. 10.2.
Here is the result (see Fig. 10.3).
We should not accept any random data; we should at least enforce some validation.
This is what we’ll see next. On the server side, we will add some model validation. The
10 Model Validation 215
Fig. 10.2 Is the same as Fig. 10.1, but no data has been entered in the form
Fig. 10.3 Shows the Index view, for InstructorController. In it, note that the last two rows are the
result of submitting empty forms to the server:
216 10 Model Validation
main reference for this page is [62] and we encourage you to look over it. There are three
main steps to follow:
. Make use of built-in validation attributes (we’ll also see how to build our own
custom validation attributes).
. Enforce validation by making use of the ModelState.
. Add validation helpers to display error messages.
There are several built-in validation attributes that we can use. Here is a short list (see
more in [62]).
. We will add [Required] to fields that must be given a value by the user. We will
occasionally make use of the ErrorMessage property to specify a custom error
message (otherwise a default error message will be used).
. We will also make use of the [Url] and [EmailAddress] attributes to check for
us that the values entered have a valid format. Note: since the properties using these
attributes are not [Required], empty values will pass the validation check.
. For the phone number, we could use [Phone], but we opted to use the
[RegularExpression] instead just for practice purposes.
Here is how the Instructor class looks like once we added the built-in validation
attributes:
10.2 Step 2: Enforce Validation by Making Use of the ModelState 217
[Required]
[Display(Name = "Academic rank")]
public Ranks Rank { get; set; }
[RegularExpression("[0-9]{3}-[0-9]{3}-[0-9]{4}",
ErrorMessage = "you must follow the format 000-000-0000!")]
[Display(Name = "Office phone number")]
public String? PhoneNumber { get; set; }
[EmailAddress]
[Display(Name = "Email address")]
public String? EmailAddress { get; set; }
[Url]
[Display(Name = "Personal webpage")]
public String? PersonalURL { get; set; }
[Required]
[StringLength(10, MinimumLength = 5)]
[Display(Name = "Password (we won't use this!)")]
[DataType(DataType.Password)]
public string? UnusedPassword { get; set; }
}
We’ll show you next how to use ModelState to enforce the validation
attributes we added above. “For web apps, it’s the app’s responsibility to inspect
ModelState.IsValid and react appropriately” [62].
Where do we want to validate user data? We want to do this when we add or edit an
instructor. Therefore, we’ll make changes in the Add action and Edit action (POST).
Change Add action (inside InstructorController.cs):
[HttpPost]
public IActionResult Add(Instructor newInstructor)
{
_fakeData.InstructorsList.Add(newInstructor); //add the new instructor to our list
return RedirectToAction("Index");
}
into
218 10 Model Validation
[HttpPost]
public IActionResult Add(Instructor newInstructor)
{
if (!ModelState.IsValid) //if the data is invalid
return View(); //go back to the view
Some explanation. Before we add any new data into our InstructorsList, we
want to make sure it is valid. So, what we did above is as follows: if the data is not valid
(we check the status of the data by checking the value of the ModelState.IsValid)
then return View. What view will it be?
Remember “convention over configuration”: in the line above, View() means the
view with the same name as the action, so in this case it will be the Add.cshtml. This
is where it was useful to use the same name for both the POST and the GET Add actions:
they both have the same name, so the View in both cases will be the same one containing
the Add form.
Similarly, change the Edit action to include the same check as above. After change,
your Edit (POST) should look as follows:
public IActionResult Edit(Instructor instructorChanges)
{
if (!ModelState.IsValid) //if the data is invalid
return View(); //go back to the view
if (instr != null) //if found, change the values in InstructorsList to match the changes
{
instr.LastName = instructorChanges.LastName;
instr.IsTenured = instructorChanges.IsTenured;
instr.HiringDate = instructorChanges.HiringDate;
instr.Rank = instructorChanges.Rank;
}
return RedirectToAction("Index");
}
There are two types of error messages we can display. We can display
. in-line error messages—display an error message right next to the input element where
the error occurs;
. summary of all error messages—display all error messages combined in one place.
10.3 Step 3: Display Error Messages via Validation Tag Helpers 219
Use the following tag helper to display all error messages in one place. You can add this
wherever you want, for example, at the beginning of the page (or alternatively at the end
of the page):
<div asp-validation-summary="All"></div>
We will add this in both the Add.cshtml and in the Edit.cshtml. We’ll add this line right
before the <FORM> element.
One can also add display validation error messages for each property individually. For
this, we will add tag helpers that will look like
<span asp-validation-for="InstructorId"></span>
Add this for all fields (and change InstructorId with the property name in each
case).
Here is how the Edit.cshtml looks like after the above-mentioned changes:
@model Instructor
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Edit</title>
</head>
<body>
<h1>Edit an instructor profile</h1>
<div asp-validation-summary="All"></div>
<form asp-action="Edit" method="post">
<input asp-for="@Model.InstructorId" type="hidden" /> @*needed so the InstructorId is
sent to the Edit(POST)*@
<label asp-for="LastName"></label>
<input asp-for="LastName" />
<span asp-validation-for="LastName"></span>
<br>
<label asp-for="IsTenured"></label>
<input asp-for="IsTenured" />
<span asp-validation-for="IsTenured"></span>
<br>
<label asp-for="HiringDate"></label>
<input asp-for="HiringDate" />
<span asp-validation-for="HiringDate"></span>
<br>
<label asp-for="Rank"></label>
<select asp-for="Rank" asp-items="@Html.GetEnumSelectList(typeof(Ranks))"></select>
<br>
<br />
<input type="submit" value="Save changes" />
<input type="button" onclick="history.back()" value="Cancel">
</form>
</body>
</html>
220 10 Model Validation
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Create a new Instructor</title>
</head>
<body>
<h1>Create a new Instructor</h1>
<div asp-validation-summary="All"></div>
Go to Add a new instructor and attempt to submit an empty form (see Fig. 10.4).
You should see the summary validation errors on top. Then, in-line, you should see
one by one each of those errors.
Here is another example (see Fig. 10.5).
Once you fix the email address error, then the next error shows up (see Fig. 10.6).
This is because some of these errors are checked in the client’s browser and hence no
HTTP request has been sent to the server yet. For this reason, the Summary list of errors
did not show up. Once you fix these errors checked on the client side (but not all errors)
you will then be able to see the Summary list of errors (for the errors caught on the server
side), as seen in Fig. 10.7.
Fixing the errors (by entering valid values), we get one last to fix (see Fig. 10.8). Can
you figure out what we missed here?
Go back to the Instructor.cs and
Fig. 10.4 If a user tries to submit an empty form, note that some validation messages (such as “last
name is required”) are being displayed in the browser:
222 10 Model Validation
Fig. 10.5 Is the same as Fig. 10.4, but some values are entered in the form so it passes some of the
input validation. Note that the Email address still does not pass the input validation:
Let’s introduce the custom validation attributes next. We’ll start with an example. Suppose
we would like to only allow the IsTenured checkbox to be checked (i.e., set to true)
if the HiringDate contains a date that is, let’s say either on or after August 15th, 2007.
In this particular example, we have two properties that need to be used for validation. How
would you enforce this?
10.5 Custom Validation Attributes (Optional) 223
Fig. 10.6 Is similar to Fig. 10.5. In here, a valid email address was entered. Now the Personal
webpage field produces an input validation error:
You should quickly realize that the built-in validation attributes are great for many
common validation scenarios, but they are not sufficient for all cases. Luckily, it’s quite
easy (see below) to create our own custom validation attributes.
For this part, let’s create a new folder, let’s call it CustomValidations (feel free to choose
a better name). In the Solution Explorer window, right-click on the project name, then
select Add > New Folder.
In this new folder create a new class, make sure it is derived from the
ValidationAttribute class, and the newly added class’s name ends
with ValidationAttribute. We will call it: TenuredOnlyAfter2007
ValidationAttribute.
224 10 Model Validation
Fig. 10.7 Is another validation error, namely an invalid office phone number
In this class, we need to overload the IsValid which is used to check if the property
value is valid. There are two overloaded IsValid methods (make use of IntelliSense to
find out more about these methods):
IsValid(object? value)
IsValid(object? value, ValidationContext validationContext)
The first one is great if you only need a custom validation attribute that only needs to
look at one (the current) property.
Since in our example we need to access two properties (the IsTenured and the
HiringDate), we’ll need to override the second method, which gives us access to
the entire model object. Here is the code we’ll use to implement our custom validation
attribute:
10.5 Custom Validation Attributes (Optional) 225
Fig. 10.8 Shows the same form as seen in Fig. 10.7, but now the validation error isn’t very clear. It
only specifies “The value ‘is invalid’”
using ASPBookProject.Models;
using System.ComponentModel.DataAnnotations;
namespace ASPBookProject.CustomValidations
{
public class TenuredOnlyAfter2007ValidationAttribute : ValidationAttribute
{
protected override ValidationResult? IsValid(object? value, ValidationContext
validationContext)
{
Instructor currentInstructor = (Instructor)validationContext.ObjectInstance;
Using the custom validation attribute created above is very easy. It’s just like using the
built-in ones. We could use the entire class name used above, or omit the “Attribute” part:
[TenuredOnlyAfter2007Validation]
[Display(Name = "Is tenured")]
public bool IsTenured { get; set; }
To make our code friendlier, we can add an ErrorMessage:
[TenuredOnlyAfter2007Validation(ErrorMessage ="Tenured only offered after Aug. 15, 2007")]
[Display(Name = "Is tenured")]
public bool IsTenured { get; set; }
Make sure your Instructor.cs file contains the proper using directives. In our case,
we have.
using ASPBookProject.CustomValidations;
using System.ComponentModel.DataAnnotations;
Let’s test our newly added custom validation attribute. Add a hiring date before Aug.
15th, 2007, and make sure the Is tenured checkbox is checked. This should result in a
model validation error as shown in Fig. 10.9.
Leave Is tenured checked and choose a hiring date after Aug. 15th, 2007 (see
Fig. 10.10). If you attempt to submit this data, the error message from our custom val-
idation attribute should go away, meaning the model validation error for this part was
resolved.
10.6 Validation Text Styling 227
Fig. 10.9 displays the same view as seen in Fig. 10.8, but now a custom validation error is been
displayed for the Is tenured field. The error message displayed is “Tenured only offered after Aug.
15, 2007”
For this part, we would like to add some styling for the validation errors displayed by
<div asp-validation-summary="All"></div>
Inside this element, we can add in-line CSS to specify the colors to be used for the
error messages displayed. In particular, you can replace the line above with
<div asp-validation-summary="All" style="color:red"></div>
228 10 Model Validation
Fig. 10.10 Is similar to Fig. 10.9. In here, the custom validation error message is not displayed since
the Hiring Date is set to 08/16/2007, which now passes the custom validation logic we defined above
Fig. 10.11 Note how the validation summary is displayed with red text (because of the CSS styling
we added above)
into
[Required(ErrorMessage = "You must choose an academic rank!")]
[Display(Name = "Academic rank")]
public Ranks? Rank { get; set; }
Note: You must also make the type for Rank a nullable type, otherwise a default value
will be sent to Validation Attribute.
Now, trying to submit an empty form will display a friendlier error message when the
user does not select an academic rank (see Fig. 10.12).
230 10 Model Validation
Fig. 10.12 Shows an error message (of “You must choose an academic rank!”) when the user omits
selecting an Academic rank
Persistent Data: Entity Framework Core
11
11.1 Introduction
In this chapter, we’ll learn how to use the Entity Framework Core to work with data from a
database. In particular, we’ll see how to work with an SQLite Database and, alternatively,
with a Microsoft SQL Server database. For more information, you may want to check out
the following sources [63–65].
An object relational mapper is a mechanism that maps objects (whose classes are
called entities) to tables. This allows you to work with data from databases using an
object-oriented approach. Some examples of object relational mappers: Entity Framework
Core, Django, and Hibernate.
Entity Framework Core is an object relational mapping (ORM) framework. It allows
a .Net application (web application, console application, …) to work with the data stored
inside a database. Entity Framework Core provides a level of abstraction between an
application and a database and, as you will see below, it simplifies your data access (the
CRUD operations) from the database.
You won’t even need to know any SQL syntax in order to work with SQL databases
(via Entity Framework Core), although it certainly helps having some foundational knowl-
edge of databases (for example, understanding what primary keys and foreign keys
are).
In this chapter, we’ll mostly use an SQLite database. Then, we’ll show you how easy it
is to switch to a Microsoft SQL Server with very little change to your existing code (which
is one of the benefits of using a layer of abstraction between your (web) application and
your database).
© The Author(s), under exclusive license to Springer Nature Switzerland AG 2024 231
R. A. Mezei, Introduction to the Development of Web Applications Using
ASP .Net (Core) MVC, Synthesis Lectures on Computer Science,
https://doi.org/10.1007/978-3-031-30626-6_11
232 11 Persistent Data: Entity Framework Core
Here is a quick introduction to what classes are involved when using Entity Framework
Core. We’ll see them again in the next section, where we’ll go over the steps needed to
set up the Entity Framework Core to work with our application.
To work with various types of databases, the Entity Framework uses different types of
so-called provider classes:
There are two main classes we’ll make of when working with Entity Framework Core:
We call entity classes the (model) classes that we map to tables in the database.
Next, we’ll go over the steps needed to add Entity Framework Core to our web appli-
cation. These steps may look challenging the first time you see them, but you will only
need to go over them once. Once you set up Entity Framework Core in your application,
11.3 Add Entity Framework Core to Our Web Application 233
you’ll see how great the benefits are: it will be quite easy to perform operations on data
stored in databases.
We call entity classes the (model) classes that we want to map to tables in the database. In
our example, we will choose the Student and the Instructor classes as our entities.
Make sure each entity class contains one property that
Such a property will be mapped as the primary key of the corresponding table, to uniquely
identify each row in the table.
Very important! Before you proceed with this step, make sure your current code has no
compilation errors. Otherwise, you may not be able to install NuGet packages!
Next, we need to get access to Entity Framework Core classes. For this, we will install
the following package:
We will also install the following NuGet package that provides support for Migrations:
. Microsoft.EntityFrameworkCore.Design.
To install NuGet packages, inside Visual Studio, go to Tools > Manage NuGet Packages for
Solution … (note: one can also use the Package Manager Console). Then, in the window
that opens up, make sure to select the Browse tab!
In there, type in the name of the package you want to install, choose the project
where to install it, and the version of the package you want to get installed. For this book,
we searched for Microsoft.EntityFrameworkCore.Sqlite and we selected the
version 6.0.12. Then, on the right side of this window, make sure to click on the checkbox
next to your project name (ASPBookProject for us), then click on the Install button.
234 11 Persistent Data: Entity Framework Core
A Preview Changes window will appear that will indicate what changes Visual Studio
will make to your project. Click OK.
Then the License Acceptance window. We clicked on I Accept button. Wait for a few
seconds so the installation finishes, then confirm that you got no errors (check out the
Error List window in Visual Studio).
Now repeat the same steps for the other package: Microsoft.Entity
FrameworkCore.Design.
In this step, we’ll create a class derived from the class DbContext. As mentioned
above, this class will be responsible for connecting to the database and performing various
operations, among other things.
First, let’s create a new folder in our project and give it a name. We chose Data, but
feel free to give it a better name if you prefer.
In this newly added folder, Data, create a new class (we called it OurDbContext)
derived from DbContext. In it, make sure to add the using directive:
wukpi Oketquqhv0Gpvkv{HtcogyqtmEqtg=
pcogurceg CURDqqmRtqlgev0Fcvc
}
rwdnke encuu QwtFdEqpvgzv<"FdEqpvgzv
}
Ä
Ä
In this class, we will need to create a DbSet property for each table in the database.
The name that we choose for each DbSet property will be used when creating/using
the table in the database (of course we can use attributes to override this if needed). The
DbSet is a generic class, and it needs to know what entity class to work with—essentially
each row in the table will be mapped to an object/instance of class: the entity class.
In our book project, we will initially work with two entities, Student and
Instructor. Let’s choose a name for these tables. Let’s say Roster (the table of
students) and Instructors (the table of instructors). For this, we will need to define
the two DbSet properties shown below:
wukpi CURDqqmRtqlgev0Oqfgnu=
wukpi Oketquqhv0Gpvkv{HtcogyqtmEqtg=
pcogurceg CURDqqmRtqlgev0Fcvc
}
rwdnke encuu QwtFdEqpvgzv <"FdEqpvgzv
}
rwdnke FdUgv>Uvwfgpv@"Tquvgt"}"igv="ugv="Ä
rwdnke FdUgv>Kpuvtwevqt@"Kpuvtwevqtu"}"igv="ugv="Ä
Ä
Ä
11.3 Add Entity Framework Core to Our Web Application 235
Next, would like to put some sample data in our database when we create the tables
specified above. To do this, we can override the OnModelCreating method (see more
in [66]) inside OurDbContext class, as shown below. For completion, we included the
entire contents of OurDbContext.cs:
wukpi CURDqqmRtqlgev0Oqfgnu=
wukpi Oketquqhv0Gpvkv{HtcogyqtmEqtg=
wukpi Oketquqhv0Gzvgpukqpu0Jquvkpi=
pcogurceg CURDqqmRtqlgev0Fcvc
}
rwdnke encuu QwtFdEqpvgzv <"FdEqpvgzv
}
rwdnke FdUgv>Uvwfgpv@"Tquvgt"}"igv="ugv="Ä
rwdnke FdUgv>Kpuvtwevqt@"Kpuvtwevqtu"}"igv="ugv="Ä
oqfgnDwknfgt0Gpvkv{>Kpuvtwevqt@*+0JcuFcvc*"11rqrwncvg"vjg"vcdng"eqpvckpkpi"Kpuvtwevqtu
pgy Kpuvtwevqt*+
}
KpuvtwevqtKf"?"322.
HktuvPcog"?"$Ocgicp$.
NcuvPcog"?"$Dqtgt$.
KuVgpwtgf"?"hcnug.
JktkpiFcvg"?"FcvgVkog0Rctug*$423:/2:/37$+.
Tcpm"?"Tcpmu0CuukuvcpvRtqhguuqt
Ä.
pgy Kpuvtwevqt*+
}
KpuvtwevqtKf"?"422.
HktuvPcog"?"$Cpvqpkgvvc"$.
NcuvPcog"?"$Googtkej$.
KuVgpwtgf"?"vtwg.
JktkpiFcvg"?"FcvgVkog0Rctug*$4244/2:/37$+.
Tcpm"?"Tcpmu0CuuqekcvgRtqhguuqt
Ä.
pgy Kpuvtwevqt*+
}
KpuvtwevqtKf"?"522.
HktuvPcog"?"$Cpvqpkgvvc$.
NcuvPcog"?"$Nguej$.
KuVgpwtgf"?"hcnug.
JktkpiFcvg"?"FcvgVkog0Rctug*$4237/23/2;$+.
Tcpm"?"Tcpmu0HwnnRtqhguuqt
Ä.
pgy Kpuvtwevqt*+
}
KpuvtwevqtKf"?"622.
HktuvPcog"?"$Cplcnk$.
NcuvPcog"?"$Lcmwdqyumk$.
KuVgpwtgf"?"vtwg.
JktkpiFcvg"?"FcvgVkog0Rctug*$4238/23/32$+.
Tcpm"?"Tcpmu0Cflwpev
Ä
+=
oqfgnDwknfgt0Gpvkv{>Uvwfgpv@*+0JcuFcvc*"11rqrwncvg"vjg"vcdng"eqpvckpkpi"Uvwfgpvu
pgy Uvwfgpv*+
}
UvwfgpvKf"?"32.
HktuvPcog"?"$Gnkuc$.
236 11 Persistent Data: Entity Framework Core
NcuvPcog"?"$Yk|c$.
Oclqt"?"Oclqt0EU.
KuXgvgtcp"?"vtwg.
IRC"?"602.
CfokuukqpFcvg"?"FcvgVkog0Rctug*$4244/2:/37$+
Ä.
pgy Uvwfgpv*+
}
UvwfgpvKf"?"42.
HktuvPcog"?"$Ukogqp$.
NcuvPcog"?"$Ugpigt$.
Oclqt"?"Oclqt0KV.
KuXgvgtcp"?"vtwg.
IRC"?"5097.
CfokuukqpFcvg"?"FcvgVkog0Pqy
Ä.
pgy Uvwfgpv*+
}
UvwfgpvKf"?"52.
HktuvPcog"?"$Dnckug$.
NcuvPcog"?"$Pkejqncu$.
Oclqt"?"Oclqt0OCVJ.
KuXgvgtcp"?"vtwg.
IRC"?"50;:.
CfokuukqpFcvg"?"FcvgVkog0Rctug*$4242/2:/37$+
Ä
+=
Ä
Ä
Ä
Note: The type of the parameter used for options is a generic class,
DbContextOptions, for which we used our own OutDbContext class as a type,
hence the use of DbContextOptions<OurDbContext>.
Here is how the OurDbContext class looks like once we added the above code and
added a few comments:
11.3 Add Entity Framework Core to Our Web Application 237
pcogurceg CURDqqmRtqlgev0Fcvc
}
rwdnke encuu QwtFdEqpvgzv <"FdEqpvgzv
}
11wugf"vq"ocr"vq"vcdngu
rwdnke FdUgv>Uvwfgpv@"Tquvgt"}"igv="ugv="Ä
rwdnke FdUgv>Kpuvtwevqt@"Kpuvtwevqtu"}"igv="ugv="Ä
11eqpuvtwevqt
rwdnke QwtFdEqpvgzv*FdEqpvgzvQrvkqpu>QwtFdEqpvgzv@"qrvkqpu+"<"dcug*qrvkqpu+
}
Ä
11wugf"vq"uggf"vjg"fcvcdcugu"ykvj"uqog"fcvc
rtqvgevgf qxgttkfg xqkf QpOqfgnEtgcvkpi*OqfgnDwknfgt"oqfgnDwknfgt+
}
11"000 umkrrgf"”
Ä
Ä
Ä
Here is the reason for the paragraphs given above. We’ll need to make use of this
non-default constructor in the code below, to register OurDbContext class as a service.
We will use the code as shown here (do not add this code yet!):
dwknfgt0Ugtxkegu0CffFdEqpvgzv>QwtFdEqpvgzv@*
qrvkqpu"?@"qrvkqpu0WugUsnkvg*$eqppgevkqp"uvtkpi"vq"{qwt"fcvcdcug$+
+=
.
.
For a Microsoft SQL Server database (on a local server, or a remote one):
.
.
So, for our application, we could use the following (don’t add this code yet!):
But instead of the code above, we’ll go one step further and make use of the appset-
tings.json file which is a file where we can put various application configuration settings.
We’ll see this file again later in this book.
238 11 Persistent Data: Entity Framework Core
Open the appsettings.json file (you should be able to see it in Solution Explorer
window). In it, add the following entry to set a connection string:
This will register Entity Framework Core (OurDbContext) as a service. We are now
able to use it throughout our application (via dependency injection).
Our Program.cs file looks as follows:
wukpi CURDqqmRtqlgev0Fcvc=
wukpi CURDqqmRtqlgev0Ugtxkegu=
wukpi Oketquqhv0Gpvkv{HtcogyqtmEqtg=
xct dwknfgt"?"YgdCrrnkecvkqp0EtgcvgDwknfgt*ctiu+=
dwknfgt0Ugtxkegu0CffUkpingvqp>KO{HcmgFcvcUgtxkeg."O{HcmgFcvcUgtxkeg@*+="11qwt"fcvc"ugtxkeg
dwknfgt0Ugtxkegu0CffEqpvtqnngtuYkvjXkgyu*+="11cffu"ugtxkegu"pggfgf"hqt"eqpvtqnngtu"
dwknfgt0Ugtxkegu0CffFdEqpvgzv>QwtFdEqpvgzv@*
qrvkqpu"?@"
qrvkqpu0WugUsnkvg*dwknfgt0Eqphkiwtcvkqp0IgvEqppgevkqpUvtkpi*$DqqmEqppgevkqpUvtkpi$++
+=
xct crr"?"dwknfgt0Dwknf*+=11ugv"wr"okffngyctg"eqorqpgpvu0
crr0WugUvcvkeHkngu*+="11pggfgf"vq"ikxg"ceeguu"vq"hkngu"kp"yyytqqv
crr0WugTqwvkpi*+=""11cffu"tqwvg"ocvejkpi"vq"vjg"okffngyctg"rkrgnkpg
crr0OcrEqpvtqnngtTqwvg*"11oqfkhkgf"fghcwnv"tqwvkpi
pcog<"$fghcwnv$.
rcvvgtp<"$}eqpvtqnngt?KpuvtwevqtÄ1}cevkqp?KpfgzÄ1}kfAÄ$+=
crr0Twp*+=
One last step and we are done with this a little long configuration process. But as
you’ll see below, it is so worth it!
The last step we want to do is the following. Once all services are set up we would like
to call the method called EnsureCreated that will make sure our database is created:
We are finally finished with setting up Entity Framework Core for our application. If you
rebuild your application, you should see that an SQLite database file (names aspbook.db)
was created. See it in the Solution Explorer window (above the Program.cs file).
Inside Visual Studio (Solution Explorer window), if you right-click on the aspbook.db
file, you’ll see the option of opening it with a program (Open With …). Click on this
option.
Then click on the Add… button and select the DbBrowser application we installed at
the beginning of the book. On my computer, that location is C:\Program Files\DB Browser
for SQLite\DB Browser for SQLite.exe. Click the OK button. Then another OK button.
Once the database file opens in DbBrowser, you should be able to see the tables in
our database (make sure you are in the Database Structure tab). For us, we see that our
database currently has three tables:
. Instructors.
. Roster.
. sqlite_sequence.
You should note that for the tables named Instructors, and Roster, these are the names
we gave to our two DbSet properties of OurDbContext class.
Now click on the Browse Data tab and you’ll see the data in our tables. By default,
the Instructors table opens up.
You can choose to see the data from Roster table (see the dropdown menu at the top
of the table, on the very left side).
Very important: Make sure to click the Close Database button before you run your
web application, otherwise you may get read access errors. This button is on the top right
of the DbBrowser window.
From now on, if you ever wish to reopen a database, the easiest way to do so is as
follows: run DbBrowser, then under the File menu, at the bottom of the menu window
that opens, click on the database name shown in that list.
240 11 Persistent Data: Entity Framework Core
Now let’s make use of our database via Entity Framework core. We have everything set
up, so using the database will be quite easy.
To use Entity Framework Core in our Controller classes, we will need to “inject” it in
there and then make use of it. This is similar to injecting services seen in the previous
chapter:
with
11kplgevkpi"vjg"Gpvkv{Htcoygyqtm"Eqtg"/ QwtFdEqpvgzv
rtkxcvg tgcfqpn{ QwtFdEqpvgzv"afdEqpvgzv=
rwdnke KpuvtwevqtEqpvtqnngt*QwtFdEqpvgzv"fdEqpvgzv+
}
afdEqpvgzv"?"fdEqpvgzv=
Ä
Optionally (only if you wish to clean up the code), you can delete all code related to
MyFakeService, since we won’t use this anymore.
Next, we will update each action, to make use of the Entity Framework Core. All we need
is to replace _fakeData.InstructorsList with _dbContext.Instructors.
pcogurceg CURDqqmRtqlgev0Eqpvtqnngtu
}
rwdnke encuu KpuvtwevqtEqpvtqnngt <"Eqpvtqnngt
}
11kplgevkpi"vjg"Gpvkv{Htcoygyqtm"Eqtg"/ QwtFdEqpvgzv
rtkxcvg tgcfqpn{ QwtFdEqpvgzv"afdEqpvgzv=
rwdnke KpuvtwevqtEqpvtqnngt*QwtFdEqpvgzv"fdEqpvgzv+
}
afdEqpvgzv"?"fdEqpvgzv=
Ä
rwdnke KCevkqpTguwnv"Kpfgz*+""
}
tgvwtp Xkgy*afdEqpvgzv0Kpuvtwevqtu+="11yknn"wug"vjg"Kpfgz0eujvon"xkgy
Ä
rwdnke KCevkqpTguwnv"Fkurnc{Cnn*+
}
tgvwtp Xkgy*$Kpfgz$."afdEqpvgzv0Kpuvtwevqtu+=11yknn"wug"vjg"Kpfgz0eujvon"xkgy
Ä
rwdnke KCevkqpTguwnv"UjqyCnn*+
}
tgvwtp TgfktgevVqCevkqp*$Kpfgz$."afdEqpvgzv0Kpuvtwevqtu+=
Ä
kh *kpuvt"#?"pwnn+"11ycu"cp"kpuvtwevqt"hqwpfA
tgvwtp Xkgy*kpuvt+=
11kh"pq"kpuvtwevqt"ycu"hqwpf"000
tgvwtp PqvHqwpf*+=
Ä
]JvvrIgv_
rwdnke KCevkqpTguwnv"Cff*+
}
tgvwtp Xkgy*+=
Ä
]JvvrRquv_
rwdnke KCevkqpTguwnv"Cff*Kpuvtwevqt"pgyKpuvtwevqt+
}
kh *#OqfgnUvcvg0KuXcnkf+"11kh"vjg"fcvc"ku"kpxcnkf
tgvwtp Xkgy*+="11iq"dcem"vq"vjg"xkgy
afdEqpvgzv0Kpuvtwevqtu0Cff*pgyKpuvtwevqt+="11cff"vjg"pgy"kpuvtwevqt"vq"qwt"nkuv
afdEqpvgzv0UcxgEjcpigu*+=
tgvwtp TgfktgevVqCevkqp*$Kpfgz$+=
Ä
242 11 Persistent Data: Entity Framework Core
]JvvrIgv_
rwdnke KCevkqpTguwnv"Gfkv*kpv kf+
}
11yg"ujqwnf"nqqm"hqt"vjg"kpuvcpeg"vjcv"jcu"vjg"ikxgp"Kf
11"""000"ncvgt"yg)nn"ugctej"kp"vjg"fcvcdcug
KpuvtwevqtA"kpuvt"?"afdEqpvgzv0Kpuvtwevqtu0HktuvQtFghcwnv*kpuv"?@"kpuv0KpuvtwevqtKf"??"kf+=
kh *kpuvt"#?"pwnn+"11kh"hqwpf."ugpf"kv"vq"vjg"xkgy
tgvwtp Xkgy*kpuvt+=
11kh"pq"ocvejkpi"kpuvtwevqt"ycu"hqwpf"000
tgvwtp PqvHqwpf*+=
Ä
]JvvrRquv_
rwdnke KCevkqpTguwnv"Gfkv*Kpuvtwevqt"kpuvtwevqtEjcpigu+
}
kh *#OqfgnUvcvg0KuXcnkf+"11kh"vjg"fcvc"ku"kpxcnkf
tgvwtp Xkgy*+="11iq"dcem"vq"vjg"xkgy
11hkpf"vjg"kpuvtwevqt"htqo"KpuvtwevqtNkuv
11"""yjq"jcu"vjg"ucog"KpuvtwevqtKf"cu"vjg"ejcpigu0KpuvtwevqtKf
KpuvtwevqtA"kpuvt"?"afdEqpvgzv0Kpuvtwevqtu0HktuvQtFghcwnv*kpuvt"?@"kpuvt0KpuvtwevqtKf"??"
kpuvtwevqtEjcpigu0KpuvtwevqtKf+=
kh *kpuvt"#?"pwnn+"11kh"hqwpf."ejcpig"vjg"xcnwgu"kp"KpuvtwevqtuNkuv"vq"ocvej"vjg"ejcpigu
}
kpuvt0NcuvPcog"?"kpuvtwevqtEjcpigu0NcuvPcog=
kpuvt0KuVgpwtgf"?"kpuvtwevqtEjcpigu0KuVgpwtgf=
kpuvt0JktkpiFcvg"?"kpuvtwevqtEjcpigu0JktkpiFcvg=
kpuvt0Tcpm"?"kpuvtwevqtEjcpigu0Tcpm=
afdEqpvgzv0UcxgEjcpigu*+=
Ä
tgvwtp TgfktgevVqCevkqp*$Kpfgz$+=
Ä
]JvvrIgv_
rwdnke KCevkqpTguwnv"Fgngvg*kpv kf+
}
11yg"ujqwnf"nqqm"hqt"vjg"kpuvcpeg"vjcv"jcu"vjg"ikxgp"Kf
11"""000"ncvgt"yg)nn"ugctej"kp"vjg"fcvcdcug
KpuvtwevqtA"kpuvt"?"afdEqpvgzv0Kpuvtwevqtu0HktuvQtFghcwnv*kpuv"?@"kpuv0KpuvtwevqtKf"??"kf+=
kh *kpuvt"#?"pwnn+"11kh"hqwpf."ugpf"kv"vq"vjg"xkgy
tgvwtp Xkgy*kpuvt+=
11kh"pq"kpuvtwevqt"ycu"hqwpf"000
tgvwtp PqvHqwpf*+=
]JvvrRquv_
rwdnke KCevkqpTguwnv"FgngvgEqphktogf*kpv kpuvtwevqtKf+
}
11yg"ujqwnf"nqqm"hqt"vjg"kpuvcpeg"vjcv"jcu"vjg"ikxgp"Kf
11"""000"ncvgt"yg)nn"ugctej"kp"vjg"fcvcdcug
KpuvtwevqtA"kpuvt"?"afdEqpvgzv0Kpuvtwevqtu0HktuvQtFghcwnv*kpuv"?@"kpuv0KpuvtwevqtKf"??"
kpuvtwevqtKf+=
kh *kpuvt"#?"pwnn+"11kh"hqwpf."fgngvg"kv"htqo"nkuv
}
afdEqpvgzv0Kpuvtwevqtu0Tgoqxg*kpuvt+=
afdEqpvgzv0UcxgEjcpigu*+=
tgvwtp TgfktgevVqCevkqp*$Kpfgz$+=
Ä
11kh"pq"kpuvtwevqt"ycu"hqwpf"000
tgvwtp PqvHqwpf*+=
Ä
Ä
Ä
Currently, if we go to Add a new instructor (see Fig. 11.1), we are asked to provide an
InstructorId.
This is.
The good news is that we can get the Id automatically generated. Go to the Add view
and remove the field that asks the user to enter an id. Remove the following lines from
Add.cshtml:
>ncdgn cur/hqt?$KpuvtwevqtKf$@>1ncdgn@
>kprwv cur/hqt?$KpuvtwevqtKf$ 1@
>urcp cur/xcnkfcvkqp/hqt?$KpuvtwevqtKf$@>1urcp@
>dt@
Fig. 11.2 Is similar to the one seen in Fig. 11.1, but it no longer contains any input field used for
InstructorId
Fig. 11.3 Shows the Index view displayed in a browser. It contains a table with multiple rows, one
for each Instructor
Now, on your own, please modify some of the existing instructors, and delete one.
Then, rebuild your application and check that the changes are persistent. Those changes
are saved in a database, so changes should survive web application restarts.
11.4.5 EnsureDeleted
As we make modifications to our tables, you may want to add the following line right
before EnsureCreated method calls (inside Program.cs):
eqpvgzv0Fcvcdcug0GpuwtgFgngvgf*+="11kh"qwt"fcvcdcug"gzkuvu."vjgp"gtcug"kv#
11.5 Practice: Update the StudentController Class 245
This way our database will be recreated each time we rebuild our application. Com-
ment this line out and only use it when you need to recreate the database (for example,
when we are modifying a property type, or other scenarios that will lead to compilation
errors related to entity framework).
To have some more functionality to work with later, let’s update the
StudentController class.
To inject entity framework (and similarly to any other service), we need the following:
Now we have access to our database via Entity Framework Core. Next, let’s
create/update actions and make use of our database.
wukpi CURDqqmRtqlgev0Fcvc=
wukpi CURDqqmRtqlgev0Oqfgnu=
wukpi Oketquqhv0CurPgvEqtg0Oxe=
wukpi Oketquqhv0Gpvkv{HtcogyqtmEqtg=
pcogurceg CURDqqmRtqlgev0Eqpvtqnngtu
}
rwdnke encuu UvwfgpvEqpvtqnngt <"Eqpvtqnngt
}
rtkxcvg tgcfqpn{ QwtFdEqpvgzv"afdEqpvgzv="11kplgev"QwtFdEqpvgzv"kp"vjku"encuu
rwdnke UvwfgpvEqpvtqnngt*QwtFdEqpvgzv"qwtFdEqpvgzv+
}
afdEqpvgzv ?"qwtFdEqpvgzv=
Ä
rwdnke KCevkqpTguwnv"Kpfgz*+
}
tgvwtp Xkgy*afdEqpvgzv0Tquvgt+=
Ä
kh *uv"#?"pwnn+"11ycu"c"uvwfgpv"hqwpfA
tgvwtp Xkgy*uv+=
11kh"pq"uvwfgpv"ycu"hqwpf"000
tgvwtp PqvHqwpf*+=
Ä
]JvvrIgv_
rwdnke KCevkqpTguwnv"Cff*+
}
tgvwtp Xkgy*+=
Ä
]JvvrRquv_
rwdnke KCevkqpTguwnv"Cff*Uvwfgpv"pgyUvwfgpv+
}
kh *#OqfgnUvcvg0KuXcnkf+"11kh"vjg"fcvc"ku"kpxcnkf
tgvwtp Xkgy*+="11iq"dcem"vq"vjg"xkgy
afdEqpvgzv0Tquvgt0Cff*pgyUvwfgpv+="11cff"vjg"pgy"uvwfgpv"vq"qwt"nkuv
afdEqpvgzv0UcxgEjcpigu*+=
tgvwtp TgfktgevVqCevkqp*$Kpfgz$+=
Ä
]JvvrIgv_
rwdnke KCevkqpTguwnv"Gfkv*kpv kf+
}
11yg"ujqwnf"nqqm"hqt"vjg"kpuvcpeg"vjcv"jcu"vjg"ikxgp"Kf
UvwfgpvA"uv"?"afdEqpvgzv0Tquvgt0HktuvQtFghcwnv*uv"?@"uv0UvwfgpvKf"??"kf+=
kh *uv"#?"pwnn+"11kh"hqwpf."ugpf"kv"vq"vjg"xkgy
tgvwtp Xkgy*uv+=
11kh"pq"uvwfgpv"ycu"hqwpf"000
tgvwtp PqvHqwpf*+=
Ä
]JvvrRquv_
rwdnke KCevkqpTguwnv"Gfkv*Uvwfgpv"uvwfgpvEjcpigu+
}
kh *#OqfgnUvcvg0KuXcnkf+"11kh"vjg"fcvc"ku"kpxcnkf
tgvwtp Xkgy*+="11iq"dcem"vq"vjg"xkgy
11hkpf"vjg"uvwfgpv"yjq"jcu"vjg"ucog"UvwfgpvKf"cu"vjg"uvwfgpvEjcpigu0UvwfgpvKf
UvwfgpvA"uv"?"afdEqpvgzv0Tquvgt0HktuvQtFghcwnv*uvwfgpv"?@"uvwfgpv0UvwfgpvKf"??"
uvwfgpvEjcpigu0UvwfgpvKf+=
kh *uv"#?"pwnn+"11kh"hqwpf."ejcpig"vjg"xcnwgu"kp"vjg"fcvcdcug"vq"ocvej"qwt"ejcpigu
}
uv0NcuvPcog"?"uvwfgpvEjcpigu0NcuvPcog=
11"cff"qvjgt"rtqrgtvkgu."cu"pggfgf
afdEqpvgzv0UcxgEjcpigu*+=
Ä
tgvwtp TgfktgevVqCevkqp*$Kpfgz$+=
Ä
11.6 How to Use Microsoft SQL Server Instead of SQLite (Optional) 247
]JvvrIgv_
rwdnke KCevkqpTguwnv"Fgngvg*kpv kf+
}
11yg"ujqwnf"nqqm"hqt"vjg"kpuvcpeg"vjcv"jcu"vjg"ikxgp"Kf
UvwfgpvA"uv"?"afdEqpvgzv0Tquvgt0HktuvQtFghcwnv*uvwfgpv"?@"uvwfgpv0UvwfgpvKf"??"kf+=
kh *uv"#?"pwnn+"11kh"hqwpf."ugpf"kv"vq"vjg"xkgy
tgvwtp Xkgy*uv+=
11kh"pq"uvwfgpv"ycu"hqwpf"000
tgvwtp PqvHqwpf*+=
]JvvrRquv_
rwdnke KCevkqpTguwnv"FgngvgEqphktogf*kpv uvwfgpvKf+
}
11yg"ujqwnf"nqqm"hqt"vjg"kpuvcpeg"vjcv"jcu"vjg"ikxgp"Kf
UvwfgpvA"uv"?"afdEqpvgzv0Tquvgt0HktuvQtFghcwnv*uvwfgpv"?@"uvwfgpv0UvwfgpvKf"??"uvwfgpvKf+=
kh *uv"#?"pwnn+"11kh"hqwpf."fgngvg"kv"htqo"nkuv
}
afdEqpvgzv0Tquvgt0Tgoqxg*uv+=
afdEqpvgzv0UcxgEjcpigu*+=
tgvwtp TgfktgevVqCevkqp*$Kpfgz$+=
Ä
11kh"pq"uvwfgpv"ycu"hqwpf"000
tgvwtp PqvHqwpf*+=
Ä
rwdnke KCevkqpTguwnv"IqVqIqqng*+
}
tgvwtp Tgfktgev*$jvvru<11yyy0iqqing0eqo1$+=
Ä
rwdnke KCevkqpTguwnv"CpqvjgtKpfgz*+
}
tgvwtp TgfktgevVqCevkqp*$Kpfgz$+=
Ä
Ä
Ä
In this section, we would like to show you how easy it is to switch your web application
such that instead of using a SQLite database it uses an Microsoft SQL Server database.
IMPORTANT: Before you continue, you may want to make a copy of your project.
The remaining chapters of this book will continue with SQLite.
For this exercise, you will need to have LocalDB installed on your machine unless you
choose to use another existing installation of Microsoft SQL server.
One way to install the SQL Server Express 2019 LocalDB database is by opening Visual
Studio Installer. Then click on the Modify button. Then, go to Individual Components
tab, and make sure the option SQL Server Express 2019 LocalDB is checked.
248 11 Persistent Data: Entity Framework Core
And the last step, update the connection string set in appsettings.json so it points to
our SQL Server database instead:
$DqqmEqppgevkqpUvtkpi$<"$Fcvc"Uqwteg?*nqecnfd+^^OUUSNNqecnFD=Kpkvkcn"Ecvcnqi?ocuvgt=Kpvgitcvgf"
Ugewtkv{?Vtwg=Eqppgev"
Vkogqwv?52=Gpet{rv?Hcnug=CrrnkecvkqpKpvgpv?TgcfYtkvg=OwnvkUwdpgvHcknqxgt?Hcnug=$
That’s it. Your controllers should work the same with this new database. Do you see the
advantage of using Entity Framework and how it provides a layer of abstraction between
our code and the database?
Run your application. You should see how the database was recreated with our seed
data (see Fig. 11.4).
Fig. 11.4 Shows the Index view that displays information from our seed data
11.6 How to Use Microsoft SQL Server Instead of SQLite (Optional) 249
To check the data that exists in our database, from inside Visual Studio, go to View
> SQL Server Object Explorer. From the SQL Server Object Explorer window, check out
the tables created in the master database (under SQL Server > (localdb)\MSSQLLocalDB
… > Databases > System Databases > master > Tables, you should see dbo.Instructors
and dbo.Roster).
Right-click on any of the two tables and select View Data. This should allow you to
see the data that is currently stored in that table.
Create a new instructor, for example (Fig. 11.5).
Then check the database again. You should see that a new row is added into the
Instructors table (if needed, make sure to press the refresh button). You should also note
that the password was saved as plain text. We’ll address this issue in Chap. 14.
Consistent Look: Layouts, Friendly Error
Pages, and Environments
12
Before we continue, make sure to return back to the version of the project that uses the
SQLite database (feel free to continue with the Microsoft SQL Server database if you
prefer, in our book we will use our SQLite database).
This chapter contains several smaller concepts put together in one chapter. In this
chapter, among other things, we’ll make our web application look pretty. We’ll add a
filter button, we’ll create a consistent look and feel for our application, and we’ll create
friendly error pages.
In this section, we will add a filter functionality that would allow a user to narrow down
the results shown in the Instructors table displayed in Index. In our example, we
will allow users to filter results based on the LastName field. For more information on
this, read the following source [68].
The first step is adding an input field (search field) and a button to narrow the results
based on the input field’s value. Where would you add these fields? In which file?
We can start by adding an input field and two buttons (one for filter, one for reset filter/
cancel) in the Index.cshtml, right after the <H1> element:
>kprwv v{rg?$vgzv$ rncegjqnfgt?$ ncuv"pcog"kpenwfgu"000$ 1@
>dwvvqp@Hknvgt"tguwnvu>1dwvvqp@
>dwvvqp@Engct"vjg"hknvgt>1dwvvqp@
© The Author(s), under exclusive license to Springer Nature Switzerland AG 2024 251
R. A. Mezei, Introduction to the Development of Web Applications Using
ASP .Net (Core) MVC, Synthesis Lectures on Computer Science,
https://doi.org/10.1007/978-3-031-30626-6_12
252 12 Consistent Look: Layouts, Friendly Error Pages, and Environments
Fig. 12.1 Shows a part of the Index view in a browser. It contains an input box and two buttons
The output looks the same as above, but now we can easily add functionality to those
buttons.
The first button can be turned into a submit button. Also, when we click on that
submit button, we want the request to be sent to the Index action. Let’s make the requests
a GET requests, so we can see the searched values in the URL. Lastly, the input text
value will not be sent to the server unless we give it a name, so let’s give it a name, say
“SearchByLastName”:
>hqto cur/cevkqp?$Kpfgz$ ogvjqf?$igv$@
>kprwv v{rg?$vgzv$ rncegjqnfgt?$ncuv"pcog"kpenwfgu"000$ pcog?$UgctejD{NcuvPcog$ 1@
>dwvvqp v{rg?$uwdokv$@Hknvgt"tguwnvu>1dwvvqp@
>dwvvqp@Engct"vjg"hknvgt>1dwvvqp@
>1hqto@
Let’s test what we have got so far. Type in some text in the textbox (see Fig. 12.2),
then click on the submit button.
Fig. 12.2 Is similar to Fig. 12.1, but now the input element contains some text (“Claude”)
12.1 Filter Results 253
You should see that the text we enter in the input box above will appear in the URL
(once we click on the Filter Results button), along with the name we defined above:
SearchByLastName:
nqecnjquv<73471AUgctejD{NcuvPcog?Encwfg
As of right now, the Index action sends all instructors, the entire list, to the view to
be displayed. It does not yet use our value from the textbox (represented by the variable
SearchByLastName). We’ll fix this next.
Let’s add code to make use of this value. Change the Index action (from
InstructorController) to match the code below.
Namely, we will add one parameter, which matches the name of the text field variable:
SearchByLastName, and then will make use of it. How is the model binding helping
us in here? The model binding grabs the value of SearchByLastName from the HTTP
request (the URL in this case) and passed it to the Index action. Then we narrow down
the results based on the parameter: SearchByLastName. And lastly, we pass the results
to the View.
rwdnke KCevkqpTguwnv"Kpfgz*uvtkpi UgctejD{NcuvPcog+
}
11xct"kpuvtwevqtu"?"htqo"kpuvt"kp"afdEqpvgzv0Kpuvtwevqtu
11"""""""""""""""""ugngev"kpuvt="11yg"uvctv"ykvj"cnn"kpuvtwevqtu"/ NKPS"u{pvcz
xct kpuvtwevqtu"?"afdEqpvgzv0Kpuvtwevqtu0CuGpwogtcdng*+="11cp"cnvgtpcvkxg
kh *UgctejD{NcuvPcog"#?"pwnn+"11pcttqy"fqyp"qwt"tguwnvu
}
kpuvtwevqtu"?"kpuvtwevqtu0Yjgtg*kpuvt"?@"kpuvt0NcuvPcog0Eqpvckpu*UgctejD{NcuvPcog++=
Ä""""
tgvwtp Xkgy*kpuvtwevqtu+=
Ä""""
This should narrow down the results. Try, for example, the following (see Fig. 12.3).
Then click on the Filter results button (you’ll be taken to a page with the URL:
localhost:5125/?SearchByLastName=er), as seen in Fig. 12.4.
Fig. 12.3 Is similar to Fig. 12.1, but now the input element contains some text (“er”)
254 12 Consistent Look: Layouts, Friendly Error Pages, and Environments
Fig. 12.4 Shows the result of narrowing the table listed in Index view. In this example, only rows
containing “er” in the Last name column are displayed
You should get the correct results. Also, if you look at the URL, it contains the filtering
value (because we chose to use the GET method!).
There is only one problem though. We would like to have the filtering value (chosen
by the user) stay in the textbox. That would be more user-friendly. One solution is to
make use of the ViewBag object (set a value in the Index action) and pass this value
to the Index view, then use this value in the view as the default value for the textbox.
In the Index action, before the return statement, add the following line:
XkgyDci0UgctejD{NcuvPcog"?"UgctejD{NcuvPcog="11rcuu"vjku"xcnwg"vq"vjg"xkgy
into
>kprwv v{rg?$vgzv$ rncegjqnfgt?$ncuv"pcog"kpenwfgu"000$ pcog?$UgctejD{NcuvPcog$
xcnwg?$BXkgyDci0UgctejD{NcuvPcog$ 1@
That’s it. If you test your work, the web application should now keep the value entered
in the text field (see Fig. 12.5).
In here, we would like to be able to click on the Clear the filter button to clear the textbox
and display the entire list. One way to do this is to give our text field an id (we chose:
LastNameFilter), so we can easily refer to it by id, using JavaScript. Below we added
id="LastNameFilter":
>kprwv v{rg?$vgzv$ rncegjqnfgt?$ncuv"pcog"kpenwfgu"000$ kf?$NcuvPcogHknvgt$
pcog?$UgctejD{NcuvPcog$ xcnwg?$BXkgyDci0UgctejD{NcuvPcog$ 1@
12.2 Filter Results Using a Dropdown List (Optional) 255
Fig. 12.5 After the user clicks on the Filter results button, the contents of the filtering input box
(“er” in this example) are preserved
Fig. 12.6 Shows the mouse hovering above the Clear the filter button
Then, for the clear button, we add the JavaScript code that set the text field above to
null. In particular, we add the JavaScript code to respond to the click event (when the
user clicks on this button). Change the Clear the filter <BUTTON> element to match the
code below:
>dwvvqp qpenkem?$fqewogpv0igvGngogpvD{Kf*)NcuvPcogHknvgt)+0xcnwg"?"pwnn$@Engct"vjg"
hknvgt>1dwvvqp@
In this part, we would like to add a dropdown list so the user can select to only view
specific ranks (for example, Assistant Professors). To make it a little more complex, we
would like to only allow in the dropdown list options that are available. For example, if
there are no Adjunct instructors in our table, this option should not be available in the
dropdown list.
256 12 Consistent Look: Layouts, Friendly Error Pages, and Environments
Fig. 12.7 Shows a figure similar to the one in Fig. 12.5. But the input field was changed to a
dropdown list selector
In the Index action (of InstructorController class), we first need to create the
list of available ranks and send it to the view (via the dynamic object ViewBag). Add
this code right before the return View statement.
xct CxckncdngTcpmu"?"htqo kpuvt"kp kpuvtwevqtu"11igv"c"nkuv"qh"cxckncdng"tcpmu
qtfgtd{ kpuvt0Tcpm
ugngev kpuvt0Tcpm=
11rcuu"vjg"cxckncdng"tcpmu"vq"vjg"xkgy"/ fkuvkpev"qpn{#
XkgyDci0CxckncdngTcpmu"?"CxckncdngTcpmu0Fkuvkpev*+="
Add the following line to the form in the Index view, right before the text field used for
filtering:
>ugngev cur/kvgou?$B*pgy UgngevNkuv*XkgyDci0CxckncdngTcpmu."$Cnn$++$@
>qrvkqp xcnwg?$$@Cnn tcpmu>1qrvkqp@
>1ugngev@
In order to be able to filter our results based on the dropdown list, we will need to pass
the dropdown selected value from the Index view back to the Index action and narrow
12.2 Filter Results Using a Dropdown List (Optional) 257
down the results based on that value. For this, add a variable name to the <SELECT>
element (the dropdown list) we used name="SelectedRank":
>ugngev cur/kvgou?$B*pgy UgngevNkuv*XkgyDci0CxckncdngTcpmu."$Cnn$++$ pcog?$UgngevgfTcpm$@
>qrvkqp xcnwg?$$@Cnn"tcpmu>1qrvkqp@
>1ugngev@
Then add a parameter to the Index action (make sure it matches the name above so
the model binding system will help passing it to the action), and make use of it to filter
down the results:
11pcttqy"fqyp"kpuvtwevqtu"dcugf"qp"UgngevgfTcpm<
kh*#uvtkpi0KuPwnnQtGorv{*UgngevgfTcpm+"+
}
kpuvtwevqtu"?"kpuvtwevqtu0Yjgtg*kpuvt"?@"kpuvt0Tcpm??Gpwo0Rctug>Tcpmu@*UgngevgfTcpm++=
Ä
Fig. 12.8 Is similar to Fig. 12.6, but the dropdown list now only contains rank values that are
contained in at least one row from the table
Fig. 12.9 Shows the result of narrowing the table by rank (using the dropdown list) and last name
(using the input field)
258 12 Consistent Look: Layouts, Friendly Error Pages, and Environments
Here is the entire code for the Index action and the newly added <FORM> in the Index
view so you can check your work:
rwdnke KCevkqpTguwnv"Kpfgz*uvtkpi UgctejD{NcuvPcog."uvtkpi UgngevgfTcpm+
}
11xct"kpuvtwevqtu"?"htqo"kpuvt"kp"afdEqpvgzv0Kpuvtwevqtu
11"""""""""""""""""ugngev"kpuvt="11yg"uvctv"ykvj"cnn"kpuvtwevqtu"/ NKPS"u{pvcz
xct kpuvtwevqtu"?"afdEqpvgzv0Kpuvtwevqtu0CuGpwogtcdng*+="11cp"cnvgtpcvkxg
kh *UgctejD{NcuvPcog"#?"pwnn+"11pcttqy"fqyp"qwt"tguwnvu
}
kpuvtwevqtu"?"kpuvtwevqtu0Yjgtg*kpuvt"?@"kpuvt0NcuvPcog0Eqpvckpu*UgctejD{NcuvPcog++=
Ä
11pcttqy"fqyp"kpuvtwevqtu"dcugf"qp"UgngevgfTcpm<
kh *#uvtkpi0KuPwnnQtGorv{*UgngevgfTcpm++
}
kpuvtwevqtu"?"kpuvtwevqtu0Yjgtg*kpuvt"?@"kpuvt0Tcpm"??"Gpwo0Rctug>Tcpmu@*UgngevgfTcpm++=
Ä
XkgyDci0UgctejD{NcuvPcog"?"UgctejD{NcuvPcog="11rcuu"vjku"xcnwg"vq"vjg"xkgy
tgvwtp Xkgy*kpuvtwevqtu+=
Ä
>hqto cur/cevkqp?$Kpfgz$ ogvjqf?$igv$@
>ugngev cur/kvgou?$B*pgy UgngevNkuv*XkgyDci0CxckncdngTcpmu."$Cnn$++$ pcog?$UgngevgfTcpm$@
>qrvkqp xcnwg?$$@Cnn"tcpmu>1qrvkqp@
>1ugngev@
>kprwv v{rg?$vgzv$ rncegjqnfgt?$ncuv"pcog"kpenwfgu"000$ kf?$NcuvPcogHknvgt$
pcog?$UgctejD{NcuvPcog$ xcnwg?$BXkgyDci0UgctejD{NcuvPcog$ 1@
>dwvvqp v{rg?$uwdokv$@Hknvgt"tguwnvu>1dwvvqp@
>dwvvqp qpenkem?$fqewogpv0igvGngogpvD{Kf*)NcuvPcogHknvgt)+0xcnwg"?"pwnn$@Engct"vjg"hknvgt>1dwvvqp@
>1hqto@
A professional web application should be easy to navigate, intuitive, and with a consistent
look and feel from one page to the next one. Our web application is (as of right now) far
from providing a consistent user experience. In this section, we’ll introduce layouts and
see how they can be used to help us create a nice consistent style for all our webpages.
Take a look, for example, at any of the following websites:
. https://www.amazon.com/
. https://www.microsoft.com/
. https://moodle.stmartin.edu/.
As you click on various links on those sites, you should notice that all pages belonging
to one website have the same look and feel. How can we achieve this?
One (bad!) option is to copy and paste the same template into every view. That should
do the job, but what happens if you want to add a new link or remove an obsolete one?
You would have to make those changes in every view page.
12.3 Consistent Webpages—Using Razor Layouts 259
A better solution is to use Layout pages (see below). Let’s see the steps involved in
using a layout. To learn more about them, we recommend you the following source [69].
To create layouts, first you need to know the location. We put them inside the folder:
Views > Shared. Let’s create the Shared folder. In the Solution Explorer window, right-
click on Views folder, then select Add > New Folder.
Then, right-click on the Shared folder and select Add > New Item then select Razor
Layout. We will use the default name ( Layout.cshtml), but feel free to choose another
name if you prefer. Let’s see what code was included in the template:
>#FQEV[RG jvon@
>jvon@
>jgcf@
>ogvc pcog?$xkgyrqtv$ eqpvgpv?$ykfvj?fgxkeg/ykfvj$ 1@
>vkvng@BXkgyDci0Vkvng>1vkvng@
>1jgcf@
>dqf{@
>fkx@
BTgpfgtDqf{*+
>1fkx@
>1dqf{@
>1jvon@
You should note that a layout looks pretty much like any other HTML page (and like
the views we’ve seen so far, but we’ll soon simplify our views). Two important things to
note in this code:
That’s pretty much for now. We’ll come back to this once we change our views to make
use of this layout.
This will make the contents of the views to be included inside the layout, at the point
where the RenderBody function is being called. You will need to copy this directive in
every view that makes use of our layout.
Side note: In this book, we only use one layout, but it is possible to define more than
one layout files (one at a time)!
If you chose a different name for your layout, make sure to use that name inside the
string above.
. Keep only the HTML code that is inside the <BODY> element (not including the
<BODY> tags).
– Also, keep the @model directive—if any.
. For each view, set a title (using the ViewBag.Title).
. If there are any CSS or JavaScript links, we can move them into the Layout file (they
will now be available to all the views that make use of our layout).
>jvon@
>jgcf@
>ogvc pcog?$xkgyrqtv$ eqpvgpv?$ykfvj?fgxkeg/ykfvj$ 1@
>nkpm jtgh?$jvvru<11efp0lufgnkxt0pgv1pro1dqqvuvtcrB704051fkuv1euu1dqqvuvtcr0okp0euu$ tgn?$uv{ngujggv$@
>uetkrv ute?$jvvru<11efp0lufgnkxt0pgv1pro1dqqvuvtcrB704051fkuv1lu1dqqvuvtcr0dwpfng0okp0lu$@>1uetkrv@
>vkvng@BXkgyDci0Vkvng>1vkvng@
>1jgcf@
>dqf{@
>fkx@
BTgpfgtDqf{*+
>1fkx@
>1dqf{@
>1jvon@
12.3 Consistent Webpages—Using Razor Layouts 261
>j3@Cnn"kpuvtwevqtu>1j3@
>hqto cur/cevkqp?$Kpfgz$ ogvjqf?$igv$@
>ugngev cur/kvgou?$B*pgy UgngevNkuv*XkgyDci0CxckncdngTcpmu."$Cnn$++$ pcog?$UgngevgfTcpm$@
>qrvkqp xcnwg?$$@Cnn"tcpmu>1qrvkqp@
>1ugngev@
>kprwv v{rg?$vgzv$ rncegjqnfgt?$ncuv"pcog"kpenwfgu"000$
kf?$NcuvPcogHknvgt$ pcog?$UgctejD{NcuvPcog$ xcnwg?$BXkgyDci0UgctejD{NcuvPcog$ 1@
>dwvvqp v{rg?$uwdokv$@Hknvgt"tguwnvu>1dwvvqp@
>dwvvqp qpenkem?$fqewogpv0igvGngogpvD{Kf*)NcuvPcogHknvgt)+0xcnwg"?"pwnn$@Engct"vjg"hknvgt>1dwvvqp@
>1hqto@
Bkh *Oqfgn0Eqwpv*+"@"2+
}
>VCDNG encuu?$vcdng"vcdng/fctm"vcdng/jqxgt$@
>VJGCF@
>VT@
>VJ@>ncdgn cur/hqt?$Hktuv*+0HktuvPcog$@>1ncdgn@>1VJ@
>VJ@>ncdgn cur/hqt?$Hktuv*+0NcuvPcog$@>1ncdgn@>1VJ@
>VJ@>ncdgn cur/hqt?$Hktuv*+0Tcpm$@>1ncdgn@>1VJ@
>VJ@Fgvcknu"*JVON"jgnrgt+>1VJ@
>VJ@Fgvcknu"*vci"jgnrgt+>1VJ@
>VJ@Gfkv">1VJ@
>VJ@Fgngvg">1VJ@
>1VT@
>1VJGCF@
>VDQF[@
>1VCDNG@
Ä
gnug
}
>j4@Pq"kpuvtwevqtu"hqwpf#">1j4@
Ä
>c cur/cevkqp?$Cff$@Cff"c"pgy"kpuvtwevqt">1c@
>j3@Kpuvtwevqt"BOqfgn0NcuvPcog"fgvcknu>1j3@
>r@>ncdgn cur/hqt?$BOqfgn0HktuvPcog$@>1ncdgn@<""BJvon0Fkurnc{Hqt*o"?@"o0HktuvPcog+>1r@
>r@>ncdgn cur/hqt?$BOqfgn0NcuvPcog$@>1ncdgn@<""BJvon0Fkurnc{Hqt*o"?@"o0NcuvPcog+>1r@
>r@>ncdgn cur/hqt?$BOqfgn0KuVgpwtgf$@>1ncdgn@<"BJvon0Fkurnc{Hqt*o"?@"o0KuVgpwtgf+>1r@
>r@>ncdgn cur/hqt?$BOqfgn0Tcpm$@>1ncdgn@<"BJvon0Fkurnc{Hqt*o"?@"o0Tcpm+>1r@
>r@>ncdgn cur/hqt?$BOqfgn0JktkpiFcvg$@>1ncdgn@<"BJvon0Fkurnc{Hqt*o"?@"o0JktkpiFcvg+>1r@
>c cur/cevkqp?$Kpfgz$@iq"vq"Kpfgz>1c@
BJvon0CevkqpNkpm*$iq"vq"Kpfgz$."$Kpfgz$+
In each view, you only need to include the data specific to that view. What needs to
be repeated for all views should be put inside the layout file.
262 12 Consistent Look: Layouts, Friendly Error Pages, and Environments
>j3@Etgcvg"c"pgy"Kpuvtwevqt>1j3@
>fkx cur/xcnkfcvkqp/uwooct{?$Cnn$ uv{ng?$eqnqt<tgf$@>1fkx@
Boqfgn"Kpuvtwevqt
B}
XkgyDci0Vkvng"?"$Gfkv"cp"kpuvtwevqt$=
Ä
>j3@Gfkv"cp"kpuvtwevqt"rtqhkng>1j3@
>fkx cur/xcnkfcvkqp/uwooct{?$Cnn$@>1fkx@
>ncdgn cur/hqt?$NcuvPcog$@>1ncdgn@
>kprwv cur/hqt?$NcuvPcog$ 1@
>urcp cur/xcnkfcvkqp/hqt?$NcuvPcog$@>1urcp@
>dt@
>ncdgn cur/hqt?$KuVgpwtgf$@>1ncdgn@
>kprwv cur/hqt?$KuVgpwtgf$ 1@
>urcp cur/xcnkfcvkqp/hqt?$KuVgpwtgf$@>1urcp@
>dt@
>ncdgn cur/hqt?$JktkpiFcvg$@>1ncdgn@
>kprwv cur/hqt?$JktkpiFcvg$ 1@
>urcp cur/xcnkfcvkqp/hqt?$JktkpiFcvg$@>1urcp@
>dt@
>ncdgn cur/hqt?$Tcpm$@>1ncdgn@
>ugngev cur/hqt?$Tcpm$ cur/kvgou?$BJvon0IgvGpwoUgngevNkuv*v{rgqh*Tcpmu++$@>1ugngev@
>dt@
>dt@
>kprwv v{rg?$uwdokv$ xcnwg?$Ucxg"ejcpigu$ 1@
>kprwv v{rg?$dwvvqp$ qpenkem?$jkuvqt{0dcem*+$ xcnwg?$Ecpegn$@
>1hqto@
>j3@Ctg"{qw"uwtg"{qw"ycpv"vq"fgngvg"vjku"kpuvtwevqtA>1j3@
>hqto cur/cevkqp?$FgngvgEqphktogf$ ogvjqf?$rquv$@
>kprwv cur/hqt?$KpuvtwevqtKf$ v{rg?$jkffgp$ 1@
>r@>ncdgn cur/hqt?$BOqfgn0HktuvPcog$@>1ncdgn@<""BJvon0Fkurnc{Hqt*o"?@"o0HktuvPcog+>1r@
>r@>ncdgn cur/hqt?$BOqfgn0NcuvPcog$@>1ncdgn@<""BJvon0Fkurnc{Hqt*o"?@"o0NcuvPcog+>1r@
>r@>ncdgn cur/hqt?$BOqfgn0Tcpm$@>1ncdgn@<"BJvon0Fkurnc{Hqt*o"?@"o0Tcpm+>1r@
>dt 1@
>kprwv v{rg?$uwdokv$ xcnwg?$[GU."fgngvg$ 1@
>kprwv v{rg?$dwvvqp$ qpenkem?$jkuvqt{0dcem*+$ xcnwg?$PQ."ecpegn$@
>1hqto@
Test Your Work—Then Link Our .css File into the Layout File
Let’s test our work so far. Run your application. It should run as before. Some pages may
look slightly different because now we included the links to Bootstrap 5 in our layout,
hence for all our views (that use our layout).
Inside the Layout.cshtml, right after the <SCRIPT> element, let’s add the following
link to our own CSS file (you can also drag and drop the CSS file into the layout file,
Visual Studio will “drop” the link below for you):
Now, when you run your application, you should see all files using the same lightblue
background color (and other elements included in the .css file):
The Index view (see Fig. 12.10).
The Add view (see Fig. 12.11).
264 12 Consistent Look: Layouts, Friendly Error Pages, and Environments
Fig. 12.10 Shows the result of adding styling to the Index view
Fig. 12.11 Shows the result of adding styling to the Add view
And so on. On your own, you should play with the personal.css file and modify it so
your web application looks better.
IMPORTANT: If you make modifications to the CSS file, and they are not reflected in
your webpages, make sure to press Ctrl + F5 inside your browser, to make your browser
download the latest version of the .css files. Otherwise, your browser may try to use a
cached version of the .css files (the ones before you made modifications).
12.3 Consistent Webpages—Using Razor Layouts 265
Next, to see how great layout files are, we’ll add a navigation bar to our layout file. This
will in turn be used on all views that make use of our layout file.
Note: Instead of having to add a layout in every view individually, we only need to do
it once, in the layout. Isn’t this great?
Let’s start by adding the navbar we saw in Chap. 4 into our layout (to learn more about
navbars, use [25]). Copy and paste that navbar into the Layout.cshtml file. The layout
file should now look as follows:
>#FQEV[RG jvon@
>jvon@
>jgcf@
>ogvc pcog?$xkgyrqtv$ eqpvgpv?$ykfvj?fgxkeg/ykfvj$ 1@
>nkpm jtgh?$jvvru<11efp0lufgnkxt0pgv1pro1dqqvuvtcrB704051fkuv1euu1dqqvuvtcr0okp0euu$ tgn?$uv{ngujggv$@
>uetkrv ute?$jvvru<11efp0lufgnkxt0pgv1pro1dqqvuvtcrB704051fkuv1lu1dqqvuvtcr0dwpfng0okp0lu$@>1uetkrv@
>nkpm jtgh?$Å1euu1rgtuqpcn0euu$ tgn?$uv{ngujggv$ 1@
>vkvng@BXkgyDci0Vkvng>1vkvng@
>1jgcf@
>dqf{@
>PCX encuu?$pcxdct"pcxdct/gzrcpf/uo"di/fctm"pcxdct/fctm$@
>FKX encuu?$eqpvckpgt/hnwkf$@
>WN encuu?$pcxdct/pcx$@
>NK encuu?$pcx/kvgo$@
>C encuu?$pcx/nkpm$ jtgh?$hktuvygdrcig0jvon$@Hktuv"rcig>1C@
>1NK@
>NK encuu?$pcx/kvgo$@
>C encuu?$pcx/nkpm$ jtgh?$ugeqpfygdrcig0jvon$@Ugeqpf"rcig>1C@
>1NK@
>NK encuu?$pcx/kvgo$@
>C encuu?$pcx/nkpm$ jtgh?$tgikuvgt0jvon$@Tgikuvgt>1C@
>1NK@
>NK encuu?$pcx/kvgo$@
>C encuu?$pcx/nkpm$ jtgh?$jvvru<11yyy0uvoctvkp0gfw1$@Uckpv"Octvkp)u"Wpkxgtukv{>1C@
>1NK@
>NK encuu?$pcx/kvgo$@
>C encuu?$pcx/nkpm$ jtgh?$jvvru<11yyy0y5uejqqnu0eqo1$@Y5Uejqqnu>1C@
>1NK@
>1WN@
>1FKX@
>1PCX@
>fkx@
BTgpfgtDqf{*+
>1fkx@
>1dqf{@
>1jvon@
Run your application. Your navbar should be on every page that uses the layout. Your
pages have a more consistent look and feel (Fig. 12.12).
Next, we’ll fix the links in our navbar. For this, we will make use of tag helpers.
IMPORTANT: The tag helpers used in the navbar should include the name of the
controller for each action; otherwise, going from one controller’s action to another
controller’s action won’t work (as you would expect).
Here is how our navbar looks after adding links to various actions:
266 12 Consistent Look: Layouts, Friendly Error Pages, and Environments
Fig. 12.12 Shows the effects of adding a navbar to the layout. In here we see again the Index view
>PCX encuu?$pcxdct"pcxdct/gzrcpf/uo"di/fctm"pcxdct/fctm$@
>FKX encuu?$eqpvckpgt/hnwkf$@
>WN encuu?$pcxdct/pcx$@
>NK encuu?$pcx/kvgo$@
>C encuu?$pcx/nkpm$ jtgh?$jvvru<11yyy0uvoctvkp0gfw1$@Uckpv"Octvkp)u"
Wpkxgtukv{>1C@
>1NK@
>NK encuu?$pcx/kvgo$@
>C encuu?$pcx/nkpm$ jtgh?$jvvru<11yyy0y5uejqqnu0eqo1$@Y5Uejqqnu>1C@
>1NK@
>NK encuu?$pcx/kvgo$@
>C encuu?$pcx/nkpm$ jtgh?$jvvru<11ngctp0oketquqhv0eqo1gp/
wu1curpgv1eqtg1oxe1qxgtxkgyAxkgy?curpgveqtg/802$@Ngctp"CUR0PGV"Eqtg"OXE>1C@
>1NK@
>NK encuu?$pcx/kvgo"ftqrfqyp$@
>c encuu?$pcx/nkpm"ftqrfqyp/vqiing$ tqng?$dwvvqp$ fcvc/du/
vqiing?$ftqrfqyp$@Kpuvtwevqtu>1c@
>WN encuu?$ftqrfqyp/ogpw$@
>NK@>c encuu?$ftqrfqyp/kvgo$ cur/eqpvtqnngt?$Kpuvtwevqt$ cur/
cevkqp?$Kpfgz$@Nkuv"cnn"kpuvtwevqtu>1c@>1NK@
>NK@>c encuu?$ftqrfqyp/kvgo$ cur/eqpvtqnngt?$Kpuvtwevqt$ cur/
cevkqp?$Cff$@Cff"c"pgy"kpuvtwevqt>1c@>1NK@
>1WN@
>1NK@
>NK encuu?$pcx/kvgo"ftqrfqyp$@
>c encuu?$pcx/nkpm"ftqrfqyp/vqiing$ tqng?$dwvvqp$ fcvc/du/
vqiing?$ftqrfqyp$@Uvwfgpvu>1c@
>WN encuu?$ftqrfqyp/ogpw$@
>NK@>c encuu?$ftqrfqyp/kvgo$ cur/eqpvtqnngt?$Uvwfgpv$ cur/
cevkqp?$Kpfgz$@Nkuv"cnn"uvwfgpvu>1c@>1NK@
>NK@>c encuu?$ftqrfqyp/kvgo$ cur/eqpvtqnngt?$Uvwfgpv$ cur/cevkqp?$Cff$@Cff"
c"pgy"uvwfgpv>1c@>1NK@
>1WN@
>1NK@
>1WN@
>1FKX@
>1PCX@
Now you should have a functional navigation bar that can be used for all views that
use our layout file (see Fig. 12.13).
You should also note that one of the reasons why you should use the Bootstrap frame-
work is that it includes responsive design. To see this, narrow down the webpage above.
See what happens to the navbar if it does not have sufficient width to display all options
side by side (Fig. 12.14).
12.4 Layout Sections (Optional) 267
Fig. 12.13 Shows the same view as seen in Fig. 12.12, but the navbar contains two more dropdown
menu options (Instructors and Students)
Next, we would like to introduce layout sections. Think of sections as pieces of code that
can be defined by each view individually, but it’s up to the layout to decide where (and
how) to display these sections.
We’ll demonstrate sections by solving the following: we want to let each view define
certain links that will appear at the bottom of the page (later we could move them if
needed) and we will place them centered.
Fig. 12.14 Shows the same Index view as seen in Fig. 12.13, but the nav bar is responsive to the
actual width of the window. If you narrow the window width sufficiently, the menu options will show
up stacked (in the upper left corner)
To display section in the layout, use @RenderSection in the layout (at the location
where you want it displayed—we’ll put them at the end of the _Layout.cshtml, right
before the </BODY> end tag).
BTgpfgtUgevkqp*$QwtHqqvgtNkpmu$."hcnug+
The second parameter is needed, it tells the compiler that not all views have a section
named “OurFooterLinks” defined. Setting that value to true would force your appli-
cation to define that section in every view that uses our layout, or get a compiling
error.
Lastly, we would like the buttons defined in the OurFooterLinks section to be
centered and displayed one line below their current position. One way to do this is as
follows. Add a <DIV> element, and nest the RenderSection directive inside a <DIV>
element, then apply the class selector (which we already defined in our personal.css file):
>DT@
>FKX kf?$EgpvgtHqqvgtNkpmu$@
BTgpfgtUgevkqp*$QwtHqqvgtNkpmu$."hcnug+
>1FKX@
Now run your application and check the changes. Make sure to press Ctrl + F5 if the
latest version of the personal.css is not yet loaded in the browser.
270 12 Consistent Look: Layouts, Friendly Error Pages, and Environments
We have already seen in Chap. 4 how to make use of Bootstrap 5 buttons, and how we
can also use Bootstrap to make links look like buttons. Let’s make use of these to improve
the look of our web application.
In particular (see Fig. 12.15), we would like to change the appearance of this view (we’ll
provide the solution at the end of this section):
Add the following to all our <a> elements: defined inside
Index.cshtml. Now the page looks a little better (see Fig. 12.16).
Let’s remove the Details (HTML helper) column. Remove the following lines:
>VJ@Fgvcknu"*JVON"jgnrgt+>1VJ@
>VF@BJvon0CevkqpNkpm*$fgvcknu$."$UjqyFgvcknu$."pgy}kf?Bkpuvtwevqt0KpuvtwevqtKfÄ+>1VF@
And rename the columns Details (tag helper) to Details. See the result in Fig. 12.17.
Lastly, let’s use the various colors available to Bootstrap 5 buttons.
Fig. 12.16 The Index view looks similar to the one in Fig. 12.15, but some of the links look like
buttons
Fig. 12.17 Shows the result of removing the Details (HTML helper) column
272 12 Consistent Look: Layouts, Friendly Error Pages, and Environments
Fig. 12.18 Shows the same contents as seen in Fig. 12.17, to which we added various Bootstrap
5 colors (we used green for the details buttons, yellow for the edit buttons, and red for the delete
buttons)
Boqfgn"KGpwogtcdng>Kpuvtwevqt@
B}
XkgyDci0Vkvng"?"$Kpfgz$=
Ä
>j3@Cnn"kpuvtwevqtu>1j3@
>hqto cur/cevkqp?$Kpfgz$ ogvjqf?$igv$@
>ugngev cur/kvgou?$B*pgy UgngevNkuv*XkgyDci0CxckncdngTcpmu."$Cnn$++$ pcog?$UgngevgfTcpm$@
>qrvkqp xcnwg?$$@Cnn"tcpmu>1qrvkqp@
>1ugngev@
>kprwv v{rg?$vgzv$ rncegjqnfgt?$ncuv"pcog"kpenwfgu"000$ kf?$NcuvPcogHknvgt$ pcog?$UgctejD{NcuvPcog$
xcnwg?$BXkgyDci0UgctejD{NcuvPcog$ 1@
>dwvvqp v{rg?$uwdokv$@Hknvgt"tguwnvu>1dwvvqp@
>dwvvqp qpenkem?$fqewogpv0igvGngogpvD{Kf*)NcuvPcogHknvgt)+0xcnwg"?"pwnn$@Engct"vjg"hknvgt>1dwvvqp@
>1hqto@
Bkh *Oqfgn0Eqwpv*+"@"2+
}
>VCDNG encuu?$vcdng"vcdng/fctm"vcdng/jqxgt$@
>VJGCF@
>VT@
>VJ@>ncdgn cur/hqt?$Hktuv*+0HktuvPcog$@>1ncdgn@>1VJ@
>VJ@>ncdgn cur/hqt?$Hktuv*+0NcuvPcog$@>1ncdgn@>1VJ@
>VJ@>ncdgn cur/hqt?$Hktuv*+0Tcpm$@>1ncdgn@>1VJ@
>VJ@Fgvcknu>1VJ@
>VJ@Gfkv">1VJ@
>VJ@Fgngvg">1VJ@
>1VT@
>1VJGCF@
>VDQF[@
>1VCDNG@
Ä
gnug
}
>j4@Pq"kpuvtwevqtu"hqwpf#">1j4@
Ä
>c cur/cevkqp?$Cff$ encuu?$dvp"dvp/rtkoct{$@Cff"c"pgy"kpuvtwevqt">1c@
12.5 Make Use of Bootstrap 5 Buttons 273
>jvon@
>jgcf@
>ogvc pcog?$xkgyrqtv$ eqpvgpv?$ykfvj?fgxkeg/ykfvj$ 1@
>nkpm jtgh?$jvvru<11efp0lufgnkxt0pgv1pro1dqqvuvtcrB704051fkuv1euu1dqqvuvtcr0okp0euu$ tgn?$uv{ngujggv$@
>uetkrv ute?$jvvru<11efp0lufgnkxt0pgv1pro1dqqvuvtcrB704051fkuv1lu1dqqvuvtcr0dwpfng0okp0lu$@>1uetkrv@
>nkpm jtgh?$Å1euu1rgtuqpcn0euu$ tgn?$uv{ngujggv$ 1@
>vkvng@BXkgyDci0Vkvng>1vkvng@
>1jgcf@
>dqf{@
>PCX encuu?$pcxdct"pcxdct/gzrcpf/uo"di/fctm"pcxdct/fctm$@
>FKX encuu?$eqpvckpgt/hnwkf$@
>WN encuu?$pcxdct/pcx$@
>NK encuu?$pcx/kvgo$@
>C encuu?$pcx/nkpm$ jtgh?$jvvru<11yyy0uvoctvkp0gfw1$@Uckpv"Octvkp)u"Wpkxgtukv{>1C@
>1NK@
>NK encuu?$pcx/kvgo$@
>C encuu?$pcx/nkpm$ jtgh?$jvvru<11yyy0y5uejqqnu0eqo1$@Y5Uejqqnu>1C@
>1NK@
>NK encuu?$pcx/kvgo$@
>C encuu?$pcx/nkpm$ jtgh?$jvvru<11ngctp0oketquqhv0eqo1gp/wu1curpgv1eqtg1oxe1qxgtxkgyAxkgy?curpgveqtg/
802$@Ngctp"CUR0PGV"Eqtg"OXE>1C@
>1NK@
>NK encuu?$pcx/kvgo"ftqrfqyp$@
>c encuu?$pcx/nkpm"ftqrfqyp/vqiing$ tqng?$dwvvqp$ fcvc/du/vqiing?$ftqrfqyp$@Kpuvtwevqtu>1c@
>WN encuu?$ftqrfqyp/ogpw$@
>NK@>c encuu?$ftqrfqyp/kvgo$ cur/eqpvtqnngt?$Kpuvtwevqt$ cur/cevkqp?$Kpfgz$@Nkuv"cnn"
kpuvtwevqtu>1c@>1NK@
>NK@>c encuu?$ftqrfqyp/kvgo$ cur/eqpvtqnngt?$Kpuvtwevqt$ cur/cevkqp?$Cff$@Cff"c"pgy"kpuvtwevqt>1c@>1NK@
>1WN@
>1NK@
>NK encuu?$pcx/kvgo"ftqrfqyp$@
>c encuu?$pcx/nkpm"ftqrfqyp/vqiing$ tqng?$dwvvqp$ fcvc/du/vqiing?$ftqrfqyp$@Uvwfgpvu>1c@
>WN encuu?$ftqrfqyp/ogpw$@
>NK@>c encuu?$ftqrfqyp/kvgo$ cur/eqpvtqnngt?$Uvwfgpv$ cur/cevkqp?$Kpfgz$@Nkuv"cnn"uvwfgpvu>1c@>1NK@
>NK@>c encuu?$ftqrfqyp/kvgo$ cur/eqpvtqnngt?$Uvwfgpv$ cur/cevkqp?$Cff$@Cff"c"pgy"uvwfgpv>1c@>1NK@
>1WN@
>1NK@
>1WN@
>1FKX@
>1PCX@
>fkx@
BTgpfgtDqf{*+
>1fkx@
>DT@
>FKX encuu?$EgpvgtHqqvgtNkpmu$@
BTgpfgtUgevkqp*$QwtHqqvgtNkpmu$."hcnug+
>1FKX@
>1dqf{@
>1jvon@
Challenge: how would you center these contents? Here is the end result (Fig. 12.20).
We’ll let you change the other views on your own, but here are some suggested
outcomes.
For the Add view (see Fig. 12.21).
For the Edit view (see Fig. 12.22).
And the Delete view (see Fig. 12.23).
Lastly, in this part, we would like to make all our Validation errors show in red (see
Fig. 12.24).
274 12 Consistent Look: Layouts, Friendly Error Pages, and Environments
Fig. 12.19 Shows the current state of the ShowDetails view for InstructorController
Fig. 12.20 Shows how we would like to make the ShowDetails view look after adding (Bootstrap
5) styling
Using the developer tool (press F12 then go to the Elements tab), you will find that
the errors are displayed in elements having the following CSS classes:
Fig. 12.21 Shows how we would like to make the Add view to look after adding (Bootstrap 5)
styling
Fig. 12.22 Shows how we would like to make the Edit view to look after adding (Bootstrap 5)
styling
And now the errors show up with red text (see Fig. 12.25).
276 12 Consistent Look: Layouts, Friendly Error Pages, and Environments
Fig. 12.23 Shows how we would like to make the Delete view to look after adding (Bootstrap 5)
styling
Fig. 12.24 Shows how we would like to make the Add view to look after adding (Bootstrap 5)
styling
12.6 Configure a Friendly Error Page 277
Fig. 12.25 Shows the same image as in Fig. 12.24, but now that we added the styling shown above,
the errors show up using a red text
12.6.1 Introduction
What happens if you send an unexpected HTTP request to a website such as Amazon.com,
Microsoft.com, or stmartin.edu? On your own, try out the following:
. https://www.amazon.com/MEZEI
. https://www.microsoft.com/MEZEI
. http://www.stmartin.edu/MEZEI
In each of these cases, you should note that a nice error page is created, most importantly
one that has a similar layout as the other pages on the same website. Some websites even
include random images displayed in their error page.
278 12 Consistent Look: Layouts, Friendly Error Pages, and Environments
. http://localhost:5125/MEZEI
“ASP.NET Core configures app behavior based on the runtime environment using an
environment variable” (read more in [70, 71]). In particular, we can use the environment
variable ASPNETCORE_ENVIRONMENT to specify if our application is in production,
in development, or in some (any) other stage. Then, we can use code to check which
environment we are in and act appropriately.
There are multiple ways to set the value of the ASPNETCORE_ENVIRONMENT envi-
ronmental variable. We will change it as follows: from Solution Explorer window open the
file Properties > launchSettings.json. In there, one can set the value of the ASPNETCORE_
ENVIRONMENT environmental variable to the desired stage. Right now, it is set to
“Development”. We will later switch it to “Production”, but please note that you
can use any other values too, for example, “Staging”, etc.
$rtqhkngu$<"}
$CURDqqmRtqlgev$<"}
$eqoocpfPcog$<"$Rtqlgev$.
$fqvpgvTwpOguucigu$<"vtwg.
$ncwpejDtqyugt$<"vtwg.
$crrnkecvkqpWtn$<"$jvvr<11nqecnjquv<7347$.
$gpxktqpogpvXctkcdngu$<"}
$CURPGVEQTGaGPXKTQPOGPV$<"$Fgxgnqrogpv$
Ä
Ä.
We will make use of this environment variable, so our application behaves differently
in various environments:
Fig. 12.26 Shows a simple error page that only contains some generic text, without including all
details of the error
xct crr"?"dwknfgt0Dwknf*+=11ugv"wr"okffngyctg"eqorqpgpvu0
Note: The app.Environment can be used to check against for environment, not
just the Development. After app.Environment type a dot, and IntelliSense (from
Visual Studio) will show you several options including IsDevelopment, IsEnvironment,
IsProduction, and IsStaging.
To check if the current environment is “TEST”, you could use code similar to:
kh*crr0Gpxktqpogpv0KuGpxktqpogpv*›VGUVfi++
During the development stage, we would like to get as many details as pos-
sible about errors. For this, we will use the following middleware component:
app.UseDeveloperExceptionPage();
You should only be using this for the Development environment (or related stages, such
as Testing, Code Review, …) because (as you will see below) the text generated by this
middleware pipeline can include portions of your source code, which is something you
do not want to disclose to your clients, especially to potential attackers.
For example, we could use the following in Program.cs:
xct crr"?"dwknfgt0Dwknf*+=11ugv"wr"okffngyctg"eqorqpgpvu0
kh *crr0Gpxktqpogpv0KuFgxgnqrogpv*++
}
crr0WugFgxgnqrgtGzegrvkqpRcig*+=""""""""""""""""11ujqy"cnn"fgvcknu"hqt"gttqtu
Ä
gnug
}
000
280 12 Consistent Look: Layouts, Friendly Error Pages, and Environments
Run your application. You should get a response page containing many details about
the error page. In particular, you can get the Stack information, the Query information,
Cookies, Headers, and Routing information. You can even get the line number responsible
for the Exception that was created.
Before you continue, please remove the line we just added (throw new
Exception("testing the...");).
Another way to test this is by renaming, let’s say the Index.cshtml file to Index2.cshtml.
You should get a similar error page full of details (as seen above).
Rename back the Index view before you continue!
You should note that some errors (such as HTTP 404—not found) do not have such
detailed HTTP responses. For example, if you use the following HTTP request: http://loc
alhost:5125/MEZEI you will get the HTTP ERROR 404 page.
Next, we’ll configure the friendly error page, so that when an error occurs, the user gets
a nice friendly response. Then, we’ll go over the same errors as seen above and check the
outcome.
During the production stage, we would like to get no details displayed to the user, just
that an error occurred, and use our layout file, so the user still has access to the other links
for an easier navigation. For this, we will use the following middleware components:
crr0WugGzegrvkqpJcpfngt*$1Gttqt1Kpfgz$+=""11ujqy"c"htkgpfn{"rcig."jkfg"cnn"fgvcknu
crr0WugUvcvwuEqfgRciguYkvjTgfktgevu*$1Gttqt1Kpfgz$+="11hqt"tgurqpugu uwej"cu"626"Pqv"hqwpf
Now, let’s explain the string "/Error/Index". One way to prepare a friendly error
page is to create an ErrorController class, with an action (we used Index above)
and a corresponding view. Then, use the path "/Error/Index" to access it. Note:
for default routing, we don’t need to specify the /Index portion. We could just use
/Error.
To our project, let’s add a new controller class, called ErrorController. Here are
the contents of ErrorController.cs:
wukpi Oketquqhv0CurPgvEqtg0Oxe=
pcogurceg CURDqqmRtqlgev0Eqpvtqnngtu
}
rwdnke encuu GttqtEqpvtqnngt <"Eqpvtqnngt
}
rwdnke KCevkqpTguwnv"Kpfgz*+
}
tgvwtp Xkgy*+=
Ä
Ä
Ä
Next, add a view for the Index action. This time, we can select Razor View—Empty
(you can also select the other option, but it will generate more code than we need, so
rather than delete extra code, we chose the Empty option this time).
In the Add New Item window that opens, make sure to use the name that matches the
action’s name, and click on the Add button.
Since we are using a layout and we included the following in ViewStart.cshtml:
our view will make use of the layout. So, in our view, we will only include the following
line:
>j3@Yg"eqwnf"pqv"hkpf"vjg"rcig"{qw"tgswguvgf0"Yg"ctg"uqtt{#>1j3@
Run your application. You should get a response similar to Fig. 12.26.
Isn’t this better? Before you continue, please remove the line we just added (throw
new Exception("testing the...");).
Another way to test this is by renaming, let’s say the Index.cshtml file to Index2.cshtml.
You should get the same friendly page as above. Please rename back the Index view before
you continue!
282 12 Consistent Look: Layouts, Friendly Error Pages, and Environments
Now let’s try the following HTTP request: http://localhost:5125/MEZEI you will again
get something similar to Fig. 12.26.
If you wish, you can also display a random image in the error page generated above.
One solution is to use the images we already have in our wwwroot > images (in
our example, we have the following files: image01.JPG, image02.JPG, image03.JPG,
image04.JPG, image05.JPG, image06.JPG, image07.JPG).
Each time a View is generated, we should randomly generate a value between 1 and 7
(since we have 7 images) and display one of them.
>j3@Yg"eqwnf"pqv"hkpf"vjg"rcig"{qw"tgswguvgf0"Yg"ctg"uqtt{#>1j3@
>fkx uv{ng?$vgzv/cnkip<egpvgt$@
B}
Tcpfqo tcpfIgpgtcvqt"?"pgy Tcpfqo*+=
kpv z"?"tcpfIgpgtcvqt0Pgzv*3.":+=
uvtkpi kocigRcvj"?"&$1kocigu1kocig2}zÄ0LRI$=
Ä
>dt 1@
>1fkx@
Now, each time you get the friendly error page displayed, a randomly chosen image
will show up (see Figs. 12.27 and 12.28). Note the URL: localhost:5125/Error/Index.
That’s it for us. ASP .Net Core has many more related topics that we did not cover,
please read more in [70, 71].
Fig. 12.27 Shows the error page seen similar to Fig. 12.26, but it now includes a randomly selected
image from web root
12.6 Configure a Friendly Error Page 283
Fig. 12.28 Is similar to Fig. 12.27, but it now includes another randomly selected image
Before we continue, please make sure to set your environment back to Development.
Otherwise, it will be more challenging to debug potential errors.
Work with Images (Optional)
13
In here, we would like to improve our web application so we can optionally store (in our
database) a profile photo for every instructor and also display it. We recommend reading
the following source for this chapter [72].
Important note: This is a very simplistic example, meant to show the steps involved in
saving images into a database and retrieving them. We did not include any security con-
siderations in this code. You are strongly encouraged to consider the security implications
and make use of mitigating techniques. Here is one resource which we did not use but
you could use as a starting point [73].
13.1 Add a New Property for the Image to the Model/Entity Class
We’ll start by adding one new property in the Instructor class that will be used to
store the image. We’ll store the image as an array of bytes since SQLite does not have a
type that can be mapped to images.
The long story short (see below for more details) is that we will store images as an
array of bytes, then we will convert them back to images when we want to display them.
Go to the Instructor.cs class and add the following property:
]Fkurnc{*Pcog"?$Rtqhkng"rjqvq$+_
rwdnke d{vg]_A"KpuvtwevqtRtqhkngRjqvq"}"igv="ugv="Ä
© The Author(s), under exclusive license to Springer Nature Switzerland AG 2024 285
R. A. Mezei, Introduction to the Development of Web Applications Using
ASP .Net (Core) MVC, Synthesis Lectures on Computer Science,
https://doi.org/10.1007/978-3-031-30626-6_13
286 13 Work with Images (Optional)
Run again the application. A new column will be created in the Instructors table
(use DbBrowser to see the columns from Instructors table): the new column name is
InstructorProfilePhoto.
You can now comment out the line containing EnsureDeleted, so data modifi-
cations remain saved (so the database tables don’t get recreated each time we run the
application).
For the remaining part of this chapter, we’ll follow each step of an HTTP request and
modify the code so it allows the user to upload an image, transform it into a byte array,
and then save it into the database. Then, we’ll follow the request from getting the byte
array from the database, change it back into an image, and display it in a browser.
In here, we’ll add the code needed so we allow the user to upload an image (any file
actually, not just images).
To allow a form to upload files to the server, you need to add the following inside the
<FORM> tag:
gpev{rg?$ownvkrctv1hqto/fcvc$
After adding the code above, the <FORM> tag, inside the Add.cshtml file, looks as
follows:
Then, we need to add input field that allows the user to select an image from their
computer and upload it with the form. Before the submit button, add the following:
>kprwv v{rg?$hkng$ pcog?$KpuvtwevqtKocigHkng$ 1@
>dt@
>dt@
Remember, in order for data from input fields to be sent to the server, we need to use
a name attribute.
To test what we accomplished so far, go to the Add action: http://localhost:5125/Instru
ctor/Add. You should see a Choose File button that allows the user to select a file from
their own computer (see Fig. 13.1).
If you click on this button, a new window opens that allows you to select a file. Once
selected, you’ll see its name next to the button (see Fig. 13.2):
>DT@
>FKX kf?$EgpvgtHqqvgtNkpmu$@
BTgpfgtUgevkqp*$QwtHqqvgtNkpmu$."hcnug+
>1FKX@
>1FKX@
This way we added some padding around our view contents. See more in here [16].
13.3 Modify the Add Action so the File Uploaded Gets Saved
into the Database
In the Add action (the POST) is where we receive user data and save it into the database.
We now need to grab the file sent by the user, convert it into a byte array, and save it into
the database, along with other data.
Add the following code after validating the user data:
11kh"c"hkng1kocig"ycu"wrnqcfgf."eqpxgtv"kv"vq"d{vg]_"cpf"ucxg"kv
kh*Tgswguv0Hqto0Hkngu0Eqwpv"@2+"11fkf"vjg"wugt"wrnqcf"c"hkngA
}
xct hkng"?"Tgswguv0Hqto0Hkngu]2_=11qwt"xkgy"qpn{"cnnqyu"qpg"hkng
Ogoqt{Uvtgco"ou"?"pgy Ogoqt{Uvtgco*+=
hkng0Eqr{Vq*ou+="11eqr{"vjg"hkng"kpvq"c"ogoqt{"uvtgco"qdlgev
pgyKpuvtwevqt0KpuvtwevqtRtqhkngRjqvq"?"ou0VqCttc{*+=11ucxg"vjg"d{vgu"kpvq"pgyKpuvtwevqt
ou0Enqug*+=
ou0Fkurqug*+=
Ä
In this code, we first check if the user uploaded a file. Since we only allow one file
to be uploaded at a time, we read the file at index 0 (otherwise we could use a for each
to read each file). Then, using a MemoryStream object we convert the file into a byte
array and save it into the InstructorProfilePhoto property. This will then be
saved into the database, along with other values.
The Add action should now look as follows:
288 13 Work with Images (Optional)
]JvvrRquv_
rwdnke KCevkqpTguwnv"Cff*Kpuvtwevqt"pgyKpuvtwevqt+
}
kh *#OqfgnUvcvg0KuXcnkf+"11kh"vjg"fcvc"ku"kpxcnkf
tgvwtp Xkgy*+="11iq"dcem"vq"vjg"xkgy
11kh"c"hkng1kocig"ycu"wrnqcfgf."eqpxgtv"kv"vq"d{vg]_"cpf"ucxg"kv
kh*Tgswguv0Hqto0Hkngu0Eqwpv"@2+"11fkf"vjg"wugt"wrnqcf"c*p{+ hkngA
}
xct hkng"?"Tgswguv0Hqto0Hkngu]2_=11qwt"xkgy"qpn{"cnnqyu"qpg"hkng
Ogoqt{Uvtgco"ou"?"pgy Ogoqt{Uvtgco*+=
hkng0Eqr{Vq*ou+="11eqr{"vjg"hkng"kpvq"c"ogoqt{"uvtgco"qdlgev
pgyKpuvtwevqt0KpuvtwevqtRtqhkngRjqvq"?"ou0VqCttc{*+=11ucxg"vjg"d{vgu"kpvq"pgyKpuvtwevqt
ou0Enqug*+=
ou0Fkurqug*+=
Ä
afdEqpvgzv0Kpuvtwevqtu0Cff*pgyKpuvtwevqt+="11cff"vjg"pgy"kpuvtwevqt"vq"qwt"nkuv
afdEqpvgzv0UcxgEjcpigu*+=
tgvwtp TgfktgevVqCevkqp*$Kpfgz$+="
Ä
To test this work, go ahead and add/create a new Instructor. Make sure to select an
image and enter some data (see Fig. 13.3).
After you click on the Create Instructor button, go ahead and check that it was saved
into the database. In particular, using DbBrowser, open your database and check that the
InstructorProfilePhoto column shows BLOB for the newly added instructor (the
last row).
With the code we have so far, we are now able to save data into the database. Next,
we’ll retrieve this data and display it in a view.
Fig. 13.3 Shows the Add view, where a user can enter information for a new instructor, and also
select an image/file to be uploaded to the server
13.5 Modify the ShowDetails View so It Displays the Profile Image 289
into
kh*kpuvt#?pwnn+"11ycu"cp"kpuvtwevqt"hqwpfA
}
kh*kpuvt0KpuvtwevqtRtqhkngRjqvq#?"pwnn+"11ku"vjgtg"cp"kocig"qp"hkngA
}
uvtkpi kocigDcug86Fcvc"?"Eqpxgtv0VqDcug86Uvtkpi*kpuvt0KpuvtwevqtRtqhkngRjqvq+="
uvtkpi kocigFcvcWTN"?"uvtkpi0Hqtocv*$fcvc<kocig1lri=dcug86.}2Ä$."kocigDcug86Fcvc+=
XkgyDci0KpuvtwevqtRtqhkngRjqvq"?"kocigFcvcWTN=
Ä
tgvwtp Xkgy*kpuvt+=
Ä
More specifically, in here we first check if we have an image (a byte array really) in
the InstructorProfilePhoto property. If we do, we convert the byte array into
an image and we provide the location of this new image to the view via the ViewBag
object.
In the view, we first check if we have an image URL saved in ViewBag. If we do, we
display it. Add the following to the ShowDetails view (wherever you would like to
display the image):
Bkh *XkgyDci0KpuvtwevqtRtqhkngRjqvq"#?"pwnn+
}
>koi ute?$BXkgyDci0KpuvtwevqtRtqhkngRjqvq$ ykfvj?$42'$
cnv?$rtqhkng"rkevwtg"hqt BOqfgn0NcuvPcog$
encuu?$koi/vjwodpckn$ 1@
Ä
Optionally, we can display a default photo for Instructors who do not have an image
saved. For this, add
gnug
}
>koi ute?$Å1kocigu1pqrtqhkngrjqvq0rpi$ ykfvj?$42'$
cnv?$igpgtke"rtqhkng"rjqvq$
encuu?$koi/vjwodpckn$ 1@
Ä
Fig. 13.4 Shows how the ShowDetails view shows up in a browser. In particular, this Instructor did
not contain a profile picture, so we displayed a generic image
Fig. 13.5 Shows how the ShowDetails view shows up in a browser. In particular, this Instructor did
contain a profile picture, so we displayed that picture
13.6 Bootstrap Card Deck for the Index Action and View (Optional) 291
Using a combination of the sections presented in this chapter, you should be able to
allow the user to edit an existing Instructor and change/keep any existing profile picture.
13.6 Bootstrap Card Deck for the Index Action and View
(Optional)
This section is just meant to stimulate your interest in researching more on your own—
Bootstrap is really awesome, you just need to look it up. In particular, you may want to
consider displaying a card deck instead of a table, or in addition to a table. For this,
check out the following resource [74].
In our Index action, let’s add the following code that will create a dictionary of
images and send them to the Index view. Add the code below right before the return
statement of Index action (from the InstructorController class).
Fkevkqpct{>kpv."uvtkpi@"cnnRjqvqu"?"pgy*+=
hqtgcej *xct"kpu"kp kpuvtwevqtu+
}
kh *kpu"#?"pwnn (("kpu0KpuvtwevqtRtqhkngRjqvq"#?"pwnn+
}
uvtkpi kocigDcug86Fcvc"?"Eqpxgtv0VqDcug86Uvtkpi*kpu0KpuvtwevqtRtqhkngRjqvq+=
uvtkpi kocigFcvcWTN"?"uvtkpi0Hqtocv*&$fcvc<kocig1lri=dcug86.}kocigDcug86FcvcÄ$+=
cnnRjqvqu0Cff*kpu0KpuvtwevqtKf."kocigFcvcWTN+=
Ä
Ä
XkgyDci0CnnKocigu"?"cnnRjqvqu=
Then, inside the view, we used that dictionary object inside a card deck and displayed
them (you can add this right below the <TABLE> element):
B,ectf"fgem",B
>fkx encuu?$eqpvckpgt$@
>fkx encuu?$tqy$@
Bhqtgcej *xct kpu"kp Oqfgn+
}
>fkx encuu?$eqn$ uv{ng?$vgzv/cnkip<egpvgt$@
>fkx encuu?$ectf$ uv{ng?$ykfvj<522rz$@
B}
uvtkpiA"uvt"?"pwnn=
dqqn tguwnv"?"BXkgyDci0CnnKocigu0Vt{IgvXcnwg*kpu0KpuvtwevqtKf."qwv uvt+=
Ä
Bkh *Buvt"#?"pwnn+""B,kh"yg"jcxg"cp"kocig"qp"hkng,B
}
>koi encuu?$ectf/koi/vqr$ ute?$Buvt$ cnv?$Ectf"kocig$ uv{ng?$ykfvj<322'$@
Ä
gnug
}"""""""""""""""""""B,kh"pq"kocig"kp"vjg"fcvcdcug."fkurnc{"uqog"fghcwnv"kocig,B
>koi encuu?$ectf/koi/vqr$ ute?$Å1kocigu1pqRtqhkngRjqvq0rpi$ cnv?$Ectf"kocig$ uv{ng?$ykfvj<322'$@
Ä
>fkx encuu?$ectf/dqf{$@
>j6 encuu?$ectf/vkvng$@Bkpu0HktuvPcog"Bkpu0NcuvPcog>1j6@
>dt encuu?$ectf/vgzv$@
>ncdgn cur/hqt?$BOqfgn0Hktuv*+0JktkpiFcvg$@>1ncdgn@<""BJvon0Fkurnc{Hqt*o"?@"kpu0Tcpm+>dt@
>ncdgn cur/hqt?$BOqfgn0Hktuv*+0KuVgpwtgf$@>1ncdgn@<"BJvon0Fkurnc{Hqt*o"?@"kpu0KuVgpwtgf+>dt@
>ncdgn cur/hqt?$BOqfgn0Hktuv*+0Tcpm$@>1ncdgn@<"BJvon0Fkurnc{Hqt*o"?@"kpu0Tcpm+>dt@
Fig. 13.6 Shows how the Index view shows up in a browser. In particular, below the table of instruc-
tors, we also included a card deck, one card for each instructor from the table. Some of those cards
contained the instructor’s profile pictures (if one was saved into an instructor’s profile) while other
cards contain the generic profile picture (if we did not have a picture saved for an instructor’s profile)
Introduction to Authentication. User Login,
Logout, and Registration
14
We finally got to our final chapter. In here we will only introduce authentication and
simple authorization. In particular, we’ll implement functionality such as user account
registration, login, and logout. To learn more about ASP .Net Core security-related topics,
we recommend [75]. In a future edition of this book, we plan to also include topics such
as Roles, Policies, and how to allow users to login using credentials from other web
applications, such as Facebook and LinkedIn.
• Authentication: How do you prove you are who you say you are?
There are multiple types of authentication techniques. In this chapter, we’ll use
passwords.
– Type 1 authentication: Something you know (e.g., password, passphrase, pin
number).
– Type 2 authentication: Something you have (e.g., passport, badge, smart card,
cookie on a system).
© The Author(s), under exclusive license to Springer Nature Switzerland AG 2024 293
R. A. Mezei, Introduction to the Development of Web Applications Using
ASP .Net (Core) MVC, Synthesis Lectures on Computer Science,
https://doi.org/10.1007/978-3-031-30626-6_14
294 14 Introduction to Authentication. User Login, Logout, and Registration
• Authorization: once you’re authenticated, what can you do? What are you allowed to
access?
In this chapter, we’ll only see simple authorization.
Read more on the IAAA concepts in [76]. In this chapter, we will only make use of the
first three.
In this chapter, we will make use of the ASP.NET Core Identity to handle login function-
ality. In particular, we’ll use Identity to provide functionality such as register new user
accounts, login, and logout. Let’s go over the steps needed to configure and use Identity,
namely the following:
Before we continue, you should uncomment the following line from Program.cs, since
our changes will modify our database (new tables will be created):
context.Database.EnsureDeleted();
14.2 Introduction to ASP .Net (Core) Identity 295
At the end of the chapter, please make sure to comment out this line so changes remain
in our database even after we restart our web application. For more information on how
to implement Identity on existing ASP .Net (Core) applications, check out [77].
For this part, we will install the following two NuGet packages:
• Microsoft.AspNetCore.Identity.EntityFrameworkCore
• Microsoft.AspNetCore.Identity.UI.
For this step, we need a class that can be used to manage the users of our application. For
this, ASP .Net Core has a class called IdentityUser. If its properties (which include
an Id, UserName, Email, and PasswordHash) are sufficient for your application’s
needs, then you are all set and can skip this step. Here are some of its properties (as seen
from Visual Studio):
If you need to add more properties, you can create your own class derived from
IdentityUser. For us, let’s say we do need to also store a FirstName and a
LastName (you can easily add more). Here is what we’ll use in our project. First, inside
the Data folder, let’s create a new class, derived from IdentityUser, and give it a
name (we called ours User).
Your code should look like
wukpi Oketquqhv0CurPgvEqtg0Kfgpvkv{=
pcogurceg CURDqqmRtqlgev0Fcvc
}
rwdnke encuu Wugt <"Kfgpvkv{Wugt
}
11vjg"dcug"encuu"cntgcf{"eqpvckpu"
11"kf."wugtpcog."gockn0"
rwdnke uvtkpiA"HktuvPcog"}"igv="ugv="Ä
rwdnke uvtkpiA"NcuvPcog"}"igv="ugv="Ä
Ä
Ä
You should note that in our database we do not store plaintext passwords (we did
not include any Password property). We instead store hash values of the passwords (in
PasswordHash property). Why is that?
with
IMPORTANT: Notice that we did not need a DbSet property for our
User/IdentityUser class. That’s because the IdentityDbContext class will
manage user tables for us.
dwknfgt0Ugtxkegu0CffFdEqpvgzv>QwtFdEqpvgzv@*
qrvkqpu"?@"qrvkqpu0WugUsnkvg*dwknfgt0Eqphkiwtcvkqp0IgvEqppgevkqpUvtkpi*$DqqmEqppgevkqpUvtkpi$++
+=
dwknfgt0Ugtxkegu0CffFghcwnvKfgpvkv{>Wugt@*11wugt"{qwt"Kfgpvkv{Wugt"encuu"kp">@
qrvkqpu"?@
}
qrvkqpu0UkipKp0TgswktgEqphktogfCeeqwpv"?"hcnug=
qrvkqpu0Rcuuyqtf0TgswktgFkikv"?"vtwg=
qrvkqpu0Rcuuyqtf0TgswktgPqpCnrjcpwogtke"?"vtwg=
qrvkqpu0Rcuuyqtf0TgswktgWrrgtecug"?"vtwg=
qrvkqpu0Rcuuyqtf0TgswktgNqygtecug"?"vtwg=
qrvkqpu0Rcuuyqtf0TgswktgfNgpivj"?":=
qrvkqpu0Wugt0TgswktgWpkswgGockn"?"vtwg=
Ä
+0CffGpvkv{HtcogyqtmUvqtgu>QwtFdEqpvgzv@*+="11wug"{qwt"FdEqpvgzvEncuu
Above, note that we were able to set various options, so that passwords must include
digits, lowercase, and uppercase letters, and also ensure that user emails are district
(unique). Check out the following source to learn about other options (including lockout
options and options related to characters allowed for usernames) available: [78].
crr0WugCwvjgpvkecvkqp*+=
crr0WugCwvjgpvkecvkqp*+=
crr0WugUvcvkeHkngu*+="11pggfgf"vq"ikxg"ceeguu"vq"hkngu"kp"yyytqqv
crr0WugCwvjgpvkecvkqp*+=
crr0WugTqwvkpi*+=""11cffu"tqwvg"ocvejkpi"vq"vjg"okffngyctg"rkrgnkpg
crr0WugCwvjqtk|cvkqp*+=
crr0OcrEqpvtqnngtTqwvg*"11oqfkhkgf"fghcwnv"tqwvkpi
pcog<"$fghcwnv$.
rcvvgtp<"$}eqpvtqnngt?KpuvtwevqtÄ1}cevkqp?KpfgzÄ1}kfAÄ$+=
Later in this chapter, we’ll enforce access to certain actions (such as edit and delete)
only to users who are logged in.
298 14 Introduction to Authentication. User Login, Logout, and Registration
Let’s rebuild our application to see what we have accomplished so far. If you reopen your
database file in DB Browser, you should note new tables being added to it by the ASP
.Net Core Identity. In particular, note the AspNetUsers table.
In the AspNetUsers table, you should observe the various columns (they include
columns for the properties of the User class). This table is now empty, but we’ll fix that
in the next section where we’ll add functionality to register new users, login, and logout.
In this subsection, we’ll create a new controller, the AccountController class, and
see how to use instances of the UserManager class to register new users, and the
SignInManager class to login/logout an existing user. We will also introduce View
Models.
pcogurceg CURDqqmRtqlgev0Eqpvtqnngtu
}
rwdnke encuu CeeqwpvEqpvtqnngt <"Eqpvtqnngt
}
rtkxcvg tgcfqpn{ UkipKpOcpcigt>Wugt@"aukipKpOcpcigt="11pggfgf"vq"nqikp1nqiqwv"ceeqwpvu
rwdnke CeeqwpvEqpvtqnngt*UkipKpOcpcigt>Wugt@"ukipKpOcpcigt+
}
aukipKpOcpcigt"?"ukipKpOcpcigt=
Ä
rwdnke KCevkqpTguwnv"Nqikp*+
}
tgvwtp Xkgy*+="11rtgugpvu"vjg"Nqikp"hqto"vq"vjg"wugt
Ä
Ä
Ä
14.2 Introduction to ASP .Net (Core) Identity 299
Note: The SignInManager is a generic class, and you need to pass to it your User
class (derived from IdentityUser), or the IdentityUser (if you did not use a
derived class). We’ll use this to login/logout our users.
pcogurceg CURDqqmRtqlgev0XkgyOqfgnu
}
rwdnke encuu NqikpXkgyOqfgn
}
]Fkurnc{*Pcog"?"$Wugt"Pcog$+_
]Tgswktgf*GttqtOguucig"?"$c"wugtpcog"ku"tgswktgf$+_
rwdnke uvtkpiA"WugtPcog"}"igv="ugv="Ä
]FcvcV{rg*FcvcV{rg0Rcuuyqtf+_
]Tgswktgf*GttqtOguucig"?"$c"rcuuyqtf"ku"tgswktgf$+_
rwdnke uvtkpiA"Rcuuyqtf"}"igv="ugv="Ä
]Fkurnc{*Pcog"?"$Tgogodgt"ogA$+_
rwdnke dqqn TgogodgtOg}"igv="ugv="Ä
Ä
Ä
Make sure to add the following using directive in the ViewImports.cshtml file so we
can use our View Model classes in views without having to specify their class names with
the namespace prepended:
Bwukpi CURDqqmRtqlgev0XkgyOqfgnu
Boqfgn"NqikpXkgyOqfgn
B}
XkgyDci0Vkvng"?"$Nqikp$=
Ä
>j3@Rngcug"nqikp>1j3@
>hqto cur/cevkqp?$Nqikp$ cur/eqpvtqnngt?$Ceeqwpv$@
>fkx cur/xcnkfcvkqp/uwooct{?$Cnn$@>1fkx@
>ncdgn cur/hqt?$WugtPcog$@>1ncdgn@ >kprwv cur/hqt?$WugtPcog$1@
>dt@
>ncdgn cur/hqt?$Rcuuyqtf$@>1ncdgn@ >kprwv cur/hqt?$Rcuuyqtf$ 1@
>dt@
>ncdgn cur/hqt?$TgogodgtOg$@>1ncdgn@ >kprwv cur/hqt?$TgogodgtOg$ 1@
>dt@
>dt@
>kprwv v{rg?$uwdokv$ xcnwg?$NQIKP$1@
>1hqto@
This form will allow users to enter their credentials. When they click on the LOGIN
button, a POST request will be sent to the Login action. Let’s create that action.
kh *tguwnv0Uweeggfgf+
}
tgvwtp TgfktgevVqCevkqp*$Kpfgz$."$Kpuvtwevqt$+=
Ä
gnug
}
OqfgnUvcvg0CffOqfgnGttqt*$$."$Hckngf"vq"nqikp$+=
Ä
Ä
tgvwtp Xkgy*nqikpKphq+=11iq"dcem"vq"nqikp"hqto"000
Ä
Add
Bkh*Wugt0Kfgpvkv{0KuCwvjgpvkecvgf+"11kh"vjg"wugt"ku"nqiigf"kp
}
>NK encuu?$pcx/kvgo$@
>c encuu?$pcx/nkpm$ cur/cevkqp?$Nqiqwv$ cur/eqpvtqnngt?$Ceeqwpv$@Nqiqwv>1c@
>1NK@
Ä
gnug 11kh"vjg"wugt"ku"pqv"nqiigf"kp
}
>NK encuu?$pcx/kvgo$@
>c encuu?$pcx/nkpm$ cur/cevkqp?$Nqikp$ cur/eqpvtqnngt?$Ceeqwpv$@Nqikp>1c@
>1NK@
Ä
We’ll test this later, but for now make sure your code compiles without any errors.
rwdnke KCevkqpTguwnv"Tgikuvgt*+
}
tgvwtp Xkgy*+="11rtgugpvu"vjg"Tgikuvgt"hqto"vq"vjg"wugt
Ä
11000
Note: The UserManager is a generic class, and you need to pass to it your User
class (derived from IdentityUser), or the IdentityUser (if you did not use a
derived class).
302 14 Introduction to Authentication. User Login, Logout, and Registration
Note: Above we have two services injected. Their order is not important. They can be
injected in any order.
pcogurceg CURDqqmRtqlgev0XkgyOqfgnu
}
rwdnke encuu TgikuvgtXkgyOqfgn
}
]Fkurnc{*Pcog"?"$Wugt"Pcog$+_
]Tgswktgf*GttqtOguucig"?"$c"wugtpcog"ku"tgswktgf$+_
rwdnke uvtkpiA"WugtPcog"}"igv="ugv="Ä
]Tgswktgf*GttqtOguucig"?"$c"rcuuyqtf"ku"tgswktgf$+_
]FcvcV{rg*FcvcV{rg0Rcuuyqtf+_
rwdnke uvtkpiA"Rcuuyqtf"}"igv="ugv="Ä
]Fkurnc{*Pcog"?"$Eqphkto"Rcuuyqtf$+_
]Tgswktgf*GttqtOguucig"?"${qw owuv"eqphkto"{qwt"rcuuyqtf$+_
]FcvcV{rg*FcvcV{rg0Rcuuyqtf+_
rwdnke uvtkpiA"EqphktoRcuuyqtf"}"igv="ugv="Ä
]Fkurnc{*Pcog"?"$Hktuv"pcog$+_
rwdnke uvtkpiA"HktuvPcog"}"igv="ugv="Ä
]Fkurnc{*Pcog"?"$Ncuv"pcog$+_
rwdnke uvtkpiA"NcuvPcog"}"igv="ugv="Ä
]Fkurnc{*Pcog"?"$Gockn"cfftguu$+_
]FcvcV{rg*FcvcV{rg0GocknCfftguu+_
]Tgswktgf*GttqtOguucig"?"$gockn"cfftguu"tgswktgf#$+_
rwdnke uvtkpiA"Gockn"}"igv="ugv="Ä
]TgiwnctGzrtguukqp*$]2/;_}5Ä/]2/;_}5Ä/]2/;_}6Ä$."GttqtOguucig"?"${qw"owuv"hqnnqy"vjg"
hqtocv"222/222/2222#$+_
]Fkurnc{*Pcog"?"$Rjqpg"pwodgt$+_
rwdnke uvtkpiA"Rjqpg"}"igv="ugv="Ä
Ä
Ä
Boqfgn"TgikuvgtXkgyOqfgn
B}
XkgyDci0Vkvng"?"$Tgikuvgt"c"pgy"ceeqwpv$=
Ä
>j3@Tgikuvgt"c"pgy"ceeqwpv>1j3@
>hqto cur/cevkqp?$Tgikuvgt$@
>fkx cur/xcnkfcvkqp/uwooct{?$Cnn$@>1fkx@
>ncdgn cur/hqt?$WugtPcog$@>1ncdgn@<">kprwv cur/hqt?$WugtPcog$ 1@>dt 1@
>ncdgn cur/hqt?$Rcuuyqtf$@>1ncdgn@<">kprwv cur/hqt?$Rcuuyqtf$ 1@>dt 1@
>ncdgn cur/hqt?$EqphktoRcuuyqtf$@>1ncdgn@<">kprwv cur/hqt?$EqphktoRcuuyqtf$ 1@>dt 1@
>ncdgn cur/hqt ?$HktuvPcog$@>1ncdgn@<">kprwv cur/hqt?$HktuvPcog$ 1@>dt 1@
>ncdgn cur/hqt?$NcuvPcog$@>1ncdgn@<">kprwv cur/hqt?$NcuvPcog$ 1@>dt 1@
>ncdgn cur/hqt?$Gockn$@>1ncdgn@<">kprwv cur/hqt?$Gockn$ 1@>dt 1@
>ncdgn cur/hqt?$Rjqpg$@>1ncdgn@<">kprwv cur/hqt?$Rjqpg$ 1@>dt 1@
This form will allow users to enter the data needed to create/register a new account.
When they click on the REGISTER ACCOUNT button, a POST request will be sent to
the Register action. Let’s create that action.
11cvvgorv"vq"tgikuvgt"vjg"pgy"ceeqwpv
xct tguwnv"?"cyckv awugtOcpcigt0EtgcvgCu{pe*pgyWugt."wugtGpvgtgfFcvc0Rcuuyqtf+=
kh *tguwnv0Uweeggfgf+
tgvwtp TgfktgevVqCevkqp*$Kpfgz$."$Kpuvtwevqt$+=
gnug
}
hqtgcej *xct"gttqt"kp tguwnv0Gttqtu+
OqfgnUvcvg0CffOqfgnGttqt*$$."gttqt0Fguetkrvkqp+=
Ä
Ä
11iq"dcem"vq"vjg"xkgy
tgvwtp Xkgy*wugtGpvgtgfFcvc+=
Ä
304 14 Introduction to Authentication. User Login, Logout, and Registration
Note: Since the CreateAsync is an asynchronous method, we had to make the entire
action asynchronous.
To find more information about the parameters used in CreateAsync method, in
Visual Studio, hover your mouse over this method and you’ll find more information
provided by IntelliSense.
pcogurceg CURDqqmRtqlgev0EwuvqoXcnkfcvkqpu
}
rwdnke encuu EqphktoRcuuyqtfXcnkfcvkqpCvvtkdwvg <"XcnkfcvkqpCvvtkdwvg
}
rtqvgevgf qxgttkfg XcnkfcvkqpTguwnvA"KuXcnkf*qdlgevA"xcnwg."XcnkfcvkqpEqpvgzv"xcnkfcvkqpEqpvgzv+
}
TgikuvgtXkgyOqfgn"kpuvcpegTgikuvgtXkgyOqfgn"?"*TgikuvgtXkgyOqfgn+xcnkfcvkqpEqpvgzv0QdlgevKpuvcpeg=
kh *kpuvcpegTgikuvgtXkgyOqfgn0Rcuuyqtf"#?"kpuvcpegTgikuvgtXkgyOqfgn0EqphktoRcuuyqtf+
tgvwtp pgy XcnkfcvkqpTguwnv*$Rcuuyqtf"cpf"Eqphkto"Rcuuyqtf"hkgnfu"owuv"ocvej#$+=
tgvwtp XcnkfcvkqpTguwnv0Uweeguu="11"cnn"qvjgt"ecugu"ctg"xcnkf
Ä
Ä
Ä
eqpvgzv0Fcvcdcug0GpuwtgFgngvgf*+="11kh"qwt"fcvcdcug"gzkuvu."vjgp"gtcug"kv#
Let’s test our work. When we run our application, we see the newly added Login and
Register new account menu options (Fig. 14.1):
14.2 Introduction to ASP .Net (Core) Identity 305
Fig. 14.1 Shows the Index view. Note that the navbar displayed at the top of the page contains the
newly added menu options Login and Register new account
Fig. 14.2 Shows the Register view. Note that the page displays validation errors for the email
address missing (this is a required field) and for non-matching passwords
We got to the last section of this book. In here we’ll make some changes to our code so
only users who are logged in can access certain actions (add, edit, and delete). All users
should still be able to view all data.
For this step, make sure both of the following middleware components were added to
Program.cs:
crr0WugCwvjgpvkecvkqp*+=
11”
crr0WugCwvjqtk|cvkqp*+=
14.2 Introduction to ASP .Net (Core) Identity 307
Fig. 14.5 Shows the validation error displayed in the Register view when the user enters a pass-
word that does not follow the server side specifications (for example, it does not include at least 8
characters, or it does not have at least one lowercase and at least one upper case letter)
Fig. 14.6 Shows the validation error displayed in the Login view when the user has invalid creden-
tials
Fig. 14.7 Shows the Index view displayed after the user logs into their account. In particular, note
the Logout menu option in the top navbar (which indicates that the user has been successfully
authenticated)
308 14 Introduction to Authentication. User Login, Logout, and Registration
dwknfgt0Ugtxkegu0CffFghcwnvKfgpvkv{>Wugt@*11wugt"{qwt"Kfgpvkv{Wugt"encuu"kp">@
with
dwknfgt0Ugtxkegu0CffKfgpvkv{>Wugt."Kfgpvkv{Tqng@*11wugt"{qwt"Kfgpvkv{Wugt"encuu"kp">@
11kh"c"hkng1kocig"ycu"wrnqcfgf."eqpxgtv"kv"vq"d{vg]_"cpf"ucxg"kv
kh *Tgswguv0Hqto0Hkngu0Eqwpv"@"2+"11fkf"vjg"wugt"wrnqcf"c"hkngA
}
xct hkng"?"Tgswguv0Hqto0Hkngu]2_=11qwt"xkgy"qpn{"cnnqyu"qpg"hkng
Ogoqt{Uvtgco"ou"?"pgy Ogoqt{Uvtgco*+=
hkng0Eqr{Vq*ou+="11eqr{"vjg"hkng"kpvq"c"ogoqt{"uvtgco"qdlgev
pgyKpuvtwevqt0KpuvtwevqtRtqhkngRjqvq"?"ou0VqCttc{*+=11ucxg"vjg"d{vgu"kpvq"pgyKpuvtwevqt
ou0Enqug*+=
ou0Fkurqug*+=
Ä
afdEqpvgzv0Kpuvtwevqtu0Cff*pgyKpuvtwevqt+="11cff"vjg"pgy"kpuvtwevqt"vq"qwt"nkuv
afdEqpvgzv0UcxgEjcpigu*+=
tgvwtp TgfktgevVqCevkqp*$Kpfgz$+=
Ä
Now rebuild your application, and if a user is already logged in, please logout. You
should be able to see the Index action of InstructorController, even if you are
not logged in.
Now click on Add a new instructor button. You should be prompted to login (see
Fig. 14.8). Before you log in, please note the URL.
Login, then try again to add a new instructor. While logged in, you should now be able
to do it.
In the URL above you should note the encoded query string:
ReturnUrl=/Instructor/Add. To make use of it, we can modify our Login (the
GET) action to capture that value:
rwdnke KCevkqpTguwnv"Nqikp*uvtkpiA"tgvwtpWtn+
}
VgorFcvc]$EcrvwtgfTgvwtpWtn$_"?"tgvwtpWtn=
tgvwtp Xkgy*+="11rtgugpvu"vjg"Nqikp"hqto"vq"vjg"wugt
Ä
14.2 Introduction to ASP .Net (Core) Identity 309
Fig. 14.8 Shows the view that prompts the user to log into their account
kh *tguwnv0Uweeggfgf+
}
kh *#uvtkpi0KuPwnnQtGorv{*VgorFcvc]$EcrvwtgfTgvwtpWtn$_"cu uvtkpi++
tgvwtp Tgfktgev*VgorFcvc]$EcrvwtgfTgvwtpWtn$_"cu uvtkpi+=
gnug
tgvwtp TgfktgevVqCevkqp*$Kpfgz$."$Kpuvtwevqt$+=
Ä
gnug
}
OqfgnUvcvg0CffOqfgnGttqt*$$."$Hckngf"vq"nqikp$+=
Ä
Ä
tgvwtp Xkgy*nqikpKphq+=11iq"dcem"vq"nqikp"hqto"000
Ä
Now, after you are prompted to login, you will be redirected to the page that sent you
to login (that needed authorization). See this for more details [79].
Note: Above we made use of temp data, which allows a controller to preserve data
between requests. This is particularly useful when performing requests. Temp data marks
values for deletion once they are read and then removed when the request has been
processed. See more in [5].
Important note: This chapter is an overly simplistic section meant to stimulate your
interest in learning more. In particular, we did not go over roles, policies, and how to allow
users to log into our web application using their credentials from other web applications,
such as Facebook and LinkedIn. Please look into the mentioned references to learn more
about these.
References
© The Editor(s) (if applicable) and The Author(s), under exclusive license 311
to Springer Nature Switzerland AG 2024
R. A. Mezei, Introduction to the Development of Web Applications Using
ASP .Net (Core) MVC, Synthesis Lectures on Computer Science,
https://doi.org/10.1007/978-3-031-30626-6
312 References