ASP.NET MVC Guide for Beginners
In this tutorial, I’m going to teach you
the fundamentals of ASP.NET MVC 5 and Entity Framework 6.
Before we get started, I’m assuming you
already have some experience with C# and Visual Studio. At a minimum, you
should be able to write code and have very basic understanding of databases.
However, no prior knowledge of ASP.NET MVC or Entity Framework is required. So,
let’s get started.
What
We’re Going to Build
As part of this tutorial, we’ll be
building a simple web application for a video rental store. We want the admins
to have a page to see the list of all videos in the store, as well as the
ability to add, edit and delete videos. I’m going to exclude advanced features
such as dealing with the stock, renting a
video or returning it, calculating
cost, etc., so we can focus on the core concepts of ASP.NET MVC. I’ll be
covering those advanced scenarios in my comprehensive ASP.NET MVC course that
will be published on Udemy soon.
So, as part of building this app, you’re
going to learn how to:
- Create a data entry form to add, update and delete records in a database. In this tutorial, we’ll build a form for managing videos, but you can take what you learn from this tutorial and apply it in any real-world scenarios.
- Display a list of records. Again, here we’ll display a list of videos, but you can apply what you’ll learn here to any real-world scenarios. You can use these techniques to display lists of tweets, photos, blog posts, and literally anything else.
- Use Entity Framework to query or update your database. Entity Framework is an Object/Relational Mapper (ORM) that sits on top of a database. This frees you from having to work directly with database connections, writing SQL queries and commands, etc. You ask Entity Framework to load or save a set of objects, and Entity Framework will take care of all the dirty work for you.
- Generate your database using code. This is what we call Code First workflow (more on this later).
By the end of this tutorial, you’ll have
a good understanding of ASP.NET MVC and Entity Framework fundamentals. Not only
will you be able to build simple applications, but you’ll also be able to
follow other tutorials more easily. So, let’s get started.
Setting
Up the environment
To build this application, you need
Visual Studio 2013. Any edition would work. You can get a free community
edition of Visual Studio here:
Once you have Visual Studio ready, launch
it. Then go to File > New >
Project. In the New Project dialog, on the left side, under Templates,
select Visual C# > Web. On
the right side, select ASP.NET Web
Application.
In the Name field, type Beaver. This
is the code name for our video rental store application. In the Location box, specify a location to
create the files on the disk. Preferably, use a local drive and not a network
drive. Otherwise, sometimes you may run into security or performance issues.
Finally, click OK.
On the next page, select MVC from templates. On the bottom
right, make sure Host in the cloud is
not checked, as we’re not going to cover deployment in this tutorial.
Next, wait a few seconds until Visual
Studio creates your project template.
Now that our application is created, we
can run it by pressing Ctrl+F5. Visual
Studio will launch your browser and take you to an address like http://localhost:1234 where 1234
is the port number. The port number on your machine might be different from
mine, but that doesn’t matter.
Beginner’s Question:
What is localhost? Every
web application needs to be hosted by a web server. Localhost is a mini web
server running on your machine. In the real world, you often publish an ASP.NET
application to a full-fledged IIS server. Web hosting companies take care of
installation of IIS for you. So you never have to worry about it. All you do is
upload your files.
Now we have an application with some
basic functionality. You can register a new account, log in, log out, change
your password, etc. All this functionality comes with the ASP.NET MVC template.
So, you never have to write code for these repetitive features that nearly
every application needs.
Let’s test-drive our sample application.
On the top right, click Register.
Fill out the form with an email address and password, and click Register.
Note that you’re automatically logged in
with the email address you supplied.
You can also log out.
All this functionality comes out of the
box with a package called ASP.NET Identity. If the default registration form
doesn’t work for you, you can always customize it. That is beyond the scope of
this tutorial, and I’m going to cover it in my comprehensive ASP.NET MVC
course.
Let’s recap: we built an ASP.NET MVC
application using the template in Visual Studio. Now, it’s time to implement
some functionality specific to our video rental store. Over the next few
sections, in each section, we’ll add a new function to our application.
Displaying
the List of Videos
The first feature I’d like to implement
here is a page with a plain list of videos. It’s much easier to build this than
a form to add or update a video. So, to build this page, first we need a
database populated with some records.
The traditional workflow is to create a
database, design the tables using table designers, and then generate domain
classes based on these tables. This approach or workflow is called Database First. But in this tutorial,
I’m going to teach you the modern way of working with a database. This approach
is called Code First, which
simply means code first, and let the tools create the database for you. Thus,
we’re never going to use table designers to create our database.
At this point, you might ask, “What
tooling do we need to auto-generate the database?” The answer is: Entity
Framework, which comes automatically with our default ASP.NET MVC template.
Here is an overview of what we’re going
to do in this section.
- We’re going to create a domain class that represents a video. This class is going to have properties such as Name, Description and Genre.
- We use Entity Framework to generate the database.
- We populate our table with some video records.
- We build a web page to display the list of videos in the database.
Step
1: Building the Model
Back to Visual Studio, go to Solution Explorer. If it’s not
visible, open it from View >
Solution Explorer. Right-click the Models folder in the project and select Add > Class… In the Add Class dialog, type Video in the Name field. Now, declare these properties in the Video class.
public class Video
{
public int Id { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public Genre Genre { get; set; }
}
public enum Genre
{
Comedy = 1,
Horror,
SciFi,
Romance,
Documentary,
Kids
}
Here, we use the Id property to uniquely identify each video. This Id will be generated by the database.
Next, open IdentityModels.cs from the Models
folder. This code is automatically generated as part of the project
template. Inside this file, we have two classes: ApplicationUser, which represents a user, and ApplicationDbContext. A DbContext is
an abstraction over the database which hides all the complexity of working with
a database, such as opening and closing connections, running commands, reading
and writing data, etc. Here is the auto-generated code for ApplicationDbContext:
public class ApplicationDbContext
: IdentityDbContext
{
public ApplicationDbContext()
: base("DefaultConnection", throwIfV1Schema: false)
{
}
public static ApplicationDbContext Create()
{
return new
ApplicationDbContext();
}
}
Note the constructor of this class. The
constructor calls the base constructor and passes “DefaultConnection”.
public ApplicationDbContext()
: base("DefaultConnection", throwIfV1Schema: false)
{
}
DefaultConnection is the name of the
connection string for the database and is declared in Web.config, which is the configuration file for our application.
Let’s have a quick look at the connection string. Going back to the Solution
Explorer, open Web.config in the
project root.
Under <connectionStrings>, note the element with the name DefaultConnection. Look at the value
of the connectionString attribute.
This is the actual connection string to the database:
<add name="DefaultConnection" connectionString="Data
Source=(LocalDb)\v11.0;AttachDbFilename=|DataDirectory|\aspnet-Beaver-20150822102023.mdf;Initial
Catalog=aspnet-Beaver-20150822102023;Integrated Security=True"
Let me break this down for you:
- Data Source: specifies the instance of your SQL Server. LocalDb is a lightweight version of SQL Server that is used for development, which is automatically installed with Visual Studio 2013. So you don’t need a full installation of SQL Server on your machine to do your development.
- AttachDbFilename: this is the path to the database file. Here DataDirectory represents the App_Data folder in your project. So if you look inside this folder, you’re going to see a file named aspnet-Beaver-xxxx. The value of xxxx depends on the date/time you create this project.
- Initial Catalog: is the name of the database. In this case, it is the same name as the database file itself.
- Integrated Security = True means you can connect to the database using your current Windows credentials, so you don’t need a separate username/password to connect to SQL Server.
Let’s quickly recap what we’ve done up to
this point. We created a Video
class and had a quick look at the auto-generated DbContext as well as the
connection string for the database.
The last part of this step is to make a
slight modification to our DbContext. Go back to IdentityModels.cs, and modify ApplicationDbContext as follows:
public class ApplicationDbContext :
IdentityDbContext<ApplicationUser>
{
public DbSet<Video>
Videos { get; set; }
public ApplicationDbContext()
: base("DefaultConnection",
throwIfV1Schema: false)
{
}
public static ApplicationDbContext Create()
{
return new ApplicationDbContext();
}
}
Here I added a property of type DbSet<Video> called Videos. DbSet is a generic class that
comes with Entity Framework. As the name implies, it represents a set (or a
table) in a database. We work with this DbSet like a collection in memory. We
add Video objects to it, we remove an object, or modify it and then when we ask
Entity Framework to write changes to the database. Entity Framework keeps track
of changes in this collection in memory, and based on those changes it will
generate corresponding SQL commands and runs them on the database.
We built our model in this step. Next,
we’re going to generate the database.
Step
2: Generating the Database
In this step, you’re going to learn how
to use Entity Framework Code First workflow to generate a database. But before
we get started, let me explain the process first, and then we’ll do it
together. The Code First workflow includes three steps:
- Enabling migrations: The very first time you need to use Code First workflow, you need to enable migrations. With this, you instruct Entity Framework to keep track of changes in your domain classes.
- Adding a migration: Once migrations are enabled, every time you make a change to your domain classes (e.g., adding a new class, modifying an existing one, etc.), you create a new migration. A migration is a class with some auto-generated code that is used to upgrade or downgrade the database. Since Entity Framework is now aware of changes in your domain classes, it will automatically generate the code to migrate the database. For example, if you create a new class like Video and then add a migration, Entity Framework will generate code to create a new table called Videos.
- Updating the database: With this step, you tell Entity Framework to run migrations you’ve created on the database. Entity Framework will compare the migrations you have in your project with the ones that are run on the database, and if there are any new migrations, it will run them sequentially to bring your database up to date.
So this is the process. Now, it’s time to
do the steps. We do all these steps in Package
Manager Console. Go to Tools
> NuGet Package Manager > Package Manager Console.
First, we need to run the Enable-Migrations command.
PM> Enable-Migrations
When you run this command, a new folder
called Migrations will be added
to your project. Inside this folder, you’ll see a file like [DATETIME]_InitialCreate.cs. Here, the
value of DATETIME depends on the
date/time you run this command.
What is this file? Let’s open it and see.
You see a migration class called InitialCreate
which derives from DbMigration
and has two methods: Up and Down, which are executed when
upgrading or downgrading the database, respectively. So with Entity Framework
Code First, you get full versioning of your database. At any time, you can
upgrade it to the latest version, or downgrade it to an earlier version. This
is extremely useful in the real world, where you may need to maintain multiple
versions of an application.
Let’s take a look at the Up method in our
migration class. Inside this method, you see a number of calls to CreateTable method. Each of these
calls aims to create a table in the database. When you run migrations, Entity
Framework will read all this code and generate corresponding SQL statements to
execute on your database. This migration aims to create tables such as AspNetRoles, AspNetUserRoles, AspNetUsers,
etc. These are the tables used internally by ASP.NET Identity to manage user
registration, login, etc.
Now, back to our migration process. I
mentioned that every time you make a change to your domain model, you need to
add a new migration. Thus, we created the Video class and added a DbSet<Video>
to our DbContext. By adding this DbSet to our DbContext, we tell Entity
Framework that this Video class needs to be stored in the database. Now, at
this point, we don’t have any tables in the database for our videos. So, we
need to add a new migration.
Back to Package Manager Console, run this command:
PM> Add-Migration AddVideosTable
This command takes an argument, which is
the name of your migration, in this case AddVideosTable. This name is arbitrary, and you can call it
anything. There is no magic behind it. It’s just a way to organize your
migrations so you know how each migration affects your database. I tend to name
my migrations based on the changes that should happen in the database. In this
case, because the Video class is a new class, I expect Entity Framework to
generate a migration to add a new table called Videos. This is why I called
this migration AddVideosTable.
When you run this command, Entity
Framework will create a new migration file in the Migrations folder, called [DATETIME]_AddVideosTable.
Let’s take a look at this file.
So, you see another migration class that
derives from DbMigration. Look at the Up method. Note the call to CreateTable,
which tries to create a table called Videos.
public override void Up()
{
CreateTable(
"dbo.Videos",
c => new
{
Id
= c.Int(nullable: false,
identity: true),
Title
= c.String(),
Description
= c.String(),
Genre
= c.Int(nullable: false),
})
.PrimaryKey(t
=> t.Id);
}
I’ve highlighted the key areas here that
you need to understand. Note the name of this table: Videos. Entity Framework uses convention over configuration. Thus,
there are certain conventions built into it that work in most cases. In this
case, because we have a class called Video,
it assumes the table should be called Videos.
Similarly, because we have four
properties in our Video class, it will create four columns with the same name
and type in the database: Id, Title,
Description and Genre.
Also, look at the last line in this
method:
.PrimaryKey(t => t.Id);
Again, based on the convention, it
assumes that the Id property or column represents a unique key for a video.
Hence, it marks this column as the primary key.
As you see, the convention is useful and
works in most cases. But if it doesn’t work for you, you can always override
it. For example, some developers prefer singular names for their database
tables. You can always supply additional configuration to override the default
conventions. This is beyond the scope of this tutorial, but it’s something I’ve
covered in my Entity Framework course that will be published on Udemy soon.
Back to our migration process. We enabled
the migrations, and added a new migration. Entity Framework detected that we
have a new class, called Videos, and generated the corresponding code to create
a new table in the database. The third step of the process is to run the
migrations.
Open up Package Manager Console again and run:
PM> Update-Database
At this point, Entity Framework runs any
pending migrations on your database. By pending, I mean any migrations that
have not been run on the database before. So, let’s take a look at the
database.
In Solution Explorer, expand App_Data
folder. You should see your database file with MDF extension. If you
don’t see it, it’s because it’s not part of the project, and by default, Visual
Studio hides files on the disk that are not in your project. To see all files,
click the Show All Files button
in the toolbar on Solution Explorer.
You should see your database file in App_Data folder. Double-click it. A
new panel called Server Explorer
pops out of the left side. Expand the Tables.
The _MigrationHistory table is used by Entity Framework to keep track
of migrations that have been run on the database. Tables starting with AspNet are used by ASP.NET Identity to
manage authentication. And finally, we have our Videos table.
As you saw in this step, with the Code
First workflow, we never wasted our time with table designers. Instead, we
focused on our code, which is what we programmers do anyway. We modify our
domain model, then add a migration and update the database. Entity Framework
will take care of bringing the database up to date. With this, not only can we
get things done faster, but we also get full versioning of our database. We can
upgrade or downgrade our database to any version. When it comes to deploying
our application to production, we can ask Entity Framework to generate a SQL
script based on all migrations in our project. Then we can take this script and
run it on the production database.
In this step you learned about Code First
migrations. Now that we have our domain class and the corresponding table, it’s
time to populate this table with some data so we can display it to the user.
Step
3: Populating the Table
In Server Explorer, right-click the Videos table and go to Show
Table Data. Then add a few records to this table. Note that the Id column is an IDENTITY column, so
you don’t need to supply any values for it. Again, by convention, Entity
Framework marks the primary key of a table as an identity column.
Step
4: Creating a Page to Display Videos in the Database
In this step, you’re going to learn how
to create new web pages with ASP.NET MVC. We’re going to create a simple page
to display all videos in the database.
Again, before getting into the mechanics,
let me give you some background on the process and how everything fits
together.
ASP.NET MVC is based on the Model View Controller (MVC) pattern.
MVC is one of the architectural patterns that aims to provide better separation
of concerns in your application, which results in better maintainability.
To understand MVC, think of a restaurant.
When you go to a restaurant, there is a waiter/waitress, a chef, one or more
kitchen hands, a manager, etc. Every person in the restaurant is responsible
for only one thing. Imagine what the restaurant would be like if all these
people did everything! What would it be like if the chef came to you, took the
order, then went shopping, and asked the cashier to cook your food? You’d
probably run away! The same concept applies in software. Clean software is like
a well-managed restaurant, where everyone has one and only role. Architectural
patterns like MVC help us to achieve this by promoting separation of tasks.
Therefore, different parts of your application will be responsible for only one
thing. In MVC terms, we have:
- Views: which are purely responsible for presenting data. This is where we write our HTML markup. Views should only have logic related to viewing data, not for processing inputs. For example, you can use a foreach block to render a list of objects in a view. But you’re not supposed to use any other kind of logic for processing user input or controlling application flow.
- Controllers: responsible for processing user input, application flow, etc.
- Models: classes that represent the state of the application. In practical terms, this often means the domain model of an application. In our application, the Video class we created is part of the application domain. When we load data from the database, we create one or more Video objects that represent the state of the application.
As you see, with this structure, we get a
clean separation. Every component is responsible for one thing.
Earlier, we created our domain model. In
this step, we’re going to create a Controller and a View.
Right-click the Controllers folder and go to Add > Controller…. In the dialog box, select the first item (MVC 5 Controller – Empty).
There are other templates here that
automatically generate some code for us, but in this tutorial I want to teach
you how to write all this code by hand so you have a good understanding of how
everything works. Click Add. Name
this controller VideosController and
click Add again. Visual Studio will create a basic
empty controller like this:
public class VideosController
: Controller
{
// GET: Videos
public ActionResult Index()
{
return View();
}
}
Our VideosController derives from the Controller class. Controller is part of ASP.NET MVC and
encapsulates the basic functionality that every controller in the application
might need.
Here, we have a method called Index which returns an ActionResult. In ASP.NET MVC, Methods
in a controller are referred to as Actions.
The job of an action is to handle a request. So, when an HTTP request comes
into the ASP.NET runtime, it will be routed to an action for processing. This
routing is based on convention, but you can always override it. The convention
determines the name of a controller and an action from the URL of the request.
So, as an example, if the URL of a request equals /videos/index, it should be handled by a controller called VideosController with the Index method (or action). I’ll explain routing in a bit more
detail later on.
Here in this method: we need to write
code to handle a request for the URL /videos/index.
This means that we need to get the list of videos from the database and
display them in a view. Change the code as follows, and then I’ll explain how
everything works:
using System.Linq;
using System.Web.Mvc;
using Beaver.Models;
public class VideosController
: Controller
{
private
ApplicationDbContext _dbContext;
public VideosController()
{
_dbContext = new ApplicationDbContext();
}
// GET: Videos
public ActionResult Index()
{
var videos =
_dbContext.Videos.ToList();
return
View(videos);
}
}
What’s going on here? First, I added two using statements on the top to import
the required namespaces.
Next, I declared a private field of type ApplicationDbContext in the controller
and initialized it in the constructor.
In the Index action, we use _dbContext to get the videos from the
database. Remember how earlier we added a DbSet<Video> to our
AppicationDbContext? I told you that this DbSet represented a set (or a
table) in the database. So, here _dbContext.Videos
represents the set of videos. The ToList method is an extension method provided
by LINQ. When we call this method, Entity Framework runs a query on the
database and pulls out all videos from the database. If you need to better
understand LINQ and extension methods, check out my C# Advanced course on Udemy.
As you see, with Entity Framework,
getting data from the database can be as simple as one line of code. We never
have to worry about working with plain ADO.NET objects such as SQL connections,
commands, etc. Our DbContext takes care of all this complexity.
Finally, in the last line of our Index action, we call the View method and pass the videos object
to it. This method is a helper method that is defined in the base Controller.
It returns an instance of the ViewResult
class. By returning an instance of a ViewResult, we tell ASP.NET MVC runtime to
return a view upon processing this request. In a more complex application, we
don’t always return a view from an action. Sometimes we may want to redirect
the user to a new URL, or return a JSON object. This is the reason the return
type of our Index action is ActionResult.
This is an abstract class that specific action results inherit from. For
example, ViewResult, RedirectResult and JsonResult all inherit from ActionResult. By using the base class
as the return type, this method can return any of its derivatives. It’s common
convention to return an ActionResult from controller actions.
Let’s recap: we built a controller with
one action that returned a view populated by the list of videos from the
database. But where is the actual view? We’re going to create it now.
Back in the Solution Explorer, expand the Views folder, right-click the Videos and go to Add >
View… By convention, since we have a controller called Videos, we should have a folder here
called Videos, which includes
all the views this controller is going to use.
In the Add View dialog, set the view name
to Index. Again, here by
convention, since our action is called Index, we can create a view with the
same name. We don’t have to follow this convention. We can always create a view
with a different name, but following the convention helps us write less code to
achieve the same thing.
Make sure Use a layout page is ticked and click ….
In the new dialog, in the Project folders pane on the left,
navigate to Views > Shared.
On the right, select the first item: _Layout.cshtml
and click OK. Click Add to finish adding a new view.
So, what is a layout? It’s a template or
a master page for all our views. By using a layout, we can ensure that all
views have the same look and feel. The default ASP.NET MVC project template
already has a layout that you saw when previewing the site. We can always
change this layout to customize it for our own needs.
Let’s
take a look at the newly generated view:
@{
ViewBag.Title = "Index";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Index</h2>
In
ASP.NET MVC, our views are files with the cshtml extension. Inside these files we can write both HTML and C#
code. To write C# code, you need to prefix it with @ or @{ } if your
code is more than one line. This syntax is based on what we call Razor Views. So, in ASP.NET MVC we
have a view engine that knows how to parse these views with this special
syntax, and generate raw HTML from our C# code.
Look at the top of this file:
Look at the top of this file:
@{
ViewBag.Title = "Index";
Layout = "~/Views/Shared/_Layout.cshtml";
}
Here we have a code block indicated by @{ }. The first line specifies the
title of this view. This title is what we see in the browser title bar when we
access this page. The second line specifies the layout file that you selected
in the Add View dialog. So you can always come back here and change the layout
if you want.
Next, we need to display the list of
videos in this view. First, we need to add this line on the very top of the
view:
@model
IEnumerable<Beaver.Models.Video>
With this line, we tell the view engine
that the model behind this view is an instance of IEnumerable<Video>. What is IEnumerable? It’s an interface
used for enumerating a list of objects. Earlier in the controller, we used the
ToList method to get the list of videos from the database:
var videos = _dbContext.Videos.ToList();
Here, this videos object is a List<Video>.
A list in C# implements the IEnumerable
interface, which allows us to iterate the list using the foreach block.
Alternatively, in the view, we could set the model to List<Video> instead of IEnumerable<Video>. Is there a difference? Yes, a tiny one.
When you use a concrete class like List<Video>, your model has to be of
that type. But if you use an interface, your model can be of any type that implements that interface.
In this example, if we use IEnumerable and later we decide to pass an object of
a different type to this view (e.g., a SortedList
or Dictionary), everything will
continue to work, because those types implement the IEnumerable interface as well. Hence, with interfaces, your
application will be more loosely coupled and breaks less as a result of
internal changes.
Back to the view: we specified the model
passed to the view. Next, change the heading of the view from:
<h2>Index</h2>
to:
<h2>Videos</h2>
Now,
write the following piece of code after the heading:
<ul>
@foreach (var video in Model)
{
<li>@video.Title
(@video.Genre)</li>
}
</ul>
Here, we’re using a UL (unordered list) to display the
list of videos.
As you see, the code includes a mix of HTML
and C# code, thanks to the Razor view engine! Note that our C# code is prefixed
with @. This tells the view
engine that this is C# code and needs to be executed.
We use a LI (list item) to render each video. Again, in this line, we have
a mix of HTML and C# code. Here we simply display the Title and Genre of
each video. In a more complex application, you can have a complex UL/LI
structure with lots of child elements and display lots of attributes to the
user. But the process is exactly the same as what we’ve done here. Thus, you
can take this knowledge and apply it to a difference scenario.
Note that as you were typing:
@video.Title
IntelliSense showed you the properties of
the video object when you used the dot notation. This is because we set the model
of this view at the top of the view. If you didn’t do that, IntelliSense
wouldn’t work, and in some cases you would even get a runtime exception.
That’s all you had to do to get the list
of videos from the database and show them to the user. We created a controller
and wrote a couple of lines of code in the Index action, then created a view
and used a foreach block to display videos.
Let’s run the application and see the
result. Press Ctrl+F5 to run the
application. Your browser will launch and navigate to /Videos/Index. This is because you pressed Ctrl+F5 when you were
viewing the Index view in the Videos folder.
Look at the content of the page. You see
the list of videos. So we successfully built the first feature of our
application. In the next section, we’ll implement the ability to add a new
video to the database.
Adding
a New Video
In this section, you’re going to learn
how to create a data entry form and save data to the database using Entity
Framework. We will be building a form for adding a new video to the database.
I’m going to break this section into two
steps:
- Creating a page to add a video
- Saving the video to the database
Step 1: Creating
a Page to Add a Video
Before we get started, let’s take a
closer look at routing.
Earlier I told you that the routing
engine determines the name of the controller and the action from the URL of the
request. The default rule (or the default convention) targets a URL with the
pattern /controller/action/id. Here
both action and id are optional. If action is not specified, Index is assumed as the action name.
Take a look at the following table for a few examples:
URL
|
Controller
|
Action
|
/videos
|
VideosController
|
Index()
|
/videos/index
|
VideosController
|
Index()
|
/videos/new
|
VideosController
|
New()
|
/videos/edit/1
|
VideosController
|
Edit(int
id)
|
For the purpose of this section, we need
a new page for the user to add a new video. To do this, we’re going to create a
new action that responds to a URL like /videos/new.
Inside this action, we’ll return a view which will include a data entry form
for adding a video.
First, go to VideosController and create a new action like this:
public ActionResult New()
{
return View();
}
All we do here is simply return a view.
Let’s create the corresponding view. In Solution Explorer, expand the Views folder, right-click the Videos folder, and go to Add > View…. Set the name of the
view to New and make sure _Layout.cshtml is selected as the
layout (similar to the last section).
Next, set the model behind this view on
the top:
@model Beaver.Models.Video
We set the model to the Video because we’re going to capture a
Video object in this form.
Now,
write the following code in the view to create an HTML form element:
@using (Html.BeginForm("Add",
"Videos",
FormMethod.Post, new
{ @class
= "form"
}))
{
}
Here we are using Razor syntax (note the
@) to write C# code. Html.BeginForm is
a helper method, which we use to render an HTML form element. It returns an
instance of MvcForm, which is a
disposable object. By wrapping it inside a using block, we can ensure that it will be properly disposed.
The first and second arguments specify
the name of the action and controller that will be called when we post this
form. In this case, we expect an action named Add in our VideosController
to be called. We haven’t created this action yet, but we’ll do so soon.
The third argument specifies the form
method. In HTML forms, we have two methods: GET and POST. When sending data for
modification to the server, we should always use the POST method.
The last argument is an anonymous object
( new {} ) that specifies any additional attributes to add to the HTML markup.
So, when this code is executed, the view engine will render something like
this:
<form action=”/videos/add”
method=”post” class=”form”>
</form>
We use the form CSS class to give a nice, modern look to our forms. This CSS
class is defined in Bootstrap, which is a CSS framework for building modern and
responsive (mobile- and tablet-friendly) web applications. When you create an
ASP.NET MVC project in Visual Studio, Bootstrap is automatically added for you.
We added the form. Now, we need to add
three input fields in this form: Title, Description and Genre. Write this code
inside the using block:
<div class="form-group">
@Html.LabelFor(m => m.Title)
@Html.TextBoxFor(m => m.Title, new { @class = "form-control"
})
</div>
<div class="form-group">
@Html.LabelFor(m => m.Description)
@Html.TextAreaFor(m =>
m.Description, new
{ @class
= "form-control",
rows = 4
})
</div>
<div class="form-group">
@Html.LabelFor(m => m.Genre)
@Html.EnumDropDownListFor(m =>
m.Genre, new
{ @class
= "form-control"
})
</div>
Here we have three sections, each wrapped
with a div with a form-group class. Again, this is one of the classes
that is defined in Bootstrap. If you follow Botostrap’s markup, you always get
a nice, clean form that renders well both on the desktop and on mobile devices.
Inside each form-group, we have a label and an input field. Look at the first form-group.
<div
class="form-group">
@Html.LabelFor(m => m.Title)
@Html.TextBoxFor(m => m.Title, new { @class = "form-control"
})
</div>
We’re
using Html.LabelFor to render a
label for the Title property of
the Video class.
@Html.LabelFor(m => m.Title)
The expression m => m.Title is a lambda expression that we use to access a
property in the model of this view. If you’re not familiar with lambda
expressions, check out my C# Advanced course on Udemy. When this
line is executed by the view engine, we’ll get an HTML markup like this:
<label
for=”Title”>Title</label>
Next, we use Html.TextBoxFor helper method to render a text box.
@Html.TextBoxFor(m => m.Title, new {
@class = "form-control" })
Again, we use a lambda expression to
specify the target property. Also, note that the second argument to this method
is an anonymous object that specifies any additional attributes to add to the
markup, in this case form-control CSS
class. This is another Bootstrap class that gives our text boxes a bit of
padding, round corners and a nice effect when the input is in focus. The result
will be HTML markup like this:
<input type=”text” id=”Title”
name=”Title” class=”form-control”></input>
Now you read the second form-group.
<div
class="form-group">
@Html.LabelFor(m => m.Description)
@Html.TextAreaFor(m =>
m.Description, new
{ @class
= "form-control",
rows = 4
})
</div>
It’s very similar to the first one,
except that here we use the Html.TextAreaFor
helper method to render a textarea input element instead of a text box. This
allows the user to write a description that is more than one line.
And finally, in the third form-group, we
use a different helper method (Html.EnumDropDownListFor)
to render a drop-down list for the Genre property, which is an enum:
<div
class="form-group">
@Html.LabelFor(m => m.Genre)
@Html.EnumDropDownListFor(m =>
m.Genre, new
{ @class
= "form-control"
})
</div>
For the last step, we need a button to
submit the form. Add this markup inside the using block, after all form
groups:
<input type="submit"
class="btn btn-primary" value="Save" />
Here, btn and btn-primary are
both Bootstrap classes that make our buttons look cool and modern.
Your entire using block should look like
this:
@using (Html.BeginForm("Add",
"Videos",
FormMethod.Post, new
{ @class
= "form"
}))
{
<div class="form-group">
@Html.LabelFor(m => m.Title)
@Html.TextBoxFor(m => m.Title, new { @class = "form-control"
})
</div>
<div class="form-group">
@Html.LabelFor(m => m.Description)
@Html.TextAreaFor(m =>
m.Description, new
{ @class
= "form-control",
rows = 4
})
</div>
<div class="form-group">
@Html.LabelFor(m => m.Genre)
@Html.EnumDropDownListFor(m =>
m.Genre, new
{ @class
= "form-control"
})
</div>
<input type="submit"
class="btn btn-primary"
value="Save"
/>
}
Now it’s time to review what we have
done. Run the application with Ctrl+F5 and look at the form we’ve built:
Note how the input fields and the button
look. This is all because of the beautiful styles defined in Bootstrap.
In this step, we added a new action to
our controller that returned a view. We created a view with a form to add a
video. In the next step, we’re going to save this video to the database.
Step
2: Saving the Video to the Database
Earlier in the last step, we defined a
form with the following code:
using (Html.BeginForm("Add",
"Videos",
FormMethod.Post, new
{ @class
= "form"
}))
{
}
As I explained before, when this form is
posted, the Add action in the Videos controller will be executed. So
in this step, we’re going to implement this action.
Open up VideosController and create a new action as follows:
public ActionResult Add(Video video)
{
_dbContext.Videos.Add(video);
_dbContext.SaveChanges();
return RedirectToAction("Index");
}
This action takes a Video object populated with the values
that the user enters into the form.
The first line adds the Video object to
our DbSet. Remember DbSet? It’s a collection of objects in memory that is
tracked by Entity Framework. When we add an object to this collection, nothing
is saved to the database yet. Everything is in memory. When we call _dbContext.SaveChanges(), Entity
Framework will look at all the changed objects in memory and will automatically
run the right SQL statements to update the database.
In the last line, we are using the RedirectToAction helper method to
redirect the user to the Index
action. This means that once the video is saved, the user will be redirected to
/videos/index where they will
see the list of all videos in the database. The RedirectToAction method returns a RedirectToRouteResult which derives from ActionResult. So, by using ActionResult
as the return type of our actions, we can return an instance of any classes
that derive from ActionResult. So far, you’ve seen RedirectToRouteResult and ViewResult.
There are a few other ActionResults which are beyond the scope of this
tutorial.
It’s time to test our application. Run
the application with Ctrl+F5 and in the browser, change the URL to go to /videos/new.
Fill out the form, and click the Save
button. The new video should appear in the list.
Before we finish this step, it would be
nice to add a link to the Add Video page in the list of videos. Open up Views > Videos > Index and type
the highlighted code:
<h2>Videos</h2>
<p>
@Html.ActionLink("Add Video", "New")
</p>
<ul>
@foreach (var video in Model)
{
<li>@video.Title
(@video.Genre)</li>
}
</ul>
Here, Html.ActionLink is yet another helper method that we use to
generate hyperlinks. The first argument is the text for the link, and the
second argument is the name of the target action. We could optionally supply a
third argument that would be the name of the controller, but in this case
because we’re in a view for the Videos controller, it’s not necessary. It’ll be
assumed by convention.
Now, you might ask, “Why do we use these
helper methods to generate HTML markup? Why don’t we just use raw HTML
elements?” We can use raw HTML, but the benefit of using these helper methods
is that they are aware of routing configuration. In this case, when we tell
this method to render a link for the New action, it will generate a link that
points to /videos/new. If you
override the default routing and map your New action to another route (eg /videos/create), this code will
continue to work. It will generate a link to /videos/create. However, if we used a raw HTML anchor element
(e.g., <a href=”/videos/new”>), the link will break because we no longer
have an action that responds to /videos/new.
So, we prefer to use helper methods to generate HTML markup.
Run the application again, and you should
see the link.
Next, let’s add the ability to edit
existing videos.
Editing
an Existing Video
In this section, you’re going to learn
how to use partial views in ASP.NET MVC (for promoting re-usability) and how to
update data using the Entity Framework.
Similar to the last section, we’re going
to implement this feature in two steps. First, we’re going to add a link in
front of each video that would take us to a page populated with the video
details. Next, we’ll implement updating the video in the database.
Step
1: Creating a Page Populated with Video Details
In this step, we’re going to add an edit
link in front of each video. The link should take us to a URL like /videos/edit/{id} where {id} is the ID
of the video.
Open up Index view inside Views
> Videos and change the <li> as follows:
<li>@video.Title (@video.Genre) @Html.ActionLink("Edit", "Edit", new
{ id = video.Id })</li>
Again, here we’re using the ActionLink
helper method you saw in the last step. The first argument is the label for the
link, the second argument is the target action, and the third argument is an
anonymous object used to supply additional parameters to the route. In this
case, we’re passing the ID of each video. With this, ActionLink will return
markup like:
<a
href=”/videos/edit/1”>Edit</a>
When the user clicks on this link, we
need to take them to a page with a form populated with the video details. We
need an action to handle this request. Inside this action, we’re going to fetch
the video with the given ID from the database, and put it on the view. Create a
new action in the VideosController as
follows:
public ActionResult Edit(int id)
{
var video =
_dbContext.Videos.SingleOrDefault(v => v.Id == id);
if (video == null)
return HttpNotFound();
return View(video);
}
In the first line, we’re using our DbSet
(_dbContext.Videos) but this
time, instead of using the ToList
method (to get all videos), we use SingleOrDefault.
Both of these methods are LINQ extension methods. If you’re not familiar with
LINQ, checkout my C# Advanced course. With this method,
we get a single object from the database. Default means that if no object
matching the criteria is found, null is returned. Note that the argument to
this method is a lambda expression that specifies the criteria.
If the video is not found, we call the HttpNotFound method and return its
value.
if (video == null)
return HttpNotFound();
This method returns HttpNotFoundResult which is yet
another ActionResult. With this
line, the user will automatically be redirected to a standard “Page Not Found”
page in your application.
Finally, if the video is found, we return
a view and populate it with the video object:
return View(video);
Now, let’s go ahead and create this view.
Inside the Views folder,
right-click Videos and go to Add > View… Set the name of this
view to Edit and ensure that the
layout is selected.
We want to display a form to edit a
video. This form looks identical to the form we created earlier for adding a
video. Following the DRY principle (Don’t Repeat Yourself) of software
engineering, let’s refactor our existing code and prevent code duplication.
Right-click the Videos folder again, and go to Add > View… Set the name of this view to _VideoForm (note the underscore) and
make sure to tick Create as a partial
view.
When you tick this option, the layout
checkbox becomes disabled. A partial view is like a small view that we can
reuse in many views. It doesn’t have a layout, so it’s not a full view. In this
case, we’re going to extract our video form and put it in this partial view so
we can reuse it in both the add and edit views.
Open up New.cshtml. Select all the markup inside the using block
(excluding the using block), and then cut and paste it into _VideoForm.cshtml. This is the markup
you need to move to the partial view:
<div
class="form-group">
@Html.LabelFor(m => m.Title)
@Html.TextBoxFor(m => m.Title, new { @class = "form-control"
})
</div>
<div
class="form-group">
@Html.LabelFor(m => m.Description)
@Html.TextAreaFor(m => m.Description, new { @class = "form-control",
rows = 4
})
</div>
<div
class="form-group">
@Html.LabelFor(m => m.Genre)
@Html.EnumDropDownListFor(m => m.Genre, new { @class = "form-control"
})
</div>
<input
type="submit"
class="btn btn-primary"
value="Save"
/>
Make sure to set the model for this
partial view at the top of the view:
@model Beaver.Models.Video
Back to New.cshtml, change the using block as follows:
@using (Html.BeginForm("Add",
"Videos",
FormMethod.Post, new
{ @class
= "form"
}))
{
Html.RenderPartial("_VideoForm");
}
Here we’re using RenderPartial helper method to include the markup in another view,
in this case _VideoForm. Note
that by convention, partials are prefixed with an underscore.
Also, note that the call to RenderPartial
is terminated by a semicolon because unlike the other helper methods you’ve
seen so far (e.g., Html.LabelFor, Html.TextBoxFor), this method directly writes
markup to the response, whereas the other helper methods return an MvcHtmlString which is then converted
to a string by the view engine.
Now we’re going to reuse this partial
view in our Edit Video page. Copy the entire using block in New.cshtml and paste it into Edit.cshtml. Then change the action
name from Add to Update. Your Edit.cshtml should look like this:
using (Html.BeginForm("Update",
"Videos",
FormMethod.Post, new
{ @class
= "form"
}))
{
Html.RenderPartial("_VideoForm");
}
Run the application with Ctrl+F5. You’ll
see a runtime error because Visual Studio redirects you to /videos/edit without specifying the ID
of the video to edit. Change the address in the browser and navigate to /videos to see the list of videos.
Click the edit link in front of one of the videos. You’ll be redirected to the
edit form populated with the video you selected.
In the next step, we’re going to save the
changes to the database.
Step
2: Updating a Video in the Database
In the last step, when we created our Edit view, we set the target action to
Update. In this step, we need to
implement this action to update the video. Add a new action to VideosController as follows:
[HttpPost]
public ActionResult Update(Video video)
{
var videoInDb =
_dbContext.Videos.SingleOrDefault(v => v.Id == video.Id);
if (videoInDb == null)
return HttpNotFound();
videoInDb.Title = video.Title;
videoInDb.Description = video.Description;
videoInDb.Genre = video.Genre;
_dbContext.SaveChanges();
return RedirectToAction("Index");
}
The code should be very familiar, except
for a few minor differences.
First, I’ve decorated this action with
the [HttpPost] attribute. This
tells the MVC runtime that this action can only be called when there is an HTTP
POST request.
Beginner’s question:
What
is HTTP POST, and why/when should we use it? HTTP requests have different
verbs, and each verb has a different meaning. We have GET (for reading data),
POST and PUT (for inserting/updating data), and DELETE for deleting data. As a
best practice, whenever you’re changing data in ASP.NET MVC, you need to mark
your actions as HttpPost. If you
don’t do this, it is possible that when a search engine spider (like Google) is
crawling your website, it modifies or deletes your data without you being aware
of that. Because these spiders use HTTP GET to navigate to different parts of
your website, when you use HTTP POST, you’re preventing a simple HTTP GET from
modifying/deleting data in your database.
Back to our action: in the first line, we
get the video with the given ID from the database.
var videoInDb =
_dbContext.Videos.SingleOrDefault(v => v.Id == video.Id);
Note that I named this variable videoInDb to distinguish it from the
video object passed as a result of posting the form.
Next, we check to see if the video was
found or not, just like before.
if (videoInDb == null)
return HttpNotFound();
Then we update the Title, Description and Genre properties of videoInDb and call _dbContext.SaveChanges() to update the
database.
videoInDb.Title = video.Title;
videoInDb.Description =
video.Description;
videoInDb.Genre = video.Genre;
_dbContext.SaveChanges();
DbContext will issue an UPDATE SQL
statement based on the properties we have updated. Finally, we redirect the
user to the list of videos.
return
RedirectToAction("Index");
Let’s run the application and see if
everything is working. Navigate to /videos,
and click the Edit in
front of the first video. Make some changes, and click the Save button. You’ll
be redirected to the 404 Not Found page. Why is that? This is one of those
issues that you may encounter when you forget to add a hidden field in your
form. Let’s go back to our action and see why exactly this happened:
var videoInDb =
_dbContext.Videos.SingleOrDefault(v => v.Id == video.Id);
If you run the application in the debug
mode and put a breakpoint here, you’ll see that the value of video.Id is 0. Thus, the request that
is sent to the server does not have a value for the ID of the video. It only
includes values for Title, Description and Genre, which come from the input
fields on the form. To send the value of Video.ID, we need to add a hidden field.
Open up _VideoForm.cshtml and add a hidden field before the Save button:
@Html.HiddenFor(m =>
m.Id)
<input type="submit"
class="btn btn-primary" value="Save" />
You can add this hidden field anywhere
here, but my personal convention is to add them before the submit button. This
way, I always know where to look when I’m looking for hidden fields.
Run the application again, try to edit a
video, make a few changes, and save. Everything should work.
Next, we’ll add the ability to delete a
video.
Deleting
a Video
In this section, you’ll learn how to
delete data in the database using the Entity Framework.
Again, we’re going to implement this
feature in two steps: first, we’re going to add a Delete link in front of each
video in the video list. When the user clicks this link, they’ll see a page
with the details of the video to delete. This is the delete confirmation page.
Next, we’ll be focusing on actually deleting the video from the database.
Step
1: Creating a Page to Confirm Deletion
This step is very similar to what we’ve
done before. Add a Delete link
in front of each video in the list. When this link is clicked, display a view
with the details of the video to delete. Use a UL/LI to display different
attributes of a video. Don’t worry about deleting the video from the database
yet. After trying this, take a look at my solution:
First, we need to add the Delete link in
front of each video. So open up Index.cshtml
in Views > Videos, and
change the LI as follows:
<li>
@video.Title (@video.Genre)
@Html.ActionLink("Edit",
"Edit",
new
{id = video.Id}) |
@Html.ActionLink("Delete", "Delete", new {id = video.Id})
</li>
Note that I added a vertical separator
between Edit and Delete to separate them.
Next, we should create a new action in
the VideosController as follows:
public ActionResult Delete(int id)
{
var video =
_dbContext.Videos.SingleOrDefault(v => v.Id == id);
if (video == null)
return HttpNotFound();
return View(video);
}
Nothing should be new to you at this
point. We’re just pulling out the video to show to the user before deleting it.
Now, let’s create the corresponding view.
Add a new view called Delete inside
Views > Videos. Make sure
it’s not a partial view and has a layout. Write the following code inside the
view.
@model
Beaver.Models.Video
@{
ViewBag.Title = "Delete";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Delete</h2>
<p>
Are you sure you would like to delete this
video?
</p>
<ul>
<li>Title: @Model.Title</li>
<li>Description:
@Model.Description</li>
</ul>
Here I am using a simple UL/LI to display
the attributes of the video. You can use anything to lay out the details of a
video. It doesn’t really matter from the perspective of learning ASP.NET MVC.
Next, write this code in the view:
@using (Html.BeginForm("DoDelete",
"Videos",
FormMethod.Post))
{
@Html.HiddenFor(m => m.Id)
<input type="submit"
class="btn btn-danger"
value="Yes,
Delete it!" />
@Html.ActionLink("No, back to videos",
"Index")
}
You might wonder why I’m using Html.BeginForm when we don’t really
have a data entry form here. The reason is that we need to call the Delete
action using HTTP POST method. Again, anytime we need to modify data in ASP.NET
MVC, we should use HTTP POST to prevent accidental data modification. In
ASP.NET MVC, the only way to use HTTP POST is by creating a form. In this
example, our form only has a submit button and no input fields.
Note that in this form, we also have a
hidden field to render the ID of the video in our markup.
@Html.HiddenFor(m => m.Id)
This way, when we post to the server,
we’ll have the ID of the video to delete.
I’ve used the btn-danger Bootstrap class to make our delete button red.
<input type="submit"
class="btn btn-danger" value="Yes, Delete it!" />
Now, run the application. You’ll see a
delete link in front of each video. Click one of them. Look at the new page we
just built.
Click No, back to the videos. So, we have a nice navigation between
these two pages. The only thing remaining is to actually delete a video from
the database. That’s what we’re going to do in the next step.
Step
2: Deleting a Video from the Database
The form we created in the last step
posts to an action named DoDelete.
@using
(Html.BeginForm("DoDelete", "Videos", FormMethod.Post))
Let’s go to VideosController and create this action:
[HttpPost]
public ActionResult DoDelete(int id)
{
var video =
_dbContext.Videos.SingleOrDefault(v => v.Id == id);
if (video == null)
return HttpNotFound();
_dbContext.Videos.Remove(video);
_dbContext.SaveChanges();
return RedirectToAction("Index");
}
Again, the code should look very familiar
to you. I’ll just highlight the important aspects.
I’ve decorated this action with [HttpPost] to make sure an HTTP GET
caused by a web crawler will not delete our records.
Inside the method, first we need to read
the video from the database and then call the Remove method of our DbSet. This is how you ask Entity Framework
to delete records in the database. You cannot directly delete a record in the
database. You should always read that record first, remove it from the memory
and then call SaveChanges. This
way, Entity Framework’s change tracker will realize that an object is deleted
and will issue a DELETE FROM SQL statement to the database.
Let’s run the application and test the
delete functionality. Delete one of the videos.
Review
If you made it this far, well done. I
hope you’ve enjoyed this tutorial and learned the basics of ASP.NET MVC.
Let’s quickly review what you’ve learned
and what the next step is. In this tutorial,
- You learned and used the core building blocks of ASP.NET MVC (Models, Views and Controllers).
- You created a model using the Entity Framework Code First workflow.
- You used Code First migrations to generate your database.
- You built a controller with different actions for viewing lists of videos, adding a new video, and editing and deleting an existing one.
- You learned about the routing engine in ASP.NET MVC and how URLs get mapped to controllers and actions.
- You used Razor syntax to mix HTML and C# code in your views.
- You refactored your code by extracting the reusable view code into a partial view.
- You learned about the difference between HTTP GET and HTTP POST, and now you’re able to build more reliable applications.
- You used many HTML helper methods in views such as ActionLink, LabelFor, TextBoxFor, TextAreaFor and BeginForm.
- You used many helper methods in actions such as View, RedirectToAction and HttpNotFound.
What’s next? ASP.NET MVC is more than
what you’ve read, and that’s why I’m planning to create a comprehensive course
to teach you lots of things you can do with ASP.NET MVC. In particular, I’ll be
teaching you:
- Authentication and authorization
- Prevention of common security attacks
- Data validation
- Caching
- Real-time connections
- Working with Javascript libraries
- Ajax
- Creating RESTful services with ASP.NET Web API
- And more
No comments:
Post a Comment