MV5
MV5
MV5
NET MVC 5
How to build a Membership Website
Contents
1. Introduction
Introduction
Other titles by the author
MVC 5 - How to build a Membership Website
C# for Beginners: The tactical guidebook
Source code and bonus materials
Disclaimer – Who is this book for?
Rights
About the author
2. Entity Framework
Introduction
Technologies used in this chapter
The use case
A brief description of the tables
Creating the membership solution
Enable Migrations
Enable database migrations
What’s in the database?
User and login tables
Subscription tables
Product tables
Content tables
Creating the database
Changing the database name
Enabling Data Annotations
Data Annotation Attributes
The Section class
Adding the table
The dbContext
Adding a DbSet for the Section class
Create and inspect the database
The Part class
Adding the table
The ItemType class
Adding the table
The Item class
Adding the table
The Product class
Adding the table
The ProductType class
Adding the table
The ProductLinkText class
Adding the table
The Subscription class
Adding the table
The ProductItem class
Adding the table
The SubscriptionProduct class
Adding the table
The UserSubscription class
Adding the table
Modifying the AspNetUser class
Modifying the table
The ApplicationDbContext class
What’s Next?
3. Create the Admin area & Menu
Introduction
Technologies used in this chapter
The use case
The different views
A brief recap of the different tables
The Admin area
The Admin menu
Creating the _AdminMenuPartial partial view
Adding the Admin menu to the navigation bars
Adding the Admin menu to the main project’s navigation bar
Adding the Admin menu to the Admin area navigation bar
Adding menu items to the Admin menu
JavaScript Hover functionality
Adding the AdminMenu JavaScript file
Adding the admin bundle for the JavaScript files
4. Adding UIs for the Minor Entities
Introduction
Technologies used in this chapter
The use case
Scaffold the Section controller and views
The Create View
Change the Create button color
Create the _BackToListButtonPartial view
The Edit View
Change the Save button color
Change the Back to List link to a button
The Details View
Change the Back to List link to a button
Create the _EditButtonPartial view
The Delete View
Change the Delete button color
Change the Back to List link to a button
The Index View
Styling the table
Styling the Create New link
The SmallButtonModel class
Creating a generic reusable partial view button
Create the partial view _TableButtonsPartial
Add the partial view _TableButtonsPartial to the Index view
Scaffold the Part controller and views
Scaffold the ItemType controller and views
Scaffold the ProductType controller and views
Scaffold the ProductLinkText controller and views
5. Adding UIs for the Major Entities
Introduction
Technologies used in this chapter
The use case
Scaffold the Item controller and views
The Create Action (HttpGet)
The Create View
Fixing the dropdown labels
Creating the GetPropertyValue extension method
Creating the ToSelectListitem extension method
Fixing the dropdowns
The Edit View
Modifying the Edit controller action (HttpGet)
Replacing the textboxes with dropdowns in the view
Styling the Edit view
The Details View
The Delete View
The Index View
Replacing the Create New link with a button
Scaffold the Product controller and views
Adding necessary data
Adding the ProductModel
Adding the ProductController and views
Altering the Index view
Creating the asynchronous Convert extension method
Altering the ProductController’s Index action
Altering the Index view
Altering the Create view
Altering the Create action in the ProductController class
Altering the Details view
Altering the view
Adding the asynchronous Convert extension method
Changing the Details action in the ProductController
Altering the Delete view
Altering the Delete view
Altering the Delete action in the ProductController
Altering the Edit view
Altering the Edit view
Altering the Edit action in the ProductController
Scaffold the Subscription controller and views
Scaffold the controller and views
The Index View
The Create View
The Edit View
The Details View
The Delete View
6. Adding UIs for the Connecting Entities
Introduction
Technologies used in this chapter
The use case
Scaffold the ProductItem controller and views
Adding the ProductItemModel
The Create View
Change @model and add @using
Changing the label descriptions and the textboxes
Changing the Create controller action (HttpGet)
Styling the Create button and the Back to List link
The Index view
Modify the table
Changing the links to buttons
Modify the Index action and create a Convert method
The Edit View
The Edit View - Display data (HttpGet)
Modifying the Edit view
Modifying the Edit controller action (HttpGet)
The Edit View - Update data (HttpPost)
Adding the CanChange method
Adding the Change method
Modifying the Edit controller action (HttpPost)
The Details View
The Details controller action
Altering the Convert method
Creating the _EditButtonDetailsPartial partial view
Creating the EditButonModel class
Modifying the Details view
The Delete view
Modifying the view
Modifying the Delete controller action (HttpGet)
Modifying the DeleteConfirmed controller action (HttpPost)
Adding UI for the SubscriptionProduct Table
Generating the SubscriptionProduct Controller and its views
Create the SubscriptionProductModel class
Copy and alter the Convert method which return a list
Copy and alter the Convert method which return a single item
Copy and alter the CanChange method
Copy and alter the Change method
The Index view
Modify the Index view
Modify the Index controller action
The Create view
Modify the Create controller action (HttpGet)
The Edit view
Modify the Edit controller action (HttpGet)
The GetSubscriptionProduct method
Modify the Edit controller action (HttpPost)
The Details View
Modify the Details view
Modify the Details controller action
The Delete View
Modify the Delete view
Modify the HttpGet Delete controller action
Modify the DeleteConfirmed controller action
7. Adding transactions to the DeleteConfirmed actions
Introduction
Technologies used in this chapter
Adding a transaction to the Item’s DeleteConfirmed action
Adding a transaction to the Product’s DeleteConfirmed action
Adding a transaction to the Subscription’s DeleteConfirmed action
Adding the delete check to the DeleteConfirmed actions
8. Users & Subscriptions
Introduction
Technologies used in this chapter
User Registration
Alter the RegisterViewModel
Alter the Register action
Alter the Register view
The Admin role
Restict access to the Admin menu and its controllers
Modify user information
Create the UserViewModel class
Create the extension methods
Adding the IdentityExtension class
Adding the GetUserFirstName extension method
Adding the GetUsers extension method
Adding the Index view
Adding the Index action
Styling the Index view
Modifying the SmallButtonModel
Modifying the _TableButtonsPartial view
Adding the _TableButtonsPartial view to the Index view
Adding the Create view
Adding the Create actions
The HttpGet Create action
The HttpPost Create action
Adding the Edit view
Adding the Edit actions
The HttpGet Edit action
The HttpPost Edit action
Adding the Delete view
Adding the Delete actions
The HttpGet Delete action
The HttpPost Delete action
Adding subscriptions to a user
Adding the UserSubscriptionModel class
Adding the UserSubscriptionViewModel class
Adding the Subscriptions view
Displaying subscriptions in a dropdown
Displaying a user’s subscriptions in a table
Adding the Subscriptions actions
Adding the HttpGet Subscriptions action
Adding the HttpPost Subscriptions action
Adding the RemoveUserSubscription action
9. Application Layout
Introduction
Technologies used in this chapter
Use case
Alter the navigation bar
Adding the navbar.css file
The navigation bar
The navigation links
Adding a Logo
Adding the logo image
Styling the menu items
Fonts
Alter the layout of the Index view
10. Display products
Introduction
Adding a subscription
Adding a product link text
Adding the Product Type
Adding the products
Adding the subscription
Adding a subscription
Adding a product to the subscription
Adding a subscription to a user
Creating thumbnails
Adding the ThumbnailModel class
Adding the ThumbnailAreaModel class
Adding the thumbnails.css style sheet
Adding the _ThumbnailAreaPartial partial view
Style the _ThumbnailAreaPartial view heading
Fetch the user id with Owin Context
The GetSubscriptionIdsAsync method
Add a ThumbnailEqualityComparer
Fetch the thumbnails from the database
Modify the Index action
Modify the Index view
Display data in the _ThumbnailAreaPartial view
Add the labels
Styling the thumbnail labels
Animating the thumbnail image and labels
Animating the lower label
Add a shadow around the thumbnail
Rotate and zoom the image
11. Display Product Page
Introduction
Adding the ProductContent controller
Adding the model classes
The ProductItemRow class
The ProductSection class
The ProductSectionEqualityComparer class
The ProductSectionModel class
Adding the Index view for the ProductContent controller
Adding the GetProductSectionsAsync extension method
Altering the Index action
Styling the Index view
Adding the _ProductSection partial view
Styling the carret symbol
The gly-rotate-90 selector
Alligning the carret to the top of the header text
Rotating the carret
The JavaScript
Adding the GetProductItemRowsAsync extension method
Altering the GetProductSectionsAsync extension method
Adding the _ProductItemRow partial view
Altering the _ProductSectionPartial view
Styling the _ProductItemRowPartial view
12. Show Content
Introduction
JW Player
Create a JW Player cloud player URL
Adding the necessary JavaScript to play a video
Register a ProductContent route
The ContentViewModel class
The GetContentAsync extension method
Adding the Content Action
Adding the Content view
Adding the Content view
Adding the title and header section
Displaying HTML conent
Displaying Video conent
Styling the Video element
Styling the article HTML markup
13. Register Subscription Code
Introduction
Adding the _RegisterCodePartial view
Adding the Register Code panel content
Styling the Register Code panel
Adding the Register Code panel to the Index view
The GetSubscriptionIdByRegistrationCode extension method
The Register extension method
The RegisterUserSubscriptionCode method
The RegisterCode controller
Adding the RegisterCode Controller
Adding the Register action method
The JavaScript functions
14. Register User
Introduction
Technologies used in this chapter
Use case
Adding the RegisterUserModel
Add the RegisterUserModel
Authentication check and calling the partial view
Add the _RegisterUserPartial view
Add the partial view
Add the panel to the view
Adding controls to the Register User panel
Adding the controls
Styling the panel and the controls
Styling the validation error summary
Removing the rounded corners
Adding space between the textboxes
Styling the icons
Styling the button
Adding the RegisterUserAsync action
The AddUserErrors method
The Register action
Adding JavaScript click events
Add the JavaScript file
Wire up the checkbox click event function
Adding the Ajax action call
Wire up the button click event function
15. Login
Introduction
The GlyphLink extension method
Adding the login styles heet and JavaScript files
Altering the _LoginPartial view
Styling the Log off link
Adding the _LoginPanelPartial view
Creating the view
Adding the login panel
Adding JavaScript and CSS to display the login panel
Adding JavaScript to close the login panel
Styling the login panel
The panel
The panel heading and body
The textboxes
The Log in button
The checkbox
The LoginAsync action
The Log in button JavaScript
16. Forgot Password
Introduction
Sending email
Web.Config
The Send extension method
The Account controller
The SendAsync method
The modal reset password form
Adding the CSS and Javbascript files
Modifying the GlyphLink extension method
Adding the password link
Building the modal form
Adding the _ForgotPasswordPanelPartial view
Adding the modal dialog to the view
Styling the modal dialog
Adding JavaScript to the modal dialog
The Forgot Password Confirmation view
The Reset Password email
The Reset Passsword view
17. Useful Tools & Links
Tools
Microsoft Web Platform Installer 5.0
Bootstrap
CodeLens
Web Essentials
Browser link
SideWaffle
Plunker
Visual Studio Code
Atom
TypeScript
Angular
Grunt
Gulp
ECMAScript 6 (JavaScript)
Entity Framework (EF Core)
Microsoft ASP.NET MVC Documentation
1. Introduction
Introduction
This course is primarily aimed at developers who have some prior experience with MVC 5
and are proficient in C#, since the language won’t be explained in any detail. Even if you
already have created a couple of MVC projects, you might find the content in this book
useful as a refresher. You might have worked in previous versions of Visual Studio and
MVC and want a fast no-fluff way to get up to speed with MVC 5.
Other titles by the author
The author has written other books and produced video courses that you might find
helpful.
MVC 5 - How to build a Membership Website
This is a comprehensive video course showing how to build the membership site that you
will be building going through this book. The course has in excess of 24 hours! of video.
In this video course you will learn how to build a membership website from scratch. You
will create the database using Entity Framework code-first, scaffold an Administrator UI
and build a front-end UI using HTML5, CSS3, Bootstrap, JavaScript, C# and MVC 5.
Prerequisites for this course are: a good knowledge of the C# language and basic
knowledge of MVC 5, HTML5, CSS3, Bootstrap and JavaScript.
You can watch the video course on Udemy following this link:
www.udemy.com/building-a-mvc-5-membership-website
C# for Beginners: The tactical guidebook
This book is for YOU if you are new to C# or want to brush up on your skills and like a
CHALLENGE. This is not your run-of-the-mill encyclopedic programming book; it is
highly modularized, tactical and practical – meaning that you learn by reading theory, and
then implement targeted exercises building many applications.
You can buy C# for Beginners: The tactical guidebook at Amazon following this link:
https://www.amazon.com/dp/B017OAFR8I
Source code and bonus materials
The source code accompanying this book is shared under the MIT License and can be
downloaded after registering for free with this site www.csharpschool.com or by emailing
the author. At this link, you can also download a free e-book describing MVC 5, CSS 3,
HTML 5, Bootstrap, JavaScript JQuery, Ajax for beginners.
Disclaimer – Who is this book for?
It’s important to mention that this book is not meant to be a get-to-know-it-all book; it’s
more on the practical and tactical side where you will learn as you progress through the
examples and build a real application in the process. Because I personally dislike having
to read hundreds upon hundreds of pages of irrelevant fluff (filler material) not necessary
for the tasks at hand, and also view it as a disservice to the readers, I will assume that we
are of the same mind on this, and will therefore only include important information
pertinent for the tasks at hand; thus making the book both shorter and more condensed,
and also saving you time and effort in the process. Don’t get me wrong, I will describe the
important things in great detail, leaving out only the things that are not directly relevant to
your first experience with MVC 5. The goal is for you to have created a working MVC
application upon finishing this book. You can always look into details at a later time when
you have a few projects under your belt. If you prefer encyclopedic books describing
everything in minute detail with short examples, and value a book by how many pages it
has rather than its content, then this book is NOT for you.
The examples in this book are presented using Visual Studio 2015, but they will work with
2013 Professional Update 4 as well. The free express version should do fine when you
follow along and implement them yourself.
Rights
All rights reserved. The content is presented as is and the publisher and author assume no
responsibility for errors or omissions. Nor is any liability assumed for damages resulting
from the use of the information in the book or the accompanying source code.
It is strictly prohibited to reproduce or transmit the whole book, or any part of the book, in
any form or by any means without the prior written permission of the author.
You can reach the author at: [email protected].
ISBN-13: 978-1535167864, ISBN-10: 1535167866
Copyright © 2015 by Jonas Fagerberg, All rights reserved.
About the author
Jonas started a company back in 1994 focusing on education in Microsoft Office and the
Microsoft operating systems. While still studying at the university in 1995, he wrote his
first book about Widows 95, as well as a number of course materials.
In the year 2000, after working as a Microsoft Office developer consultant for a couple of
years, he wrote his second book about Visual Basic 6.0.
Between 2000 and 2004, he worked as a Microsoft instructor with two of the largest
educational companies in Sweden. First teaching Visual Basic 6.0, when Visual
Basic.NET and C# were released, he started teaching those languages, as well as the .NET
Framework. He was also involved in teaching classes at all levels for beginner to
advanced developers.
From the year 2005, Jonas shifted his career towards consulting once again, working
hands-on with the languages and framework he taught.
Jonas wrote his third book C# programming aimed at beginner to intermediate developers
in 2013, and in 2015, his fourth book C# for beginners - The Tactical Guide was
published.
Jonas has also produced a 24h+ video course titled MVC 5 – How to build a Membership
Website (www.udemy.com/building-a-mvc-5-membership-website), showing in great
detail how to build a membership website.
2. Entity Framework
Introduction
In this chapter you will learn the basics of Entity Framework and how to create a code-
first database; meaning that no design surface will be used – only plain C# classes. The
database will be used throughout the remainder of this book to fetch and update data. You
will implement two scenarios. The first is a rudimentary admin portal for adding, deleting
and updating memberships, courses and content. The second scenario is successively
building a user interface where users can log in and view the content they subscribe to.
The content will be in the form of videos, articles and downloadable content.
The database will constitute the backend storage for the content you share through your
products.
The database you will handle four areas which are connected to one another: users,
subscriptions, products and content.
Technologies used in this chapter
Entity Framework Code-First - You will model the database using entity classes
which then are used to create the tables when building the database.
C# - Is used to create the entity classes.
Attributes - Will decorate classes and properties to enable specific functionality
such as storing HTML in a column, client side validation, and restricting the
content size.
The use case
The customer wants you to build a database for their upcoming membership site. They
request that you create it using Entity Framework Code-First technology since there is no
existing database to model it on. They have however provided a database diagram for you
which is available later in this chapter. Below is a brief description of the tables.
All id fields are of integer type, except the UserId field which must be stored as a string
with a max length of 128 characters, since that is a limitation imposed by the Entity
Framework user id in the AspNetUser table. A more in-depth description of each table is
available in their respective section in this chapter.
A brief description of the tables
Section - Stores a list of available section titles which can be used by multiple
content pieces (items). The section title Module 1 could for instance be used by
Video 1 and Video 2. One scenario could be that the product you are creating has
videos, articles and downloadable material belonging to the same section, which
means that the same chapter title must be reusable. Other scenarios could be that
you have a series of videos or articles on the same subject.
ItemType - Stores a list of the different item types an item can have, for instance
Video, Article and Download.
Part - Can be viewed as a sub-categorization of a section. A section can contain
many parts or sub-headers.
Item - Is a content piece which can be part of one or many products.
Product - Is a list of available products. A product can have many content pieces
(items) tied to it through the ProductItem connection table, and can belong to
many subscriptions through the ProductSubscription connection table.
Subscription – A list of available subscriptions a user can subscribe to. A
subscription can have many products tied to it through the ProductSubscription
connection table.
UserSubscription – A list of subscriptions a specific user has access to. This is the
“entry point” for a user, granting access to certain content based on which
subscriptions are listed here. The primary key for the table is a composite key of a
subscription id (from the Subscription table) and a user id (from the AspNetUser
table).
ProductItem - A connection table between the Product and Item tables storing
information about what content belongs to what product.
ProductSubscription - A connection table between the Subscription and Product
tables storing information about what products are part of what subscription.
ProductLinkText - Contains label texts that can be displayed with the product
thumbnail.
Creating the membership solution
In this section you will focus on creating the solution for the membership site. Because the
name Membership is already used in the .NET Framework, you can use the plural version
Memberships instead when naming the MVC Web Application project. Add a folder
called Entities to the MVC project after you have created the solution. You will later add
the entity (table) classes to this folder and use them to generate the database.
1. Click on the New Project link on Visual Studio’s start page. The Dialog boxes in
Visual Studio 2013 and Visual Studio 2015 are very similar; the difference is that
you have more project templates in Visual Studio 2015.
2. Select ASP.NET Web Application in the list, name the project Memberships in the
Name field and make sure the Create directory for solution checkbox is selected.
If you are using Team Foundation Server (TFS) or GIT for source control, then
check the Add to source control checkbox. Click the OK button.
3. Select the MVC template in the dialog’s list, and make sure that Individual User
Accounts is selected, then click OK. If you have or want to create an Azure
account to host the application in the cloud, then check the Host in the cloud
checkbox and follow the steps outlined in the guide. Hosting this web application
in the cloud is not necessary, you can run and debug the web application locally.
You can add the application to Azure at a later time.
4. Add a folder called Entities to the project where you add the entity classes. This
helps keep the project organized. Right-click on the project name in the Solution
Explorer and select New-Folder and name it Entities.
Enable Migrations
To keep track of the changes made to the database, a migrations folder will be created in
the project when the database is first created. There will also be a __MigrationHistory
table in the database keeping track of the changes in case you need to revert back to an
older version.
You can let .NET Framework create a database with the default AspNet tables for you the
first time you launch the application.
To be able to make changes to the database and seed it with data, you need to enable
migrations to create the Migrations folder and files. To enable migrations, you open the
Package Manager Console window and execute the enable-migrations command.
When the enable-migrations command has finished executing, you can enable two
properties in the Configuration.cs migrations file to make it easier to work with database
updates and migrations. By enabling these two settings you are telling Entity Framework
that you allow it to perform automatic migrations, and that you allow loss of data while
doing a database update. If you don’t enable automatic migrations, you have to manually
create a new migration before attempting any database updates. These settings are
normally disabled for production databases, but can be of great help when designing a new
database.
Public Configuration()
{
AutomaticMigrationsEnabled = true;
AutomaticMigrationDataLossAllowed = true;
}
Enable database migrations
1. Open the Package Manager Console by typing in Package Manager into the quick
launch search bar at the top right corner of Visual Studio and select it. Or select
View-Other Windows-Package Manager Console in the menu.
2. Type in the command enable-migrations and press Enter on the keyboard.
3. When the Configuration.cs migrations file is displayed, you can set the following
two properties in the constructor to be able to work more smoothly with updates to
the database. Note that it’s not recommended to have these enabled when working
in a production database.
AutomaticMigrationsEnabled = true;
AutomaticMigrationDataLossAllowed = true;
What’s in the database?
Many tables are involved when storing information about memberships. I have however
tried to keep the number of tables down in this book to give you a foundation on which
you can build when you create your own membership site.
User and login tables
Users and logins will be handled with Individual User Accounts, meaning that users have
to register to login. The application will use the AspNet tables which are created
automatically when the database is generated the first time. The focus here will be on the
AspNetUsers (for registered users), AspNetUserRoles (for the roles associated with a
user) and AspNetRoles (defines the available roles) tables. You will alter the
AspNetUsers table to store additional data.
You will implement basic login functionality, which relatively easy, can be extended to use
Facebook, Twitter and Google authentication.
Make sure that the Individual User Accounts setting is selected when creating the MVC
solution.
Subscription tables
Subscriptions are handled by three tables. Subscription which stores information about
available subscriptions such as its title, description and registration code.
UserSubscription which connects a specific user in the AspNetUser table with a specific
subscription in the Subscription table. ProductSubscription which is a connection table
between the Subscription table and the Product table storing information about what
products belong to what subscription(s).
Product tables
Products are handled by six tables. Product which stores information about available
products such as title and description. ProductType which stores information about
possible product types such as Book, Course and Articles. This information is used to filter
data and to display information about the product to the user in the product thumbnail.
ProductSubscription is a connection table between the Subscription table and the
Product table which stores information about what products belong to what sub-
scription(s). ProductItem is a connection table between the Product table and the Item
table which stores information about what items belong to what product(s). The Product-
LinkText stores the text for labels that can be displayed on the product thumbnails such as
“Read more +”, and when the label is clicked, it will open the product’s content view.
Content tables
The individual items (content pieces), information about available videos, articles and
downloadable files, are stored in a table called Item, which in turn reference the Item-
Type table which stores information about available item types such as video, article and
download. The Section table stores information about available sections that the items can
belong to. The Part table stores information about available parts items can belong to. The
Idea is that an item (a piece of content) can belong to a section which has a number of
parts; think of a book where each section has a main heading (chapter) and sub headings
(parts) or videos can be divided into parts.
Creating the database
Now that you have seen an overview of what tables the database will contain, it is time to
actually create the database; without it you won’t be able to complete upcoming exercises
in this book. Let’s start with the peripheral tables and work our way to the core tables.
When you want to add a table to the database using code-first, it has to be represented by
an entity class describing its columns and settings by using properties and attributes. If, for
instance, you want to add a Name column to a table, you define it as a string property and
assign attributes to it. Attributes can for instance limit the number of characters allowed,
or define it as a required value in the table.
Let’s start by exploring how to add a table and how to create and update the database by
adding the Section table.
Changing the database name
You can change the database name before you create it by opening the Web.Config file and
altering the DefaultConnection property; change the database name and Initial Catalog
values to the name you want the database to have. By default, the name is set to aspnet-
YourProjectName-UniqueNumber.mdf. For example aspnet-Memberships-20150125111.
The first occurrence of the name in the DefaultConnection property is the database file
name, and the second is the name used internally by SQL Server.
Default connection string:
<connectionStrings>
<add name=”DefaultConnection” connectionString=”Data Source=
(LocalDb)\v11.0;AttachDbFilename=|DataDirectory|\aspnet-Memberships-20150411124038.mdf;Initial Catalog=aspnet-
Memberships-20150411124038;Integrated Security=True” providerName=”System.Data.SqlClient” />
</connectionStrings>
Modified connection string:
<connectionStrings>
<add name=”DefaultConnection” connectionString=”Data Source=
(LocalDb)\v11.0;AttachDbFilename=|DataDirectory|\MembershipsDb.mdf;Initial Catalog=MembershipsDb;Integrated
Security=True” providerName=”System.Data.SqlClient” />
</connectionStrings>
Enabling Data Annotations
Enabling data annotations in the project will make it possible to add certain database-
related attributes to properties and entity classes, specifying the name of the table as well
as column behaviors in the database table. Annotations can also be used when verifying
form input on the client.
To enable data annotations, you have to add a reference to the System.Components.
DataAnnotations assembly if it’s not already added, as well as using statements in the de-
sired parts of the added assembly in the entity classes.
1. Open the References folder in the Solution Explorer and make sure that the
DataAnnotations assembly is referenced in the list of references; if it’s not, then
continue with the rest of the bullets in this list to add it.
2. Right-click on the References folder in the Solution Explorer.
3. Select Add Reference in the context menu.
4. Search for DataAnnotations, select the assembly in the list and click OK.
Data Annotation Attributes
Attribute Description
Note that the class name is given in singular but the auto-generated table name in the
database will be in plural. This is a matter of choice and preference. You can change this
by adding the Table attribute to the class.
Adding the table
To be able to add data annotation attributes to the properties, you must add a reference to
the DataAnnotations assembly and then add the following two using statements to the
class.
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
1. Right-click on the project name in the Solution Explorer and select Add-New
Folder in the context menu. Name the folder Entities.
2. Right-click on the Entities folder and select Add-Class in the context menu.
3. Enter Section in the Name textbox and click Add.
4. Add the following two namespaces to the class to be able to use data annotations.
You can add them manually or right-click on the attribute name and select resolve-
using xyz in the context menu.
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
5. Add the Table attribute to the class to specify the name you want the table to have.
If you leave this attribute out, the table name will be pluralized in the database.
[Table(“Section”)]
public class Section
6. Add the Id property and decorate it with the DatabaseGenerated attribute.
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
7. Add the Title property and restrict it to allow a maximum of 255 characters and
that it is a required field.
[MaxLength(255)]
[Required]
public string Title { get; set; }
The dbContext
When creating and interacting with the database in the exercises, you will use the default
dbContext class called ApplicationDbContext. In a real world scenario you might opt to
rename the ApplicationDbContext or have multiple contexts responsible for different
parts of the database such as Identity and Membership. Because the focus of this book is
geared toward MVC and not the Entity Framework, it will only describe how to use the
default context.
The ApplicationDbContext class is located in the IdentityModels.cs file in the Models
folder.
For a table to be created in the database, you have to add a DbSet entry for it as a property
in the ApplicationDbContext class. The DbSet will, among other things, make it possible
to query tables for data with LINQ and Lambda.
Adding a DbSet for the Section class
For the Section class to be used as a model for the Section table, you have to add a DbSet
property for it in the ApplicationDbContext class.
public DbSet<Section> Sections { get; set; }
3. When the execution has finished, click on the Show All Files button in the
Solution Explorer and expand the App_Data folder to make the database visible.
4. Double-click on the database name in the Solution Explorer to open it in the Server
Explorer window. As you can see, the Section table has been created along with
the AspNet tables.
5. To open the Section table, right-click on it and select Show table data. Then when
it is open, you can add and modify its data inside Visual Studio.
The Part class
The Part table is defined by a class with the same name and contains only two columns Id
of type int and Title of type string. The properties have the same attributes as the prop-
erties in the Section class.
1. Add the Part class to the Entities folder.
2. Add the Table attribute to the class to specify the same table name as the class
name.
3. Add the Id property and decorate it with the DatabaseGenereated attribute.
4. Add the Title property and decorate it with the Required and MaxLength(255)
attributes.
5. Add a DbSet for the Part class and name it Parts in the IdentityModels.cs file.
6. Open the Package Manager Console and execute the update-database command.
7. Open the Tables folder in the database and make sure that the table has been added.
The complete Part class:
[Table(“Part”)]
public class Part
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[MaxLength(255)]
[Required]
public string Title { get; set; }
}
The ItemType class
The ItemType table is defined by a class with the same name and contains only two
columns Id of type int and Title of type string. The properties have the same attribute
decorations as the ones in the Part class.
Note that you can use the plural form on the property name if you want it to appear that
way on the dbContext object when coding, inspecting it in Visual Studio during debug-
ging and in IntelliSense lists.
By creating the reference properties as public virtual ICollection<T> lazy loading is
enabled, as well as more efficient change tracking. Lazy loading means that no data is
loaded into the collection until you request it; this can make the data you need load much
faster.
1. Add the Item class to the Entities folder.
2. Add the Table attribute to the class to specify the same name for the table as the
class name.
3. Add the Id property and decorate it with the DatabaseGenereated attribute.
4. Add the Title property and decorate it with the Required and MaxLength(255)
attributes.
5. Add the Description property and decorate it with the MaxLength(2048) attribute.
6. Add the Url property and decorate it with the MaxLength(1024) attribute.
7. Add the ImageUrl property and decorate it with the MaxLength(1024) attribute.
8. Add the HTML property and decorate it with the AllowHtml attribute.
9. Add the HTMLShort property and decorate it with the NotMapped attribute.
10. Add three int properties representing the primary key value of the referenced tables
ItemTypeId, PartId and SectionId.
11. Add the Sections property as a public virtual ICollection<Section>.
public virtual ICollection<Section> Sections { get; set; }
14. Add the DisplayName attribute to the properties with composite names to add a
space between the words making up the property names in order to make them
more readable.
15. Add a DbSet for the Item class and name it Items in the IdentityModels.cs file.
16. Open the Package Manager Console and execute the update-database command.
17. Open the Tables folder in the database and make sure that the table has been
added.
The complete Item class:
[Table(“Item”)]
public class Item
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[MaxLength(255)]
[Required]
public string Title { get; set; }
[MaxLength(2048)]
public string Description { get; set; }
[MaxLength(1024)]
public string Url { get; set; }
[MaxLength(1024)]
[DisplayName(“Image Url”)]
public string ImageUrl { get; set; }
[AllowHtml]
public string HTML { get; set; }
[DefaultValue(0)]
[DisplayName(“Wait Days”)]
public int WaitDays { get; set; }
public string HTMLShort { get { return HTML == null ||
HTML.Length < 50 ? HTML : HTML.Substring(0, 50); } }
public int ItemTypeId { get; set; }
public int SectionId { get; set; }
public int PartId { get; set; }
DisplayName(“Is Free”)]
public bool IsFree { get; set; }
DisplayName(“Item Types”)]
public ICollection<ItemType> ItemTypes { get; set; }
public ICollection<Section> Sections { get; set; }
public ICollection<Part> Parts { get; set; }
}
The Product class
The Product table is defined by a class with the same name. This is one of the most
significant tables in the database because it stores information about specific products.
Each row in the table represents one product which can be referenced by many sub-
scriptions. A product can be comprised of several content pieces (items) represented by
rows in the Item table connected through the ProductItem table which you will add later.
This table references two other tables with a many-to-many relationship; which means
that connection tables have to be involved. You will create these tables later, but for now
let’s concentrate on the Product table. You have already created one of the tables that will
be referenced through one of the intermediary tables: the Item table.
The Product table has the following properties: an Id of type int decorated with the
DatabaseGenerated attribute, a required Title of type string limited to 255 characters
and a Description of type string limited to 2048 characters.
It also has some reference ids to other tables, all of type int: ProductTypeId and
ProductLinkTextId.
Adding the table
To be able to add data annotation attributes to the properties, you must already have added
a reference to the DataAnnotations assembly and add the following three using
statements to the class.
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
1. Add the Product class to the Entities folder.
2. Add the Table attribute to the class to specify the same name for the table as the
class name.
3. Add the Id property and decorate it with the DatabaseGenereated attribute.
4. Add the Title property and decorate it with the Required and MaxLength(255)
attributes.
5. Add the Description property and decorate it with the MaxLength(2048) attribute.
6. Add two int properties representing the primary key values of the referenced tables
ProductTypeId and ProductLinkTextId.
7. Add the ImageUrl property and decorate it with the MaxLength(1024) attribute.
8. Add a DbSet for the Product class in the IdentityModels.cs file and name it
Products.
9. Open the Package Manager Console and execute the update-database command.
10. Open the Tables folder in the database and make sure that the Product table has
been added.
The complete Product class would look like this:
[Table(“Product”)]
public class Product
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[MaxLength(255)]
[Required]
public string Title { get; set; }
[MaxLength(1024)]
[DisplayName(“Image Url”)]
public string ImageUrl { get; set; }
[MaxLength(2048)]
public string Description { get; set; }
public int ProductLinkTextId { get; set; }
public int ProductTypeId { get; set; }
}
The ProductType class
The ProductType table is defined by a class with the same name and will hold the pos-
sible product types a product can have. A product type could for instance be a course, a
book, articles or something else.
The entity class defining the table contains two properties: one called Id of type int and
one called Title of type string limited to a maximum of 25 characters . The Id should be
generated automatically by the database when new product types are added.
The entity class defining the table contains two properties called Id of type int and Title
of type string limited to a maximum of 255 characters. The Id should be generated auto-
matically by the database when new product links are added.
Adding the table
To be able to add data annotation attributes to the properties, you must already have added
a reference to the DataAnnotations assembly and add the following two using statements
to the class.
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
1. Add the ProductLinkText class to the Entities folder.
2. Add the Table attribute to the class to specify the same name for the table as the
class name.
3. Add the Id property and decorate it with the DatabaseGenerated attribute.
4. Add the Title property and decorate it with the Required and MaxLength(25)
attributes.
5. Open the Package Manager Console and execute the update-database command.
6. Open the Tables folder in the database and make sure that the ProductLinkText
table has been added.
The complete ProductLinkText class:
[Table(“ProductLinkText”)]
public class ProductLinkText
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[MaxLength(25)]
[Required]
public string Title { get; set; }
}
The Subscription class
The Subscription table is defined by a class with the same name. This is one of the most
significant tables in the database because it stores information about specific subscrip-
tions. Each row in the table represenst one subscription which can be referenced by many
users in the UserSubscription table. A subscription can be comprised of several products
represented by rows in the Product table and connected through the Subscription-
Product table which you will add later.
This table references another table with a many-to-many relationship; which means that a
connection table has to be involved. You will create this table later, but for now let’s
concentrate on the Subscription table.
The Subscription table has the following properties: an Id of type int decorated with the
DatabaseGenerated attribute, a required Title of type string limited to 255 characters, a
Description of type string limited to 2048 characters and a RegistrationCode of type
string limited to 20 characters. This code will be entered into a form by the user to gain
access to the subscription content.
Adding the table
To be able to add data annotation attributes to the properties, you must already have added
a reference to the DataAnnotations assembly and add the following three using
statements to the class.
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
1. Add the Subscription class to the Entities folder.
2. Add the Table attribute to the class to specify the same name for the table as the
class name.
3. Add an Id property of type int and decorate it with the DatabaseGenereated
attribute.
4. Add a Title property of type string and decorate it with the Required and
MaxLength(255) attributes.
5. Add a Description property of type string and decorate it with the
MaxLength(2048) attribute.
6. Add a RegistrationCode property of type string and decorate it with the
MaxLength(20) attribute.
7. Add a DbSet for the Subscription class and name it Subscription in the
IdentityModels.cs file.
8. Open the Package Manager Console and execute the update-database command.
9. Open the Tables folder in the database and make sure that the Subscription table
has been added.
The complete Subscription class:
[Table(“Subscription”)]
public class Subscription
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[MaxLength(255)]
[Required]
public string Title { get; set; }
[MaxLength(2048)]
public string Description { get; set; }
[MaxLength(20)]
[DisplayName(“Registration Code”)]
public string RegistrationCode { get; set; }
}
The ProductItem class
The ProductItem table is defined by a class with the same name. This table is a
connection table between the rows in the Item table and the rows in the Product table.
Each row in the table connects a piece of content (item) with a product. By adding the
ProductItem table, one piece of content (item) can be added to multiple products without
duplicating its data.
Adding the ProductItem class also makes it easier to gain access to the table data for a
product and its corresponding items from the code.
This is a small class consisting of two properties which are persisted in the ProductItem
table and two properties which are used to store temporary ids during application
execution. The trick here is to use the primary keys from the tables that you want to
connect as a composite key – the ProductId and ItemId. To achieve this, you add two
attributes to each of the properties, specifying that they should be considered part of the
primary key of the ProductItem table; where the Key attribute specifies that the proper-
ties should be part of a composit key and the Column attribute specifies in which order
they will make up the composite key. The two properties should also be marked as
Required values. Below is an example of how the attributes can be added.
The two int properties, OldProductId and OldItemId, should not be mapped to the table.
Adding these properties saves you the hassle of creating a separate view model. Some
puritans say that you only should include properties that will map to columns in the table.
In my opinion, it comes down to preference. I personally would create view models if
there are many properties that are calculated or don’t map to the table. I would also think
twice before adding methods to an entity class. Try to keep the entity classes as clean as
possible.
[Required]
[Key, Column(Order = 1)]
public int ProductId { get; set; }
[Required]
[Key, Column(Order = 2)]
public int ContentId { get; set; }
1. Add the ProductItem class to the Entities folder.
2. Add the Table attribute to the class to specify the same name for the table as the
class name.
3. Add the ProductId property and decorate it with the Required, Key and Column
attributes.
4. Add the ItemId property and decorate it with the Required, Key and Column
attributes.
5. Add the OldProductId and the OldItemId properties and decorate them with the
NotMapped attribute, denoting that they should not be columns in the table.
[NotMapped]
public int OldProductId { get; set; }
[NotMapped]
public int OldItemId { get; set; }
6. Add a DbSet for the ProductItem class and name it ProductItems in the
IdentityModels.cs file.
7. Open the Package Manager Console and execute the update-database command.
8. Open the Tables folder in the database and make sure that the ProductItem table
has been added.
The complete ProductItem class:
[Table(“ProductItem”)]
public class ProductItem
{
[Required]
[Key, Column(Order = 1)]
public int ProductId { get; set; }
[Required]
[Key, Column(Order = 2)]
public int ItemId { get; set; }
[NotMapped]
public int OldProductId { get; set; }
[NotMapped]
public int OldItemId { get; set; }
}
The SubscriptionProduct class
The SubscriptionProduct table is defined by a class with the same name. This table is a
connection table between the rows in the Subscription table and the rows in the Product
table. Each row in the table connects a product with a subscription. By adding the
SubscriptionProduct table, one product can be added to multiple subscriptions without
duplicating its data.
Adding the SubscriptionProduct class makes it easier to gain access to the involved
tables from the code.
This is a small class consisting of two properties which are persisted in the Subscription-
Product table and two properties which are used to store temporary ids during application
execution. The trick here is to use the two table primary keys that you want to connect as a
composite key, the SubscriptionId and ProductId. To achieve this you add two attributes
to each of them, specifying that they should be considered part of the primary key of the
SubscriptionProduct table, where the Key attribute specifies that the properties should be
part of a composite key and the Column attribute specifies in which order they will make
up the composite key. The two properties should also be marked as required values. See
the previous section for an example of how the attributes can be added.
The two int properties OldSubscriptionId and OldProductId should not be mapped to
the table. Adding these properties saves you the hassle of creating a separate view model.
Some puritans say that you only should include properties that will map to columns in the
table, in my opinion it comes down to taste. I personally would create view models if there
are many properties that are calculated or do not map to the table. I would also think twice
of adding methods to an entity class. Try to keep the entity classes as clean as possible.
Adding the table
To be able to add data annotation attributes to the properties, you must already have added
a reference to the DataAnnotations assembly and add the following two using statements
to the class.
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
There is a one-to-many relationship between the AspNetUser table and the UserSub-
scription table to keep them separated from one another and not mix .NET Framework-
created data with your data. Instead, the user id can be used multiple times in the User-
Subscription table as part of a composite key containing the UserId and SubscriptionId
columns.
There is a one-to-many relationship between the UserSubscription table and the Sub-
scription table making it possible for a user to have many subscriptions but only one entry
for a specific subscription.
There should also be two null-able DateTime properties called StartDate and EndDate
keeping track of whether the user has access to the registered subscriptions based on when
the subscription was registered and when it runs out.
Adding the table
To be able to add data annotation attributes to the properties, you must already have added
a reference to the DataAnnotations assembly and add the following four using statements
to the class.
Using System;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
Add a partial view named _AdminMenuPartial to the main project’s Views-Shared
folder not in the Admin area and add the code described above to it.
1. Locate the Views-Shared folder in the project, not in the Admin area, and right-
click on it.
2. Select Add-View in the context menu.
3. Enter _AdminMenuPartial in the View name textbox.
4. Check the Create as partial view checkbox.
5. Leave the rest of the options as they are and click on the Add button.
6. Type in the HTML markup above in the view’s code window.
7. Save the view by pressing Ctrl+S on the keyboard.
Adding the Admin menu to the navigation bars
Let’s begin by adding the Admin menu to the main project’s menu navigation bar and see
what it looks like before you add it to the Admin menu navigation bar.
4. Save the _Layout view and run the application to view it in the browser.
Adding the Admin menu to the Admin area navigation bar
If you could navigate to a view in the Admin area you would find that it has no menu by
default. To add the partial view to the Admin navigation bar, you have to locate it in the
_Layout view in the Admin area and find the empty unordered list <ul> element to which
you add the partial view by searching for navbar-nav in the _Layout view.
You add the partial Admin menu view using the @Html.Partial method.
<ul class=“nav navbar-nav”>
@Html.Partial(“_AdminMenuPartial”)
</ul>
1. Locate the Views-Shared folder in the project not in the Areas folder.
2. Copy the _Layout partial view by right clicking on it and selecting Copy in the
context menu.
3. Locate the Areas folder and expand it.
4. Locate the Views-Shared folder in the Areas folder and paste in the copied
_Layout partial view by right-clicking on the Views-Shared folder and select
Paste in the context menu.
5. Open the _Layout partial view you pasted in.
6. Locate the navigation bar by searching for navbar-nav and add the partial view to
the unordered list using the @Html.Partial extension method to have it render as
part of the navigation bar.
@Html.Partial(“_AdminMenuPartial”)
8. Save the _Layout view. You cannot view it in the Admin navigation bar yet
because there are no controllers available in the Admin area to navigate to.
Adding menu items to the Admin menu
Now that the Admin menu has been created and added to all the navigation bars it is time
to add the menu items that the user can click on to navigate. The menu item links are very
easy to figure out; you just take the area name and add on the controller name like
/Admin/Section for the SectionController and its views. The links will take the user to
the respective Index views.
You add <li> elements with a nested <a> element for all the menu choices you want
available in the Admin menu. Set the href of the <a> element to the desired controller
path.
<li><a href=”/Admin/Section”> Section</a></li>
Use the Bootstrap dropdown-header class on a <li> element to add a menu header.
<li class=“dropdown-header”>Minor Entities</li>
Use the Bootstrap divider class on a <li> element to add a menu divider line.
<li class=“divider” />
3. Add list items for Section, Part, Item Type, Product Type and Product Link
Text. They are all the same except for the description and href.
<li><a href=”/Admin/Section”> Section</a></li>
4. Add a dropdown-header with the text Major Entities and a divider to the <ul>
element.
5. Add list items for Item, Product and Subscription. They are the same as the
Section <li> except for the description and href.
6. Add a dropdown-header with the text Connector Entities and a divider to the
<ul> element.
7. Add list items for Product Item and Subscription Product. They are the same as
the Section <li> except for the description and href.
8. Add a dropdown-header with the text Users & Subscriptions and a divider to the
<ul> element.
9. Add a list item for Users & Subscriptions and assign /Account to its href.
10. Save the partial view. There is one more thing you have to do before the menu will
drop down, and that is to add a JavaScript for the hover method.
The complete code for the _AdminMenuPartial view:
<li class=“dropdown” data-admin-menu>
<a href=”#”>
Admin
<span class=“glyphicon glyphicon-chevron-down”></span>
</a>
<ul class=“dropdown-menu” role=“menu”>
<li class=“dropdown-header”>Minor Entities</li>
<li class=“divider”/>
<li><a href=”/Admin/Section”> Section</a></li>
<li><a href=”/Admin/Part”> Part</a></li>
<li><a href=”/Admin/ItemType”> Item Type</a></li>
<li><a href=”/Admin/ProductType”> Product Type</a></li>
<li><a href=”/Admin/ProductLinkText”>
Product Link Text</a></li>
<li class=“divider” />
<li class=“dropdown-header”>Major Entities</li>
<li class=“divider” />
<li><a href=”/Admin/Item”> Item</a></li>
<li><a href=”/Admin/Product”> Product</a></li>
<li><a href=”/Admin/Subscription”>
Subscription</a></li>
<li class=“divider” />
<li class=“dropdown-header”>Connector Entities</li>
<li class=“divider” />
<li><a href=”/Admin/ProductItem”> Product Item</a></li>
<li><a href=”/Admin/SubscriptionProduct”>
Subscription Product</a></li>
<li class=“divider” />
<li class=“dropdown-header”>Users & Subscriptions</li>
<li class=“divider” />
<li><a href=”/Account”>
Users & Subscriptions</a></li>
</ul>
</li>
JavaScript Hover functionality
Now that the Admin menu has been finished, it is time to create a JavaScript that wires up
the hover function to the menu to make it drop down when a user hovers over it.
You can use the JQuery hover function to wire up the hover event to the admin menu. To
keep the view clean and separate concerns a separate JavaScript file will be used for the
Admin menu scripts. To keep the registration of this and future JavaScript files for the
Admin menu as easy as possible you will create a new minification bundle called admin
for them in the BundleConfig.cs file.
To access the Admin menu list item <li> element you will add an attribute called data-
admin-menu to it which will be referenced from the JavaScript when wiring up the hover
event and toggling the open class, which determines if the menu should be open or closed.
Because the hover event handles both mouse enter and mouse leave, you only need this
one event to handle the toggling.
The AdminMenu.js JavaScript file has to be added to the Scripts folder in the main
project, not in the Admin area, to make it reachable from both the Admin area and the
main project.
Adding the AdminMenu JavaScript file
1. Locate the Scripts folder in the main project and expand it, not in the Admin area.
2. Right-click on the folder and select Add-JavaScript File.
3. Enter AdminMenu in the Item name textbox and click the OK button.
4. Add the Document Ready function to make the scripts entered in it load when the
DOM elements for the web page have been loaded. The easiest way to do this is to
use the $() JQuery function.
$(function () {
// Code goes here
});
5. Add the hover event referencing the data-admin-menu attribute from the
Document Ready function.
$(’[data-admin-menu]’).hover(function () {
// Code goes here
});
6. Add the toggleClass method to the hover event to hide and show the Admin
menu. Again you reference the menu by its attribute.
$(’[data-admin-menu]’).toggleClass(‘open’);
8. Run the application and select Section in the Admin menu to navigate to the page
rendered by the Index view in the SectionController. Note that the URL is
/Admin/Section because the view is located in the Admin area.
9. Selecting any other Admin menu option will generate a 404 error because there are
no controllers available to process those requests.
10. Let’s add a section entry in the database by clicking on the Create new link. If you
are still on the error page then click the browser Back button first to navigate back
to the section Index page. Note that the URL changes to /Admin/Section/Create to
target the Create action method in the controller and render the view with the same
name.
11. Enter the text Chapter 1 into the Title textbox and click the Create button. When
the Create action method has finished processing the request, a redirect to the
Index page is made displaying the newly added section fetched from the Section
table in the database. Note that three new links are available to the right of the
section title – these links are referencing the id of that section entry in the database.
12. Click the Edit link to edit the section data. Change the title to Chapter 2 and click
the Save button. Again you are redirected to the Index page where you can see the
updated title.
13. Click the Details link to the right of the section title. This will show static data
about the section. Click the Back to List link to navigate back to the Index page.
14. Click the Delete link to the right of the section title. This will take you to a page
where you can delete the section. Click the Delete button to remove the section and
navigate back to the Index page.
15. Now you are back where you started with an empty list on the Index page.
The Create View
Let’s put some color and buttons in the Create view using Bootstrap. Since you will add
many views which have a Back to List link, it can be beneficial to place that code in a
partial view which can replace that link in all the views. While you are at it, you might as
well spruce it up a little and change it into a (blue) primary button with a list glyph icon.
When you have created the _BackToListButtonPartial partial view, you swap out the
@Html.ActionLink extension method for the @Html.Partial passing in the name of the
partial view as a parameter.
Also change the Create button’s background color to the (green) success color by adding
the btn-success Bootstrap class to it.
This is the end result you will achieve.
Change the Create button color
1. Open the Create view in the Views-Section folder in the Admin area.
2. Locate the submit button and change the class btn-default to btn-success.
<input type=”submit” value=”Create” class=”btn btn-success” />
5. Add an anchor tag <a> element in the view window which is opened. Set its type
attribute to button. Add the btn, btn-primary and btn-sm Bootstrap classes to
make it into a button with the (blue) primary color and a little smaller than a
normal button. Also add the @Url.Action extension method to the href attribute
passing in the name of the Index view where the button will navigate when the
user clicks it.
<a type=”button” class=”btn btn-primary btn-sm” href=”@Url.Action(“Index”)“></a>
6. Next, add the list glyph icon by adding a <span> inside the <a> element. Note that
you have to add both the glyphicon and glyphicon-list Bootstrap classes for the
icon to be displayed on the button. Also note that a glyph icon always is added as a
class on an empty <span> element.
<span class=”glyphicon glyphicon-list” aria-hidden=“true”></span>
7. Then add the description to the right of the icon in a <span> element. You have to
place it in a <span> for it to be displayed correctly when using a glyph icon.
<span>Back to List</span>
11. Run the application and select Section in the Admin menu.
12. Click the Create New link on the Index page. The Create page should display the
changes.
13. Click the Back to list button to see that it works, then add a new section called
Chapter 3.
Here is the complete code for the _BackToListButtonPartial partial view:
<a type=“button” class=“btn btn-primary btn-sm” href=”@Url.Action(“Index”)“>
<span class=“glyphicon glyphicon-list”></span>
<span>Back to List</span>
</a>
The id property in the anonymous object is then matched with a parameter of the Edit
action in the SectionController class.
public async Task<ActionResult> Edit(int? id)
Two partial views will be used here, the _BackToListButtonPatial and a new one called
_EditButtonPartial which will define the Edit button.
This is what the Details view will look like after the changes:
Change the Back to List link to a button
1. In the Details view, locate the @Html.ActionLink extension method and swap it
for a @Html.Partial extension method call.
@Html.Partial(”_BackToListButtonPartial”)
6. Add an anchor tag <a> element in the view window. Set its type attribute to
button. Add the btn, btn-primary and btn-sm Bootstrap classes to make it into a
button with the (blue) primary color and a little smaller than a normal button. Also
add the @Url.Action extension method to the href attribute passing in the name of
the Edit view where the button will navigate, and the current section id through the
@model directive.
<a type=”button” class=”btn btn-primary btn-sm” href=”@Url.Action(”Edit”, new { id = Model })“></a>
7. Next, add the pencil glyph icon by adding a <span> inside the <a> element. Note
that you have to add both the glyphicon and glyphicon-pencil Bootstrap classes
for the icon to be displayed on the button. Also note that a glyph icon is always
added as two classes on an empty <span> element.
8. <span class=”glyphicon glyphicon-pencil” aria-hidden=“true”></span>
9. Then add the text to be displayed to the right of the icon in a <span> element. You
have to place it in a <span> for it to be displayed correctly when using a glyph
icon.
<span>Edit</span>
13. Run the application and select Section in the Admin menu.
14. Click the Details link on the Index page next to the section you want to view.
15. Check that the view’s layout has changed.
Here is the complete code for the _EditButtonPartial partial view:
@model int
<a type=“button” class=“btn btn-primary btn-sm” href=”@Url.Action(“Edit”, new { id = Model })“>
<span class=“glyphicon glyphicon-pencil” aria-hidden=“true”></span>
<span>Edit</span>
</a>
with
@Html.Partial(“_EditButtonPartial”, Model.Id)
@Html.Partial(“_BackToListButtonPartial”)
Then all you have to do is to add the success class (green) to the header row to make it
stand out in another color.
<tr class=“success”>
8. Save the view and open the Index view in the Views-Section folder.
9. Replace the @Html.ActionLink with a call to @Html.Partial for the Create
New link.
@Html.Partial(”_CreateNewButtonPartial”)
10. Run the application and select Section in the Admin menu to check that the
link has been swapped for a button.
The complete HTML markup for the _CreateNewButtonPartial view:
<a type=“button” class=“btn btn-primary btn-sm” href=”@Url.Action(“Create”)“>
<span class=“glyphicon glyphicon-plus” aria-hidden=“true”></span>
<span>Create New</span>
</a>
7. Check with an if-statement if the Id property is not null and is greater than 0,
then append a formatted string to the param variable containing the name and
value of the parameter.
if (Id != null && Id > 0)
param.Append(String.Format(“{0}={1}&”, “id”, Id));
8. Next, do the same for the ItemId, ProductId and SubscriptionId properties.
9. Return the string representation of the param variable by calling the ToString
and Substring methods on it. The Substring method is used to remove the last
&-character or the ?-mark (if no matching id’s were found) from the parameter
list.
return param.ToString().Substring(0, s.Length - 1);
6. Add the table data <td> element and set its width to 120px.
<td style=”width:120px;“>
@*Button group goes here*@
</td>
7. Add the button group <div> element, assign the btn-group class and Role
attribute to it and assign a new Guid to the aria-label attribute.
<div class=“btn-group” role=“group” aria-label=”@Guid.NewGuid()“>
@*Button code goes here*@
</div>
8. Add three @Html.Partial method calls to add the buttons (see code below).
The full code for the TableButtonsPartial partial view:
@model MyMembership.Areas.Admin.Models.SmallButtonModel
@using MyMembership.Areas.Admin.Models
<td style=”width:120px;“>
<div class=“btn-group” role=“group” aria-label=”@Guid.NewGuid()“>
@Html.Partial(“_SmallButtonPartial”,
new SmallButtonModel { Action = “Edit”,
ButtonType = “btn-primary”, Glyph = “pencil”, Text = “Edit”,
Id = Model.Id,
ItemId = Model.ItemId,
ProductId = Model.ProductId,
SubscriptionId = Model.SubscriptionId })
@Html.Partial(“_SmallButtonPartial”, new SmallButtonModel {
Action = “Details”, ButtonType = “btn-success”,
Glyph = “list”, Text = “Details”,
Id = Model.Id,
ItemId = Model.ItemId,
ProductId = Model.ProductId,
SubscriptionId = Model.SubscriptionId })
@Html.Partial(“_SmallButtonPartial”, new SmallButtonModel {
Action = “Delete”, ButtonType = “btn-danger”, Glyph = “trash”,
Text = “Delete”,
Id = Model.Id,
ItemId = Model.ItemId,
ProductId = Model.ProductId,
SubscriptionId = Model.SubscriptionId })
</div>
</td>
With:
@Html.Partial(“_TableButtonsPartial”, new SmallButtonModel { Id = item.Id })
3. Find the foreach loop and remove the following HTML markup
<td>
@Html.ActionLink(“Edit”, “Edit”, new { id=item.Id })|
@Html.ActionLink(“Details”, “Details”, new { id=item.Id })|
@Html.ActionLink(“Delete”, “Delete”, new { id=item.Id })
</td>
4. Add the following HTML markup in its place. Note that an instance of the
SmallButtonModel is passed in as a second parameter to the @Html.Partial
method.
@Html.Partial(“_TableButtonsPartial”, new SmallButtonModel { ItemId = item.Id })
5. Save and run the application and check that the buttons are displayed in the
table and that they are working.
The complete code for the Index view:
@model IEnumerable<Memberships.Entities.Section>
@using Memberships.Areas.Admin.Models;
@{ ViewBag.Title = “Index”; }
<h2>Index</h2>
<p>@Html.Partial(“_CreateButtonPartial”)</p>
<table class=“table table-striped table-condensed”>
<tr class=“success”>
<th>@Html.DisplayNameFor(model => model.Title)</th>
<th></th>
</tr>
@foreach (var item in Model) {
<tr>
<td>@Html.DisplayFor(modelItem => item.Title)</td>
@Html.Partial(“_TableButtonsPartial”,
new SmallButtonModel { Id = item.Id })
</tr>
}
</table>
Scaffold the Part controller and views
The Part table is virtually identical to the Section table; it’s basically just the name that
differs, so creating and altering the controller and views should be a breeze by now. If you
get stuck you can always go back to the section describing how to set it up for the Section
table.
The fastest way to implement the controller and views for the Part table is probably to
create a controller without views, copy the Section views and make a couple of small
changes to them.
1. Right-click on the Controllers folder and select Add-Controller in the context
menu.
2. Fill in the Add Controller dialog according to the image above and make sure that
the Generate views checkbox is unchecked and the name is PartController. Apart
from adding the controller, a folder named Part will be added to the Views folder.
3. Select all views in the Section folder by selecting the first one and while holding
down shift selecting the last one.
4. Right-click on the selection and select Copy in the context menu.
5. Right-click on the Part folder and select Paste in the context menu.
6. Now you have to go through the views in the Part folder one by one and change
the @model directive to point to the Part class and the <h4> heading in the form
to Part. The Index view does not have the <h4> element.
@model IEnumerable<Memberships.Entities.Part>
and
<h4>Part</h4>
7. Run the application and select Part in the Admin menu to check that all the views
are working.
Scaffold the ItemType controller and views
The ItemType table is virtually identical to the Section table; it’s basically just the name
that differs, so creating and altering the controller and views should be a breeze by now. If
you get stuck you can always go back to the section describing how to set it up for the
Section table.
The fastest way to implement the controller and views for the ItemType table is to create
a controller without views, copy the Section views and make a couple of small changes to
them.
1. Right-click on the Controllers folder and select Add-Controller in the context
menu.
2. Fill in the Add Controller dialog as described in the image above and make sure
that the Generate views checkbox is unchecked and the name is
ItemTypeController. Apart from adding the controller in the Controllers folder, an
empty folder named ItemType will be added to the Views folder.
3. Select all views in the Section folder by selecting the first one and while holding
down shift selecting the last one.
4. Right-click on the selection and select Copy in the context menu.
5. Right-click on the ItemType folder and select Paste in the context menu.
6. Now you have to go through the views in the ItemType folder one by one and
change the @model directive to point to the ItemType class and the <h4> heading
in the form to Item Type. The Index view doesn’t have the <h4> element.
@model IEnumerable<Memberships.Entities.ItemType>
and
<h4>Item Type</h4>
7. Run the application and select Item Type in the Admin menu and check that all
the views are working.
Scaffold the ProductType controller and views
The ProductType table is virtually identical to the Section table; it’s basically just the
name that differs, so creating and altering the controller and views should be a breeze by
now. If you get stuck, you can always go back to the section describing how to set it up for
the Section table.
The fastest way to implement the controller and views for the ProductType table is to
create a controller without views, copy the Section views and make a couple of small
changes to them.
1. Right-click on the Controllers folder and select Add-Controller in the context
menu.
2. Fill in the Add Controller dialog as described in the image above and make sure
that the Generate views checkbox is unchecked and the name is
ProductTypeController. Apart from the controller in the Controllers folder, an
empty folder named ProductType will be added to the Views folder.
3. Select all views in the Section folder by selecting the first one and while holding
down shift selecting the last one.
4. Right-click on the selection and select Copy in the context menu.
5. Right-click on the ProductType folder and select Paste in the context menu.
6. Now you have to go through the views in the ProductType folder one by one and
change the @model directive to point to the ProductType class and the <h4>
heading in the form to Product Type. The Index view doesn’t have the <h4>
element.
@model IEnumerable<Memberships.Entities.ProductType>
and
<h4>Product Type</h4>
7. Run the application and select Product Type in the Admin menu and make sure
that all the views are working.
Scaffold the ProductLinkText controller and views
The ProductLinkText table is virtually identical to the Section table; it’s basically just the
name that differs, so creating and altering the controller and views should be a breeze by
now. If you get stuck you can always go back to the section describing how to set it up for
the Section table.
The fastest way to implement the controller and views for the ProductLinkText table is to
create a controller without views, copy the Section views and make a couple of small
changes to them.
1. Right-click on the Controllers folder and select Add-Controller in the context
menu.
2. Fill in the Add Controller dialog as described in the image above and make sure
that the Generate views checkbox is unchecked and the name is
ProductLinkTextController. Apart from the controller in the Controllers folder, an
empty folder named ProductLinkText will be added to the Views folder.
3. Select all views in the Section folder by selecting the first one, and while holding
down shift, selecting the last one.
4. Right-click on the selection and select Copy in the context menu.
5. Right-click on the ProductLinkText folder and select Paste in the context menu.
6. Now you have to go through the views in the ProductLinkText folder one by one
and change the @model directive to point to the ProductLinkText class and the
<h4> heading in the form to Product Link Text. The Index view doesn’t have the
<h4> element.
@model IEnumerable<Memberships.Entities.ProductLinkText>
and
<h4>Product Link Text</h4>
7. Run the application and select Product Link Text in the Admin menu and check
that all the views are working.
5. Adding UIs for the Major Entities
Introduction
As you add controllers and views to the Admin area in this chapter, the corresponding
Admin menu items will begin to work, making it possible to navigate more easily be-
tween the Index views for the different tables you are using on the back-end to store data.
Technologies used in this chapter
1. MVC – To structure the application and scaffold views and controllers and to
create partial views for reusable components such as buttons.
2. C# - Creating Extension methods, helper methods, to keep the controller actions
clean and easy to read. Creating the view models needed for all the views.
Reflection is used to fetch property values for generic objects.
3. Razor – To incorporate C# in the views where necessary.
4. HTML 5 – To build the views.
5. Bootstrap – To style the HTML 5 components.
The use case
Your task is to scaffold the controllers and views for the Product, Item and Subscription
tables in the database to enable reading and writing data to and from them. The controllers
will be called ProductController, ItemController and SubscriptionController, and with
each controller, five views will be created in a folder with the same name as the con-
troller’s corresponding table.
Index - The main view with a list of the available table records and buttons to the
other views.
Create - A view for adding new table records.
Delete - A view for deleting a selected table record.
Edit - A view to modify the table data.
Details - A view to show the data stored for one table record.
Scaffold the Item controller and views
The Item table contains information about each content piece; items can then be added to
a product and subsequently to a subscription. Because the views contain a lot more infor-
mation than the previously added views, it is best to scaffold them with the ItemCon-
troller controller.
Use scaffolding when creating the Item controller and its views in the Admin area, then
alter the views to use the partial view you have already created and spruce it up with some
added Bootstrap design classes.
The controller will be based on the Item entity class located in the Entities folder in the
main project.
1. Right-click on the Controllers folder in the Admin area and select Add-
Controller in the context menu.
2. Select the template for scaffolding a controller with Entity Framework action
methods (the same as in the two previous chapters).
3. Fill in the form according to the image above. Remember that the controller name
should be in singular and that the Generate views checkbox should be checked.
This will add a controller called ItemController with read and write actions as
well as the views associated with the actions.
The Create Action (HttpGet)
Before altering the Create view, you have to make some changes to the Create action in
the ItemController class in order to be able to display dropdowns for the Section, Part
and ItemType values. If you don’t do this, then the values will be handled by textboxes –
which isn’t a good user experience and invites problems and errors. If possible, it is
always best to let the user select from a dropdown rather than typing in a value because
then, only the available values can be selected by their title.
There are two actions named Create, and the one you want to alter is the one without
parameters because it is called when the view is rendered. The other Create action, which
is called when the user submits the form, doesn’t have to be changed.
You have to alter the Create action to send an instance of the Item class which has been
populated from the database.
1. Open the ItemController class in the Solution Explorer.
2. Locate the Create action without parameters.
public ActionResult Create()
{
return View();
}
3. Create an instance of the Item class called model and fill its ItemTypes collection
with data from the ItemType table, Parts collection with data from the Part table
and Sections collection with data from the Section table. Use the
ApplicationDbContext variable called db located at the beginning of the
ItemController class when accessing the database.
var model = new Item()
{
ItemTypes = db.ItemTypes.ToList(),
Parts = db.Parts.ToList(),
Sections = db.Sections.ToList()
};
return View(model);
}
The Create View
Now that the Create view has a model with lists of products, sections, parts, and item
types, you will alter the appearance of the web page, changing the textboxes to dropdown
controls. If you look closely you can see that the default labels aren’t displayed properly;
they have the wrong description. To fix this, you have to add the DisplayName attribute to
the ItemTypes collection in the Item model class. You also have to change the Lambda
expression in the LabelFor method calls for the collections in the Razor view to go to the
collection property instead of the id property.
Changes to the Item model class and the Create view:
[DisplayName(“Item Types”)]
public virtual ICollection<ItemType> ItemTypes { get; set; }
@Html.LabelFor(model => model.ItemTypes, htmlAttributes: new { @class = “control-label col-md-2” })
Displaying dropdown lists is a bit more challenging since you are using models for other
tables in your collection as described in the previous section. What makes it challenging is
that the DropDownFor extension method requires an Ienumerable<SelectListitem>
which means that you have to convert the lists in your view model to that type of list, and
since there are no available convert methods that you can use, you have to create a convert
method. Let’s create it as a generic extension method which can be used on any collection
with a given set of properties, making it possible to reuse it for all DropDownFor calls in
this and future views. This will require reflection to look at the properties of the passed in
collection.
Fixing the dropdown labels
1. Open the Item class located in the Entities folder.
2. Find the ItemTypes collection and add the DisplayName attribute to it.
[DisplayName(“Item Types”)]
public virtual ICollection<ItemType> ItemTypes { get; set; }
3. Open the Create view in the Views-Item folder in the Admin area.
4. Change the Lambda expression in the LabelFor method calls to target the
collections.
@Html.LabelFor(model => model.ItemTypes, htmlAttributes: new {
@class = “control-label col-md-2” })
@Html.LabelFor(model => model.Sections, htmlAttributes: new {
@class = “control-label col-md-2” })
@Html.LabelFor(model => model.Parts, htmlAttributes: new {
@class = “control-label col-md-2” })
Use reflection in the method to fetch the property value for the property matching the
name sent in through the propertyName parameter. Use the GetType method to find the
type definition of the generic object, the GetProperty method on the type definition to
fetch the property matching the name passed in to it, and lastly the GetValue method to
fetch the actual value stored in the object.
1. Create a folder called Extensions in the project, not in the Admin area.
2. Right-click on the folder and select Add-Class.
3. Name the class ReflectionExtensions.
4. Change the class to public static.
public static class ReflectionExtensions
5. Add a public static method called GetPropertyValue which takes a parameter of
type T called item and is decorated with the this keyword and a second parameter
of type string called propertyName.
public static string GetPropertyValue<T>(this T item, string propertyName)
6. Use the GetType method to find the type definition of the item object, the
GetProperty method on the type definition to fetch the property matching the
passed in name, and lastly the GetValue method to fetch the actual value stored in
the object.
return item.GetType().GetProperty(propertyName).GetValue(item, null).ToString();
The complete code for the GetPropertyValue extension method looks like this:
public static class ReflectionExtensions
{
public static string GetPropertyValue<T>(this T item,
string propertyName)
{
// Reflecting over the item and pulling out the property value
return item.GetType().GetProperty(propertyName)
.GetValue(item, null).ToString();
}
}
Loop over the items in the collection parameter to create an IEnumerable<SelectList-
Item> collection and assign values to the Text, Value and Selected properties for each
new instance added to it. Assign their values by calling the GetPropertyValue extension
method you created in the previous section.
Text = item.GetPropertyValue(“Title”)
The Text property is the text to display for the item in the dropdown, the Value property is
the item id and the Selected property determines if the current item should be the selected
dropdown item.
1. Right-click on the Extension folder you created in the last section and select Add-
Class.
2. Name the class ICollectionExtensions.
3. Change the class to be public static.
Public static class IcollectionExtensions
4. Add a public static method called ToSelectListItem which takes two parameters:
the first is the ICollection<T> which has to be decorated with the this keyword.
The second is of type int and is called selectedValue. It keeps track of which item
in the list should be marked as selected and be displayed when the list is rendered.
The purpose of calling this method is to convert a collection of type T into a
collection of type SelectListItem.
public static IEnumerable<SelectListItem> ToSelectListItem<T>(this ICollection<T> items, int selectedValue)
{
// Code goes here
}
The complete ICollectionExtensions class:
public static class ICollectionExtensions
{
public static IEnumerable<SelectListItem> ToSelectListItem<T>(
this ICollection<T> items, int selectedValue)
{
return from item in items
select new SelectListItem
{
// Cannot get to properties directly
// because the item is of type T!
// We can use reflection to solve this…
// Let’s call the GetPropertyValue method
// which return a string containing the
// property value.
Text = item.GetPropertyValue(“Title”),
Value = item.GetPropertyValue(“Id”),
Selected = item.GetPropertyValue(“Id”)
.Equals(selectedValue.ToString())
};
}
}
3. Locate the form-group <div> containing product information and delete it. When
creating a new item, you might not want to link it to a specific product right away.
4. Locate the @Html.EditorFor method for the ItemTypes and change it to
@Html.DropDownListFor and add a third parameter in between the existing
parameters, calling the ToSelectListItem extension method on the ItemTypes
collection passed in through the model to convert it to a collection suitable for a
dropdown.
@Html.DropDownListFor(model => model.ItemTypeId,
Model.ItemTypes.ToSelectListItem(Model.ItemTypeId), new { @class = “form-control” })
5. Now do the same changes for the Sections and the Parts collections.
The complete changes for the labels and dropdowns in the Create view:
<div class=“form-group”>
@Html.LabelFor(model => model.ItemTypes,
htmlAttributes: new { @class = “control-label col-md-2” })
<div class=“col-md-10”>
@Html.DropDownListFor(model => model.ItemTypeId,
Model.ItemTypes.ToSelectListItem(
Model.ItemTypeId),new { @class = “form-control” })
@Html.ValidationMessageFor(model => model.ItemTypeId, ””,
new { @class = “text-danger” })
</div>
</div>
<div class=“form-group”>
@Html.LabelFor(model => model.Sections,
htmlAttributes: new { @class = “control-label col-md-2” })
<div class=“col-md-10”>
@Html.DropDownListFor(model => model.SectionId,
Model.Sections.ToSelectListItem(Model.SectionId),
new { @class = “form-control” })
@Html.ValidationMessageFor(model => model.SectionId, ””,
new { @class = “text-danger” })
</div>
</div>
<div class=“form-group”>
@Html.LabelFor(model => model.Parts, htmlAttributes:
new { @class = “control-label col-md-2” })
<div class=“col-md-10”>
@Html.DropDownListFor(model => model.PartId,
Model.Parts.ToSelectListItem(Model.PartId),
new { @class = “form-control” })
@Html.ValidationMessageFor(model => model.PartId, ””,
new { @class = “text-danger” })
</div>
</div>
The Edit View
The Edit view is very similar to the Create view. You can copy the code for the
@Html.DropDownListFor dropdowns from the Create view and replace the
corresponding @Html.EditorFor code in the Edit view. You also need to add a using
statement to the Memberships.Extensions namespace under the @model directive in the
Edit view to get access to the ToSelectListitem extension method you created earlier in
this chapter.
To style the view a bit, you can change the btn-default to btn-success for the submit
button and replace the @HtmlActionLink extension method call to a call to the @Html.
Partial and pass the _BackToListButtonPartial name to render that partial view display-
ing a button instead.
To show data in the dropdown controls for item type, section and part, you have to load
their data in the Edit controller action method in the ItemController class. Since the
action is asynchronous, it is possible to load the data from the database tables using the
ToListAsync extension method on the table in conjunction with the await keyword which
frees up the current thread for other work. A word of caution: in a production
environment, you probably want to run bench tests to see that you get a performance
benefit from executing asynchronously.
The image below shows the end result after all the changes to the Edit view.
Modifying the Edit controller action (HttpGet)
1. Open the ItemController class and locate the Edit action with only one parameter.
2. Add the code to load the data from the ItemType, Section and Part tables
immediately above the return statement. Store the result in the already existing
content variable’s ItemTypes, Sections and Parts collections.
item.ItemTypes = await db.ItemTypes.ToListAsync();
item.Parts = await db.Parts.ToListAsync();
item.Sections = await db.Sections.ToListAsync();
The complete HttpGet Edit action:
public async Task<ActionResult> Edit(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Item item = await db.Items.FindAsync(id);
if (item == null)
{
return HttpNotFound();
}
item.ItemTypes = await db.ItemTypes.ToListAsync();
item.Parts = await db.Parts.ToListAsync();
item.Sections = await db.Sections.ToListAsync();
return View(item);
}
<div class=“form-group”>
<div class=“col-md-offset-2 col-md-10”>
<input type=“submit” value=“Save” class=“btn btn-success” />
</div>
</div>
<div>
@Html.Partial(“_BackToListButtonPartial”)
</div>
The Details View
The Item Details view is very similar to the Section Details view. You can copy the two
lines of code you need to change from the Section Details view. Locate the
@Html.Partial for the two buttons and copy the code and then replace the two
corresponding @Html.ActionLink calls in the Item Details view.
The image below shows the Details view after the changes.
1. Open the Details view in the Section folder.
2. Locate the two @Html.Partial method calls and copy the code.
@Html.Partial(“_EditButtonPartial”, Model.Id)
@Html.Partial(“_BackToListButtonPartial”)
Changes to the Details view:
<p>
@Html.Partial(“_EditButtonPartial”, Model.Id)
@Html.Partial(“_BackToListButtonPartial”)
</p>
The Delete View
The Item Delete view is very similar to the Section Delete view. You can copy the two
lines of code you need to change from the Section Delete view. At the very end of the
view, you will find the Delete button and the @Html.Partial method call you need to
copy. Replace the corresponding code in the Item Delete view.
The image below shows the Delete view after the changes.
1. Open the Delete view in the Section folder.
2. Locate the Delete button and the @Html.Partial method call and copy the code.
<input type=“submit” value=“Delete” class=“btn btn-danger btn-sm” />
@Html.Partial(“_BackToListButtonPartial”)
Changes to the Details view:
<div class=“form-actions no-color”>
<input type=“submit” value=“Delete” class=“btn btn-danger btn-sm” />
@Html.Partial(“_BackToListButtonPartial”)
</div>
The Index View
The Item Index view is very similar to the Section Index view; it’s basically only the
number of columns that differs. You can copy a lot of the code from the Section Index
view and paste it in.
You can copy the @Html.Partial code for creating the _CreateNewButtonPartial and
replace the corresponding @Html.ActionLink in the Item Index view with the copied
code.
You can also copy the <table> and first <tr> tags from the Section Index view which is
styled with Bootstrap, and replace the corresponding tags in the Item Index view.
Next, you have to copy the @using statement at the top of the Section Index view to get
access to the SmallButtonModel class needed to create the three buttons.
Lastly, you can replace the <td> element holding the three @Html.ActionLink method
calls at the bottom of the view with a call to the @Html.Partial method call for the
_TableButtonsPartial view.
Then there is a bit of cleaning up in the table; you do not want to display the URL and id
columns. To remove them, simply delete their code from the view.
This is what the Item Index view looks like before any changes:
And this is what it look like after the changes:
Replacing the Create New link with a button
1. Open the Index view in the Section folder and copy the @using statement at the
top of the view.
@using Memberships.Areas.Admin.Models
2. Open the Index view in the Item folder and paste in the code in the corresponding
place.
3. Open the Index view in the Section folder and copy the code for the Create New
button.
@Html.Partial(“_CreateNewButtonPartial”)
4. Open the Index view in the Item folder and replace the corresponding
@Html.ActionLink with the copied code.
@Html.ActionLink(“Create New”, “Create”)
5. Open the Index view in the Section folder and copy the code for the <table> and
<tr> elements.
<table class=“table table-striped”>
<tr class=“success”>
6. Open the Index view in the Item folder and replace the corresponding elements
with the copied code.
<table class=“table”>
<tr>
7. Open the Index view in the Section folder and copy the code for the partial view
for the table buttons.
@Html.Partial(“_TableButtonsPartial”, new SmallButtonModel { Id = item.Id })
8. Open the Index view in the Item folder and replace the last <td> element and its
three @Html.ActionLink method calls with the copied code.
<td>
@Html.ActionLink(“Edit”, “Edit”, new { id = item.Id }) |
@Html.ActionLink(“Details”, “Details”, new { id = item.Id }) |
@Html.ActionLink(“Delete”, “Delete”, new { id = item.Id })
</td>
9. Open the Index view in the Item folder and delete the following table headings to
keep the table clean and more readable.
<th>@Html.DisplayNameFor(model => model.Url)</th>
<th>@Html.DisplayNameFor(model => model.ImageUrl)</th>
<th>@Html.DisplayNameFor(model => model.ItemTypeId)</th>
<th>@Html.DisplayNameFor(model => model.SectionId)</th>
<th>@Html.DisplayNameFor(model => model.PartId)</th>
9. Add a string property called ProductType with the set-block removed which
returns the title from the ProductTypes collection based on the ProductTypeId.
Return an empty string if the ProductTypes collection is null or if it has no values.
public string ProductType {
get {
return ProductTypes == null ||
ProductTypes.Count.Equals(0) ?
string.Empty : ProductTypes.First(pt =>
pt.Id.Equals(ProductTypeId)).Title;
}
}
}
Adding the ProductController and views
The fastest way to implement the controller and views for the Product table is to create a
controller without views, copy the Item views to the Views-Product folder and make a
couple of small changes to them.
There are only two things you need to change in the views – the @model directive from
Item to Product and the <h4> heading from Item to Product.
1. Right-click on the Controllers folder and select Add-Controller in the context
menu.
2. Fill in the Add Controller dialog according to the image above and make sure that
the Generate views checkbox is unchecked and the name is ProductController.
Apart from the controller, a folder named Product will be added to the Views
folder.
3. Select all views in the Item folder by selecting the first one, and while holding
down shift, selecting the last one.
4. Right-click on the selection and select Copy in the context menu.
5. Right-click on the Product folder and select Paste in the context menu.
6. Now you have to go through the views in the Product folder one by one and
change the @model directive to point to the Product class and change the <h4>
heading in the form to Product. The Index view doesn’t have the <h4> element.
@model IEnumerable<Memberships.Areas.Admin.Models.ProductModel>
and
<h4>Product</h4>
7. Remove the HTML elements for the Url, HTML and WaitDays labels, textboxes
and validation. Also remove the IsFree checkbox and label.
Altering the Index view
In order to display all the necessary information in the Index view, you have to modify the
view itself, as well as the ProductModel class, and create a new asynchronous extension
method called Convert which converts an IEnumerable<Product> into an
Ienumerable<ProductModel>. The Convert method is used in the ProductController’s
Index action.
3. Add an async method called Convert which takes two parameters and returns
Task<IEnumerable<ProductModel>>. The two parameters are the product list to
act on and an instance of the ApplicationDbContext enabling database access.
public static async Task<IEnumerable<ProductModel>> Convert(
this IEnumerable<Product> products, ApplicationDbContext db)
{
}
4. Pass the result in the products variable to the Convert method and store the result
in a variable called model.
var model = await products.Convert(db);
You will also have to change the missing WaitDays, IsFree and HTML properties in the
HTML table to the ProductLinkText and ProductType properties in the model. Then you
will add the corresponding id’s in parenthesis to the right of the property texts (see
previous image).
1. Open the Index view in the Product folder in the Admin area.
2. Change the @model directive to use the ProductModel class.
@model Ienumerable<Memberships.Areas.Admin.Models.ProductModel>
3. Change the properties used for label descriptions in the table headers for the
missing data.
<th>
@Html.DisplayNameFor(model => model.ProductLinkTexts)
</th>
5. Run the application and select Product in the Admin menu to check that the view
is rendered correctly.
The complete HTML markup for the Index view:
@model Ienumerable<Memberships.Areas.Admin.Models.ProductModel>
@using Memberships.Areas.Admin.Models
@{
ViewBag.Title = “Index”;
}
<h2>Index</h2>
<p>
@Html.Partial(“_CreateNewButtonPartial”)
</p>
<table class=”table table-striped table-condensed”>
<tr class=”success”>
<th>@Html.DisplayNameFor(model => model.Title)</th>
<th>@Html.DisplayNameFor(model => model.Description)</th>
<th>@Html.DisplayNameFor(model => model.ProductLinkTexts)</th>
<th>@Html.DisplayNameFor(model => model.ProductTypes)</th>
<th></th>
</tr>
@foreach (var item in Model) {
<tr>
<td>@Html.DisplayFor(modelItem => item.Title)</td>
<td>@Html.DisplayFor(modelItem => item.Description)</td>
<td>@Html.DisplayFor(modelItem => item.ProductLinkText)
(@{@Html.DisplayFor(modelItem => item.ProductLinkTextId)})
</td>
<td>@Html.DisplayFor(modelItem => item.ProductType)
(@{@Html.DisplayFor(modelItem => item.ProductTypeId)})
</td>
@Html.Partial(“_TableButtonsPartial”, new SmallButtonModel {
Id = item.Id })
</tr>
}
</table>
Altering the Create view
You have to change the @model directive to ProductModel in the Create view as well as
the dropdown properties, so that they reflect the correct properties in the ProductModel.
You also have to create a ProductModel instance in the Create action and fill the
ProductLinkTexts and ProductTypes collections with data from the ProductLinkText
and ProductType tables in the database. Then you have to return that model with the
view.
You will have to change the @model directive to ProductModel in the Create view in
order to display items in the dropdowns.
Remove the form groups for the Url, HTML, IsFree and WaitDays properties since they
don’t exist in the view model.
Change the dropdowns for the ItemTypes, Sections and Parts collections to display items
from the ProductLinkTexts and ProductTypes collections.
1. Open the Create view located in the Product folder in the Admin area.
2. Change the @model directive to ProductModel.
@model Memberships.Areas.Admin.Models.ProductModel
3. Remove the form-groups for the Url, HTML, IsFree and WaitDays properties.
4. Change the dropdowns for the ItemTypes, Sections and Parts collections to
display items from the ProductLinkTexts and ProductTypes collections.
@Html.DropDownListFor(model => model.ProductLinkTextId,
Model.ProductLinkTexts.ToSelectListItem(
Model.ProductLinkTextId), new { @class = “form-control” })
@Html.ValidationMessageFor(model => model.ProductLinkTextId, ””,
new { @class = “text-danger” })
3. Change the missing HTML, WaitDays, and ProductId properties in the HTML
data list to the ProductLinkTexts/ProductLinkText and
ProductTypes/ProductType properties in the model.
<dt>@Html.DisplayNameFor(model => model.ProductLinkTexts)</dt>
<dd>@Html.DisplayFor(model => model.ProductLinkText)</dd>
4. Remove the HTML data list items for the Url, ItemTypeId, SectionId, PartId and
IsFree properties.
The complete Details view:
@model Memberships.Areas.Admin.Models.ProductModel
@{
ViewBag.Title = “Details”;
}
<h2>Details</h2>
<div>
<h4>Product</h4>
<hr />
<dl class=“dl-horizontal”>
<dt>@Html.DisplayNameFor(model => model.Title)</dt>
<dd>@Html.DisplayFor(model => model.Title)</dd>
<dt>@Html.DisplayNameFor(model => model.Description)</dt>
<dd>@Html.DisplayFor(model => model.Description)</dd>
<dt>@Html.DisplayNameFor(model => model.ImageUrl)</dt>
<dd>@Html.DisplayFor(model => model.ImageUrl)</dd>
<dt>@Html.DisplayNameFor(model => model.ProductTypes)</dt>
<dd>@Html.DisplayFor(model => model.ProductType)</dd>
<dt>@Html.DisplayNameFor(model => model.ProductLinkTexts)</dt>
<dd>@Html.DisplayFor(model => model.ProductLinkText)</dd>
</dl>
</div>
<p>
@Html.Partial(“_EditButtonPartial”, Model.Id)
@Html.Partial(“_BackToListButtonPartial”)
</p>
5. Change the names of the variables texts and types to text and type.
6. Change the ToListAsync method calls to FirstOrDefaultAsync to return a single
item instead of a list for each of the tables. Use the id properties when fetching the
data.
var text = await db.ProductLinkTexts.FirstOrDefaultAsync(p => p.Id.Equals(product.ProductLinkTextId));
7. Switch out the return statement for a variable named model and assign a new
empty lists to the ProductLinkText and ProductType properties.
var model = new ProductModel
{
…
ProductLinkTexts = new List<ProductLinkText>(),
ProductTypes = new List<ProductType>()
};
8. Add two variables (text and type) to their corresponding list in the ProductModel
instance.
model.ProductLinkTexts.Add(text);
4. Run the application and click the Product item in the Admin menu.
5. Click on the Details button (the green middle button) for a product to make sure
that the details are displayed.
The complete Details action:
public async Task<ActionResult> Details(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Product product = await db.Products.FindAsync(id);
if (product == null)
{
return HttpNotFound();
}
var model = await product.Convert(db);
return View(model);
}
Altering the Delete view
In order to display all the necessary information in the Delete view, you have to modify
the view itself, in addition to using the overloaded version of the asynchronous Convert
extension method which returns a filled instance of the ProductModel class given an
instance of the Product class. The Convert method is used in the ProductController’s
Delete action.
Change the @model directive in the Delete view to the ProductModel class.
@model Memberships.Areas.Admin.Models.ProductModel
Change the properties in the HTML data list to the properties in the ProductModel. You
can copy the <dl> element and its sub-elements from the Details view.
Change the Delete action in the ProductController. Copy the model variable and the call
to the View method at the end of the Details action and replace the call to the View
method in the Delete action with the copied code.
Altering the Delete view
You have to change the @model directive to ProductModel and replace the existing
HTML data list with the one from the Details view.
1. Open the Details view located in the Product folder in the Admin area.
2. Copy the @model directive located at the top of the view.
@model Memberships.Areas.Admin.Models.ProductModel
3. Open the Delete view located in the Product folder in the Admin area.
4. Replace the current @model directive with the one you just copied.
5. Switch to the Details view and copy the HTML data list and its content.
<dl class=“dl-horizontal”>
… Content …
</dl>
6. Switch to the Delete view again and replace the current HTML data list with the
one you copied.
The complete Delete view:
@model Memberships.Areas.Admin.Models.ProductModel
@{
ViewBag.Title = “Delete”;
}
<h2>Delete</h2>
<h3>Are you sure you want to delete this?</h3>
<div>
<h4>Product</h4>
<hr />
<dl class=“dl-horizontal”>
<dt>@Html.DisplayNameFor(model => model.Title)</dt>
<dd>@Html.DisplayFor(model => model.Title)</dd>
<dt>@Html.DisplayNameFor(model => model.Description)</dt>
<dd>@Html.DisplayFor(model => model.Description)</dd>
<dt>@Html.DisplayNameFor(model => model.ImageUrl)</dt>
<dd>@Html.DisplayFor(model => model.ImageUrl)</dd>
<dt>@Html.DisplayNameFor(model => model.ProductTypes)</dt>
<dd>@Html.DisplayFor(model => model.ProductType)</dd>
<dt>@Html.DisplayNameFor(model => model.ProductLinkTexts)</dt>
<dd>@Html.DisplayFor(model => model.ProductLinkText)</dd>
</dl>
@using (Html.BeginForm()) {
@Html.AntiForgeryToken()
<div class=“form-actions no-color”>
<input type=“submit” value=“Delete” class=“btn btn-danger btn-sm” />
@Html.Partial(“_BackToListButtonPartial”)
</div>
}
</div>
4. Locate the Delete action method and replace the call to the View method with the
copied code.
5. Run the application and click the Product item in the Admin menu.
6. Click on the Delete button (the right most red button) on a product row in the
Index view and make sure that the data in the Delete view is displayed properly.
The complete Delete action:
public async Task<ActionResult> Delete(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Product product = await db.Products.FindAsync(id);
if (product == null)
{
return HttpNotFound();
}
var model = await product.Convert(db);
return View(model);
}
Altering the Edit view
You have to change the @model directive to the ProductModel class and the form to
display the correct properties. You can do this by copying the @model directive and the
<div> containing the form controls from the Create view and replacing the corresponding
code in the Edit view.
Then you have to alter the Edit action in the ProductController class. You can use the
already exisiting Convert extension method to change a product to a ProductModel, but
you have to wrap the product in a list because the Convert method takes a list of products.
Lastly, you pass the first product model in the result from the call to the Convert method
to the View method as its model.
Altering the Edit view
You have to change the @model directive to ProductModel and replace the existing
<div> containing the form controls with the one you copy from the Create view.
1. Open the Create view located in the Product folder in the Admin area.
2. Copy the @model directive located at the top of the view.
@model Memberships.Areas.Admin.Models.ProductModel
3. Open the Edit view located in the Product folder in the Admin area.
4. Replace the current @model directive with the one you just copied.
5. Switch to the Create view and copy the <div> containing the form controls.
<div class=“form-horizontal”>
… Form Controls …
</div>
6. Switch to the Edit view again and replace the <div> containing the form controls
with the one you copied.
7. Change the text displayed on the button from Create to Save in the <input>
element.
<input type=“submit” value=“Save” class=“btn btn-success” />
4. Add the product that was fetched earlier in the Edit action to the list.
5. Add a variable called productModel which stores the result from the call to the
Convert method on the prod variable.
var productModel = await prod.Convert(db);
6. Replace the current return statement with one that returns the first ProductModel
instance from the productModel list variable. It will only contain one item but
since the Convert method return a list you have to fetch the first item from it.
return View(productModel.First());
7. Run the application and click the Product item in the Admin menu.
8. Click on the Edit button (the left most blue button) on a product row in the Index
view and check that the data in the Edit view is displayed properly and that you
can alter and save data for the product.
The complete Edit action:
public async Task<ActionResult> Edit(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Product product = await db.Products.FindAsync(id);
if (product == null)
{
return HttpNotFound();
}
var prod = new List<Product>();
prod.Add(product);
var ProductModel = await prod.Convert(db);
return View(ProductModel.First());
}
Scaffold the Subscription controller and views
This is one of the most important tables since it holds information about available sub-
scriptions. A subscription is a collection of products that can be assigned to users when
they subscribe or sign up with the web site. You could give the users access to free teaser
subscriptions without them having to sign up and other subscriptions they get auto-
matically when they register with the web site. You could then have paid subscriptions that
they can buy from you to gain access to the content.
The first thing you need to do is to scaffold out the Subscription controller and its views,
then you have to do some minor modifications to the views.
Scaffold the controller and views
The image below shows the settings needed to scaffold the Subscription controller and its
views.
1. Right-click on the Controllers folder and select Add-Controller in the context
menu.
2. Select MVC 5 Controller with views, using Entity Framework and click the
Add button.
3. Fill in the dialog according to the image above and click the Add button.
The Index View
The Index view is almost done out of the box. You only have to replace the Create New
link with the partial view _CreateNewButtonPartial, replace the table row links with the
buttons in the partial view _TableButtonsPartial, style the table and its header using the
table-striped and success Bootstrap classes and remove the ProductId column.
This is what the Index view looks like out of the box:
This is what the Index view look like after it has been modified:
1. Open the Index view in the Section folder and copy the @Html.Partial method
call to the partial view _CreteButtonPartial.
@Html.Partial(“_CreateNewButtonPartial”)
2. Open the Index view in the Subscriptions folder and replace the submit button
and the @Html.ActionLink method call with the copied code.
3. Add a using statement to the Admin-Models folder immediately below the
@model directrive at the top of the view.
@using Memberships.Areas.Admin.Models
4. Locate the <th> element and <td> elements displaying the ProductId property and
delete them.
<th>@Html.DisplayNameFor(model => model.ProductId)</th>
<td>@Html.DisplayFor(modelItem => item.ProductId)</td>
5. Locate the <table> element and add the class table-striped to its class attribute.
<table class=“table table-striped“>
6. Locate the <tr> element under the <table> element and add the class success to a
new class attribute.
<tr class=”success“>
7. Find the last <td> which contains the @Html.ActionLink calls and replace the
entire <td> element with the partial view _TableButtonsPartial.
@Html.Partial(“_TableButtonsPartial”, new SmallButtonModel { Id = item.Id })
3. Open the Create view in the Section folder and copy the @Html.Partial for the
partial view _BackToListButtonPartial.
4. Open the Create view in the Subscription folder and replace the Back to List
@Html.ActionLink with the copied code.
Changes to the Create view:
<div class=“form-group”>
<div class=“col-md-offset-2 col-md-10”>
<input type=“submit” value=“Create” class=“btn btn-success” />
</div>
</div>
<div>
@Html.Partial(“_BackToListButtonPartial”)
</div>
The Edit View
The Edit view is almost done out of the box. You only have to style the Save button using
the btn-success Bootstrap class and replace the Back to List link with the partial view
_BackToListButtonPartial.
This is what the Edit view looks like out of the box:
Here is what the Edit view looks like when modified:
1. Open the Edit view in the Subscription folder.
2. Locate the Save button and add the btn-success Bootstrap class to its class
attribute.
<input type=“submit” value=“Save” class=“btn btn-success” />
3. Open the Edit view in the Section folder and copy the @Html.Partial for the
partial view _BackToListButtonPartial.
4. Open the Edit view in the Subscription folder and replace the Back to List
@Html.ActionLink with the copied code.
Changes to the Edit view:
<div class=“form-group”>
<div class=“col-md-offset-2 col-md-10”>
<input type=“submit” value=“Create” class=“btn btn-success” />
</div>
</div>
<div>
@Html.Partial(“_BackToListButtonPartial”)
</div>
The Details View
The Details view is almost done out of the box. You only have to replace the Back to List
link with the partial view _BackToListButtonPartial and the Edit link with the partial
view _EditButtonPartial.
This is what the Details view looks like out of the box:
This is what the Details view looks like after it has been modified:
1. Open the Details view in the Section folder and copy the two @Html.Partial
method calls at the bottom of the view.
2. Open the Details view in the Subscriptions folder and replace the two
@Html.ActionLink method calls with the copied code.
Changes to the Details view:
<p>
@Html.Partial(“_EditButtonPartial”, Model.Id)
@Html.Partial(“_BackToListButtonPartial”)
</p>
The Delete View
The Delete view is almost done out of the box. You only have to replace the Back to List
link with the partial view _BackToListButtonPartial and style the Delete button with the
two Bootstrap classes btn-danger (red) and btn-sm (small button).
This is what the Delete view looks like out of the box:
This is what the Delete view looks like after it has been modified:
1. Open the Delete view in the Section folder and copy the submit button and the
@Html.Partial method call at the bottom of the view.
<input type=“submit” value=“Delete” class=“btn btn-danger btn-sm“/>
@Html.Partial(“_BackToListButtonPartial”)
2. Open the Delete view in the Subscriptions folder and replace the submit button
and the @Html.ActionLink method call with the copied code.
Changes to the Delete view:
<div class=“form-actions no-color”>
<input type=“submit” value=“Delete” class=“btn btn-danger btn-sm” />
@Html.Partial(“_BackToListButtonPartial”)
</div>
6. Adding UIs for the Connecting Entities
Introduction
In this chapter you will continue to implement controllers and views for the database you
have created. As you add controllers and views to the Admin area, the corresponding
menu items will work, making it possible to navigate between the Index views.
Technologies used in this chapter
1. MVC - To structure the application and scaffold views and controllers and to create
partial views for reusable components such as buttons.
2. C# - Creating Extension methods, helper methods, to keep the controller actions
clean and easy to read. Creating the view models needed for all the views.
Reflection is used to fetch property values for generic objects.
3. Razor - To incorporate C# in the views where necessary.
4. HTML 5 - To build the views.
5. Bootstrap - To style the HTML 5 components.
The use case
Your task is to scaffold the controllers and views for the ProductItem and Subscription-
Product tables in the database to enable reading and writing data to and from them, and in
so doing, connecting a product with an item or a subscription. The controllers will be
called ProductItemController and SubscriptionProductController and with each con-
troller five views will be created in a folder with the same name as the controller’s corre-
sponding table.
Index - The main view with a list of the available table records and buttons to the
other views.
Create - A view for adding new table records.
Delete - A view for deleting a selected table record.
Edit - A view to modify the data in table record.
Details - A view to show the data stored about one table record in the
corresponding table.
Scaffold the ProductItem controller and views
The connection table ties a piece of content in the Item table to one or more products in
the Product table. Even though it only contains two values, it is one of the more complex
tables to create a controller and views for, the reason being that the values are stored as
integer values in the table; but you want to display the descriptive titles in dropdowns
instead of as integers in textboxes, creating a better user experience and minimizing the
risk for errors.
The image below displays the settings in the Add Controller dialog for the PrductItem-
Controller.
The first thing you need to do is to scaffold out the ProductItemController and its views.
1. Right-click on the Controllers folder in the Admin area.
2. Select Add-Controller in the context menu.
3. Select the ProductItem entity model in the Model class dropdown.
4. Make sure that the Generate views checkbox is checked.
5. Change the name to its singular form and click the Add button.
Adding the ProductItemModel
To be able to display the title from the Product and Item tables in the Create view, you
need to create a view model which has collections for the products and items, as well as
the product id and item id which will represent the selected dropdown values. Thinking
ahead, you can also add two string properties for the product and item titles which will be
used when displaying information about a product-item connection in other views.
1. Right-click on the Models folder in the Admin area.
2. Select Add-Class in the context menu.
3. Name the class ProductItemModel.
Public class ProductItemModel
{
}
6. Add a property of type Icollection<Product> called Products which will hold the
available products in the Products table.
Public Icollection<Product> Products { get; set; }
7. Add a property of type Icollection<Item> called Items which will hold the
available content pieces in the Item table.
Public Icollection<Item> Items { get; set; }
8. Add the DisplayName attribute where applicable to change the lable text in the
view.
The complete code for the ProductItemModel:
public class ProductItemModel
{
[DisplayName(“Product Id”)]
public int ProductId { get; set; }
[DisplayName(“Item Id”)]
public int ItemId { get; set; }
[DisplayName(“Product Title”)]
public string ProductTitle { get; set; }
[DisplayName(“Item Title”)]
public string ItemTitle { get; set; }
public Icollection<Product> Products { get; set; }
public Icollection<Item> Items { get; set; }
}
The Create View
The first view you will alter is the Create view which adds a new connection between a
product in the Product table and an item in the Item table, adding a piece of content to a
product. As you can see in the image below, the data is displayed in textboxes by default.
You need to change this to dropdowns to provide a better user experience and minimize
the risk of errors.
The image below shows the finished view.
The first thing you need to do is to change the @model directive to use the ProductItem-
Model class and add a using statement to the Memberships.Areas.Admin.Extensions
namespace below the @model directive to gain access to the ToSelectListItem extension
method you created earlier, which is needed to convert an ICollection<T> to an
IEnumerable<SelectListItem> to display items in a dropdown.
@model Memberships.Areas.Admin.Models.ProductItemModel
@using Memberships.Areas.Admin.Extensions
Next, you need to change the textboxes to dropdowns using the @Html.DropDownFor
extension method. Looking back, you have already done this for textboxes in the Create
view for the Product table, so you copy one of the form-group <div> elements and re-
place the existing two form-group <div> elements in the Create view for the
ProdutItem. Then you simply change the id and collection in the @Html.DropDownFor
extension methods.
Next, you need to change the @Html.LabelFor method calls to reference the Products
and Items collections instead of ProductId and ItemId; this will change the label text for
the controls.
@Html.LabelFor(model => model.Products …
Because you have changed the textboxes to dropdowns, they need values in the model
collections that you used when adding the @Html.DropDownFor method calls. To fill
the collections, you need to call the database from the parameter-less Create action in the
ProductItem controller. Create an instance of the ProductItemModel class in the Create
action and fill its two collections with values from the Product and Item tables. Return
the model with the view.
The last thing you need to do is to style the button and link. Give the button green color by
replacing the btn-default Bootstrap class with btn-success. Replace the Back to List link
with the partial view _BakToLinkButtonPartial.
Change @model and add @using
1. Open the Create view in the ProductItem folder.
2. Change the @model directive to ProductItemModel.
@model Memberships.Areas.Admin.Models.ProductItemModel
5. Locate the second @Html.LabelFor method call and change the Lambda
expression to go to Item instead of the current id. This will change the label to
display the text Items.
@Html.LabelFor(model => model.Items …
6. Locate the first @Html.DropDownFor and change the id to ProductId and the
collection to Products.
@Html.DropDownListFor(model => model.ProductId,
Model.Products.ToSelectListItem(Model.ProductId),
new { @class = “form-control” })
7. Locate the second @Html.DropDownFor and change the id to ItemId and the
collection to Items.
@Html.DropDownListFor(model => model.ItemId,
Model.Items.ToSelectListItem(Model.ItemId),
new { @class = “form-control” })
The complete code for the Create view:
@model Memberships.Areas.Admin.Models.ProductItemModel
@using Memberships.Extensions
@{
ViewBag.Title = “Create”;
}
<h2>Create</h2>
@using (Html.BeginForm())
{
@Html.AntiForgeryToken()
<div class=“form-horizontal”>
<h4>ProductItem</h4>
<hr />
@Html.ValidationSummary(true, ””, new { @class = “text-danger” })
<div class=“form-group”>
@Html.LabelFor(model => model.Products, htmlAttributes:
new { @class = “control-label col-md-2” })
<div class=“col-md-10”>
@Html.DropDownListFor(
model => model.ProductId,
Model.Products.ToSelectListItem(Model.ProductId),
new { @class = “form-control” })
@Html.ValidationMessageFor(model => model.ProductId, ””,
new { @class = “text-danger” })
</div>
</div>
<div class=“form-group”>
@Html.LabelFor(model => model.Items, htmlAttributes:
new { @class = “control-label col-md-2” })
<div class=“col-md-10”>
@Html.DropDownListFor(
model => model.ItemId,
Model.Items.ToSelectListItem(Model.ItemId),
new { @class = “form-control” })
@Html.ValidationMessageFor(model => model.ItemId, ””,
new { @class = “text-danger” })
</div>
</div>
<div class=“form-group”>
<div class=“col-md-offset-2 col-md-10”>
<input type=“submit” value=“Create” class=“btn btn-success” />
</div>
</div>
</div>
}
<div>
@Html.Partial(“_BackToListButtonPartial”)
</div>
Changing the Create controller action (HttpGet)
Because you have changed the textboxes to dropdowns, they need values in the model
collections that you used when adding the @Html.DropDownFor method calls. To fill
the collections, you need to call the database from the parameter-less Create get action in
the ProductItem controller and fill the two collections with values.
1. Open the ProductItemController class in the Controllers folder in the Admin
area.
2. Locate the parameter-less Create action method.
3. Change it to an asynchronous method adding the async keyword and return the
data as a Task.
Public async Task<ActionResult> Create()
The complete code for the HttpGet Create action:
public async Task<ActionResult> Create()
{
var model = new ProductItemModel
{
Items = await db.Items.ToListAsync(),
Products = await db.Products.ToListAsync()
};
return View(model);
}
3. Style the table adding the table-striped Bootstrap class to the <table> element and
the success class to the table header row <tr> element.
<table class=“table table-striped”>
<tr class=“success”>
4. Change the table headers to display the display name of the ProductTitle and
ItemTitle properties and add an empty table header for the button column.
<th>@Html.DisplayNameFor(model => model.ProductTitle)</th>
<th>@Html.DisplayNameFor(model => model.ItemTitle)</th>
<th></th>
5. Add the data in the loop using @Html.DisplayFor method calls for the
ProductTitle and ItemTitle properties.
<td>@Html.DisplayFor(modelItem => item.ProductTitle)</td>
<td>@Html.DisplayFor(modelItem => item.ItemTitle)</td>
6. Open the ProductItemModel class and add the DisplayName attribute displaying
“Product” and “Item” for the ProductTitle and ItemTitle properties.
[DisplayName(“Product”)]
public string ProductTitle { get; set; }
[DisplayName(“Item”)]
public string ItemTitle { get; set; }
2. Locate the @Html.ActaionLink for the Create New link and replace it with a call
to the @Html.Partial method passing in the name of the _CreateNewButton-
Partial partial view.
@Html.Partial(“_CreateNewButtonPartial”)
The complete code for the Index view:
@model IEnumerable<Memberships.Areas.Admin.Models.ProductItemModel>
@using Memberships.Areas.Admin.Models
@{
ViewBag.Title = “Index”;
}
<h2>Index</h2>
<p>
@Html.Partial(“_CreateNewButtonPartial”)
</p>
<table class=“table table-striped”>
<tr class=“success”>
<th>@Html.DisplayNameFor(model => model.ProductTitle)</th>
<th>@Html.DisplayNameFor(model => model.ItemTitle)</th>
<th></th>
</tr>
@foreach (var item in Model) {
<tr>
<td>@Html.DisplayFor(modelItem => item.ProductTitle)</td>
<td>@Html.DisplayFor(modelItem => item.ItemTitle)</td>
@Html.Partial(“_TableButtonsPartial”, new SmallButtonModel {
ItemId = item.ItemId, ProductId = item.ProductId })
</tr>
}
</table>
6. Open the ProductItemController class and locate the Index action method and
change the return statement to call the Convert extension method on the
ProductItems collection.
return View(await db.ProductsItems.Convert(db));
var model = await (
from pi in productItems
select new ProductItemModel
{
ItemId = pi.ItemId,
ProductId = pi.ProductId,
ItemTitle = db.Items.FirstOrDefault(
i => i.Id.Equals(pi.ItemId)).Title,
ProductTitle = db.Products.FirstOrDefault(
p => p.Id.Equals(pi.ProductId)).Title
}).ToListAsync();
return model;
}
The complete Index action:
public async Task<ActionResult> Index()
{
return View(await db.ProductItems.Convert(db));
}
The Edit View
The Edit view is very similar to the Create view you just created. The default Edit view
doesn’t allow any changes since it only contains two hidden fields for the ProductId and
ItemId. You therefore have to alter the view to make it possible to select a product and an
item, like in the Create view, to update the ProductItem table based on the selections the
user make.
The image below shows the altered Edit view.
The Edit View - Display data (HttpGet)
You can start by copying the @model directive and the @using statement in the Create
view located in the ProductItem folder and paste it into the Edit view located in the same
folder. Then, copy the two form-grooup <div> elements from the Create view and paste
it into the Edit view below the last HiddenFor method call.
Modify the two HiddenFor method calls storing the ProductId and ItemId in hidden
form fields to instead store the property values as hidden form fields called OldProductId
and OldItemId using @Html.Hidden method calls. You will need these values to do a
look up in the database from the ProductItem controller’s HttpPost Edit action method.
@Html.Hidden(“OldProductId”, Model.ProductId)
For the view to work properly, you need to modify its Edit controller action. The first
thing you need to do is to add second int? (null-able int) parameter called productId.
after the already existing id parameter.
Because you are changing the parameter list, you also have to change the first if-statement
handling the bad request return to check if the productId parameter is null. If either of
them are null then the if-block should be executed.
The existing FindAsync method call no longer works and needs to be replaced with a new
look up in the ProductItem table. Create a new asynchronous method called GetProd-
uctItem at the end of the ProfductItemContoller class; use a LINQ query to find the
record in the table matching the two ids you send in to the Edit action as parameters. You
do this to check that the record actually exists before displaying the data in the view.
If the record exists in the ProductItem table, then call a new overloaded version of the
Convert method which creates a new model using the ProductItemModel class and
assigns the values to the id properties with data from the passed in ProductItem item.
Also, assign items from the Item table to the Items property and the products from the
Product table to the Products property. Use the await keyword with the ToListAsync
method to call the database asynchronously.
When the model is complete, return it with the View method in the Edit view.
Modifying the Edit view
1. Open the Create view located in the in the ProductItem folder and copy the
@model directive and the @using statement.
2. Open the Edit view located in the in the ProductItem folder and replace the
existing @model directive with the copied code.
3. Hide the ProductId and ItemId as hidden form fields called OldProductId and
OldItemId using @Html.Hidden method calls inside the Html.BeginForm code
block. You can modify the exisiting HiddenFor method calls or delete them and
write new Hidden method calls.
@Html.Hidden(“OldProductId”, Model.ProductId)
@Html.Hidden(“OldItemId”, Model.ItemId)
4. Open the Create view again and copy the two form-group <div> elements and
their content.
5. Open the Edit view and paste in the copied code below the last Hidden method
call.
6. Replace the btn-default Bootstrap class with btn-success for the submit button.
<input type=“submit” value=”Save” class=“btn btn-success” />
3. Because you added the productId parameter, you have to check if it is null in the
first if-statement (bad request). Neither paramter can be null when changing the
ProductItem.
if (id == null || productId == null)
5. Add a try/catch-block to the method where the catch-block return null. Add all
the remaining code you write for this method in the try-block.
6. Parse the the two parameters with the int.TryParse method and store the out
parameter value in variables called itmId and prdId of type int.
int itmId = 0, prdId = 0;
int.TryParse(itemId.ToString(), out itmId);
7. Create a variable called productItem which holds the result from a LINQ query
that fetches the ProductItem matching the parameter values.
var productItem = await db.ProductsItems.FirstOrDefaultAsync(pi =>
pi.ProductId.Equals(prdId) && pi.ItemId.Equals(itmId));
10. If the record exists in the ProductItem table, then create a model by calling a new
overloaded version of the Convert method acting on a ProductItem instance
which will be created in the ConversionExtensions class. The method should have
two parameters, the first called productItem of type ProductItem which holds the
instance that the method is acting on and an instance of the
ApplicationDbContext class called db. The method should be asynchronous and
return a Task<ProductItemModel>.
public static async Task<ProductItemModel> Convert(this ProductItem productItem, ApplicationDbContext
db) { }
11. Create a model using the ProductItemModel class in the current method. Assign
the ids from the productItem parameter to their corresponding properties in the
model instance and fill the two collections in the model by calling the
ToListAsync method on the Item and Product tables. Use the await keyword with
the ToListAsync method to call the database asynchronously.
var model = new ProductItemModel
{
ItemId = productItem.ItemId,
ProductId = productItem.ProductId,
Items = await db.Items.ToListAsync(),
Products = await db.Products.ToListAsync()
};
12. When the Convert method call has finished, then return it with the View method.
return View(model);
The complete altered HttpGet Edit action:
public async Task<ActionResult> Edit(int? itemId, int? productId)
{
if (itemId == null || productId == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
ProductItem productItem = await GetProductItem(itemId, productId);
if (productItem == null)
{
return HttpNotFound();
}
return View(await productItem.Convert(db));
}
The complete code for the GetProductItem method:
private async Task<ProductItem> GetProductItem(int? itemId,
int? productId)
{
try
{
int itmId = 0, prdId = 0;
int.TryParse(itemId.ToString(), out itmId);
int.TryParse(productId.ToString(), out prdId);
var productItem = await db.ProductsItems.FirstOrDefaultAsync(pi =>
pi.ProductId.Equals(prdId) && pi.ItemId.Equals(itmId));
return productItem;
}
catch { return null; }
}
The complete code for the Convert method:
public static async Task<ProductItemModel> Convert(this ProductItem productItem, ApplicationDbContext db)
{
var model = new ProductItemModel
{
ItemId = productItem.ItemId,
ProductId = productItem.ProductId,
Items = await db.Items.ToListAsync(),
Products = await db.Products.ToListAsync()
};
return model;
}
Add a try/catch-block inside the using-block where the catch-block calls the Dispose
method on the transaction variable ending the transaction.
Remove the old ProductItem record and add the new ProductItem record to the
ProductItem database table and call the db.SaveChangesAsync method asynchronously
to add the changes to the database. Then, commit the transaction to persist the changes to
the database or roll them back if either the Remove or Add method fails, by calling the
Complete method on the transaction variable.
Call the CanChange method on the productItem variable at the beginning of the Model-
State.Valid if-block to make sure that the record can be changed. Store the result in a
variable called canChange.
If the value in the canChange variable is true, then call the Change method on the prod-
uctItem variable to persist the changes to the database.
Remove the following two lines of code:
db.Entry(productItem).State = EntityState.Modified;
await db.SaveChangesAsync();
3. Use the CountAsync method on the ProductItem table to count the number of
times the combination of OldProductId and OldItemId exist in the table and store
the result in a variable called oldPI.
var oldPI = await db.ProductsItems.CountAsync(pi =>
pi.ProductId.Equals(productItem.OldProductId) &&
pi.ItemId.Equals(productItem.OldItemId));
4. Use the CountAsync method on the ProductItem table to count the number of
times the combination of ProductId and ItemId exist in the table and store the
result in a variable called newPI.
5. Return true if the oldPi equals 1 and the newPI variable equals 0 which mean that
the original ProductItem exist but the new don’t.
return oldPI.Equals(1) && newPI.Equals(0);
9. Add a try/catch-block inside the using-block where the catch-block calls the
Dispose method on the transaction variable to end the transaction if something has
gone wrong.
10. Call the Remove method on the ProductItems collection in the try-block to
remove the original record from the ProductItem table.
Db.ProductsItems.Remove(oldProductItem);
11. Call the Add method on the ProductItems collection in the try-block to add the
new record to the ProductItem table.
Db.ProductsItems.Add(newProductItem);
12. Call the SaveChangesAsync method on the db instance in the try-block to save
the changes to the database.
Await db.SaveChangesAsync();
13. Because a transaction is handling the two database changes, it has to be completed
for the data to be persisted in the table. Add a call to the Complete method on the
transaction variable in the try-block to persist the changes.
transaction.Complete();
3. Call the CanChange method from within the ModelState.Valid if-block and store
the result in a variable called canChange.
var canChange = await productItem.CanChange(db);
7. Run the application and edit an existing ProductItem. Add one if none exist and
then edit it.
The complete code for the Edit action (HttpPost) looks like this:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Edit([Bind(Include = “ProductId,ItemId,OldProductId,OldItemId”)] ProductItem
productItem)
{
if (ModelState.IsValid)
{
// Check if the ProductItem can be changed
var canChange = await productItem.CanChange(db);
if (canChange)
{
// Change the ProductItem
await productItem.Change(db);
}
return RedirectToAction(“Index”);
}
return View(productItem);
}
The Details View
To be able to display the details for a ProductItem record with user friendly data such as
the item and product titles, the Details view and Details action in the ProductItem-
Controller have to be drastically changed.
The image below shows the finished Details view.
The Details controller action
First you have to add a new int? parameter called productId to the Details action method.
Then you have to add a null check for that parameter in the first (BadRequest) if-
statement; if either of the id or productId are null then the if-block should be executed.
Next, you need to change how the ProductItem is fetched from the database which can be
done by calling the GetProductItem method you created earlier.
The ProductItem instance can then be converted to a ProductListModel using an altered
version of the Convert method you created earlier. Store the instance in a variable called
model.
When the model is complete, return it with the View method.
1. Open the ProductItemController located in the Controllers folder in the Admin
area.
2. Change the name of the id parameter to itemId for all occurences in this action
method.
3. Add a new int? parameter called productId to the Details action method.
public async Task<ActionResult> Details(int? itemId, int? productId) { … }
5. Change how the ProductItem instance is fetched from the database by calling the
asynchronous GetProductItem method you created earlier.
ProductItem productItem = await GetProductItem(itemId, productId);
4. Add item and product titles for the ItemTitle and ProductTitle properties in the
model by fetching the matching item and product from the Item and Product
tables and use their Title values. You can use the await keyword with the
FirstOrDefaultAsync method to fetch the titles asynchronously.
ItemTitle = (awaitdb.Items.FirstOrDefaultAsync(i => i.Id.Equals(productItem.ItemId))).Title,
3. Replace the @Url.Action method call with the one described below. Note that the
Link property is outside the parenthesis.
href=”@Url.Action(“Edit”)@Model.Link”
4. Return the resulting string from the property. Note that the last character is
removed since it is an ampersand that shouldn’t be part of the paraemter list.
return s.ToString().Substring(0, s.Length - 1);
3. Add name-value pairs to the <dl> element using <dt> and <dd> elements. Add
key-value pairs for the ItemTitle and ProductTitle properties in the model.
<dl class=“dl-horizontal”>
<dt>@Html.DisplayNameFor(model => model.ItemTitle)</dt>
<dd>@Html.DisplayFor(model => model.ItemTitle)</dd>
<dt>@Html.DisplayNameFor(model => model.ProductTitle)</dt>
<dd>@Html.DisplayFor(model => model.ProductTitle)</dd>
</dl>
4. Replace the incomplete Edit link with the partial view _EditButtonDetailsPartial.
Send in an instance of the EditButtonModel class with the appropriate properties
assigned from values in the Model properties.
@Html.Partial(“_EditButtonDetailsPartial”, new EditButtonModel { ItemId = Model.ItemId, ProductId =
Model.ProductId }))
5. Replace the Back to List link with the with the partial view
_BackToListButtonPartial.
@Html.Partial(“_BackToListButtonPartial”)
6. Run the application and cllick the Details button to view the product item’s details.
7. Click the Edit button in the Details view to make sure that the Edit view is
displayed for the current product item.
The modified Details view:
@model Memberships.Areas.Admin.Models.ProductItemModel
@{
ViewBag.Title = “Details”;
}
<h2>Details</h2>
<div>
<h4>Product Item</h4>
<hr />
<dl class=“dl-horizontal”>
<dt>@Html.DisplayNameFor(model => model.ItemTitle)</dt>
<dd>@Html.DisplayFor(model => model.ItemTitle)</dd>
<dt>@Html.DisplayNameFor(model => model.ProductTitle)</dt>
<dd>@Html.DisplayFor(model => model.ProductTitle)</dd>
</dl>
</div>
<p>
@Html.Partial(“_EditButtonDetailsPartial”, new EditButtonModel {
ItemId = Model.ItemId, ProductId = Model.ProductId }))
@Html.Partial(“_BackToListButtonPartial”)
</p>
The Delete view
The Delete view is very similar to the Details view in that it displays the same informa-
tion about the current product and item.
The first thing you need to do is to change the @model directive to use the ProductItem-
Model class.
Copy the <h4>, <hr/> , <dl>, <dt> and <dd> elements in the Details view and replace
the <h4>, <hr/> and <dl> elements in the Delete view with the copied code.
Change the submit button by adding the two btn-danger (red) and btn-sm (small button)
Bootstrap classes.
Replace the Back to List @Html.ActionLink with a call to the @Html.Partial extension
method passing in the _BackToListButtonPartial view.
Open the ProductItem controller and copy the Details action and replace the Delete
action with the copied code and change the method name to Delete.
Alter the DeleteConfirmed action method to take another int parameter called productId.
Replace the current way of fetching the ProcuctItem instance with a call to the asyn-
chronous GetProductItem method you created earlier.
This is what the Delete view look like after it has been modified:
Modifying the view
1. Open the Details view in the ProcuctItem folder and copy the @model directive
at the top of the view.
@model Memberships.Areas.Admin.Models.ProductItemModel
2. Open the Delete view in the ProcuctItem folder and replace the existing @model
directive with the copied code.
3. Open the Details view and copy the <h4>, <hr/> and <dl> elements and their
content.
<h4>Product Item</h4>
<hr />
<dl class=“dl-horizontal”>
<dt>@Html.DisplayNameFor(model => model.ItemTitle)</dt>
<dd>@Html.DisplayFor(model => model.ItemTitle)</dd>
<dt>@Html.DisplayNameFor(model => model.ProductTitle)</dt>
<dd>@Html.DisplayFor(model => model.ProductTitle)</dd>
</dd>
6. Open the Details view and copy the @Html.Partial extension method for the
_BackToListButtonPartial view.
@Html.Partial(“_BackToListButtonPartial”)
5. Replace the call to the FindAsync method with a call to the GetProductItem
method passing in the two parameters to it.
ProductItem productItem = await GetProductItem(itemId, productId);
The complete code for the Convert method:
public static async Task<List<SubscriptionProductModel>> Convert(
this IQueryable<SubscriptionProduct> subscriptionProduct, ApplicationDbContext db)
{
if (subscriptionProducts.Count().Equals(0))
return new List<SubscriptionProductModel>();
var model = await (
from pi in subscriptionProduct
select new SubscriptionProductModel
{
SubscriptionId = pi.SubscriptionId,
ProductId = pi.ProductId,
SubscriptionTitle = db.Subscriptions.FirstOrDefault(
i => i.Id.Equals(pi.SubscriptionId)).Title,
ProductTitle = db.Products.FirstOrDefault(
p => p.Id.Equals(pi.ProductId)).Title
}).ToListAsync();
return model;
}
Copy and alter the Convert method which return a single item
This asynchronous method returns a single record from the SubscriptionProduct table
matching the values passed in through its productId and subscriptionId parameters.
1. Open the ConversionExtensions class and copy the Convert method that return
Task< ProductItemModel> and paste it in at the end of the class. Change the
return type to Task<SubscriptionProductModel> and its first parameter to
SubscriptionProduct subscriptionProduct.
public static async Task<SubscriptionProductModel> Convert(
this SubscriptionProduct subscriptionProduct,
ApplicationDbContext db, bool addListData = true)
The complete code for the Convert method:
public static async Task<SubscriptionProductModel> Convert(
this SubscriptionProduct subscriptionProduct,
ApplicationDbContext db, bool addListData = true)
{
var model = new SubscriptionProductModel
{
SubscriptionId = subscriptionProduct.SubscriptionId,
ProductId = subscriptionProduct.ProductId,
Subscriptions = addListData ?
await db.Subscriptions.ToListAsync() : null,
Products = addListData ? await db.Products.ToListAsync() : null,
SubscriptionTitle = (await db.Subscriptions.FirstOrDefaultAsync(
i => i.Id.Equals(subscriptionProduct.SubscriptionId))).Title,
ProductTitle = (await db.Products.FirstOrDefaultAsync(p =>
p.Id.Equals(subscriptionProduct.ProductId))).Title
};
return model;
}
Copy and alter the CanChange method
This asynchronous method returns true if there is a record with the OldProductId and
OldSubscriptionId values in the SubscriptionProduct table and no record exists
matching the values in the ProductId and SubscriptionId properties.
1. Open the ConversionExtensions class and copy the CanChange method and paste
it in at the end of the class. Change its first parameter to SubscriptionProduct
subscriptionProduct.
public static async Task<bool> CanChange(this SubscriptionProduct subscriptionProduct,
ApplicationDbContext db)
2. Add a LINQ query which counts the records matching the OldProductId and
OldSubscriptionId values in a variable called oldPI.
var oldPI = await db.SubscriptionProducts.CountAsync(pi =>
pi.ProductId.Equals(subscriptionProduct.OldProductId) &&
pi.SubscriptionId.Equals(subscriptionProduct.OldSubscriptionId));
3. Add a LINQ query which counts the records matching the ProductId and
SubscriptionId values in a variable called newPI.
4. If the oldPI equals 1, a record with the original values exists; and if the newPI
equals 0, no record for the new values from the dropdowns exists. Return true if
oldPI equals 1 and newPI equals 0.
return oldPI.Equals(1) && newPI.Equals(0);
The complete code for the CanChange method:
public static async Task<bool> CanChange(
this SubscriptionProduct subscriptionProduct, ApplicationDbContext db)
{
var oldPI = await db.SubscriptionProducts.CountAsync(pi =>
pi.ProductId.Equals(subscriptionProduct.OldProductId) &&
pi.SubscriptionId.Equals(
subscriptionProduct.OldSubscriptionId));
var newPI = await db.SubscriptionProducts.CountAsync(pi =>
pi.ProductId.Equals(subscriptionProduct.ProductId) &&
pi.SubscriptionId.Equals(
subscriptionProduct.SubscriptionId));
return oldPI.Equals(1) && newPI.Equals(0);
}
Copy and alter the Change method
This asynchronous method returns a Task (void). The method uses a transaction to ensure
that both the old record is removed and the new record is added, or all the changes are
rolled back.
1. Open the ConversionExtensions class and copy the Change method and paste it
in at the end of the class. Change its first parameter to SubscriptionProduct
subscriptionProduct.
public static async Task Change(this SubscriptionProduct subscriptionProduct, ApplicationDbContext db)
2. Add a LINQ query that fetches the record matching the OldProductId and
OldSubscriptionId values and store it in a variable called
oldSubscriptionProduct.
var oldSubscriptionProduct = await db.SubscriptionProducts.FirstOrDefaultAsync(pi =>
pi.ProductId.Equals(subscriptionProduct.OldProductId) &&
pi.SubscriptionId.Equals(subscriptionProduct.OldSubscriptionId));
3. Add a LINQ query that fetches the record matching the ProductId and
SubscriptionId values and store it in a variable called newSubscriptionProduct.
4. Add an if-block checking that the oldSubscriptionProduct is not null (the record
exist) and the newSubscriptionProduct is null (don’t exist).
if (oldSubscriptionProduct != null && newSubscriptionProduct == null){ }
5. The rest of the code will be placed inside the if-block. Create a new instance of the
SubscriptionProduct class and store it in the newSubscriptionProduct variable.
Assign the SubscriptionId and ProductId from the subscriptionProduct
parameter to the instance’s SubscriptionIs and ProductId properties.
newSubscriptionProduct = new SubscriptionProduct
{
SubscriptionId = subscriptionProduct.SubscriptionId,
ProductId = subscriptionProduct.ProductId
};
8. Add a try/catch-block inside the using-block where the catch-block calls the
Dispose method on the transaction variable to end the transaction if anything goes
wrong.
9. Remove the existing SubscriptionProduct record from the SubscriptionProduct
table and add the new SubscriptionProduct record to the table.
// Remove the exisiting ProductItem
db.SubscriptionProducts.Remove(oldSubscriptionProduct);
// Add the new (changed) ProductItem
db.SubscriptionProducts.Add(newSubscriptionProduct);
10. Save the changes by calling the SaveToChangesAsync method with the await
keyword.
11. Persist the changes by completing the transaction calling the Complete method on
the transaction variable. If you skip this step, no data will be saved to the database
since the transaction ends without committing the changes.
The complete code for the Change method:
public static async Task Change(this SubscriptionProduct subscriptionProduct, ApplicationDbContext db)
{
var oldSubscriptionProduct = await
db.SubscriptionProducts.FirstOrDefaultAsync(pi =>
pi.ProductId.Equals(subscriptionProduct.OldProductId) &&
pi.SubscriptionId.Equals(subscriptionProduct.OldSubscriptionId));
var newSubscriptionProduct = await
db.SubscriptionProducts.FirstOrDefaultAsync(pi =>
pi.ProductId.Equals(subscriptionProduct.ProductId) &&
pi.SubscriptionId.Equals(subscriptionProduct.SubscriptionId));
if (oldSubscriptionProduct != null && newSubscriptionProduct == null)
{
newSubscriptionProduct = new SubscriptionProduct
{
SubscriptionId = subscriptionProduct.SubscriptionId,
ProductId = subscriptionProduct.ProductId
};
// TransactionScope requires a reference to System.Transactions
using (var transaction = new TransactionScope(
TransactionScopeAsyncFlowOption.Enabled))
{
try
{
// Remove the exisiting SubscriptionProduct
db.SubscriptionProducts.Remove(oldSubscriptionProduct);
// Add the new (changed) SubscriptionProduct
db.SubscriptionProducts.Add(newSubscriptionProduct);
await db.SaveChangesAsync();
transaction.Complete();
}
catch
{
transaction.Dispose();
}
}
}
}
The Index view
Because you copied the Index view from the ProductItem folder, a few changes have to
be made to it. You have to change the @model directive to use the SubscriptionProduct-
Model class and replace all ItemId properties with SubscriptionId and replace all Item-
Title properties with SubscriptionTitle
One of the new asynchronous Convert methods will have to be called from the Index
action method in the SubscriptionProductController to fetch the necessary data from the
database.
This is what the finished Index view will look like:
Modify the Index view
1. Open the Index view located in the SubscriptionProduct folder.
2. Change the @model directive to use the SubscriptionProductModel class.
@model Memberships.Areas.Admin.Models.SubscriptionProductModel
The complete code for Create view:
@model Memberships.Areas.Admin.Models.SubscriptionProductModel
@using Memberships.Extensions
@{
ViewBag.Title = “Create”;
}
<h2>Create</h2>
@using (Html.BeginForm())
{
@Html.AntiForgeryToken()
<div class=“form-horizontal”>
<h4>Subscription Product</h4>
<hr />
@Html.ValidationSummary(true, ””, new { @class = “text-danger” })
<div class=“form-group”>
@Html.LabelFor(model => model.Products, htmlAttributes:
new { @class = “control-label col-md-2” })
<div class=“col-md-10”>
@Html.DropDownListFor(
model => model.ProductId,
Model.Products.ToSelectListItem(Model.ProductId),
new { @class = “form-control” })
@Html.ValidationMessageFor(model => model.ProductId, ””,
new { @class = “text-danger” })
</div>
</div>
<div class=“form-group”>
@Html.LabelFor(model => model.Subscriptions, htmlAttributes:
new { @class = “control-label col-md-2” })
<div class=“col-md-10”>
@Html.DropDownListFor(
model => model.SubscriptionId,
Model.Subscriptions.ToSelectListItem(
Model.SubscriptionId),
new { @class = “form-control” })
@Html.ValidationMessageFor(model => model.SubscriptionId,
””, new { @class = “text-danger” })
</div>
</div>
<div class=“form-group”>
<div class=“col-md-offset-2 col-md-10”>
<input type=“submit” value=“Create”
class=“btn btn-success” />
</div>
</div>
</div>
}
<div>
@Html.Partial(“_BackToListButtonPartial”)
</div>
2. Change the hidden value called OldItemId to OldSubscriptionId and replace the
property with Model.SubscriptionId.
@Html.Hidden(”OldSubscriptionId”, Model.SubscriptionId)
The complete code for Edit view:
@model Memberships.Areas.Admin.Models.SubscriptionProductModel
@using Memberships.Extensions
@{
ViewBag.Title = “Edit”;
}
<h2>Edit</h2>
@using (Html.BeginForm())
{
@Html.AntiForgeryToken()
<div class=“form-horizontal”>
<h4>Subscription Product</h4>
<hr />
@Html.ValidationSummary(true, ””, new { @class = “text-danger” })
@Html.Hidden(“OldProductId”, Model.ProductId)
@Html.Hidden(“OldSubscriptionId”, Model.SubscriptionId)
<div class=“form-group”>
@Html.LabelFor(model => model.Products, htmlAttributes:
new { @class = “control-label col-md-2” })
<div class=“col-md-10”>
@Html.DropDownListFor(
model => model.ProductId,
Model.Products.ToSelectListItem(Model.ProductId),
new { @class = “form-control” })
@Html.ValidationMessageFor(model => model.ProductId, ””,
new { @class = “text-danger” })
</div>
</div>
<div class=“form-group”>
@Html.LabelFor(model => model.Subscriptions, htmlAttributes:
new { @class = “control-label col-md-2” })
<div class=“col-md-10”>
@Html.DropDownListFor(
model => model.SubscriptionId,
Model.Subscriptions.ToSelectListItem(
Model.SubscriptionId),
new { @class = “form-control” })
@Html.ValidationMessageFor(model => model.SubscriptionId,
””, new { @class = “text-danger” })
</div>
</div>
<div class=“form-group”>
<div class=“col-md-offset-2 col-md-10”>
<input type=“submit” value=“Save”
class=“btn btn-success” />
</div>
</div>
</div>
}
<div>
@Html.Partial(“_BackToListButtonPartial”)
</div>
4. Change the first if-statement to check that none of the parameters are null.
if (subscriptionId == null || productId == null)
5. Replace the call to the FindAsync method with a call to a method called
GetSubscriptionProduct and pass in the Subscriptionid and productId as
parameters to it. Use the await keyword to call the method asynchronously. Don’t
be alarmed that the call reports an error it will be fixed when the method is created
in the next section.
SubscriptionProduct subscriptionProduct = await
GetSubscriptionProduct(subscriptionId, productId);
6. Create an new variable called model by calling the Convert method on the
subscriptionProduct instance you just fetched.
var model = await subscriptionProduct.Convert(db);
7. Adding transactions to the
DeleteConfirmed actions
Introduction
In this chapter you will modify the DeleteConfirmed actions for the Product-, Item- and
Subscription controllers. It is essential to not only delete the item from its table when it is
removed, you also need to remove the items in the connecting ProductItem and
SubscriptionProduct tables to avoid leaving records without id’s for exising records from
both tables invloved.
If, for instance, you remove a product, you also have to remove any records with that
product id in the the SubscriptionProdut and ProductItem connecting tables; otherwise
you will end up with subscriptions and items without a product in the Index listings.
You will also implement a safety check when removing minor entities to make sure that
only records that are unused can be deleted from the table.
Technologies used in this chapter
1. C# - Creating controller actions and view models to handle users and subscriptions.
2. Entity framework - To remove records from the involved tables.
Adding a transaction to the Item’s DeleteConfirmed action
Let’s start by adding a transaction to the ItemController‘s DeleteConfirmed action.
Apart from removing the item itself from the Item table we also have to remove any entry
in the ProductItem table with that item id; and to be certain that both the item and the
entries in the ProductItem table are removed as an atomic action where all or notihng is
removed then a transaction has to surround the database calls.
1. Open the ItemController class.
2. Locate the DeleteConfirmed acation.
3. Add a using-block with a TransactionScope instance around the Remove and
SaveChanges method calls.
using (var transaction = new TransactionScope(
TransactionScopeAsyncFlowOption.Enabled))
{
db.Items.Remove(item);
await db.SaveChangesAsync();
}
4. Fetch all records in the ProductItem table matching the item id passed into the
DeleteConfirmed action above the Remove method call. Then remove those
records from the ProductItem table.
var prodItems = db.ProductItems.Where(
pi => pi.ItemId.Equals(id));
db.ProductItems.RemoveRange(prodItems);
5. Add a call to the Complete method on the transaction object variable (from the
transaction scope) below the SaveChanges method call.
transaction.Complete();
4. Fetch all records in the ProductItem table matching the product id passed into the
DeleteConfirmed action above the Remove method call. Then remove those
records from the ProductItem table.
var prodItems = db.ProductItems.Where(
pi => pi. ProductId.Equals(id));
db.ProductItems.RemoveRange(prodItems);
5. Fetch all records in the SubscriptionProduct table matching the product id passed
into the DeleteConfirmed action above the Remove method call. Then remove
those records from the ProductItem table.
var prodSubscr = db.SubscriptionProducts.Where(
sp => sp.ProductId.Equals(id));
db.SubscriptionProducts.RemoveRange(prodSubscr);
6. Add a call to the Complete method on the transaction object variable (from the
transaction scope) below the SaveChanges method call.
transaction.Complete();
4. Fetch all records in the SubscriptionProduct table matching the product id passed
into the DeleteConfirmed action above the Remove method call. Then remove
those records from the ProductItem table.
var prodSubscr = db.SubscriptionProducts.Where(
sp => sp.SubscriptionId.Equals(id));
db.SubscriptionProducts.RemoveRange(prodSubscr);
5. Add a call to the Complete method on the transaction object variable (from the
transaction scope) below the SaveChanges method call.
transaction.Complete();
4. Add an if-block checking the IsUnused variablre’s value around the Remove and
SaveChanges merthods.
if (isUnused)
{
db.Sections.Remove(section);
await db.SaveChangesAsync();
}
5. Now, do the same in the DeleteConfirmed actions in the Part and ItemType
controllers.
6. Now, do the same in the DeleteConfirmed actions in the ProductType and
ProductLinkText controllers. Replace the Item table with the Product table in the
LINQ query.
var isUnused = await db.Products.CountAsync(i => i.ProductTypeId.Equals(id)) == 0;
The complete code for the PartController‘s DeleteConfirmed action:
[HttpPost, ActionName(“Delete”)]
[ValidateAntiForgeryToken]
public async Task<ActionResult> DeleteConfirmed(int id)
{
Part part = await db.Parts.FindAsync(id);
var isUnused = await db.Items.CountAsync(i =>
i.PartId.Equals(id)) == 0;
if (isUnused)
{
db.Parts.Remove(part);
await db.SaveChangesAsync();
}
return RedirectToAction(“Index”);
}
The complete code for the ItemTypeController‘s DeleteConfirmed action:
[HttpPost, ActionName(“Delete”)]
[ValidateAntiForgeryToken]
public async Task<ActionResult> DeleteConfirmed(int id)
{
ItemType itemType = await db.ItemTypes.FindAsync(id);
var isUnused = await db.Items.CountAsync(i =>
i.ItemTypeId.Equals(id)) == 0;
if (isUnused)
{
db.ItemTypes.Remove(itemType);
await db.SaveChangesAsync();
}
return RedirectToAction(“Index”);
}
The complete code for the ProductLinkTextController‘s DeleteConfirmed action:
[HttpPost, ActionName(“Delete”)]
[ValidateAntiForgeryToken]
public async Task<ActionResult> DeleteConfirmed(int id)
{
ProductLinkText productLinkText = await
db.ProductLinkTexts.FindAsync(id);
var isUnused = await db.Products.CountAsync(i =>
i.ProductLinkTextId.Equals(id)) == 0;
if (isUnused)
{
db.ProductLinkTexts.Remove(productLinkText);
await db.SaveChangesAsync();
}
return RedirectToAction(“Index”);
}
The complete code for the ProductTypeController‘s DeleteConfirmed action:
[HttpPost, ActionName(“Delete”)]
[ValidateAntiForgeryToken]
public async Task<ActionResult> DeleteConfirmed(int id)
{
ProductType productType = await db.ProductTypes.FindAsync(id);
var isUnused = await db.Products.CountAsync(i =>
i.ProductTypeId.Equals(id)) == 0;
if (isUnused)
{
db.ProductTypes.Remove(productType);
await db.SaveChangesAsync();
}
return RedirectToAction(“Index”);
}
8. Users & Subscriptions
Introduction
In this chapter you will create a controller and views which will handle users and their
subscriptions from the Admin menu. You will also fix the broken Registration action and
RegistrationViewModel; as you might recall, new columns were added to the AspNet-
User table earlier, these columns were not reflected in the Registration action.
To ensure that only users in a specific Admin role have access to the Admin menu, you
will add the Admin role to the AspNetRoles table and connect a user to that role through
the AspNetUserRoles table. To ensure that no user malliciously can be granted admin
privileges through the UI, admin privileges will be added manually in the AspNetUser-
Roles table.
Technologies used in this chapter
3. C# - Creating controller actions and view models to handle users and subscriptions.
4. Razor - To incorporate C# in the views where necessary.
5. HTML 5 - To build the views.
6. Bootstrap - To style the HTML 5 components.
7. Database - To add and chane role and user data.
User Registration
In an earlier chapter you added columns to the AspNetUser table to store additional
information about the user. This change broke the registration action because the new
information hasn’t been added to the ApplicationUser entity model defining a user in the
AspNetUser table. In this section, you will add the necessary data to the ApplicationUser
entity model and update the RegisterViewModel and Register view with a field for the
FirstName property.
Alter the RegisterViewModel
Because the customer wants to be able to store the user’s first name in the database, a new
string propery called FirstName has to be added to the RegisterViewModel class.
1. Open the AccountViewModels.cs file in the Models folder in the main project.
2. Locate the RegisterViewModel class.
3. Add a string property called FirstName.
4. Apply the Required attribute to force the user to enter a value in the textbox.
5. Apply the Display attribute to change the descriptive lable text “First Name”.
The new FirstName property:
[Required]
[Display(Name = “First Name”)]
public string FirstName { get; set; }
Alter the Register action
Four values have to be added to the ApplicationUser entity model object in the Register
action in the AccountController when registering a new user.
The value for the FirstName column is fetched from the FirstName property in the
RegisterViewModel object sent into the Register action from the Register view when the
user clicks the Register submit button.
The value for the IsActive column is assigned true since the user will be active once
registered. The value for the Registered column is assigned the current date since that is
the date the user registered. The value for the EmailConfirmed column is assigned true
because otherwise the system will wait indefinately for email confirmation that never will
arrive because no email was sent in the first place.
1. Open the AccountController class located in the Controllers folder in the main
project.
2. Locate the Register action method.
3. Assign the FirstName property from the model parameter to the FirstName
property in the ApplicationUser instance.
4. Assign true to the IsActive and EmailConfirmed properties in the
ApplicationUser instance.
5. Assign the current date to the Registered property in the ApplicationUser
instance.
The changes to the ApplicationUser instance:
var user = new ApplicationUser {
UserName = model.Email,
Email = model.Email,
FirstName = model.FirstName,
IsActive = true,
Registered = DateTime.Now,
EmailConfirmed = true
};
Alter the Register view
Since the RegisterViewModel model has a new required FirstName property, the
Register view has to be altered to display a textbox for that value. The easiest way to alter
the view is to copy the email address form-group <div> and change the Email property
to FirstName.
Add yourself as a user with your email address using the Register view when it has been
altered to display a first name textbox.
The image below show the Register view after it has been altered.
1. Open the Register view located in the Views-Account folder.
2. Copy the form-group <div> for the email address and paste it in above the email
form-group.
3. Change the Email property to FirstName in the pasted in code.
4. Run the application and click the Register link in the upper right corner of the
navigation bar.
5. Add a user named Admin with email [email protected].
6. Note that the user has access to the Admin menu even though the Admin role has
not been assigned to the user. The reason for this is that the Admin menu hasn’t
been restricted for regular users yet.
The changes to the ApplicationUser instance:
<div class=“form-group”>
@Html.LabelFor(m => m.FirstName,
new { @class = “col-md-2 control-label” })
<div class=“col-md-10”>
@Html.TextBoxFor(m => m.FirstName,
new { @class = “form-control” })
</div>
</div>
The Admin role
The Admin role is meant to limit access to the Admin menu and the controllers associated
with that menu. Only users added to the Admin role through the AspNetUserRoles table
will be able to see and access the Admin menu.
The first thing you need to do is to add the Admin role to the AspNetRoles table and copy
the UserId value for the user you added in the previous section, then add a record in the
AspNetUserRoles table with the user Id and Admin role Id; this connects the user to the
role. This connection table makes it possible to give users belonging to a specifc role
access to certain features of the application though conditional logic.
1. Open the AspNetRoles table in the Sever Explorer.
2. Add a new role called Admin with Id 1.
3. Open the AspNetUsers table in the Sever Explorer.
4. Copy the value in the Id column for the Admin user you added.
5. Open the AspNetUserRoles table in the Sever Explorer.
6. Add a new record with the user Id you copied and the Admin role Id.
Restict access to the Admin menu and its controllers
Now that the Admin role has been added, it’s time to restrict access to the Admin menu.
You will restrict access in two ways; the first is by using the Request.IsAuthenticated
property to check if a user is logged in and authenticated. The other second check is to see
if the authenticated user is associated with the Admin role. This can be achieved by
calling the User.IsInRole(“Admin”) method.
To restrict access to the Admin menu you open the _AdminMenuPartial view and
surround the code with an if-block checking the IsAuthenticated property and call the
IsInRole method.
It’s not enough to restrict access to the Admin menu; you also have to restrict access to
the controllers which are called when a menu item is clicked. If you don’t do this, any user
can access the Admin actions by entering a correct URL in the browser. You can restrict
access to all actions in a controller by adding the Authorize attribute with the Admin role
specified to the class.
1. Open the _AdminMenuPartial view located in the Views-Shared folder in the
main project.
2. Add an if-block aound the code in the view and have it check the IsAuthenticated
property and call the IsInRole method. If both return true, then the view should be
rendered and the menu displayed in the navigation bar.
@if (Request.IsAuthenticated && User.IsInRole(“Admin”))
{
//… View code …
}
5. Now do the same for all controllers in the Controllers folder in the Admin area.
6. Run the application and logout.
7. Register a new user called Joe with email [email protected] which is not added
to the Admin role.
8. Note that the new user doesn’t have access to the Admin menu because he’s not an
admin.
9. Logout Joe and login as the Admin user and check that the Admin menu is
accessible.
10. Logout the Admin user and check that the menu isn’t displayed.
Modify user information
In this section you will add actions to the AccountController and views to the Views-
Account folder which will help maintain your user information; the Index view will be
reachable from the Users & Subscriptions Admin menu option. The new views will
make it possible for admins to add, change and delete users in the AspNetUsers table.
The views will need a model called UserViewModel containing UserId, Email,
FirstName and Password string properties.
To make it easier to fetch the user’s first name and to fill a list with users, a new static
class called IdentityExtensions with two extension methods GetUserFirstName and
GetUsers has to be added to a new folder called Extensions. The GetUserFirstName
should act on a parameter of the IIdentity interface and the GetUsers sould act on a
parameter of List<UserViewModel>.
Four views called Index, Create, Edit and Delete will have to be added to the Views-
Account folder to list and alter user data.
Create the UserViewModel class
You can’t use any of the existing models in the AcountViewModels file because none of
them contain all the data needed in the model for the view you are about to create. Instead,
you will create a new class called UserViewModel in the Models folder in the main
project.
The properties needed are Id, Email, FirstName and Password; where the Email
property is decorated with the Required and EmailAddress attributes, the FirstName
property will allow a maximum of 30 characters and the Password property should be
decorated with the Required, have a maximum string length of 100 characters and be
declared as a password field.
1. Add a class called UserViewModel to the Models folder in the main project.
2. Add a string property called Id decorated with the Display attribute with the text
“User Id”.
[Display(Name = “User Id”)]
public string Id { get; set; }
3. Add a string property called Email decorated with the Display attribute with the
text “Email”, the Required attribute and the EmailAddress attribute.
4. Add a string property called FirstName decorated with the Display attribute with
the text “First Name” and the StringLenght attribute specifying that a maximum
of 30 characters are allowed.
[Display(Name = “First Name”)]
[StringLength(30, ErrorMessage = “The {0} must be at least {2} characters long.”, MinimumLength = 2)]
public string FirstName { get; set; }
5. Add a string property called Password decorated with the Display attribute with
the text “Password”, the Required attribute, the DataType attribute set to
Password and the StringLenght attribute specifying that a maximum of 100
characters are allowed.
[Required]
[StringLength(100, ErrorMessage = “The {0} must be at least {2} characters long and have 1 non letter, 1
digit, 1 uppercase (‘A’-‘Z’).”, MinimumLength = 6)]
[DataType(DataType.Password)]
[Display(Name = “Password”)]
public string Password { get; set; }
3. Use the FirstOrDefault method on the db.Users collection to fetch the user where
the Name property in the identity instance match the UserName field in the Users
collection which represent the AspNetUsers table.
var user = db.Users.FirstOrDefault(u =>
u.UserName.Equals(identity.Name));
4. Return the value in the FirstName field if a match was found, otherwise return an
empty string.
5. Open the _LoginPartial view.
6. Add a @using statement to the Extensions folder under the @model directive.
7. Replace the User.Identity.GetUserName() method call with a call to the
User.Identity.GetUserFirstName() method you just created.
8. Run the application and logout and login again, the first name should be displayed
to the left of the Logout button where the email was displayed before.
The code for the GetUserFirstName extension method:
public static string GetUserFirstName(this IIdentity identity)
{
var db = ApplicationDbContext.Create();
var user = db.Users.FirstOrDefault(u =>
u.UserName.Equals(identity.Name));
return user != null ? user.FirstName : string.Empty;
}
4. Create variable called users as a List<UserViewModel> which will hold the users’
account data.
5. Call the GetUsers extension method asynchronously on the users variable to fill it.
await users.GetUsers();
6. Pass in the users variable with the call to the Views method to render the view.
7. Run the application and navigate to Users & Subscriptions with the Admin menu.
The users should be listed in a table.
The Index action code:
[Authorize(Roles = “Admin”)]
public async Task<ActionResult> Index()
{
var users = new List<UserViewModel>();
await users.GetUsers();
return View(users);
}
Styling the Index view
Let’s style the Index view to make it more appealing and add a button partial view with
buttons to the Edit, Delete and Subscriptions views.
Because the _CreateNewButtonPartial, _SmallButtonPartial and
_TableButtonsPartial partial views will be used from views in the Admin area and the
Account views in the main project, they have to be moved to the Views-Shared folder in
the main project to be available in the whole project.
The image below shows the altered Index view:
1. Locate the _CreateNewButtonPartial, _SmallButtonPartial and
_TableButtonsPartial partial views in the Views-Shared folder in the Admin area
and move them to the Views-Shared folder in the main project. You can do this by
selecting them and dragging them to the folder or use the Cut-Paste approach.
2. Open the Index view in the Account folder.
3. Add the table, table-condensed and table-striped Bootstrap classes to the <table>
element to remove some padding on the rows and to display every other row wth a
light gray color.
4. Add the success Bootstrap class to the <tr> element to give the header row a
(green) background color.
5. Replace the Create new ActionLink with a call to the _CreateNewButtonPartial
view you moved to the Views-Shared folder.
@Html.Partial(“_CreateNewButtonPartial”)
3. Append the UserId to the paramter list if it isn’t null and not an empty string.
if (UserId != null && !UserId.Equals(string.Empty))
param.Append(string.Format(“{0}={1}&”, “userId”, UserId));
The alterations of the Details button and the addition of the Subscription button:
@if (Model.UserId == null || Model.UserId.Equals(string.Empty))
{
@Html.Partial(“_SmallButtonPartial”,
new SmallButtonModel
{
Action = “Details”,
ButtonType = “btn-success”,
Glyph = “list”,
Text = “Details”,
Id = Model.Id,
ItemId = Model.ItemId,
ProductId = Model.ProductId,
SubscriptionId = Model.SubscriptionId,
UserId = Model.UserId
})
}
@if (Model.UserId != null && !Model.UserId.Equals(string.Empty))
{
@Html.Partial(“_SmallButtonPartial”,
new SmallButtonModel
{
Action = “Subscriptions”,
ButtonType = “btn-info”,
Glyph = “list”,
Text = “Subscriptions”,
Id = Model.Id,
ItemId = Model.ItemId,
ProductId = Model.ProductId,
SubscriptionId = Model.SubscriptionId,
UserId = Model.UserId
})
}
The image below displays the Create view after it has been altered.
The complete code for the Create view:
@model Memberships.Models.UserViewModel
@{
ViewBag.Title = “Create”;
}
<h2>Create</h2>
@using (Html.BeginForm())
{
@Html.AntiForgeryToken()
<div class=“form-horizontal”>
<h4>Create User</h4>
<hr />
@Html.ValidationSummary(true, ””, new { @class = “text-danger” })
<div class=“form-group”>
@Html.LabelFor(model => model.Email, htmlAttributes:
new { @class = “control-label col-md-2” })
<div class=“col-md-10”>
@Html.EditorFor(model => model.Email, new {
htmlAttributes = new { @class = “form-control” } })
@Html.ValidationMessageFor(model => model.Email, ””,
new { @class = “text-danger” })
</div>
</div>
<div class=“form-group”>
@Html.LabelFor(model => model.FirstName, htmlAttributes:
new { @class = “control-label col-md-2” })
<div class=“col-md-10”>
@Html.EditorFor(model => model.FirstName, new {
htmlAttributes = new { @class = “form-control” } })
@Html.ValidationMessageFor(model => model.FirstName,
””, new { @class = “text-danger” })
</div>
</div>
<div class=“form-group”>
@Html.LabelFor(model => model.Password, htmlAttributes:
new { @class = “control-label col-md-2” })
<div class=“col-md-10”>
@Html.EditorFor(model => model.Password, new {
htmlAttributes = new { @class = “form-control” } })
@Html.ValidationMessageFor(model => model.Password, ””,
new { @class = “text-danger” })
</div>
</div>
<div class=“form-group”>
<div class=“col-md-offset-2 col-md-10”>
<input type=“submit” value=“Create”
class=“btn btn-success btn-sm” />
</div>
</div>
</div>
}
<div>
@Html.Partial(“_BackToListButtonPartial”)
</div>
Adding the Create actions
For the Create view to work, you have to add two action methods called Create to the
AccountController class: one for displaying the view and one to handle submitted form
values and save them to the AspNetUsers table in the database.
The HttpGet Create action
This action is called when the controller serves up the Create view to the user and
displays it in the browser. This is a very simple action since it only serves up an empty
view and therefore doesn’t have model data to render.
The method returns an ActionResult and takes no parameters. Don’t forget to add the
Authorize attribute to restrict access only to users in the Admin role.
1. Open the AccountController class in the Controllers folder and navigate to the
end of the class.
2. Add a method called Create which returns an ActionResult and takes no
parameters.
3. The only call in the method is to the View method to render the Create view.
4. Add the Authorize attribute to the method.
[Authorize(Roles = “Admin”)]
If the ModelState is valid, then create an instance of the ApplicationUser class and fill it
with data from the model parameter. Also assign true to the IsActive and EmailCon-
firmed properties and the current date to the Registred property. The ApplicationUser
class is the entity used when working with user data in the AspNetUsers table.
Call the UserManager.CreateAsync and await the result. If successful, the user will be
added to the AspNetUser table in the database. Store the return value from the method
call in a variable called result.
var result = await UserManager.CreateAsync(user, model.Password);
If the Succeeded property in the result variable is true, then redirect to the Index view
that belongs to the AccountController, else call the AddErrors method to add the errors
to the response sent back to the view.
return RedirectToAction(“Index”, “Account”);
1. Open the AccountController class in the Controllers folder and navigate to the
end of the class.
2. Add an asynchronous action method called Create, which returns a
Task<ActionResult> and has a UserViewModel parameter. Decorate the action
with the three attributes HttpPost, ValidateAntiForgeryToken and Authorize.
[HttpPost]
[ValidateAntiForgeryToken]
[Authorize(Roles = “Admin”)]
public async Task<ActionResult> Create(UserViewModel model)
{
}
3. Add a try/catch-block to the action and leave the catch-block empty. Return the
view with the model originally sent into the action to keep the user on the same
form with the values preserved. The rest of the code will be aded inside the try-
block.
try { }
catch { }
return View(model);
5. Add an if-block that check if the model state is valid. The rest of the code will be
added inside this if-block.
if (ModelState.IsValid) { }
6. Create an instance of the ApplicationUser entity and fill it with data form the
passed in model (the values from the controls in the Create view).
var user = new ApplicationUser
{
UserName = model.Email,
Email = model.Email,
FirstName = model.FirstName,
IsActive = true,
Registered = DateTime.Now,
EmailConfirmed = true
};
7. Call the UserManager.CreateAsync method with the user instance you just
created. If the result is successful, then redirect to the Index view belonging to the
AccountController; else, store the errors in the response sent to the client by
calling the AddErrors method with the result object.
var result = await UserManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
return RedirectToAction(“Index”, “Account”);
}
AddErrors(result);
8. Run the application and navigate to the Index view beloning to the
AccountController. Click the Create new button and add a new user. If
successful, the new user should appear in the Index view which is loaded
automatically after the user has been added to the AspNetUsers table in the
database.
The complete HttpPost Create action:
[HttpPost]
[ValidateAntiForgeryToken]
[Authorize(Roles = “Admin”)]
public async Task<ActionResult> Create(UserViewModel model)
{
try
{
if (model == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
if (ModelState.IsValid)
{
var user = new ApplicationUser
{
UserName = model.Email,
Email = model.Email,
FirstName = model.FirstName,
IsActive = true,
Registered = DateTime.Now,
EmailConfirmed = true
};
// EmailConfirmed = true is a prerequisite for
// sending “Forgot Password” email
var result = await UserManager.CreateAsync(
user, model.Password);
if (result.Succeeded)
{
return RedirectToAction(“Index”, “Account”);
}
AddErrors(result);
}
}
catch { }
// If we got this far something failed, re-display the form
return View(model);
}
Adding the Edit view
To add the possibility for admins to chage user data, you have to add an Edit view
belonging to the AccountController using the UserViewModel class as its model.
You add the Edit view to the Views-Account folder and add its actions to the Account-
Controller in the Controllers folder.
1. Right-click on the Views-Account folder and add the Edit view with the values
specified in the image above.
2. Change the btn-default Bootstrap class to btn-success on the Save button to give
it (green) color.
<input type=“submit” value=“Save” class=“btn btn-success” />
The image below shows the Edit view after it has been altered.
The complete code for the Edit view:
@model Memberships.Models.UserViewModel
@{
ViewBag.Title = “Edit”;
}
<h2>Edit</h2>
@using (Html.BeginForm())
{
@Html.AntiForgeryToken()
<div class=“form-horizontal”>
<h4>Edit User</h4>
<hr />
@Html.ValidationSummary(true, ””, new { @class = “text-danger” })
@Html.HiddenFor(model => model.Id)
<div class=“form-group”>
@Html.LabelFor(model => model.Email, htmlAttributes:
new { @class = “control-label col-md-2” })
<div class=“col-md-10”>
@Html.EditorFor(model => model.Email, new {
htmlAttributes = new { @class = “form-control” } })
@Html.ValidationMessageFor(model => model.Email, ””,
new { @class = “text-danger” })
</div>
</div>
<div class=“form-group”>
@Html.LabelFor(model => model.FirstName, htmlAttributes:
new { @class = “control-label col-md-2” })
<div class=“col-md-10”>
@Html.EditorFor(model => model.FirstName, new {
htmlAttributes = new { @class = “form-control” } })
@Html.ValidationMessageFor(model => model.FirstName,
””, new { @class = “text-danger” })
</div>
</div>
<div class=“form-group”>
@Html.LabelFor(model => model.Password, htmlAttributes:
new { @class = “control-label col-md-2” })
<div class=“col-md-10”>
@Html.EditorFor(model => model.Password, new {
htmlAttributes = new { @class = “form-control” } })
@Html.ValidationMessageFor(model => model.Password, ””,
new { @class = “text-danger” })
</div>
</div>
<div class=“form-group”>
<div class=“col-md-offset-2 col-md-10”>
<input type=“submit” value=“Save”
class=“btn btn-success btn-sm” />
</div>
</div>
</div>
}
<div>
@Html.Partial(“_BackToListButtonPartial”)
</div>
Adding the Edit actions
For the Edit view to work, you have to add two action methods called Edit to the
AccountController class: one for displaying the view and one to handle submitted form
values and save them to the AspNetUsers table in the database.
The HttpGet Edit action
This asynchronous action is called when the controller serves up the Edit view to the user
and displays it in the browser. It is slightly more complex than the Create action in that it
has to look up the user in the database before serving up the data to the view. It therefore
needs a string parameter called userId.
The method returns a Task<ActionResult>. Don’t forget to add the Authorize attribute to
restrict access only to users in the Admin role.
Return a BadRequest to the client if the userId parameter is null or contain an empty
string.
Fetch the user with the passed in userId by calling the UserManager.FindByIsAsync
method ad store the awaited result in a variable called user.
Return Http Not Found if the user variable is null by calling the HttpNotFound method.
If the user exists, then create an instance of the UserViewModel class in a variable called
model and assign the values from the fetched user to its properties before passing the
object to the View method for rendering.
1. Open the AccountController class in the Controllers folder and navigate to the
end of the class.
2. Add an async action method called Edit which returns a Task<ActionResult> and
takes a string parameter called userId. Only Admin users should have access to
this action.
[Authorize(Roles = “Admin”)]
public async Task<ActionResult> Edit(string userId)
{
}
4. Fetch the user asynchronously with the userId parameter and store the result in a
variable called user.
ApplicationUser user = await UserManager.FindByIdAsync(userId);
5. Return Http Not Found if the user variable is null by calling the HttpNotFound
method.
if (user == null)
{
return HttpNotFound();
}
6. Create an instance of the UserViewModel, assign values from the fetched user and
return the model with the View method.
var model = new UserViewModel
{
Email = user.Email,
FirstName = user.FirstName,
Id = user.Id,
Password = user.PasswordHash
};
return View(model);
6. Assign the values from the Email and FirstName properties in the model to the
Email, UserName and FirstName properties in the fetched user object.
7. The value in the model’s Password property has to be hashed before it can be
stored in the PasswordHash property. But it should only be assigned if the value in
the PasswordHash property isn’t equal to the value in the model’s Password
property.
user.Email = model.Email;
user.UserName = model.Email;
user.FirstName = model.FirstName;
// Hash the password if a new password has been entered
// before saving the edited user to the database
if (!user.PasswordHash.Equals(model.Password))
user.PasswordHash = UserManager.PasswordHasher.HashPassword(
model.Password);
8. Change the CretateAsync method call to UpdateAsync to update the user instead
of creating a new user.
var result = await UserManager.UpdateAsync(user);
9. Return an empty view from the catch-block.
return View();
AddErrors(result);
10. Run the application and navigate to the Index view belonging to the
AccountController. Click the Edit button on one of the user rows and update
some info about the user. If successful, the updated user information should appear
in the Index view which is loaded automatically after the user has been updated in
the AspNetUsers table in the database.
The complete HttpPost Edit action:
[HttpPost]
[ValidateAntiForgeryToken]
[Authorize(Roles = “Admin”)]
public async Task<ActionResult> Edit(UserViewModel model)
{
try
{
if (model == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
if (ModelState.IsValid)
{
var user = await UserManager.FindByIdAsync(model.Id);
if (user != null)
{
user.Email = model.Email;
user.UserName = model.Email;
user.FirstName = model.FirstName;
// Hash the password if a new password has been entered
// before saving the edited user to the database
if (!user.PasswordHash.Equals(model.Password))
user.PasswordHash = UserManager.PasswordHasher
.HashPassword(model.Password);
var result = await UserManager.UpdateAsync(user);
if (result.Succeeded)
{
return RedirectToAction(“Index”, “Account”);
}
AddErrors(result);
}
}
}
catch { }
return View(model);
}
Adding the Delete view
To add the possibility for admins to delete a user, you have to add a Delete view belonging
to the AccountController using the UserViewModel class as its model.
You add the Delete view to the Views-Account folder and add its actions to the
AccountController in the Controllers folder.
1. Right-click on the Views-Account folder and add the Delete view with the values
specified in the image above.
2. Delete the <dt> and <dd> elements for the Password property.
3. Add all the model properties as hidden form fields inside the form below the
@html.AntiForgeryToken() method call using @Html.HiddenFor. You need to
do this for the model to be valid in the HttpPost Delete action.
@Html.HiddenFor(model => model.FirstName)
@Html.HiddenFor(model => model.Email)
@Html.HiddenFor(model => model.Id)
@Html.HiddenFor(model => model.Password)
4. Change the btn-default Bootstrap class to btn-danger on the Save button to give
it (green) color.
<input type=“submit” value=“Delete” class=“btn btn-success” />
The image below shows the Delete view after it has been altered.
The complete code for the Delete view:
@model Memberships.Models.UserViewModel
@{
ViewBag.Title = “Delete”;
}
<h2>Delete</h2>
<h3>Are you sure you want to delete this?</h3>
<div>
<h4>UserViewModel</h4>
<hr />
<dl class=“dl-horizontal”>
<dt>@Html.DisplayNameFor(model => model.Email)</dt>
<dd>@Html.DisplayFor(model => model.Email)</dd>
<dt>@Html.DisplayNameFor(model => model.FirstName)</dt>
<dd>@Html.DisplayFor(model => model.FirstName)</dd>
</dl>
@using (Html.BeginForm()) {
@Html.AntiForgeryToken()
@Html.HiddenFor(model => model.Password)
@Html.HiddenFor(model => model.Email)
@Html.HiddenFor(model => model.FirstName)
@Html.HiddenFor(model => model.Id)
<div class=“form-actions no-color”>
<input type=“submit” value=“Delete”
class=“btn btn-danger btn-sm” />
@Html.Partial(“_BackToListButtonPartial”)
</div>
}
</div>
Adding the Delete actions
For the Delete view to work, you have to add two action methods called Delete to the
AccountController class: one for displaying the view and one to handle submitted form
values and delete the corresponding user from the AspNetUsers table in the database.
The HttpGet Delete action
This asynchronous action is called when the controller serves up the Delete view to the
user and displays it in the browser. It is very similar to the Edit action in that it has to look
up the user in the database before serving up the data to the view; it therefore need a
string parameter called userId.
The method returns a Task<ActionResult>. Don’t forget to add the Authorize attribute to
restrict access only to users in the Admin role.
Return a BadRequest to the client if the userId parameter is null or contains an empty
string.
Fetch the user with the passed in userId by calling the UserManager.FindByIsAsync
method ad store the awaited result in a variable called user.
Return Http Not Found if the user variable is null by calling the HttpNotFound method.
If the user exists, then create an instance of the UserViewModel class in a variable called
model and assign the values from the fetched user to its properties, except the password
which you assign the value “Fake password” before passing the object to the View method
for rendering. You use the text “Fake password” because it is pointless to send the
password to the client in this scenario, but the model needs a value other than null or
empty string to validate the model when the user clicks the Delete button and the
HttpPost Delete action is called.
The easiest way to add this action is to copy the corresponding Edit action and alter it.
1. Open the AccountController class in the Controllers folder and navigate to the
HttpGet version of the Edit action and copy the method and its attributes.
2. Navigate to the end of the AccountController class and paste in the code.
3. Change the action name to Delete.
4. Assign “Fake password” to the Password property in the UserViewModel
instnace.
Password = “Fake password”
7. Add code to delete all the user’s subscriptions from the UserSubscriptions table.
var db = new ApplicationDbContext();
var subscriptions = db.UserSubscriptions.Where(u =>
u.UserId.Equals(user.Id));
db.UserSubscriptions.RemoveRange(subscriptions);
await db.SaveChangesAsync();
8. Run the application and navigate to the Index view belonging to the
AccountController. Click the Delete button on one of the user rows and then click
on the Delete button to remove that user from the database. If successful, the
deleted user should not appear in the Index view.
The complete HttpPost Delete action:
[HttpPost]
[ValidateAntiForgeryToken]
[Authorize(Roles = “Admin”)]
public async Task<ActionResult> Delete(UserViewModel model)
{
try
{
if (model == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
if (ModelState.IsValid)
{
var user = await UserManager.FindByIdAsync(model.Id);
var result = await UserManager.DeleteAsync(user);
if (result.Succeeded)
{
var db = new ApplicationDbContext();
var subscriptions = db.UserSubscriptions.Where(u =>
u.UserId.Equals(user.Id));
db.UserSubscriptions.RemoveRange(subscriptions);
await db.SaveChangesAsync();
return RedirectToAction(“Index”, “Account”);
}
AddErrors(result);
}
}
catch { }
return View(model);
}
Adding subscriptions to a user
In this section, you will make it possible for an adimn to add and remove subscriptions to
a user. The middle (light blue) button you modified earlier for the user rows will be
connected to a new view called Subscriptions which will display a list of available
subscriptions that can be assigned to the user for whom you opened the Subscriptions
view. The view will also display a table with the already assigned subscriptions and a
remove button per subscription row, making it possible to remove a specific subscription
from the current user.
The image below shows the finished Subscriptions view.
Adding the UserSubscriptionModel class
This class represents one subscription a user has access to and combines all columns from
the Subscription table with the StartDate and EndDate columns from the
UserSubscription table.
1. Add a class called UserSubscriptionModel to the Models folder in the main
project.
2. Open the Subscription entity model in the Entities folder and copy all properties
and their attributes except for the DatabaseGenerate attribute on the Id property.
3. Go back to the UserSubscriptionModel class and paste in the properties and their
attributes.
4. Add two DateTime? propeties called StartDate and EndDate.
public DateTime? StartDate { get; set; }
public DateTime? EndDate { get; set; }
8. Style the Save button with the btn-success Bootstrap class and disable it if the
DisableDropDown property is true.
<input type=“submit” value=“Save” class=“btn btn-success” disabled=”@Model.DisableDropDown” />
2. Add a foreach loop iterating over the items in the UserSubscriptions collection
below the table header row (<tr>)
@foreach (var item in Model.UserSubscriptions)
{
//Display individual subscriptions with delete button here
}
3. Add a table row for each item in the UserSubscriptions collection and display data
for the corresponding table column headers and a Delete button.
<tr>
<td>@Html.DisplayFor(modelItem => item.Id)</td>
<td>@Html.DisplayFor(modelItem => item.Title)</td>
<td>@Html.DisplayFor(modelItem => item.RegistrationCode)</td>
<td>@Html.DisplayFor(modelItem => item.StartDate)</td>
<td>@Html.DisplayFor(modelItem => item.EndDate)</td>
<td>
<a type=“button” class=“btn btn-danger btn-sm”
href=”@Url.Action(“RemoveUserSubscription”)
[email protected]&[email protected]“>
<span class=“glyphicon glyphicon-trash”
aria-hidden=“true”></span>
</a>
</td>
</tr>
To figure out if the DisableDropDown property should be true or false, you can count the
number of available subscriptions in the Subscriptions collection and return true if the
count is 0.
Don’t forget to add the Authorize attribute to the method, restricting it to only admin
users.
Return the model variable with the View method call.
1. Open the AccountController class in the Controllers folder in the main project.
2. Locate the end of the class and add the HttpGet Subscriptions action.
[Authorize(Roles = “Admin”)]
public async Task<ActionResult> Subscriptions(string userId)
{
}
3. Check if the userId parameter is null and return a BadRequest if it is.
4. Create the UserSubscriptionViewModel model variable.
var model = new UserSubscriptionViewModel();
7. Extract the subscription id’s from the UserSubscriptions collection and store them
in a variable called ids.
var ids = model.UserSubscriptions.Select(us => us.Id);
8. Fetch the subscriptions from the Subscriptions table whose id’s aren’t in the ids
collection.
model.Subscriptions = await db.Subscriptions.Where(s =>
!ids.Contains(s.Id)).ToListAsync();
3. Add a try/catch-block to the action where the catch-block is empty and a redirect to
the HttpGet Subscriptions action using a RedirectToAction method call.
try
{
}
catch { }
return RedirectToAction(“Subscriptions”, “Account”, new { userId = model.UserId });
4. The rest of the code should be added to the try-block. Return BadRequest if the
model parameter is null.
5. Check if the model state is valid. If it is then create an instance of the
UserSubscription entity class and populate it with data from the model parameter
before saving it to the UserSubscription table in the database.
var db = new ApplicationDbContext();
db.UserSubscriptions.Add(new UserSubscription
{
UserId = model.UserId,
SubscriptionId = model.SubscriptionId,
StartDate = DateTime.Now,
EndDate = DateTime.MaxValue
});
await db.SaveChangesAsync();
6. Run the application and add a subscription to one of the users. The list of
subscriptions for that user should be updated automatically.
The complete code for the HttpPost Subscriptions action:
[HttpPost]
[Authorize(Roles = “Admin”)]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Subscriptions(UserSubscriptionViewModel model)
{
try
{
if (model == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
if (ModelState.IsValid)
{
var db = new ApplicationDbContext();
db.UserSubscriptions.Add(new UserSubscription
{
UserId = model.UserId,
SubscriptionId = model.SubscriptionId,
StartDate = DateTime.Now,
EndDate = DateTime.MaxValue
});
await db.SaveChangesAsync();
}
}
catch { }
return RedirectToAction(“Subscriptions”, “Account”,
new { userId = model.UserId });
}
3. Add a try/catch-block to the action where the catch-block is empty and a redirect
to the HttpGet Subscriptions action using a RedirectToAction method call.
try
{
}
catch { }
return RedirectToAction(“Subscriptions”, “Account”, new { userId = userId });
4. The rest of the code should be added to the try-block. Return BadRequest if the
userId parameter is null or emty or the subscriptionId parameter is less than 1.
5. Check if the model state is valid. If it is then create an instance of the
ApplicationDbContext class to fetch the record matching the userId and
subscriptionId from the UserSubscription table in the database.
var db = new ApplicationDbContext();
var subscriptions = db.UserSubscriptions.Where(u =>
u.UserId.Equals(userId) &&
u.SubscriptionId.Equals(subscriptionId));
7. Run the application and remove a subscription from a user. The list of subscriptions
for that user should be updated automatically.
The complete code for the RemoveUserSubscription action:
[Authorize(Roles = “Admin”)]
public async Task<ActionResult> RemoveUserSubscription(string userId, int subscriptionId)
{
try
{
if (userId == null || userId.Length.Equals(0) ||
subscriptionId <= 0)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
if (ModelState.IsValid)
{
var db = new ApplicationDbContext();
var subscriptions = db.UserSubscriptions.Where(u =>
u.UserId.Equals(userId) &&
u.SubscriptionId.Equals(subscriptionId));
db.UserSubscriptions.RemoveRange(subscriptions);
await db.SaveChangesAsync();
}
}
catch { }
return RedirectToAction(“Subscriptions”, “Account”,
new { userId = userId });
}
9. Application Layout
Introduction
In this chapter you will create the layout for the application grid and the navigation bar
which will give it a cleaner look.
Technologies used in this chapter
1. MVC - To create the grid used by all upcoming exercises.
2. HTML 5 - To create the grid.
3. Bootstrap - To create rows and columns in the grid.
4. CSS - To style HTML elements.
Use case
Create the foundational layout for the whole application using Bootstrap and CSS and add
a logo in two sizes to the navigation bar – a large icon displayed on large, medium and
small devices and a small icon displayed on extra small (mobile) devices.
In the upcoming chapters you will use Bootstrap Glyphicons library to decorate com-
ponents.
Alter the navigation bar
To give the navigation bar a cleaner and airier look, you will remove the navbar-inverse
Bootstrap class from the navigation bar. Because the “hamburger” menu displayed on
extra small devices should be displayed in the inverse of the navigation bar, the navbar-
inverse class has to be added to the icon-bar <span> elements.
Adding a nice logo to the site lifts the overall look of the site and makes it appear more
professional. Keep in mind that images can take time to load on mobile devices. It’s
therefore advisable to have a smaller version of the logo for that scenario.
Add a CSS file named navbar.css to the Content folder and add it to the
BundleConfig.cs file in the App_Start folder and the _Layout view. This CSS file will
be used to style the navigation bar links.
Adding the navbar.css file
There are three steps to adding the CSS file. The first is to add it to the Content folder, the
second is to add it to the BundleConfig file in the App_Start folder and and the third is to
add a link to the bundle in the _Layout view.
1. Right-click on the Content folder and select Add-Style Sheet in the menu.
2. Name the file navbar.css in the dialog and click the OK button.
3. Open the BundleConfig file in the App_Start folder.
4. Add a new StyleBundle called membership for the navbar.css file at the end of the
RegisterBundles method. You can copy and alter one of the existing bundles.
bundles.Add(new StyleBundle(“~/Content/membership”).Include(
“~/Content/navbar.css”));
5. Open the _Layout view and link in the membership bundle you just created. Copy
one of the existing Styles.Render method calls and alter it.
@Styles.Render(“~/Content/membership”)
3. Paste in the navbar-inverse class to the three icon-bar <span> class attributes.
<span class=“icon-bar navbar-inverse”></span>
4. Open the navbar.css file and add a definition for the navbar class. Add border-
bottom and background-color properties where the border-bottom is 1px and
gray and the background-color is white.
.navbar {
border-bottom: 1px solid #C8C8C8;
background-color: #fff;
}
To change the background color of the link that the user hovers over, the :hover pseudo
class has to be added to the targeted <a> element.
1. Open the navbar.css file and target <a> elements located in an element decorated
with the nav class and a color property changing the color to gray.
.nav a {
color: #555;
}
2. Target <a> elements located in an element decorated with the nav class and add the
:hover pseudo class. Add a color property changing the color to a light green.
.nav a:hover {
color: #72C02C;
}
3. To change the link background color to white when hovering over it, you want to
be very specific in your targeting, because if you target all <a> elements, the menu
items in the Admin menu will be affected also. To affect only the link, or rather the
<li> element containing the link the user hovers over, you have to include only the
<li> element that are direct decendants of the <ul> element decorated with the nav
class.
.nav > li > a:hover {
background-color: white;
}
4. Run the application and hover over the links. Note that the color changes when
entering or leaving a link and that the link background is white.
Adding a Logo
Adding a logo can seem like a straightforward thing, but you must take into consideration
that the logo can be viewed on devices with different screen sizes. You might for instance
want a larger logo when users are viewing the site on a laptop compared to when viewing
it on a smart phone. To solve this dilemma, you can have two or more logos of different
sizes which are served up depending on the device size.
In this section, you will add two logos and use the hidden-xs and visible-xs Bootstrap
classes to determine which should be displayed.
You will replace the ActionLink method call described below with an anchor tag <a>
which contain the two logos. To place the logo in the correct position in the navigation bar
the <a> element has to be decorated with the Bootstrap class navbar-brand. The reason
you place the logos in an <a> element is to take advantage of its href attribute turning the
image into a link; you can use the href attribute to redirect to any view but let’s reditect to
the Index view of the HomeController.
@Html.ActionLink(“Application name”, “Index”, “Home”, new { area = ”” }, new { @class = “navbar-brand” })
As you can see in the image below, the logo messes up the links in the menu and hide part
of the body-content element. The reason for this is that the default navigation bar is not as
high as the one with the logo. To counteract this, you can add one CSS rule taregting the
<a> elements residing in the navbar-nav <ul> element adding a few pixels to its top
margin and another rule targeting the body-content element adding a few pixels to its top
margin.
Adding the logo image
1. Add a folder named Logos to the Content folder.
2. Add the logo images to the Logos folder. You can drag them to the folder from a
Windows Explorer window or right-click on the Logos folder and select Add
Existing item in the menu.
3. Open the _Layout view in the Admin area and make sure that the following
ActionLink is present; alter the existing AlctionLink if it differs. The area
property set to an empty string will target the main project where the /Home/Index
view is located omitting this property will cause an error when clicking the link
while in the Admin area.
@Html.ActionLink(“Application name”, “Index”, “Home”, new { area = ”” }, new { @class = “navbar-brand”
})
4. Open the _Layout view in the main project and delete the following ActionLink.
@Html.ActionLink(“Application name”, “Index”, “Home”, new { area = ”” }, new { @class = “navbar-brand”
})
5. Add an <a> element where the ActionLink was located. Add the navbar-brand
class to its class attribute and /Home/Index to its href attribute.
<a class=“navbar-brand” href=”/Home/index”>
@*Add logo images here*@
</a>
6. You can drag the logos from the Logos folder to the <a> element to add them.
7. Add the height attribute to the <image> elements and assign 45 to the large image
and 30 to the small image.
8. Add the hidden-xs Bootstrap class to the large image and visible-xs to the small
image.
<img src=”~/Content/Logo/membership-icon-large.png” height=“45” class=“hidden-xs” />
<img src=”~/Content/Logo/membership-icon-small.png” height=“30” class=“visible-xs” />
3. Add a CSS selector targeting the body-content class and add a 40px top margin
property to it to move down the content hidden by the navigation bar and add some
space between the navigation bar and the content.
.body-content {
margin-top: 40px;
}
1. Open the Index view in the Views-Home folder.
2. Delete all HTML markup below the Razor section containing the ViewBag.
3. Add a new <div> element decorated with the row class.
<div class=“row”>
</div>
4. Add an if/else-block inside the <div> element checking if the user is logged in.
@if (User.Identity.IsAuthenticated)
{
// Displayed if the user is logged in
}
else
{
// Displayed if the user isn’t logged in
}
5. Add a <div> decorated with the col-xs-12 Bootstrap class inside the if-block to
make it take up all the avaialble room on the row. Add an <h2> element with the
text I’m logged in.
<div class=“col-xs-12”>
<h2>I’m logged in</h2>
</div>
6. Add a <div> element inside the else-block and decorate it with the following
column classes: col-lg-9, col-md-8, col-sm-7. This will represent the left column of
the grid layout defining specific column widths for different device sizes. Add an
<h2> element with the text I’m not logged in.
<!—left column—>
<div class=”col-lg-9 col-md-8 col-sm-7“>
<h2>I’m not logged in</h2>
</div>
7. Add another <div> below the one you just added and decorate it with the following
column classes: col-lg-3, col-md-4, col-sm-5. This will represent the right column
of the grid.
<!—right column—>
<div class=”col-lg-3 col-md-4 col-sm-5“>
</div>
8. Run the application and logout and login to make sure that the text changes and the
if/else-block is working.
The complete code in the Index view:
@{
ViewBag.Title = “Home Page”;
}
<div class=“row”>
@if (User.Identity.IsAuthenticated)
{
// Displayed if the user is logged in
<div class=“col-xs-12”>
<h2>I’m logged in</h2>
</div>
}
else
{
// Displayed if the user isn’t logged in
<!—left column—>
<div class=“col-lg-9 col-md-8 col-sm-7”>
<h2>I’m not logged in</h2>
</div>
<!—right column—>
<div class=“col-lg-3 col-md-4 col-sm-5”>
</div>
}
</div>
10. Display products
Introduction
In this chapter you will add a subscription and display its products in the user interface to
subscribing customers. The products will be displayed as Bootstrap thumbnail compo-
nents in a partial view to keep the Index view as clean as possible.
Since there can be various types of products, you might want to display them in different
areas; for instance, you might want an area at the top of the page dedicated to promotional
content such as excerpts from books you are selling with links to a shopping cart. Then
there might be content that you want to display ordered in different areas such as courses
that belong togheter. Each row can hold four thumbnails where each thumbnail represent a
product. In order to achieve this, a model called ThumbnailAreaModel has to be added
to the project; the model will contain a Title property for the area and a collection
containing the thumbnails for that area where each thumbnail is defined by a class called
ThumbnailModel.
A ThumbnailEqualityComparer class must be implemented to make it possible to com-
pare two ThumbnailModel objects on their ProductId properties. If this is not done, the
default object comparer will be used comparing their object references.
An IEnumerable<ThumbnailAreaModel> will be used as the view model for the Index
view and each area object in the ThumbnailAreaModel’s collection will be passed to the
_ThumbnailAreaPartial partial view to be rendered as thumbnails.
The thumbnail images will be animated, making the image rotate and zoom slightly and
move the link text left when the user enters or exits the thumbnail. Below are examples of
thumbnails in the user interface.
The thumbnail on the left is the inactive version, and the one on the right shows the
thumbnail when a user is pointing to it with the mouse.
The top label on the thumbnail image shows what type of product the content belongs to.
If, for instance, you are sharing content for a book, you use the Book label even though
the book itself is not part of the content. The content could be related videos, downloads
and PDF documents.
Adding a subscription
Before creating the thumbnail partial view, you have to add a product and a subscription.
Let’s use the example products from the thumbnails above. To add the two products C#
For Beginners and Unit Testing, you will have to use the Admin user interface. Because
the UI only will display the products and not their content at this stage, you will only be
adding the product container and a subscription. In upcoming exercises you will add
content items to the product.
The scenario is that you have written a book called C# For Beginners and want to share
bonus content with the reader through this site. In order to do so, you add two products;
one called C# For Beginners which can contain the zipped source code, videos and other
content and another product called Unit Testing which is a couple of bonus articles they
will receive when purchasing the book. The articles will be displayed on-line and stored as
HTML in the database.
Adding a product link text
To be able to display the lower lable in the thumbnail image for products, you have to add
product link texts to the ProductLinkText table in the database, which then can be
selected in the Create New and Edit views for a product.
This label is in reality a link that can be clicked by the user to go to the product content.
1. Run the application and login as an admin.
2. Select Product Link Text in the Admin menu.
3. Click the Create New button.
4. Add the following product link text if it hasn’t already been added: Read more +.
5. Click the Create button.
Adding the Product Type
The product type is used by the system to keep track of what type of product it is and is
strored in the ProductType table in the database which then can be selected in the Create
New and Edit views for a product.
1. Run the application and login as an admin.
2. Select Product Type in the Admin menu.
3. Click the Create New button.
4. Add the following product types if they haven’t already been added: Course,
Book, Articles and Misc..
5. Click the Create button.
Adding the products
The product C# For Beginners is a collection of videos belonging to a book and the Unit
Testing product is a collection of articels displayed as HTML. Both products will be dis-
played in the Index view of the HomeController.
1. Run the application and login as an admin.
2. Select Product in the Admin menu.
3. Click the Create New button.
4. Fill out the form with the following values:
1. Title: C# For Beginners.
2. Description: Videos describing chapter 1-9 in the C# For Beginners book.
3. Image Url: Add a viable URL to a thumbnail image for instance:
/Content/Images/csharp-for-beginners.png.
4. Product Link Texts: Select Read more + in the dropdown.
5. Product Types: Select Book in the dropdown.
5. Click the Create button.
6. Click the Create New button again.
7. Fill out the form with the following values:
1. Title: Unit Testing.
2. Description: A starter course in how to unit test a MVC solution.
3. Image Url: Add a viable URL to a thumbnail image for instance:
/Content/Images/unit-testing.png.
4. Product Link Texts: Select Read more + in the dropdown.
5. Product Types: Select Articles in the dropdown.
8. Click the Create button.
Adding the subscription
For users to be able to add products, they have to subscribe to one or more subscriptions in
the Subscription table. To add a subscription to a user, an entry must be added to the
UserSubscription table, and since you haven’t added a subscription control to the site yet,
you will have to do it manually through the Admin interface.
The value in the Registration Code field is the vaue the customer (user) has to enter in
order to gain access to the products associated with the subscription.
Adding a subscription
1. Run the application and login as an admin.
2. Select Subscription in the Admin menu.
3. Click the Create New button.
4. Fill out the form with the following values:
1. Title: C# For Beginners Subscription.
2. Description: Course materials for the C# For Beginners course.
3. Registration Code: CSharp
5. Click the Create button.
Adding a product to the subscription
1. Run the application and login as an admin.
2. Select Subscription Product in the Admin menu.
3. Click the Create New button.
4. Select a product and a subscription in the dropdowns and click the Save button.
Add the two products C# For Beginners, and Unit Testing to the C# For Beginners
Subscription subscription.
Adding a subscription to a user
This section describes how an admin can add a subscription to a user. Later, you will add a
control to the Index view of the HomeController where the user will be able to enter a
subscription code to get access to content.
1. Run the application and login as an admin.
2. Select User & Subscriptions in the Admin menu.
3. Click the Create New button.
4. Fill out the form with the following values:
1. First Name: Ray.
2. Email: [email protected].
3. Password: Enter a password that is easy for you to remember.
5. Click the Create button.
6. Click the Subscriptions button (the middle button) on the row with Ray’s
credentials.
7. Select C# For Beginners Subscription in the dropdown.
8. Click the Save button. The subscription should be added to the list att the bottom of
the page indicating that Ray now is subscribing to the content in the subscription.
Creating thumbnails
Now that you have added a subscription to a user, the user will automatically get access to
all products belonging to that subscription.
The next step is to display the products to the subscribing users. In order to do that, the
Index view has to be updated with a view model containing a string property called Title
representing the thumbnail area title and an IEnumerable<ThumbnailModel> where
every item in the collection is an instance of the ThumbnailModel class defining the
content of a thumbnail.
The thumbnails in the collection will be rendered by a partial view called _Thumbnail-
Area.
Area title
The area title is implemented as a string property called Title in the
ThumbnailAreaModel class.
Product type
Shows the user what type of product the thumbnail represent. Because you might
want to use the thimbnail in other scenarios, the property displaying the product
type is a string proeprty called ContentTag.
Product image
Is a valid URL to an image that will be displayed as the product thumbnail. It’s
declared as a string property called ImageUrl.
Product link text
A link text enticing the user to click on the thumbnail. Because you might want to
use the thumbnail in other scenarios, the property displaying the product link text is
a string property called TagText. The <a> element displaying the text will also use
a string property called Link containing the value (/ProductContent/Index/) in it’s
href attribute.
Product title
The product title is represented by an <a> element that uses a string property
called Title and a string property called Link as the value (/ProductContent/
Index/) in its href attribute.
Product description
The product description is represented by a <p> element that displays the text from
a string property called Description.
ProductId and SubscriptionId
These two int id’s must be present in the model to target the correct subscription
and product.
Adding the ThumbnailModel class
Use the information in the list above to add a class called ThumbnailModel.
1. Right-click on the Models folder in the main project and select Add-Class.
2. Name the class ThumbnailModel and click on the Add button.
3. Add two int proerties called ProductId and SubscriptionId.
4. Add six string properties called Title, Description, TagText, ImageUrl, Link and
ContentTag.
5. Save the class.
The complete code for the ThumbnailModel class:
public class ThumbnailModel
{
public int ProductId { get; set; }
public int SubscriptionId { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public string TagText { get; set; }
public string ImageUrl { get; set; }
public string Link { get; set; }
public string ContentTag { get; set; }
}
Adding the ThumbnailAreaModel class
The ThumbnailAreaModel class is used as a view model for the Index and
_ThumbnailAreaPartial views to display products thumbnails to the user. It only has two
properties; a string property caled Title which is displayed as a title above the thumbnail
area and a collection of ThumbnailModel instances called Thumbnails where each
instance represents a product thumbnail.
1. Right-click on the Models folder in the main project and select Add-Class.
2. Name the class ThumbnailAreaModel and click on the Add button.
3. Add a string property called Title.
4. Add an IEnumerable<ThumbnailModel> property called Thumbnails.
5. Save the class.
The complete code for the ThumbnailAreaModel class:
public class ThumbnailAreaModel
{
public string Title { get; set; }
public IEnumerable<ThumbnailModel> Thumbnails { get; set; }
}
Adding the thumbnails.css style sheet
The thumbnails.css style sheet will contain all styling associated with thumbnails. Add the
style sheet to the Content folder.
1. Right-click on the Content folder in the main project and select Add-Style Sheet.
2. Name the style sheet thumbnails and click the OK button.
3. Remove the body definition.
4. Open the BundleConfig file in the App_Start folder.
5. Add the thumbnails.css file to the memberships bundle.
bundles.Add(new StyleBundle(“~/Content/membership”).Include(
“~/Content/navbar.css”,
“~/Content/thumbnails.css”));
Add a <div> element decorated with the Bootstrap caption class below the image. This
element will hold the thumbnail title and the description.
Add an <h3> element with the text Thumbnail title and a <p> element with the text The
thumbnail description to the caption <div>.
1. Open the Index view in the Views-Home folder.
2. Add an IEnumerable<ThumbnailAreaModel> @model directive.
@model IEnumerable<Memberships.Models.ThumbnailAreaModel>
3. Add an @Html.Partial call inside the if-block in the Index view to render the
_ThumbnailAreaPartial view. Pass in an new instance of the the
ThumbnailAreaModel class; you will later change this code to loop over the
model passed into the Index view.
@if (User.Identity.IsAuthenticated)
{
@Html.Partial(“_ThumbnailAreaPartial”,
new ThumbnailAreaModel())
}
4. Right-click on the Views-Shared folder in the main project and select Add-View.
5. Name the view _ThumbnailAreaPartial and fill out the textboxes as described in
the image above and click the Add button. It’s important that you check the Create
as a partial view checkbox.
6. Add a <div> element containing an <h3> header inside with the text The area title.
<div>
<h3>The area title</h3>
</div>
7. Add a <div> element decorated with the row class below the previous <div>. This
will be the thumbnail area for the thumbnails.
<div class=“row”>
</div>
8. Add a <div> decoreated with the col-sm-3 Bootstrap class inside the <div> with
the row class. This <div> will determine the space a thumbnail is allowed to
occupy in the area row, which in this case is 3 grid columns.
<div class=“col-sm-3”>
</div>
9. Add a <div> decorated with the Bootstrap thumbnail class inside the previous
<div>. This <div> is the container for the Bootstrap thumbnail component and
will contain all the elements you want to display in the thumbnail.
<div class=“thumbnail”>
</div>
10. Add an empty <img> element to the thumbnail <div> and make sure to add … to
the src attribute and leave the alt attribute empty.
<img src=”…” alt=””>
11. Add a <div> element decorated with the Bootstrap caption class below the image.
This element will hold the thumbnail title and the description. Add an <h3>
element with the text Thumbnail title and a <p> element with the text The
thumbnail description to the caption <div>.
<div class=“caption”>
<h3>Thumbnail title</h3>
<p>The thumbnail description</p>
</div>
12. Run the application and make sure that the title and thumbnail is displayed.
The code so far for the _ThumbnailAreaPartial view:
@model Memberships.Models.ThumbnailAreaModel
<div>
<h3>The area title</h3>
</div>
<div class=“row”>
<div class=“col-sm-3”>
<div class=“thumbnail”>
<img src=”…” alt=””>
<div class=“caption”>
<h3>Thumbnail title</h3>
<p>The thumbnail description</p>
</div>
</div>
</div>
</div>
Style the _ThumbnailAreaPartial view heading
Style the title to make it stand out more by adding a CSS class called headline to the
<div> element surrounding the <h3> title element. Define a selector for the class in the
thumbnails.css file which adds a green 2px solid line under the title and removes any
margin except for a 15px bottom margin.
The changes to the _ThumbnailAreaPartial view:
<div class=“headline”>
<h3>The area title</h3>
</div>
Fetch the user id with Owin Context
The underlying ASP.NET foundational framework classes were completely rebuilt from
the ground up for the Visual Studio 2015 release, and in doing so, they changed the way
the Identity Framework handles authentication. To get to the user information, you go
through the Owin Context on the current HttpContext object.
The user id is used when fetching the subscriptions the logged in user is subscribed to.
You will create an extension method called GetUserId acting on the current HttpContext
to fetch the user id. Add the method to a public static class called HttpContext-
Extensions in the Extensions folder.
The Owin Context works with claims when handling identities and the claim you are
interested in for the user id is the nameidentifier.
Add a string constant called nameidentifier to the class which contains the path to the
nameidentifier claim. It’s important that the nameidentifier address is added exactly as
displayed below for it to fetch the user information; remove any spaces.
private const string nameidentifier = “http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier”;
Use the ctx instance variable inside the try-block to call the GetOwinContext method to
get the Owin Context; call the generic Get method using the
ApplicationsSignInManager class to get access to the ApplicationManager which
contains the user’s claims. Fetch the first claim matching the nameidentifier or return the
default value of the Claim class in a variable called claim.
var claims = ctx.GetOwinContext()
.Get<ApplicationSignInManager>()
.AuthenticationManager.User.Claims
.FirstOrDefault(claim => claim.Type.Equals(nameidentifier));
If the claims variable contains a claim, then assign the value of that claim to the uid
variable by accessing the claims variable’s Value property.
if (claims != default(Claim))
uid = claims.Value;
3. Add a constant called nameidentifier to the class and assign the claims path to it.
private const string nameidentifier = “http://schemas.xmlsoap.org /ws/2005/05/identity/claims/nameidentifier”;
4. Add a string variable called uid to the class and assign an empty string to it.
5. Add a try/catch-block to the class where the catch-block is empty.
6. Return the uid variable below the catch-block.
7. Use the ctx instance variable inside the try-block to call the GetOwinContext
method to get the Owin Context and call the generic Get method using the
ApplicationsSignInManager class to get access to the ApplicationManager
which contains the user’s claims. Fetch the first claim matching the nameidentifier
or return the default value of the Claim class and store the result in a variable
called claim.
var claims = ctx.GetOwinContext()
.Get<ApplicationSignInManager>()
.AuthenticationManager.User.Claims
.FirstOrDefault(claim => claim.Type.Equals(nameidentifier));
8. The user id is located in the claims object’s Value property. Assign the user id to
the uid variable if the claim contain a value.
if (claims != default(Claim))
uid = claims.Value;
Add the following using statements to the HomeController:
using Microsoft.AspNet.Identity; // Needed for HttpContext Identity
Add the following code to the Index action in the HomeController:
var userId = Request.IsAuthenticated ? HttpContext.User.Identity.GetUserId() : null;
The GetSubscriptionIdsAsync method
To fetch the user’s products, you first have to fetch the user’s subscription id’s because
without them you won’t be able to get to the products. As you might recall, the user id can
be used to fetch the user’s subscription id’s from the UserSubscription table which in turn
is connected to the SubscriptionProducts table which has the product id’s that can be
used to fetch the products in the Product table.
In this step, you will focus on fetching the subscription id’s from the UserSubscription
table and return them from the method. Add a private asynchronous method called Get-
SubscriptionIdsAsync which returns Task<List<int>> and has two parameters: one of
type string called userId and one called db of type ApplicationDbContext. Both
parameters should have a default value of null. The method will be called from a method
called GetProductThumbnailsAsync which you will add later.
Add the two methods to a public static class called ThumbnailExtensions in the Exten-
sions folder.
Add a try/catch-block to the method where the catch-block is empty.
Return an empty List<int> in the try-block if the userId parameter is null.
Create a new instance of the ApplicationDbContext in the db parameter if it is null. You
can call the Create method on the ApplicationDbContext class to create the instance.
Fetch the subscription id’s for the user asynchronously from the UserSubscription table
and return them from the method using the value in the userId parameter.
Return an empty List<int> below the catch-block.
1. Add a public static class called ThumbnailExtensions to the Extensions folder.
2. Add a private asynchronous method called GetSubscriptionIdsAsync to the class
which returns Task<List<int>> and has two parameters one of type string called
userId and one called db of type ApplicationDbContext. Both parameters should
have a default value of null.
Private static async Task<List<int>> GetSubscriptionIdsAsync(
string userId = null, ApplicationDbContext db = null)
6. Fetch the subscription id’s for the user asynchronously from the UserSubscription
table and return them from the method using the value in the userId parameter.
Return await (
from us in db.UserSubscriptions
where us.UserId.Equals(userId)
select us.SubscriptionId).ToListAsync();
7. Return an empty list at the end of the method below the catch-block.
The complete code for the GetSubscriptionIdsAsync method:
private static async Task<List<int>> GetSubscriptionIdsAsync(string userId = null, ApplicationDbContext db = null)
{
try
{
if (userId == null) return new List<int>();
if (db == null) db = ApplicationDbContext.Create();
return await (
from us in db.UserSubscriptions
where us.UserId.Equals(userId)
select us.SubscriptionId).ToListAsync();
}
catch { }
return new List<int>();
}
Add a ThumbnailEqualityComparer
To compare thumbnails on their product id’s instead of on their references, you need to
add a public comparer class called ThumbnailEqualityComparer which implements the
IEqualityComparer<ThumbnailModel> interface to a new folder called Comparers.
The IEqualityComparer interface defines two methods called Equals and
GetHashCode. The Equals method is used when you want to compare two objects with
your logic and rules; in this case the objects should be compared on the ProjectId
property. The GetHashCode method returns a unique id for an object – in this case, the
ProjectId.
1. Add a new folder called Comparers to the main project.
2. Add a public class called ThumbnailEqualityComparer to the folder and have it
implement the IEqualityComparer interface for the ThumbnailModel class.
public class ThumbnailEqualityComparer
: IEqualityComparer<ThumbnailModel>
{
}
10. Make the Index action asynchronous by adding the async keyword, and have it
return a Task<ActionResult>. You need to do this to await results.
public async Task<ActionResult> Index()
Return the model variable with the View method to render its data in the Index view.
1. Open the Index action in the HomeController.
2. Find out how many areas should be created by dividing the number of thumbnails
by 4 and store the result in a variable called count.
var count = thumbnails.Count() / 4;
4. Add a for loop and Iterate the number of times specified by the count variable.
for (int i = 0; i <= count; i++)
5. Create instances of the ThumbnailAreaModel class inside the for-block and add
them to the model variable. Assign the thumbails in the thumbnails collection to
the current ThumbnailAreaModel instance. Only assign a title to the first
ThumbnailAreaModel instance.
model.Add(new ThumbnailAreaModel
{
Title = i.Equals(0) ? “My Content” : String.Empty,
Thumbnails = thumbnails.Skip(i * 4).Take(4)
});
3. Move the <div> decorated with the row class above the if-block to create the row
for the area and not the thumbnail.
4. Add an if-block around the thumbnail HTML markup below the title if-block
which checks that the Thumbnails collection property in the model isn’t null. Note
that the last closing </div> belong to the <div> decorated with the row class and
should not be included in the if-block.
@if (Model.Thumbnails != null)
5. Add a foreach loop inside the if-block iterating over the thumbnails in the model’s
Thumbnails collection property.
foreach (var thumb in Model.Thumbnails)
6. Create a folder called Images in the Content folder and copy two thumbnail
images (246x156) called csharp-for-beginners.png and unit-testing.png to the new
folder. If you used different names for the thumbnail images then name the images
acordingly.
7. Replace the … with the model’s ImageUrl property in the <img> element’s src
attribute.
<img src=”@thumb.ImageUrl” alt=””>
8. Replace the text Thumbnail title with an <a> element where the content is the value
in the loop variable’s Title property (the thumbnail title) and the href attribute is
assigned the URL in the Link property. Note that the link won’t work at the
moment because the necessary controllers and views have not been added yet.
<h3><a href=”@thumb.Link“>@thumb.Title</a></h3>
9. Replace the text The Thumbnail Description with in the the loop variable’s
Decription property (the thumbnail description).
<p>@thumb.Description</p>
2. Add a <div> element around the <img> element and decorate it with a CSS class
called thumbnail-img-container.
<div class=“thumbnail-img-container”>
<img class=“thumbnail-img” src=”@thumb.ImageUrl” alt=””>
</div>
3. Add an <a> element below the <div> decorated with the thumbnail-img-
container class. Add two classes called tag and text-tag to the <a> element and
assign the Link property to the href attribute and the TagText as its content.
<a class=“tag text-tag” href=”@thumb.Link“>
@thumb.TagText</a>
4. Add a <div> element below the <a> element and decorate it with two CSS classes
called tag and content-tag. Use the ContentTag property as its content.
<div class=“tag content-tag”>@thumb.ContentTag</div>
Add a rule which targets elements with the tag class inside an element with the thumbnail
class to style the two labels. Position them -10px from the right border of the container
(this will make them apear as though they are sticking out) and make the text white. Add
1px padding to the top and bottom and 5px to the left and right sides. Position the labels
with absolute positioning and display them as inline blocks.
.thumbnail .tag {
right: -10px;
color: #FFF;
padding: 1px 6px;
position: absolute;
display: inline-block;
}
Add a rule which targets elements with the thumbnail-img-container class inside an
element with the thumbnail class to style the <div> containing the thumbnail image. Hide
any overflowing image parts; this will be useful when animating the image.
.thumbnail .thumbnail-img-container {
overflow: hidden;
}
Add a rule which targets elements with the thumbnail-container class inside an element
with the thumbnail class to give the <div> relative positioning making it possible to
position its chilren with absolute positioning relative to the <div>.
.thumbnail .thumbnail-container {
position: relative;
}
Add a rule which targets elements with the text-tag class inside an element with the
thumbnail class to style the bottom label. Change the background color to green and
position it 5px from the bottom of its closest-positioned parent which is the <div> deco-
rated with the thumbnail-container class.
.thumbnail .text-tag {
background-color: #72C02C;
bottom: 5px;
}
Add a rule which targets elements with the content-tag class inside an element with the
thumbnail class to style the top label. Change the background color to orange and
position it 5px from the top of its closest-positioned parent which is the <div> decorated
with the thumbnail-container class.
.thumbnail .content-tag {
background-color: #f79a47;
top: 5px;
}
Add a rule which targets elements with the thumbnail-img class inside an element with
the thumbnail class to style the thumbnail image. Make the image scale to 100% of the
width and use automatic scaling of the height. This will make the whole image fit inside
its container even when the browser size changes.
.thumbnail .thumbnail-img {
width: 100%;
height: auto;
}
Add a rule which targets <a> elements inside an element with the thumbnail class to
remove the outline and text under line in the links.
.thumbnail a, a:focus, a:hover, a:active {
outline: 0px none !important;
text-decoration: none;
}
Add a rule which targets <a> elements when hovering over the link, when it is active or
has focus and it resides inside an element with the thumbnail class. Use the rule to change
the text color to the same green color that is used when hovering over links in the main
menu.
.thumbnail > a:focus, a:hover, a:active {
color: #72C02C;
}
Use a media query which targets extra small devicees and add a rule which targets
elements with the thumbnail class. The rule should set a maximum width of 260px to
elements decorated with the class. The rule will make the thumbnail look better on
smartphones and such devices.
@media screen and (min-width: 480px) {
.thumbnail {
max-width: 260px;
}
}
Animating the thumbnail image and labels
Now let’s make the thumbnails a bit more fun by animating the image to rotate and zoom.
Move the lower label to the left and give the thumbnail a shadow when hovering over the
image. All CSS rules are added to the thumbnails.css file.
I suggest that you add the CSS rules one by one and note the changes to the various
elements involved in the animations.
Animating the lower label
To move the lower label 5px to the left from the image’s right side and to make the label
larger when hovering over it, you need to define three CSS rules.
The first rule is a class called hover-effect and will define an animation on the <a>
element making it ease in and out over a period of 0.4 seconds when the mouse pointer
enters or leaves the element. Add the CSS class to the <a> element and the rule to the
thumbnails.css file.
.thumbnail .hover-effect {
transition: all 0.4s ease-in-out 0s;
}
<a class=“tag text-tag hover-effect” href=”@thumb.Link“>
@thumb.TagText</a>
The second rule uses the already existing text-tag CSS class to target the <a> element
when hovering over it and adds a box shadow to the <a> element making it appear larger.
.thumbnail a.text-tag:hover {
box-shadow: 0px 0px 0px 2px #5FB611;
}
The third rule uses the already existing text-tag CSS class to target the <a> element when
hovering over the thumbnail. This rule will be applied as soon as the mouse pointer enters
the thumbnail and will move the label 5px from the parent container’s right border.
.thumbnail:hover a.text-tag {
right: 5px;
}
You also need to add a 0.8 second transition and reset all other ease in and out transitions
to the the already exisitng .thumbnail .thumbnail-img CSS rule.
.thumbnail .thumbnail-img {
width: 100%;
height: auto;
transition: all 0.8s ease-in-out 0s;
}
11. Display Product Page
Introduction
In this chapter you will add the page which displays a product’s content. This page can
contain several sections for sections/modules and a section for downloadable material.
The sections will be displayed using collapsible Bootstrap panel components and the
product items will be displayed as <div> elements containing a Bootstrap grid layout.
By the end of this chapter, the application will display the product items grouped by the
section they belong to for a selected product. The sections can for instance be chapters or
downloads. This view will be displayed when the user clicks on one of the product
thumbnails you added in the previous chapter. The image below shows what the end result
will look like.
Each item will display a thumbnail, the item title and a description. In a future chapter,
you will add the views which will display the item content when on one of the item links
is clicked.
In the previous chapter, you added an extension method called GetProductThumbnails-
Async in the ThumbnailExtensions class to display the product thumbnails. In the LINQ
query for the thumbnails, you populated a property called Link with a unique link to the
product. This link will now be used to fetch the items belonging to the selected product
and open a view to display the content.
The sections will be created using data from the Section table; note that this table can
contain section names other than Chapter xyz, for instance Downloads.
Adding the ProductContent controller
To be able to render the product listings, you first have to add an empty controller named
ProductContent.
1. Right-click on the Controllers folder in the main project and select Add-
Controller in the menu.
2. Make sure that the MVC 5 Controller - Empty option is selected and click the
Add button.
3. Enter the name ProductContentController in the text field and click the Add
button.
4. Add the Authorize attribute to the class to ensure that only logged in users can
reach this controller.
[Authorize]
public class ProductContentController : Controller
Add the Index view to the Views-ProductContent folder and select the empty template.
Use the ProductSectionModel class as its model and remove the data context class. The
image below shows the necessary settings.
6. Create an instance of the ProdcutSectionModel class, fill it with data and return it
from the method.
The code in the GetProductSectionsAsync method so far:
public static class SectionExtensions
{
#region Product Methods for the ProductController
public static async Task<ProductSectionModel>GetProductSectionsAsync(
int productId, string userId)
{
var db = ApplicationDbContext.Create();
var sections = await (from p in db.Products
join pi in db.ProductsItems on p.Id equals pi.ProductId
join i in db.Items on pi.ItemId equals i.Id
join s in db.Sections on i.SectionId equals s.Id
where p.Id.Equals(productId)
orderby s.Id
select new ProductSection
{
Id = s.Id,
ItemTypeId = i.ItemTypeId,
Title = s.Title
}).ToListAsync();
var result = sections.Distinct(new ProductSectionEqualityComparer())
.ToList();
var model = new ProductSectionModel
{
Sections = result,
Title = await (from p in db.Products
where p.Id.Equals(productId)
select p.Title).FirstOrDefaultAsync()
};
return model;
}
#endregion
}
Altering the Index action
Now that you have created the extension method needed to fetch the product title and
sections, it is time to add a call to it in the Index action method in the ProductContent
controller.
Use the GetUserId method on the HttpContext class to fetch the user id and store it in a
variable called userId.
Call the GetProductSectionsAsync method on the SectionExtensions class and pass in
the product id from the id action parameter and the user id from the userId variable.
Await the result and store it in a variable called sections.
Pass in the sections object to the View method.
1. Open the ProductContentController class.
2. Fetch the user id if the user is authenticated orherwise return null.
var userId = Request.IsAuthenticated ? HttpContext.GetUserId() : null;
10. Create a Bootstrap panel for each section in the loop and add the panel-success
class to give the panel a (green) color. Also, add a line break after the panel’s
closing </div> element.
<div class=“panel panel-success”>
</div>
<br />
11. Add a <div> element decorated with the Bootstrap panel-heading class inside the
panel <div> element.
<div class=“panel-heading”>
</div>
12. Add a <h4> element decorated with the Bootstrap panel-title class inside the
panel-heading <div> element.
<h4 class=“panel-title”></h4>
13. Add an <a> element to the panel-title <h4> element which can be used to collapse
the panel-body <div> element to hide the section items.
<a data-toggle=“collapse” class=“panel-carret” href=”#collapse@{@s.Id}“>@s.Title</a>
14. And an empty <span> with a carret icon (glyphicon-play) to the left of the title in
the <a> element.
<a data-toggle=“collapse” class=“panel-carret” href=”#collapse@{@s.Id}“>
<span class=“pull-left glyphicon glyphicon-play gly-rotate-
90”></span>
@s.Title
</a>
15. Add a <div> element decorated with the necessary classes to make it collapsible
when the <a> element in the header is clicked. Add the panel-body <div> to the
<div>, the panel-body <div> will later contain the section items.
<div id=“collapse@{@s.Id}” class=“panel-collapse collapse in”>
<div class=“panel-body”>
</div>
</div>
9. Assign a value to the IsDownload property that will determine if the link is to a
downloadable file or an item in the database. This property will be used to
determine if the item should be displayed in the Downolads section in the product
items list. Use the ItemType title or id to determine if it’s a downloadable.
10. Save the class.
The complete code for the GetProductItemRowsAsync method:
public static async Task<IEnumerable<ProductItemRow>> GetProductItemRowsAsync(int productId, int sectionId,
string userId, ApplicationDbContext db = null)
{
if (db == null) db = ApplicationDbContext.Create();
var today = DateTime.Now.Date;
var items = await (from i in db.Items
join it in db.ItemTypes on i.ItemTypeId equals it.Id
join pi in db.ProductsItems on i.Id equals pi.ItemId
join sp in db.SubscriptionProducts on pi.ProductId
equals sp.ProductId
join us in db.UserSubscriptions on sp.SubscriptionId
equals us.SubscriptionId
where i.SectionId.Equals(sectionId) &&
pi.ProductId.Equals(productId) &&
us.UserId.Equals(userId)
orderby i.PartId
select new ProductItemRow
{
ItemId = i.Id,
Description = i.Description,
Title = i.Title,
Link = it.Title.Equals(“Download”) ? i.Url :
“/ProductContent/Content/” + pi.ProductId + “/” + i.Id,
ImageUrl = i.ImageUrl,
ReleaseDate = DbFunctions.CreateDateTime(
us.StartDate.Value.Year, us.StartDate.Value.Month,
us.StartDate.Value.Day + i.WaitDays, 0, 0, 0),
IsAvailable = DbFunctions.CreateDateTime(today.Year,
today.Month, today.Day, 0, 0, 0) >=
DbFunctions.CreateDateTime(us.StartDate.Value.Year,
us.StartDate.Value.Month, us.StartDate.Value.Day +
i.WaitDays, 0, 0, 0),
IsDownload = it.Title.Equals(“Download”)
}).ToListAsync();
return items;
}
Altering the GetProductSectionsAsync extension method
Now that you have added the GetProductItemRowsAsync method, it can be called from
GetProductSectionsAsync method to fetch the items for each product section.
Add a foreach loop iterating over the sections in the sections variable immediately below
the LINQ query in the GetProductSectionsAsync method and assign the result from
calling the GetProductItemRowsAsync method to the Items property of the current loop
object (section).
foreach (var section in sections)
section.Items = await GetProductItemRowsAsync(productId, section.Id,
section.ItemTypeId, userId);
Because the Downloads section should be displayed as the last section at the end of the
view, you have to change the LINQ query to be ordered by the section Title property.
orderby s.Title
select new ProductSection
And create a union between the sections without the word download in the title and the
once with the word download in the title.
var union = result.Where(r => !r.Title.ToLower().Contains(“download”))
.Union(result.Where(r => r.Title.ToLower().Contains(“download”)));
The finished code for the GetProductSectionsAsync method:
public static async Task<ProductSectionModel> GetProductSectionsAsync(int productId, string userId)
{
var db = ApplicationDbContext.Create();
var sections = await (
from p in db.Products
join pi in db.ProductsItems on p.Id equals pi.ProductId
join i in db.Items on pi.ItemId equals i.Id
join s in db.Sections on i.SectionId equals s.Id
where p.Id.Equals(productId)
orderby s.Title
select new ProductSection
{
Id = s.Id,
ItemTypeId = i.ItemTypeId,
Title = s.Title
}).ToListAsync();
foreach (var section in sections)
section.Items = await GetProductItemRowsAsync(productId,
section.Id, userId);
var result = sections.Distinct(new ProductSectionEqualityComparer())
.ToList();
var union = result.Where(r => !r.Title.ToLower()
.Contains(“download”)).Union(result.Where(r => r.Title.ToLower()
.Contains(“download”)));
var model = new ProductSectionModel
{
Sections = union.ToList(),
Title = await (
from p in db.Products
where p.Id.Equals(productId)
select p.Title).FirstOrDefaultAsync()
};
return model;
}
Adding the _ProductItemRow partial view
To display the product items, you have to add a partial view that renders a single product
item and loop over the Items collection in the ProductSection model in the _Product-
Section partial view.
The _ProductItemRow partial view should take a single instance of the
ProductItemRow class as its model. The view should have a container <div> element
decorated with the product-item and row classes; the former class will be used to target
the intrinsic elements with CSS.
There should be two <div> elements acting as columns decorated with the col-sm-2 and
col-sm-10 classes respectively inside the row <div>. Place an <img> element with a fixed
width of 140px that will display the item thumbnail inside the first column <div>. Inside
the second column <div>, you will have to perform some logic to determine if the item is
available by checking the IsAvailable property; if it isn’t available, then display a <p>
element with a text telling the user that the item will be released on the date stored in the
ReleaseDate property.
Add a second logic check if the item is available inside an <h2> element and display the
value in the item Title as static text if it isn’t 1vailable. Otherwise, add the Title as an <a>
element with the target attribute set to _blank if it is a downloadable item or as an <a>
element without a target attribute if it isn’t downloadable. Use the model’s Link property
in the <a> element’s href attribute to add the destination URL.
Add a <p> element displaying the value in the model’s Description property below the
<h2> element.
Add a line break (<br />) below the row <div> to add some space between the product
items.
The image below shows the settings for the _ProductItemRow partial view.
1. Right-click on the Views-Shared folder and select Add-View.
2. Name the view _ProductItemRow in the View name field.
3. Select Empty in the Template drop-down.
4. Select ProductItemRow in the Model class drop-down.
5. Delete the text in the Data context class drop-down.
6. Make sure that the Create as a partial view checkbox is checked.
7. Click the Add button.
8. Add a <div> element and decorate it with the product-item and row classes.
9. Add a <div> element inside the previous <div> and decorate it with the col-sm-2
class.
10. Add an <img> element inside the previous <div> with a fixed width of 140px and
has the model’s ImageUrl property assigned to its src attribute.
<img src=”@Model.ImageUrl” width=“140” />
11. Add a <div> element below the previous <div> and decorate it with the col-sm-10
class as well as the text-color, line-bottom, min-height classes that will be used to
style its content.
12. Add an if-block inside the previously added <div> that will display the text Will be
released: followed by the date from the model’s ReleaseDate property in a <p>
element if the model’s IsAvailable property is false.
@if (!Model.IsAvailable)
{
<p>
Will be released: @Model.ReleaseDate.Value.ToShortDateString()
</p>
}
15. Add a <p> element displaying the model’s Diescription property below the <h2>
element.
16. Add a <br /> below the last closing </div>.
17. Save the partial view.
The complete code for the _ProductItemRow partial view:
@model Memberships.Models.ProductItemRow
<div class=“product-item row”>
<div class=“col-sm-2”>
<img src=”@Model.ImageUrl” width=“140” />
</div>
<div class=“col-sm-10 text-color line-bottom min-height”>
@if (!Model.IsAvailable)
{
<p>Will be released:
@Model.ReleaseDate.Value.ToShortDateString()</p>
}
<h2>
@if (Model.IsAvailable)
{
if (Model.IsDownload)
{
<a class=”” href=”@Model.Link” target=“_blank”>
@Model.Title</a>
}
else
{
<a class=”” href=”@Model.Link“>@Model.Title</a>
}
}
else
{
@Model.Title
}
</h2>
<p>@Model.Description</p>
</div>
</div>
<br />
Altering the _ProductSectionPartial view
To display only the sections that contain items, you have to place an if-block around all
the markup inside the foreach loop, checking that there are items in the model’s Items
collection.
Add a foreach loop iterating over the items in the model’s Items collection inside the
<div> decorated with the panel-body class. Use the @Html.Partial extension method to
render the the _ProductItemRow partial view for each item. Pass in the current item in
the loop to the partial view.
The image below shows the product content page after the _ProductSection partial view
has been activated.
1. Open the _ProductSection partial view.
2. Add an if-block checking that there are items available in the model’s Items
collection around the markup inside the foreach loop.
if (s.Items.Count() > 0)
3. Add a foreach loop iterating over the items in the model’s Items collection inside
the <div> decorate with the panel-body class and render the _ProductItemRow
partial view for each item. Pass in the current item in the loop to the partial view.
@foreach (var item in s.Items)
{
@Html.Partial(“_ProductItemRow”, item)
}
Add a 2px top margin and change the font size to 22px for <h2> elements that are children
to the <div> decorated with the product-item class.
.product-item h2 {
margin-top: 2px;
font-size: 22px;
}
Remove any text decoration, such as link underlines, for <a> elements that are children to
the <div> decorated with the product-item class.
.product-item a {
text-decoration: none;
}
Add the line separating the items to the <div> decorated with the product-item and line-
bottom classes.
.product-item .line-bottom {
border-bottom: 1px solid #E4E9F0;
}
3. Call the jwplayer function in the JavaScript library you add to the <head> section.
It takes one parameter which is the id of the <div> element where the video will be
displayed.
4. Call the setup method on the jwplayer method to pass in setup information:
1. file: The URL to the video.
5. Open the BundleConfig class and add the JWPlayer.js JavaScript file to the
membership script bundle.
The complete jwVideo function:
function jwVideo(video) {
//Play single video
jwplayer(“video”).setup({
file: video
});
}
Register a ProductContent route
To be able to reach the new action method you are about to add to the ProductContent
controller, you need to add a new route with two paramters to the RouteConfig class. The
first parameter is the product id and the second the item id. Assign ProductContent to the
controller and name properties and Index to the action property.
1. Open the RouteConfig class.
2. Copy the existing default route and paste it in above it.
3. Change the name and controller properties to ProductContent.
4. Remove the id property from the defaults object.
5. Rename the id placeholder itemId.
6. Add a productId placeholder to the Url property between the action and the
itemId place holders.
The complete ProductContent route:
routes.MapRoute(
name: “ProductContent”,
url: “{controller}/{action}/{productId}/{itemId}/”,
defaults: new
{
controller = “ProductContent”,
action = “Index”
}
);
The ContentViewModel class
This class will contain the necessary data to display any piece of content, be it HTML or
Video. The class must contain a ProductId property of type int for navigation purposes,
Title and Description properties of type string to describe the content piece, a property
called HTML of type string for article content in HTML markup and a VideoUrl
proeprty of type string for a video URL.
1. Create a new class called ContentViewModel in the Models folder in the main
project.
2. Add a public int property called ProductId.
3. Add four public string properties called Title, Description, HTML and VideoUrl.
4. Save the class.
The complete code for the ContentViewModel class:
public class ContentViewModel
{
public int ProductId { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public string HTML { get; set; }
public string VideoURL { get; set; }
}
The GetContentAsync extension method
In order to display a piece of content (item) to the user, a new extension method called
GetContentAsync has to be added to the SectionExtensions class. It will take two int
parameters called productId and Itemid.
Create an instance of the ApplicationDbContext called db in the method to make it
possible to fetch the necessary data from the database.
Add an asynchronous LINQ query that join the Item and ItemType tables and filters on
the itemId property. Have the query return a single object of the ContentViewModel
class and the method a Task<ContentViewModel>.
1. Open the SectionExtensions class.
2. Add a new asynchronous extension method called GetContentAsync that returns a
Task<ContentViewModel> and takes two int parametes called productId and
Itemid.
public static async Task<ContentViewModel> GetContentAsync(int productId, int itemId) { … }
3. Call the GetContentAsync extension method and store the result in a variable
called model.
var model = await SectionExtensions.GetContentAsync(productId, itemId);
4. Pass in the name of the view (Content) along with the model variable to the View
method.
return View(“Content”, model);
4. Add a <div> element below the <div> element decorated with the headline class.
Display the content in the model’s Description proeprty inside the <div>.
5. Add a line break after the last <div>.
Displaying HTML conent
1. Add an if-block checking that the model’s HTML property isn’t null below the
line break.
2. Add a <div> element decorated with a class called article that will be used later to
add styling to the HTML content. Use the @Html.Raw method to display the
HTML markup in the model’s HTML property.
<div class=“article”>@Html.Raw(Model.HTML)</div>
Displaying Video conent
1. Add an if-block checking that the model’s VideoUrl property isn’t null below the
previous if-block.
2. Add a <div> element decorated with a class called video-margin that will be used
to remove the margin around the video. Add the id video to the element; this id will
be used by the JW Player to find the correct element to display the streamed video.
<div id=“video” class=“video-margin”> </div>
3. Add another <div> below the previous video inside the if-block and give it the id
hiddenUrl. Add the content in the model’s VideoUrl property to the element; the
content will be fetched with JavaScript and sent to the jwVideo function to display
the video when the view has finished loading. Use the hidden attribute to hide the
content.
<div id=“hiddenUrl” hidden=“hidden”>@Model.VideoUrl</div>
4. Add a scripts section below the last if-block for the necessary JavaScript.
@section scripts { … }
5. Call the jwVideo function from the Document Ready function inside the scripts
section. Pass in the hidden url and the string Video to the method.
jwVideo($(“#hiddenUrl”).text(), ‘Video’);
Override the text color for the <H2> and <H3> elements and give them a light blue color
(#0094ff) .
.article h2, .article h3 {
color: #0094ff;
}
Add the html.css style sheet to the membership style bundle in the BundleConfig class.
13. Register Subscription Code
Introduction
In this chapter you will add a panel for entering a subscription code. The idea is that when
customers purchase a product from you, you send them a code that they then enter in this
panel to unlock access to the products associated with that subscription. The images below
show what the panel will look like when finished; it will contain a text field, a submit
button and an alert label where a success or fail message can be delivered.
The registration panel should only be displayed to logged in users.
If a code is registered successfully, the text field should be cleared and a success message
should be displayed. If no code or a non-exisiting code is entered, different error messages
should be displayed.
Adding the _RegisterCodePartial view
To keep the markup clean in the Index view, the pannel will be added as a partial view
called _RegisterCodePartial to the Views-Shared folder. Adding it as a partial view will
make it possible to update only that portion of the Index view when the Register action of
the RegisterCode controller renders its content.
Create the partial view as an empty view without a model.
1. Right-click on the Views-Shared folder in the main project.
2. Select Add-View in the menu.
3. Name the view _RegisterCodePartial in the View name field.
4. Select Empty (without model) in the Template drop-down.
5. Make sure that the Create as a partial view checkbox is checked.
6. Click the Add button.
Adding the Register Code panel content
You will have to start by adding an if-block checking that the user is logged in and
authenticated. This can be done by checking the value in the Request.IsAuthenticated
property. Place all the markup inside the if-block.
Add a Bootstrap panel to the if-block and the panel-primary and register-code-panel
classes to it; the former class will give it a blue color and the latter will be used to style the
panel using CSS.
Use an <h4> heading with the text Register Code for the panel title.
Add the Bootstrap input-group class to the panel body <div> to group the controls used
in it and to make them look nicer. Add a text field decorated with the form-control class
to make the field behave like a form control and look more aesthetically pleasing. Also
add a <button> element decorated with the btn, btn-primary and input-group-btn
Bootstrap classes; the latter class will place the button flush to the left side of the text field
making them look like a single control.
Add a <div> element decorated with the alert, alert-danger and hidden classes below the
panel body <div>; the first class will make it look and behave as a Bootstrap alert
component, the second class will give it a red background color and the third will hide the
<div>. The alert-danger and hidden classes will be accessed with JavaScript depending
on if the asynchronous action call to the controller fails or is successful.
1. Open the _RegisterCodePartial partial view if it’s not already open.
2. Add an if-block checking that the user is authenticated.
@if (Request.IsAuthenticated) { /* Add the HTML here */ }
8. Add a <button> element decorated with the btn, btn-primary and input-group-
btn Bootstrap classes. Have the button display the text Submit. The but’on’s Click
event will later be wired up with JavaScript.
<button cla”s=“btn btn-primary input-group-”tn” ty”e=“but”on”>Submit</button>
9. Add a <div> decorated with the alert, alert-danger and hidden classes. Also add
the default text Could not register code to the <div>.
<div cla”s=“alert alert-danger hid”en”>Could not register code</div>
Removing the border radius from the panel and its elements:
.register-code-panel,
.register-code-panel .panel-heading,
.register-code-panel * {
border-radius:0px;
}
3. Add exception handling to the method and return Int32.MinValue from the catch-
block.
4. Return Int32.MinValue if the IDbSet<Subscription> parameter is null or the
code string parameter is null or an empty string.
if (subscription == null || code == null || code.Length <= 0) return Int32.MinValue;
5. Fetch the subscription matching the value in the code parameter asynchronously
and return its subscription id.
var subscriptionId = await (
from s in subscription
where s.RegistrationCode.Equals(code)
select s.Id).FirstOrDefaultAsync();
5. Add the userId and subscriptionId values asynchronously as a new record in the
UserSubscription table.
The complete code for the Register extension method:
public static async Task Register(this IDbSet<UserSubscription> userSubscription, int subscriptionId, string userId)
{
try
{
if (userSubscription == null ||
subscriptionId.Equals(Int32.MinValue) ||
userId.Equals(String.Empty))
return;
// Check if user already has the subscription
var exist = await Task.Run<int>(() => userSubscription.CountAsync(
s => s.SubscriptionId.Equals(subscriptionId) &&
s.UserId.Equals(userId))) > 0;
if (!exist)
await Task<UserSubscription>.Run(() =>
userSubscription.Add(
new UserSubscription
{
UserId = userId,
SubscriptionId = subscriptionId,
StartDate = DateTime.Now,
EndDate = DateTime.MaxValue
}));
}
catch { }
}
The RegisterUserSubscriptionCode method
To keep the Register action method as clean possible, you will create an asynchronous
method called RegisterUserSubscriptionCode in the SubscriptionExtensions class
which will attempt to register the code entered in the Register Code panel. If successful,
the user will gain access to the products associated with that subscription.
The method will return a Task<bool> signalling if the code was successfully registered or
not for the logged in user. It will take two parameters of type string called code and
userId.
This method will in turn call the two GetSubscriptionIdByRegistrationCode and
Register extension methods to attempt to register the code with the logged in user.
Use the ChangeTracker.HasChanged method on the ApplicationDbContext instance to
check if there are any changes to save to the database, and only call the SaveChanges
method to persist the data to the database if there are changes.
1. Add an asynchronous method called RegisterUserSubscriptionCode to the
SubscriptionExtensions class. The method should return Task<bool> and take
two parameters of type string called code and userId.
public static async Task<bool> RegisterUserSubscriptionCode(string code, string userId)
4. Fetch the user id and store it in a variable called userId. by calling the GetUserId
extension method on the HttpContext object.
var userId = HttpContext.GetUserId();
Next, you will add a function called displayMessage, which takes two parameters called
success and message. Replace the text in the <div> decorated with the alert class with the
text in the message parameter.
If the success parameter is true, then remove the alert-danger class and add the alert-
success class giving the alert element a green background color, else remove the alert-
success class and add the alert-danger class giving the alert element a red background
color.
Remove the hidden class from the alert element, making it visible.
function displayMessage(success, message) {
var alert_div = $(“.register-code-panel .alert”);
alert_div.text(message);
if (success)
alert_div.removeClass(‘alert-danger’).addClass(‘alert-success’);
else
alert_div.removeClass(‘alert-sucess’).addClass(‘alert-danger’);
alert_div.removeClass(‘hidden’);
}
14. Register User
Introduction
In this chapter you will create a partial view called _RegisterUserPartial which can be
used by site visitors to register with the site. It should be rendered as part of the Home
controller’s Index view and Ajax will be used to make an asynchronous call to the
controller when the user clicks the Sign me Up! button. The response from the server will
be used to update only the partial view.
To confirm that the visitor has read the User Agreement, they have to check the I accept
the User Agreement checkbox to enable the submit button.
A new view model called RegisterUserModel has to be created with the correct
validation and data annotation attributes with its properties.
If you don’t want to use Bootstrap Glyphicons to decorate the textboxes with icons, you
can use the Font Awesome library. Browse to the following link to learn more about
installing the http://fontawesome.github.io/Font-Awesome/ library. You should be aware
that it can be tricky to get Font Awesome to work with Azure if you plan to host your web
applications in the cloud.
Technologies used in this chapter
1. MVC - To create the partial view controller and action needed to register a user.
2. C# - Creating a view model with validation attributes used to register a user.
3. Razor - To incorporate C# in the views where necessary.
4. HTML 5 - To build the partial view.
5. Bootstrap - To style the HTML 5 components.
6. Glyphicons - To decorate the textboxes with icons.
7. CSS - To style the registration panel.
8. JavaScript/JQuery - To hook in to component events and make Ajax calls.
9. Ajax - To make an asynchronous call to the server and use the response to update a
partial view.
Use case
Create a partial view which can be used to register a user with the site. The form should be
visible for non-logged in users and hidden when the user has logged in successfully.
The user’s first name, email and encrypted password will be stored in the database when a
user registers with the site. The password should be stored using one-way encryption
where the stored encrypted password (not an unecrypted version of it) will be compared to
an encrypted version of the password submitted on subsequent logins.
The textboxes should have descriptive icons and descriptive placeholder texts.
The Sign me Up! submit button should be disabled (unclickable) until the user checks the
I accept the User Agreement checkbox.
When clicked, the submit button will call a JavaScript function making an asynchronous
Ajax call to the server and use the response to update the register panel with any error
messages in a validation summary above the textboxes.
This is what the finished register panel will look like:
Adding the RegisterUserModel
The Index view needs a model to display user information in the register panel and send
that information to the controller. When the view renders the first time, it will need an
empty model, but if erroneous data is sent back to the controller, you want to retain the
already entered data in the text fields to make it faster to fill out the form a second time by
changing the already provided information.
Create a class called RegisterUserModel in a the Models folder. The model’s properties
will be mapped to specific components in the register panel and display validation
messages if the data is erroneous. The validation rules are added to the model as property
attributes. You can provide you own error messages for the validation attributes or rely on
the default messages provided by .NET framework.
Since the registration form asks the visitor to enter a first name, email address and
password, you need to add properties for these values in the model class. You also need to
add a bool property for the checkbox value to keep track of whether it has been checked or
not.This saves the visitor the hassle of checking it again if erroneous data was entered.
You will add the following validation attributes to the properties where applicable:
Required - The field must have content.
EmailAddress - Checks that a valid email address has been entered in the textbox.
Display - The text to be displayed in the control’s label. It can be left out if you
want to use the property name instead.
StringLength - Determines the maximum and minimum number of characters
allowed in the textbox. It can also hold an error message which is displayed if too
few or too many characters have been entered in the textbox.
DataType - Can be used to check that the password meets the set standard for
passwords as well as display the entered text as dots.
Add the RegisterUserModel
1. Right-click on the Models folder and select Add-Class in the context menu.
2. Name the class RegisterUserModel and click the Add button.
3. Add a string property called Email and decorate it with the Required,
EmailAddress, and Display attributes.
[Required]
[EmailAddress]
[Display(Name = “Email”)]
public string Email { get; set; }
4. Now do the same for a property called Name but use the StringLength attribute
instead of the EmailAddress attribute. The StringLength attribute should specify
a maximum of 30 characters and a minimum of 2.
[Required]
[Display(Name = “Name”)]
[StringLength(30, ErrorMessage = “The {0} must be at least {2} characters long.”, MinimumLength = 2)]
public string Name { get; set; }
The complete code for the RegisterUserModel class:
public class RegisterUserModel
{
[Required]
[EmailAddress]
[Display(Name = “Email”)]
public string Email { get; set; }
[Required]
[Display(Name = “Name”)]
[StringLength(30, ErrorMessage =
“The {0} must be at least {2} characters long.”,
MinimumLength = 2)]
public string Name { get; set; }
[Required]
[StringLength(100, ErrorMessage =
“The {0} must be at least {2} characters long. Have 1 non letter,
1 digit, 1 uppercase (‘A’-‘Z’).”, MinimumLength = 6)]
[DataType(DataType.Password)]
[Display(Name = “Password”)]
public string Password { get; set; }
[Required]
public bool AcceptUserAgrrement { get; set; }
}
Authentication check and calling the partial view
Because this panel should only be visible for non-logged in users, you have to perform an
authentication check before rendering the partial view. You can achieve this by checking
the IsAuthenticated property on the User.Identity object. The call to the partial view
should be surrounded by an if-statement checking that property.
So far, the _ThumbnailAreaPartial partial view is displayed if the user is logged in and a
the text Please register or login is displayed if the visitor hasn’t logged in.
<div class=“row”>
@if (User.Identity.IsAuthenticated)
{
foreach (var area in Model)
{
@Html.Partial(“_ThumbnailAreaPartial”, area)
}
}
else
{
<!—left column—>
<div class=“col-lg-9 col-md-8 col-sm-7”>
<h2>Please register or login</h2>
</div>
<!—right column—>
<div class=“col-lg-3 col-md-4 col-sm-5”>
<!—Add the Register User Panel here—>
</div>
}
</div>
1. Locate the right column in the else-block close to the end of the of the Index view
in the Home folder.
2. Use Razor syntax to render a partial view called _RegisterUserPartial which you
will create shortly.
<!—right column—>
<div class=“col-lg-3 col-md-4 col-sm-5”>
@Html.Partial(“_RegisterUserPartial”, new RegisterUserModel {
Email = ””, Name = ””, Password = ”” })
</div>
Add the _RegisterUserPartial view
Separating the register user functionality from the rest of the Index view using a partial
view will make it easier to update only that specific area; Add a partial view for the
register panel called _RegisterUserPartial to the Views-Shared folder.
The first thing you will do in the partial view is to add and style the panel which will hold
the input controls. In upcoming sections, you will add the panel’s intrinsic controls and
style them.
You can do your own styling if you like or you can stick with the styling described in this
chapter.
The panel you add has to be placed inside a <div> decorated with a class called register-
user-panel class. The <div> will be used when populating the partial view with a server
response from the asynchronous Ajax call made to the server (will be implemented in an
upcoming section).
You can go to the Bootstrap web site and copy the code for a panel component with a
header and title and paste it in to the <div> element you just created. This panel will be
the start of the register panel. The only class you have to change is panel-default to
panel-primary. Then change the title to Register For A Free Account or whatever you
want the heading to say. Add the text Panel Content to the <div> element decorated with
the panel-body class.
The image below shows what the panel looks like so far:
Add the partial view
1. Right-click on the Views-Shared folder and select Add-View in the context menu.
2. Enter the name _RegisterUserPartial in the View name textbox.
3. Select Empty in the Template drop-down.
4. Select RegisterUserModel in the Model class drop-down.
5. Delete the text from the Data context class drop-down.
6. Check the Create as a partial view checkbox.
7. Click on the Add button.
Add the panel to the view
1. Add a <div> element decorated with a class called register-user-panel to the
partial view below the @model directive.
2. Either type in the code for a panel with a header and title by hand or copy the code
from the Bootstrap web site and paste it into the <div>. Change the panel-default
class to panel-primary. Change the panel title to Register For A Free Account and
the panel title to a <h4> element. Add the text Panel Content to the panel-body
element.
3. Run the application and log out to view the result.
The _RegisterUserPartial view markup so far:
@model Memberships.Models.RegisterUserModel
<div class=”register-user-panel”>
<div class=”panel panel-primary”>
<div class=”panel-heading”>
<h4 class=”panel-title”>
Register For A Free Account
</h4>
</div>
<div class=”panel-body”>
Panel Content
</div>
</div>
</div>
Adding controls to the Register User panel
Now it’s time to make the panel useful to the user by adding controls to it. Since you will
make an Ajax call to the Account controller’s RegisterUserAsync action, you can add the
panel controls in a form. If you look at the image of the panel, you can see that it holds
three textboxes decorateds with glyph icons, a checkbox, some text with a link and a
button. The first textbox is for the user’s first name, the second is for an email and the
third for a password.
The checkbox must be checked by the user to enable the button; this ensures that he or she
has read and agree to the user agreement reachable by the link. In this example, the link is
unused but you could easily redirect the user to a separate page or tab in the browser when
clicked.
Glyph icons are used to decorate the textboxes with descriptive icons and placeholders
instead of space-consuming labels. Glyph icons have to be added as classes to an empty
<span> element.
Adding the controls
1. Open the _RegisterUserPartial view and replace the Panel Content text with a
postable form.
@using (Html.BeginForm(“RegisterUserAsync”, “Account”, FormMethod.Post, new { @class = “form-
horizontal”, role = “form” })) { … }
2. The first thing you need to add inside the form is an AntiForgeryToken to make it
harder to send fraudulent data to the server.
@Html.AntiForgeryToken()
3. Add a summary of any errors and validation violations that occurs; an example
would be that data is left out from one or more of the textboxes or if the data does
not conform to the validation for a particular control.
@Html.ValidationSummary(false, ””, new { @style = ”” })
4. Place the first textbox for the user’s first name below the validation summary. In
order to display an icon with the textbox, you have to wrap it and the icon in a
<div> element decorated with the input-group class. You can use the
@Html.TextBoxFor extension method along with the Name property of the model
to add the textbox inside the <div> element. You can decorate the textbox with the
form-control class to make it look nicer. Add the placeholder attribute to display
the text “First Name” as descriptive text inside the textbox. The glyphicon <span>
must be decorated with the input-group-addon class to place it beside the textbox.
Lastly you need to add a class called first-name to the textbox to makeit easier to
target it from CSS and JavaScript.
<div class=“input-group input-group-md”>
<span class=“input-group-addon glyphicon
glyphicon-user”></span>
@Html.TextBoxFor(m => m.Name, new { @class = “form-control
first-name”, @placeholder = “First Name” })
</div>
5. Repeat step 4 and 5 for the Email property in the model and change the icon to
glyphicon-envelope, the placeholder text to Email and the class to email.
6. Repeat step 4 and 5 for the Password property in the model and change the icon to
glyphicon-lock, the placeholder text to New Password and the class to password.
7. Add a checkbox which will enable and disable the registration button below the
three textboxes. By checking the checkbox, the user agrees to the user agreement
available through the link placed in the text beside the checkbox. Use the
@Html.CheckBoxFor extension method to add the checkbox targeting the
AcceptUserAgreement property of the model.
<div class=”register-checkbox”>
@Html.CheckBoxFor(m => m.AcceptUserAgreement,
new { @data_register_user_agreement_click = “” })
<span>I accept the @Html.ActionLink(“Terms of use”,
“TermsOfUse”, “Legal”).</span>
</div>
8. The last control in the panel is the register button. Add the pull-right class (to send
the button to the right side of the container), the btn and btn-success classes to
style it as a Bootstrap success (green) button and the disabled class to disable the
button when the Index page loads.
<button type=”button” class=”pull-right btn btn-success disabled”>
Sign me up!
</button>
Replace the RedirectToAction return with the _RegisterUserPartial view you created
earlier in this chapter, use the PartialView method to render the partial view.
Return PartialView(“_RegisterUserPartial”, model);
Since you only want to render the partial view and not the whole Index view, you have to
replace the View method call at the end of the RegisterUserAsync action to the same
PartialView call as described above.
Copy the AddErrors method, paste the copy into the region you created earlier in this
section and rename the method AddUserErrors.
You want to exclude the check for errors stating that the Name … is already taken in the
method since that does not apply to this solution. To achieve this, you have to add an if-
statement ignoring these validation errors in the AddUserErrors method; the default
implementation does not allow the same name twice.
if (error.StartsWith(“Name”) && error.EndsWith(“is already taken.”))
continue;
ModelState.AddModelError(””, error);
You can also delete all the comments in the RegisterUserAsync action since you won’t be
using email confirmation.
The AddUserErrors method
The AddUserErrors method is used to create a list of all errors that occur during a call to
the RegisterUserAsync action call. You can modify it to exclude errors that have to do
with the name in the UserName column, since it always will contain the user’s email;
earlier you added a non-unique field called FirstName to store the first name of the user
during registration.
1. Locate and copy the AddErrors method in the AccountController class.
2. Paste in the region you created for the RegisterUserAsync action and change its
name to AddUserErrors.
3. Add an if-statement excluding errors containing the text “Name” and “is already
taken” above the ModelState.AddModelError method call. You can simply call
the continue command to skip to the next error in the loop.
if (error.StartsWith(“Name”) && error.EndsWith(“is already taken.”)) continue;
ModelState.AddModelError(””, error);
7. If you want to assign default subscriptions to every registered user, then you can
call the RegisterUserSubscriptionCode in the SubscriptionExtensions class that
you added in a previous chapter.
await SubscriptionExtensions.RegisterUserSubscriptionCode(“Free”, user.Id);
9. Replace the call to the AddErrors method with a call to the AddUserErrors
method.
10. Change the View call to a call to PartialView rendering the _RegisterUserPartial
view passing in the model as its parameter.
return PartialView(“_RegisterUserPartial”, model);
4. Open the _Layout view and locate the last @Scripts.Render method call at the
end of the view and add a new one below that one for the membership bundle if it
does not already exist.
@Scripts.Render(”~/bundles/membership”)
Wire up the checkbox click event function
1. The first thing you will have to add in the JavaScript file is the Document Ready
function; its content will be loaded when the web page (view) is loaded into the
browser but before the page is displayed to the user. The short-hand JQuery
notation is used for the Ready function. Note that this JavaScript file has to be
loaded after the JQuery library in the _Layout view, more on that later.
$(function () {
/*Your JavaScript code goes here*/
});
2. Next, you will wire up the click event for the checkbox which will toggle the
button’s enabled/disabled state. You cache it in a variable for easy access and reuse
in the onRegisterUserClick method which you will add later.
var registerUserCheckBox = $(“#AcceptUserAgreement”).click(
onToggleRegisterUserDisabledClick);
4. If you run the application and click the checkbox, the button should toggle between
being disabled and enabled. If you click the button, nothing will happen because
you haven’t implemented the Ajax call to the controller action yet.
The complete code for the checkbox:
var registerUserCheckBox = $(“#AcceptUserAgreement”).click(
onToggleRegisterUserDisabledClick);
/* User Agreement Check Box */
function onToggleRegisterUserDisabledClick() {
$(“.register-user-panel button”).toggleClass(“disabled”);
}
Adding the Ajax action call
This is what you have been working towards in this chapter – making the button actually
perform a task. Here, you will wire up the button’s click event function and make the call
to the AccountController‘s RegisterUserAsync action which will add a new user to the
AspNetUser database table.
Wire up the button click event function
1. Add a new variable called registerUserButton below the previously added
variable, assign the button click event function for easy access and reuse. Make the
click event function call a function called onRegisterUserClick, which you will
add shortly. Use the register-user-panel class and the button element selector to
target it.
var registerUserButton = $(“.register-user-panel button”).click(
onRegisterUserClick);
2. Add a function called onRegisterUserClick below the other function you created
earlier.
function onRegisterUserClick() {
/*Your JavaScript code and Ajax call goes here*/
};
3. Add a variable called url inside the function which stores the URL used when
navigating to the action method with Ajax.
var url = “/Account/RegisterUserAsync”;
4. Next fetch the anti-forgery token from the hidden field created by the
@Html.AntiForgeyToken method call in the panel HTML markup.
var antiforegry = $(’[name=“__RequestVerificationToken”]’).val();
5. Target the input boxes in the panel with their respective name classes to fetch their
values and store them in variables.
var name = $(‘.register-user-panel .first-name’).val();
var email = $(‘.register-user-panel .email’).val();
var pwd = $(‘.register-user-panel .password’).val();
6. Add the Ajax call to the controller action using the $.post method passing in the
url variable as the first parameter and the other variables as named parameters in
an anonymous object as the second parameter.
$.post(url, { __RequestVerificationToken: antiforegry, email: email, name: name, password: pwd,
acceptUserAgrrement: true },
function (data) {
//Code executed when returning from a successful action call
}).fail(function (xhr, status, error) {
//Error code goes here
});
7. The data parameter in the “success” function of the Ajax call will contain the
rendered partial view returned from the action. To search for potential errors in the
returned HTML, you first have to parse the data to HTML markup using the
pasrseHTML JQuery method.
var parsed = $.parseHTML(data);
8. You can then use the parsed data to find out if it contains any errors using a regular
expression to remove line breaks from the [data-valmsg-summary ] attribute and
then checking its length. If the length is greater than 0, the view has errors.
hasErrors = $(parsed).find(“[data-valmsg-summary]”).text()
.replace(/\n|\r/g, ””).length > 0;
9. Use an if-else statement to check if the view has errors. If it does, then replace the
HTML markup in the partial view <div> element with the register-user-panel
class to re-display the partial view and its errors. Then wire up the button and
checkbox click event functions again and remove the disabled CSS class from the
button to make it clickable.
If (hasErrors == true) {
$(“.register-user-panel”).html(data);
registerUserButton = $(“.register-user-panel button”).click(
onRegisterUserClick);
registerUserCheckBox = $(“#AcceptUserAgreement”).click(
onToggleRegisterUserDisabledClick);
$(“.register-user-panel button”).removeClass(“disabled”);
}
10. If no errors were found, then re-register the click event functions for the button and
the checkbox and redirect to the /Home/Index action to update the page for the
logged in user (a registered user automatically gets logged in by the framework).
else {
registerUserButton = $(“.register-user-panel button”).click(
onRegisterUserClick);
registerUserCheckBox = $(“#AcceptUserAgreement”).click(
onToggleRegisterUserDisabledClick);
location.href = ‘/Home/Index’;
}
11. If you run the application, you should now be able to register a user using the panel
controls. Note that the panel disappears when the Index view is re-rendered after
successful registration.
The complete code for the RegisterUser.js JavaScript file:
$(function () {
var registerUserButton = $(“.register-user-panel button”).click(
onRegisterUserClick);
var registerUserCheckBox = $(“#AcceptUserAgreement”).click(
onToggleRegisterUserDisabledClick);
/* User Agreement Check Box */
function onToggleRegisterUserDisabledClick() {
$(“.register-user-panel button”).toggleClass(“disabled”);
}
/* Register User Button*/
function onRegisterUserClick() {
var url = “/Account/RegisterUserAsync”;
var antiforegry = $(‘[name=“__RequestVerificationToken”]’).val();
var name = $(‘.register-user-panel .first-name’).val();
var email = $(‘.register-user-panel .email’).val();
var pwd = $(‘.register-user-panel .password’).val();
$.post(url, { __RequestVerificationToken: antiforegry, email:
email, name: name, password: pwd, acceptUserAgrrement: true },
function (data) {
var parsed = $.parseHTML(data);
hasErrors = $(parsed).find(“[data-valmsg-summary]”).text()
.replace(/\n|\r/g, ””).length > 0;
if (hasErrors == true) {
$(“.register-user-panel”).html(data);
registerUserButton = $(“.register-user-panel button”)
.click(onRegisterUserClick);
registerUserCheckBox = $(“#AcceptUserAgreement”).click(
onToggleRegisterUserDisabledClick);
$(“.register-user-panel button”).removeClass(
“disabled”);
}
else {
registerUserButton = $(“.register-user-panel button”)
.click(onRegisterUserClick);
registerUserCheckBox = $(“#AcceptUserAgreement”).click(
onToggleRegisterUserDisabledClick);
location.href = ‘/Home/Index’;
}
}).fail(function (xhr, status, error) {
//Error handling code goes here
});
}
});
15. Login
Introduction
In this chapter you will add a login panel made visible when hovering over the Log in
link. The panel should have a close button defined by the glyphicon-remove icon in the
upper right corner, two textboxes for email and password, a checkbox for the option to
keep the user logged in and a Log in button calling the LoginAsync action you will add to
the Account controller. The textboxes should change background color if the log in is
unsuccessful.
Change the panel’s header and the Log in button’s background color to a dark blue
(#4765A0). Make the button’s background color 75% opaque when hovering over the
button by changing its alpha channel to 0.75 using the rgba CSS function.
background-color:rgba(71, 101, 160, 0.75);
Instead of using a partial view to create an anchor tag with a glyphicon for the Log in link,
you will create an extension method that can be used with @Html helper; it should render
an <a> element containing a <span> for the glyphicon and a link text. The extension
method should act on object of the HtmlHelper class.
The login panel should be created as a partial view rendered from the _LoginPartial view
when the visitor hovers over the Log in link and hasn’t logged in.
The call to the server should be done using Ajax and the necessary events for the panel’s
intrinsic controls should be wired up using JavaScript. Wire up the hover and click events
in a JavaScript file called login.js and add any JavaScript necessary for the log in panel to
it.
All CSS should be added to a style sheet called login.css.
When the user has successfully logged in, the Log in and pwd links will be relpaced by
the user’s first name and a red Log off button.
The GlyphLink extension method
This extension method will render an anchor tag (<a>) containing a <span> element for
the glyphicon and the link text. The method must take one HtmlHelper declared with the
this keyword and six string prameters called controller, action, text, glyphicon,
cssClasses and id where the two last parameters have empty strings as default values.
Name the method GlyphLink and have it return a MvcHtmlString and act on the
HtmlHelper class which makes it possible to access it through the @Html helper class.
Use the TagBuilder class to create the <a> element inside the method.
1. Add a public static class called HtmlExtensions to the Extensions folder.
2. Add a public static method called GlyphLink to the class. It should take one
HtmlHelper parameter declared with the this keyword and six string prameters.
public static MvcHtmlString GlyphLink(this HtmlHelper htmlHelper,
string controller, string action, string text, string glyphicon, string cssClasses = ””, string id = ””) { … }
3. Use String.Format to create a variable called glyph for the <span> element with
its glyph CSS classes.
var glyph = string.Format(“<span class=‘glyphicon glyphicon-{0}’></span>”, glyphicon);
5. Add the href attribute to the <a> element and use the controller and action
parameters to create a destination URL.
anchor.MergeAttribute(“href”, string.Format(“/{0}/{1}/”, controller, action));
6. Use String.Format to add the <span> and the link text to the InnerHtml property
of the <a> element making it part of its content. Use the glyph variable and the
text parameter.
anchor.InnerHtml = string.Format(“{0} {1}”, glyph, text);
7. Add the CSS classes in the cssClasses parameter to the <a> element.
anchor.AddCssClass(cssClasses);
8. Add an id to the <a> element using the id parameter and the GenerateId method.
anchor.GenerateId(id);
Remove the Register link; it is no longer needed since new users can register with the
registration panel you added in a previous chapter.
Add two using statements to the view one for the Extensions folder and one for the
Models folder. You need the first using to get access to the GlyphLink extension and the
second to get access to the LoginViewModel.
Replace the Log in action link with a call to the GlyphLink extension method; pass in
Account for the controller parameter, Login for the action parameter, Log in for the text
parameter, user for the glyphicon parameter, an empty string for the cssClasses parameter
and loginLink for the id parameter.
Add a <div> element which is hidden for extra small devices below the GlyphLink
method call. Add a data- attribute called login-panel-partial to the <div>; this attribute
will be used to repopulate the _LoginPanelPartial partial view which will contain the
login panel.
You will create the _LoginPanelPartial partial view in the next section, but you will add
the markup to render it now. Use the Partial extension method in the last <div> you
added to render the partial view. Pass an instance of the LoginViewModel with empty
string values as a second parameter to the Partial method.
1. Open the _LoginPartial view in the Views-Shared folder.
2. Add two using statements to the view one for the Extensions folder and one for
the Models folder.
@using Memberships.Extensions
@using Memberships.Models
3. Add the area property to the form’s routeValues parameter and assign an empty
string to it. This will target the main project’s route and make it possible to logout
from both user interfaces; the user interface and the admin user interface located in
the Admin area.
using (Html.BeginForm(“LogOff”, “Account”, new { area = ”” }, FormMethod.Post, new { id =
“logoutForm”, @class = “navbar-right”}))
4. Add the area property to the manage link’s routeValues parameter and assign an
empty string to it.
@Html.ActionLink(“Hello “ + User.Identity.GetUserFirstName() + “!”, “Index”, “Manage”, routeValues: new
{ area = ”” }, htmlAttributes: new { title = “Manage” })
5. Change the Log off link to a “danger” button using Bootstrap classes and add a
data- attribute for targeting.
<li><a class=“btn btn-danger” data-logout-button href=“javascript:
document.getElementById(‘logoutForm’).submit()”>Log off</a></li>
8. Add a call to the GlyphLink extension method to create a Log in link with a
glyphicon where the previous Log in link was located.
@Html.GlyphLink(“Account”, “Login”, “Log in”, “user”, ””, “loginLink”)
9. Add a <div> element which is hidden for extra small devices below the
GlyphLink method call and add a data- attribute called login-panel-partial to it.
<div class=“hidden-xs” login-panel-partial>
10. Use the Partial extension method to render the partial view. Pass in an instance of
the LoginViewModel with empty string property values.
@Html.Partial(“_LoginPanelPartial”, new LoginViewModel { Email = ””, Password = ””, RememberMe =
false })
Change the button’s background color to orangered when the user hovers over it with the
mouse. To achieve this, you will have to use a much more specific targeting to outrank
CSS classes added by Bootstrap.
.nav > li > a[data-logout-button]:hover {
color:white;
background-color:orangered;
border-color:orangered;
}
Adding the _LoginPanelPartial view
To display the login panel as a pop-up, you will create it as a patial view called _Login-
PanelPartial which is rendered when the user hovers over the Log in link and stays open
either until the user closes it with the close button or when a successful login has been
achieved.
The view should have two textboxes; one for the email address and one for the password.
It should also have a checkbox that the user can check to remain logged in the next time
he or she visits the site. A close button displaying the glyphicon-remove icon should be
available in the top right corner of the panel’s heading area. When the Log in button
located in the lower right corner of the panel body is clicked, an action called LoginAsync
in the Account controller should be called using Ajax.
The panel body needs to contain an AntiForgeryToken to make secure calls to the server
and a ValidationSummary to handle any errors that might occur.
Use the TextBoxFor, PasswordFor and CheckBoxFor helper methods to create the
controls in the panel’s body area and sync them with the corresponding properties in the
LoginViewModel model object in the view.
Creating the view
The image below shows the view settings for creating the _LoginPanelPartial view.
2. Add a Bootstrap panel with a heading and a body inside the <div> element.
<div class=“panel”>
<div class=“panel-heading”></div>
<div class=“panel-body”></div>
</div>
3. Add the text Log In and an <a> element with a <span> element for the glyphicon-
remove icon to the panel heading <div>.
Log In<a id=“close-login” class=“pull-right”>
<span class=“glyphicon glyphicon-remove”></span></a>
8. Repeat step 5-7 for the password textbox. Don’t forget to change the icon to
glyphicon-lock, the class to password and the property to m.Password.
9. Add a <div> element decorated with a class called remember-me-checkbox and
add the checkbox element to it followed by the text Keep me logged in and the Log
in button.
<div class=“remember-me-checkbox”>
@Html.CheckBoxFor(m => m.RememberMe)
<span>Keep me logged in</span>
<button id=“login-button” type=“button” class=“pull-right
btn btn-default” data-login-action=”/Account/LoginAsync”
data-login-return-url=”/Home/Index”>
Log in
</button>
</div>
The image below shows what the panel would look like if displayed without any addi-
tional CSS styling. Needless to say, it could do with a make-over, which you will provide
in the next section.
The complete markup for the _LoginPanelPartial view:
@model Memberships.Models.LoginViewModel
<div class=“dropdown-menu dropdown-panel” data-login-user-area>
<div class=“panel”>
<div class=“panel-heading”>
Log In<a id=“close-login” class=“pull-right”>
<span class=“glyphicon glyphicon-remove”></span></a>
</div>
<div class=“panel-body”>
@Html.AntiForgeryToken()
@Html.ValidationSummary(false, ””, new { @class = “text-danger
hidden” })
<div class=“input-group”>
<span class=“input-group-addon glyphicon
glyphicon-envelope”></span>
@Html.TextBoxFor(m => m.Email, new { @class = “form-control
email”, @placeholder = “Email” })
</div>
<div class=“input-group”>
<span class=“input-group-addon glyphicon
glyphicon-lock”></span>
@Html.PasswordFor(m => m.Password, new { @class =
“form-control password”, @placeholder = “New Password”})
</div>
<div class=“remember-me-checkbox”>
@Html.CheckBoxFor(m => m.RememberMe)
<span>Keep me logged in</span>
<button id=“login-button” type=“button” class=“pull-right
btn btn-default” data-login-action=”/Account/LoginAsync”
data-login-return-url=”/Home/Index”>
Log in
</button>
</div>
</div>
</div>
</div>
Adding JavaScript and CSS to display the login panel
To make the login panel pop-up, you’ll have to wire up the hover event for the Log in link
in the login.js file. The panel will be displayed when the open class has been added to the
<div> surrounding the panel and a CSS selector for the open class has been added to the
login.css file and the display property has been added to it displaying the <div> as a
block.
Wire up the hover event to the Log in link and have it call a function named onLogin-
LinkHover which adds the open class to the <div> decorated with the [data-login-user-
area] attribute. Store the delegate in a variable called loginLinkHover.
$(function () {
// Wire up the hover event
var loginLinkHover = $(“#loginLink”).hover(onLoginLinkHover);
// The function called by the hover event
function onLoginLinkHover() {
$(‘div[data-login-user-area]’).addClass(‘open’);
};
});
Now that the hover event is wired up, you have to add a CSS selector for the <div> with
the open class added.
Open the login.css style sheet and add a selector for the <div> with the open class and
display it as a block. Also remove the top padding, add 3px bottom padding and a 10px
right margin.
[data-login-user-area].open {
display:block;
padding-top: 0px;
padding-bottom: 3px;
margin-right:10px;
}
Run the application and hover over the Log in link to open the panel. Close the panel
again using the X-button in the upper right corner of the panel. You don’t have to close the
application when adding new styles; keep it running and the changes will be visible as
soon as they are added.
Styling the login panel
The form is a bit lackluster as it stands right now, but with a few styling modifications that
can be remedied. Open the login.css file and let’s add some CSS selectors and properties
to make the login panel pop.
The panel
Add a selector to the <div> surrounding the panel and change the border color to a dark
blue (#4765A0).
[data-login-user-area] {
border-color:#4765A0;
}
Remove any rounded corners on the surrounding <div> and its intrinsic controls. The as-
terisk (*) targets all elements within the <div>.
[data-login-user-area], [data-login-user-area] * {
border-radius: 0px;
}
Remove the box shadow, add a 3px bottom margin and make the panel <div> element at
least 300px wide.
[data-login-user-area] .panel {
box-shadow: none;
margin-bottom: 3px;
min-width: 300px;
}
The image below is a before/after comparison of the login panel. Note that all elements
have sharp corners after the border radius has been removed.
The panel heading and body
Add a selector for the panel-heading <div> and change the background color to a dark
blue (#4765A0). Also change the text color to white to contrast it with the dark blue
background and add a 7px bottom margin.
[data-login-user-area] .panel-heading {
margin-bottom: 7px;
background-color:#4765A0;
color:white;
}
Change the cursor to a hand when hovering over the glyphicon-remove icon and change
the icon color to white.
[data-login-user-area] .glyphicon-remove {
cursor: pointer;
color:white;
}
Add a selector to the panel-body <div> and change the top and bottom padding to 7px.
[data-login-user-area] .panel-body {
padding-top: 7px;
padding-bottom: 7px;
}
The textboxes
First, let’s allign the icons with the textboxes and remove the background color behind the
icons and their borders.
[data-login-user-area] .glyphicon-envelope,
[data-login-user-area] .glyphicon-lock {
background-color:white;
border:none;
padding-left:0px;
}
Add a 3px top and bottom margin to the textboxes to add some space between them.
[data-login-user-area] input[type=“text”].form-control,
[data-login-user-area] input[type=“password”].form-control {
margin-bottom:3px;
margin-top:3px;
}
Add a selector for class called data-login-error that can be added to the textboxes with
JavaScript, changing their background color to a light red if the login fails when the user
clicks the Log in button. This style will not be applied before the Ajax call to the server
has been implemented.
.data-login-error {
background-color:rgba(249, 224, 223, 1);
border-color:rgba(208, 89, 94, 1);
}
The Log in button
The Log in button need a 3px top margin to add a little more space between it and the
password textbox. Also change its background and border color to the same blue color
used in the panel heading (#4765A0) and its text color to white.
#login-button {
margin-top: 3px;
background-color:#4765A0;
border-color:#4765A0;
color:white;
}
Use the RGBA function to make the button’s background less opaque when hovering over
it. Change the alpha channel to 0.75 (75%).
#login-button:hover {
background-color:rgba(71, 101, 160, 0.75);
}
The checkbox
The checkbox could do with a 27px left margin to align it with the textboxes and a 12px
top margin to align it vertically with the Log in button.
[data-login-user-area] input[type=“checkbox”] {
margin-top:12px;
margin-left: 27px;
}
The LoginAsync action
You need to add a new asynchronous action called LoginAsync to the Account controller
which will be called asynchronously from the client using Ajax when the user clicks the
Log in button.
Note: The async keyword on the action has nothing to do with calling it asynchronously
with Ajax; it is added to be able to use the await keyword to temporarily give back the
main thread to the application during long-running tasks.
The action should be decorated with the HttpPost, AllowAnonymous and ValidateAnti-
ForgeryToken attributes. The first attribute makes it possible to call the action during
posts from the client. The second attribute makes it possible for non-logged in visitors to
use the action. The third attribute validates the anti forgery token sent with the Ajax call to
the server to verify that the call originated on the same sever.
Two parameters are needed where the first is the LoginViewModel that will be filled with
data on the client and the second is a string holding the URL that will be called upon
successful login.
Add an if-block which is executed if the model state is valid. Add the error message
Invalid login attempt to the model state if the model state is invalid and return the
_LoginPanelPartial view with the model.
You need to use the UserManager class inside the if-block to fetch the user matching the
supplied email if it exists in the database and store the result in a variable called user.
Add an if-block below the user variable inside the previous if-block which checks that the
user variable contains a user name.
Await a call to the PasswordSignInAsync method on the SinInManager class and store
the result in a variable called result inside the inner if-block. Pass in the user name from
the user.UserName property, the model’s Password and RememberMe properties and a
parameter called shouldLockout set to false to prevent the user from being locked out if
providing wrong login credentials.
Return the _LoginPanelPartial view with the model if the result variable contains the
value SignInStatus.Success.
1. Open the AccountController class.
2. Add a region called Log in at the bottom of the class.
3. Add an async action called LoginAsync decorated with the HttpPost,
AllowAnonymous and ValidateAntiForgeryToken attributes and takes one
LoginViewModel parameter called model and a string parmeter called returnUrl.
#region Log in
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> LoginAsync(LoginViewModel model, string returnUrl)
{
}
#endregion
7. Use the UserManager class to fetch the user matching the supplied email if it
exists in the database inside the if-block. Store the result in a variable called user.
var user = UserManager.Users.FirstOrDefault(u => u.Email.Equals(
model.Email));
8. Add an if-block below the user variable inside the previous if-block which checks
that the user variable contains a user name.
9. Try to sign in to the site using the PasswordSignInAsync method on the
SinInManager class inside the inner if-block. Store the result in a variable called
result.
var result = await SignInManager.PasswordSignInAsync(
user.UserName, model.Password, model.RememberMe, shouldLockout: false);
10. Return the _LoginPanelPartial view with the model if the result variable contains
the value SignInStatus.Success.
if (result.Equals(SignInStatus.Success))
return PartialView(“_LoginPanelPartial”, model);
Make the Ajax call to the server using the $.post method passing in the values from the
variables.
Check if any errors have been reported by the server by parsing the data function para-
meter with the parseHTML function and check if the [data-valmsg-summary] attribute
is empty and store the result in variable called hasErrors.
var parsed = $.parseHTML(data);
var hasErrors = $(parsed).find(“[data-valmsg-summary]”).text().replace(
/\n|\r/g, ””).length > 0;
Keep the log in panel open if there are errors by adding the conent of the data function
parameter, which contain the panel, to the <div> with the [data-login-panel-partial]
attribute surrounding the panel calling the html method on the element.
$(“div[data-login-panel-partial]”).html(data);
Add the open class to the <div> with the [data-login-user-area] attribute to keep the log
in panel open.
Add the data-login-error class to the textboxes to change their background color to a
light red if there are errors.
If no errors have occurred and the login was successful, then remove the open class on the
panel’s container <div> and the data-login-error class from the textboxes.
Redirect to the URL in the return_url variable (/Home/Index).
location.href = return_url;
Regardless if the log in was successful or not, the link and button events have to be wired
up again using the variables you added at the beginning of this section.
loginButton = $(“#login-button”).click(onLoginClick);
loginLinkHover = $(“#loginLink”).hover(onLoginLinkHover);
loginCloseButton = $(“#close-login”).click(onCloseLogin);
Chain on the fail function on the success function to handle errors that might occur. Add
the data-login-error class to the textboxes and wire up the events again.
1. Open the login.js file.
2. Add a variable called loginButton, below the already defined variables, which is
used to wire up the click event to the Log in button in the panel. Have the event
call a function named onLoginClick.
var loginButton = $(“#login-button”).click(onLoginClick);
5. Add an Ajax call, below the variable declarations, which calls the LoginAsync
action in the Account controller.
$.post(url, { __RequestVerificationToken: antiforegry, email: email, password: pwd, RememberMe:
remember_me },
function (data) {
// Success function
}).fail(function (xhr, status, error) {
// Fail function
});
6. Check if any errors have been reported by the server by parsing the data function
parameter with the parseHTML function
var parsed = $.parseHTML(data);
8. Keep the log in panel open if there are errors by adding the content of the data
function parameter to the <div> with the [data-login-panel-partial] attribute.
$(“div[data-login-panel-partial]”).html(data);
9. Open the panel by adding the open class to the <div> with the [data-login-user-
area] attribute.
$(‘div[data-login-user-area]’).addClass(‘open’);
10. Add the data-login-error class to the textboxes to change their background color
to a light red.
$(‘#Email’).addClass(”data-login-error”);
$(‘#Password’).addClass(”data-login-error”);
11. Remove the open and data-login-error classes if the login was successful.
$(‘div[data-login-user-area]’).removeClass(‘open’);
$(‘#Email’).removeClass(“data-login-error”);
$(‘#Password’).removeClass(“data-login-error”);
13. Regardless if the log in was successful or not, the link and button events have to
tbe wired up again.
loginButton = $(“#login-button”).click(onLoginClick);
loginLinkHover = $(“#loginLink”).hover(onLoginLinkHover);
loginCloseButton = $(“#close-login”).click(onCloseLogin);
14. If an error occurs and the fail function is executed, then add the data-login-error
class to the textboxes and wire up the events again.
$(‘#Email’).addClass(“data-login-error”);
$(‘#Password’).addClass(“data-login-error”);
/* Wire up events */
loginButton = $(“#login-button”).click(onLoginClick);
loginLinkHover = $(“#loginLink”).hover(onLoginLinkHover);
loginCloseButton = $(“#close-login”).click(onCloseLogin);
15. Run the application and try to log in using a non-existing email and make sure that
the login panel stays open and the background color of the textboxes are changed
to a light red.
16. Try to login using an exisitng email and password. The user should be logged in
and the Index view in the Home controller should be displayed. The Log in link
should be replaced with a red Log out button; clicking the button should log out
the user.
The complete code for the Log in button:
var loginButton = $(“#login-button”).click(onLoginClick);
function onLoginClick() {
var url = $(‘#login-button’).attr(‘data-login-action’);
var return_url = $(‘#login-button’).attr(‘data-login-return-url’);
var email = $(‘#Email’).val();
var pwd = $(‘#Password’).val();
var remember_me = $(‘#RememberMe’).prop(‘checked’);
var antiforegry = $(‘[name= “__RequestVerificationToken”]’).val();
$.post(url, { __RequestVerificationToken: antiforegry, email: email,
password: pwd, RememberMe: remember_me },
function (data) {
var parsed = $.parseHTML(data);
var hasErrors = $(parsed).find(“[data-valmsg-summary]”).text()
.replace(/\n|\r/g, ””).length > 0;
if (hasErrors == true) {
$(“div[data-login-panel-partial]”).html(data);
$(‘div[data-login-user-area]’).addClass(‘open’);
$(‘#Email’).addClass(“data-login-error”);
$(‘#Password’).addClass(“data-login-error”);
}
else {
$(‘div[data-login-user-area]’).removeClass(‘open’);
$(‘#Email’).removeClass(“data-login-error”);
$(‘#Password’).removeClass(“data-login-error”);
location.href = return_url;
}
/* Wire up events */
loginButton = $(“#login-button”).click(onLoginClick);
loginLinkHover = $(“#loginLink”).hover(onLoginLinkHover);
loginCloseButton = $(“#close-login”).click(onCloseLogin);
}).fail(function (xhr, status, error) {
$(‘#Email’).addClass(“data-login-error”);
$(‘#Password’).addClass(“data-login-error”);
/* Wire up events */
loginButton = $(“#login-button”).click(onLoginClick);
loginLinkHover = $(“#loginLink”).hover(onLoginLinkHover);
loginCloseButton = $(“#close-login”).click(onCloseLogin);
});
};
16. Forgot Password
Introduction
In this chapter you will implement a password recovery mechanism whereby the user can
request a password rest link to his or her mailbox. This is a more secure way of resetting a
password compared to dispalying a form with a password textbox directly.
There are a few steps that you have to go through to unlock this functionality, among
which are implementing a method that sends the recovery email and defining the correct
SMTP data in the Web.Config file. Get any of these wrong and the email will not be sent.
It’s impossible to list SMTP settings for all the providers, so you will have to contact your
email provider and ask them or Google it to find the answer. You will need a from email
address and its password, the the email provider’s host name and the port they use for
SMTP traffic.
When testing the password recovery, you should use an email which isn’t connected in
any way to the from email address you specified in the Web.Config file. If the from
email address is set to forward emails to the email address you try to change the password
for, it will likely not work.
A modal dialog will be used to display the textbox and button needed to send a password
reset request and in the background JavaScript will be used to make an Ajax call to the
ForgotPassword action in the Account controller.
You’ll need to call the Send extension method you implement from the SendAsync
method in the EmailService class. This method is called by the framework to send the
reset email when a new password is requested.
The ForgotPassword action in the Account controller has some commented out code at
the end of the method that you need to uncomment in order to activate the password
recovery process.
The GlyphLink extension method you created in a previous chapter must be modified to
take a list of attributes which are added to the <a> element it generates.
The recovery process:
1. The user clicks the pwd link in the navigation area which opens the modal form
where the email is entered.
2. When the Reset Password button is clicked an Ajax call is made to the
ForgotPassword action in the Account controller.
3. After the model is verified as valid, the user is fetched from the database to make
sure that a user with that email exists.
1. If the user doesn’t exist, the user is redirected to the
ForgotPasswordConfirmation view. It’s a security measure, not to show
that the password recovery failed.
2. If the user exists, an email with a recovery link is sent to the provided email
address.
4. When the user clicks the link in the recovery email, the ResetPassword view is
displayed where the user enters the email address and the new password.
5. When the user clicks the Reset button in the ResetPassword view the new
password is stored in the database and the ResetPasswordConfirmation view is
displayed.
6. The user can now login with the new password.
Sending email
The first thing you need to do is to get the SMTP settings for the email address you want
to send the email from. Contact the provider or Google it. You need the following infor-
mation:
The email address to send the emails from.
The password for the email address.
The host name; it usually starts with smtp like smtp.gmail.com.
You can read more about Gmail’s SMTP settings here
(https://support.google.com/a/answer/176600?hl=en) or search for Gmail SMTP to
find the proper settings outlined.
The port number used by the provider to send email, which as of this writing is port
587 for Gmail and many other providers.
Web.Config
Add the information to the Web.Config file’s <appSettings> element.
<appSettings>
…
<!—Email settings—>
<add key=”from” value=”[email protected]” />
<add key=”host” value=”smtp01.prvider.com” />
<add key=”port” value=”587” />
<add key=”password” value=“—the email password—” />
<!—End Email settings—>
</appSettings>
The Send extension method
This method is called when the recovery email is sent. One way to send emails will be
described in this section, it is however possible to implement it differently if you are so
inclined.
You will implement the Send method as an extension of the IdentityMessage class which
is used to define an email message.
Read the email settings from the Web.Config file’s <appSettings> element and store
them in variables called from, pasword, host and port.
var password = ConfigurationManager.AppSettings[“password”];
Use an instance of the MailMessage class to create the email that will be sent and call the
Send method on an instance of the SmtpClient to send the email.
1. Right-click on the Extensions folder and select Add-Class.
2. Name the class EmaiExtensions.
3. Add the following using statements.
using Microsoft.AspNet.Identity;
using System.Configuration;
using System.Net.Mail;
4. Make the class static and add a static void method called Send that uses the this
keyword to target instances of the IdentityMessage class.
public static void Send(this IdentityMessage message) { … }
5. Fetch the four settings for the email from the Web.Config file.
var password = ConfigurationManager.AppSettings[“password”];
var from = ConfigurationManager.AppSettings[“from”];
var host = ConfigurationManager.AppSettings[“host”];
var port = Int32.Parse(ConfigurationManager.AppSettings[“port”]);
6. Use an instance of the MailMessage class and the data in the message parameter to
create the email that will be sent.
var email = new MailMessage(from, message.Destination, message.Subject, message.Body);
email.IsBodyHtml = true;
7. Create the SmtpClient instance that will send the email to the recipient.
var client = new SmtpClient(host, port);
client.EnableSsl = true;
client.Credentials = new System.Net.NetworkCredential(from, password);
8. Add a try/catch-block around the code in the method to prevent any errors from
crashing the application. I suggest that you add an Exception parameter and a
breakpoint to the catch-block during debugging for easy access to the error
messages.
try
{
…
client.Send(email);
}
catch(Exception ex)
{
}
3. Add an if-statement before the href attribute assignment which checks that the
controller parameter contains a value. Add an else-statement after the previous
href assignment and assign # to the href.
if(controller.Length > 0)
anchor.MergeAttribute(“href”, string.Format(“/{0}/{1}/”,
controller, action));
else
anchor.MergeAttribute(“href”, “#”);
4. Add an if-statement below the href assignment which checks if the attributes
parameter is not null and iterate over the attributes in the attributes parameter
adding them one at a time to the <a> element.
if(attributes != null)
foreach(var attribute in attributes)
anchor.MergeAttribute(attribute.Key, attribute.Value);
4. Run the application and make sure that he pwd link is displayed properly with its
lock icon.
Building the modal form
To create the modal form, you need to add two <div> elements where the first defines the
modal dialog and the second specifies where the form area begins. The first element
should be decorated with the modal class and have the role attribute set to dialog. It can
also be decorated with the fade class to make the dialog fade in and out. The second
element should be decorated with the modal-dialog class to specify that it is the container
for the dialog content.
Within the modal-dialog container, you add the elements that will be part of the dialog. In
this exercise, you will use a Bootstrap panel to which you add a link decorated with the
glyphicon-remove which will close the dialog, a textbox for the email address, the Reset
Password button which will send a password recover email to the user and a support
email link at the bottom.
2. Add a <div> decorated with the modal-dialog class inside the previous <div>.
<div class=“modal-dialog”>
3. Add a Bootstrap panel-primary panel with heading and body elements to the inner
<div>.
<div class=“panel panel-primary”>
<div class=“panel-heading”>
</div>
<div class=“panel-body”>
</div>
</div>
4. Add an <h4> element containing the text Forgot your password? and an <a>
element displaying a glyphicon-remove icon using a <span>. The <a> element
should be pulled to the far right of the panel heading by decorating it with the pull-
right class. It also should have a data-dismiss attribute set to modal to close the
dialog when clicked and an id of close-forgot-password.
<h4>Forgot your password?
<a id=“close-forgot-password” class=“pull-right”
data-dismiss=“modal”>
<span class=“glyphicon glyphicon-remove”></span>
</a>
</h4>
6. Add a paragraph below the anti-forgery token with the text: Enter the email
address associated with your account to receive instructions on how to reset your
password.
7. Add a <div> decorated with the input-group class which encompasses the
envelope glypicon, the textbox and the Reset Password button. The <span> for
the glyphicon must also be decorated with the input-group-addon for it to stick to
the textbox as If they were one control. The textbox should be decorated with the
form-control and reset-email classes, the latter for CSS and JavaScript targeting.
The <button> element should have an id of resetPwd.
<div class=“input-group”>
<span class=“input-group-addon glyphicon
glyphicon-envelope”></span>
@Html.TextBoxFor(m => m.Email, new { @class = “form-control
reset-email”, @placeholder = “Enter Your Email” })
<button id=“resetPwd” class=“btn btn-primary” type=“button”>
Reset Password
</button>
</div>
8. Add a <div> decorated with a class called support-text for CSS targeting.
9. Add the text Having trouble? send an email to our support followed by a link to an
email address.
10. Open the Index view in the Home folder. Render the view using the
@Html.Partial method below the _RegisterUserPartial view.
@Html.Partial(“_ForgotPaswordPanelPartial”,
new ForgotPasswordViewModel { Email = ”” })
The panel heading need the top margin removed and less top- and bottom padding.
.modal-dialog .panel-heading {
margin-top: 0px;
padding-top: 1px;
padding-bottom: 1px;
}
The close button must have 100% opacity to be displayed as white. It could also benefit
from the same font size as the heading (18px) and a negative 2px top margin for vertical
positioning. Also, change the mouse pointer to a hand pointer.
#close-forgot-password {
opacity: 1;
font-size:18px;
margin-top:-2px
color: white;
cursor: pointer;
}
To align the glyphicon-envelope icon background with the textbox, it needs a 0px top
positioning.
.modal-dialog .glyphicon-envelope {
top:0px;
}
The Reset Password button needs a -4px left margin to be displayed flush to the right side
of the textbox.
#resetPwd{
margin-left:-4px;
}
The support text needs a 10px top margin to get some space from the controls above it.
.modal-dialog .support-text {
margin-top:10px;
}
Add this CSS if the backdrop blocks the modal input form.
.modal-backdrop {
position: static;
}
Adding JavaScript to the modal dialog
Apply the following JavaScript in the forgot-password.js file.
Add the document ready function to the script file and add the subsequent code to it.
$(function () {
});
Hook up the hover event to the pwd link to close the Log in pop-up if it is open.
var pwdLinkHover = $(“#pwdLink”).hover(onCloseLogin);
function onCloseLogin() {
$(‘div[data-login-user-area]’).removeClass(‘open’);
}
Hook up the click event for the Reset Password (#resetPwd) button and have it call the
/Account/ForgotPassword action with the anti-forgery token and the email address.
When the Ajax call to the server returns to the client, redirect to the /Account/Forgot-
PasswordConfirmation action even if an error has ocurred. This will take the the user to
a page displaying a confirmation message.
On the server side, an attempt is made to send an email to the email address provided to
the ForgotPassword action. Below the code is a sequence of images showing what
happens when a successful password reset is completed.
The complete code for the forgot-password.js file:
$(function () {
var pwdLinkHover = $(“#pwdLink”).hover(onCloseLogin);
var resetPwd = $(“#resetPwd”).click(onResetPassword);
function onCloseLogin() {
$(‘div[data-login-user-area]’).removeClass(‘open’);
}
function onResetPassword() {
var email = $(“.modal-dialog .reset-email”).val();
var antiforegry = $(‘[name= “__RequestVerificationToken”]’).val();
var url = “/Account/ForgotPasswordConfirmation”;
$.post(“/Account/ForgotPassword”, { __RequestVerificationToken:
antiforegry, email: email },
function (data) {
location.href = url;
}).fail(function (xhr, status, error) {
location.href = url;
});
}
});