Dot Net Merged
Dot Net Merged
An introduction
to web programming
with ASP.NET Core
MVC
<html>
<head>
<title>Example Web Page</title>
</head>
<body>
<p>This is a sample web page</p>
</body>
</html>
namespace GuitarShop.Controllers
{
public class ProductController : Controller
{
public IActionResult Detail(string id)
{
Product product = DB.GetProduct(id);
return View(product);
}
namespace GuitarShop
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
}
How to develop
a single-page
web app
namespace FutureValue.Controllers
{
public class HomeController : Controller
{
public IActionResult Index()
{
ViewBag.Name = "Mary";
ViewBag.FV = 99999.99;
return View();
}
}
}
namespace FutureValue
{
public class Startup
{
// Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services) {
services.AddControllersWithViews();
}
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}
}
}
[HttpPost]
public IActionResult Index(FutureValueModel model)
{
ViewBag.FV = model.CalculateFutureValue();
return View(model);
}
}
h1 {
margin-top: 0;
color: navy;
}
label {
display: inline-block;
width: 10em;
padding-right: 1em;
}
div {
margin-bottom: .5em;
}
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>@ViewBag.Title</title>
<link rel="stylesheet" href="~/css/custom.css" />
</head>
<body>
<div>
@RenderBody()
</div>
</body>
</html>
namespace FutureValue.Models
{
public class FutureValueModel
{
[Required(ErrorMessage =
"Please enter a monthly investment.")]
[Range(1, 500, ErrorMessage =
"Monthly investment amount must be between 1 and 500.")]
public decimal? MonthlyInvestment { get; set; }
[Required(ErrorMessage =
"Please enter a yearly interest rate.")]
[Range(0.1, 10.0, ErrorMessage =
"Yearly interest rate must be between 0.1 and 10.0.")]
public decimal? YearlyInterestRate { get; set; }
How to make
a web app responsive
with Bootstrap
Knowledge
1. Explain what responsive web design is and how it’s implemented
using Bootstrap.
2. Describe how the Bootstrap grid system works.
3. Describe how to use the Bootstrap CSS classes to format and align
labels, text boxes, and buttons within a form.
4. Describe how to use the Bootstrap CSS classes to format images,
HTML tables, and text.
<script src="~/lib/jquery/jquery.min.js"></script>
<script src="~/lib/popper.js/popper.min.js"></script>
<script src="~/lib/bootstrap/js/bootstrap.min.js"></script>
<script src="~/lib/jquery-validate/
jquery.validate.min.js"></script>
<script src="~/lib/jquery-validation-unobtrusive/
jquery.validate.unobtrusive.min.js"></script>
</head>
<body>
@RenderBody()
</body>
</html>
How to develop
a data-driven
MVC web app
Knowledge
1. Describe how you can code a primary key for an entity by
convention and describe when the value for that primary key will be
automatically generated.
2. Describe how a DB context class maps related entity classes to a
database and seeds the database with initial data.
3. Name the file that’s typically used to store a connection string for a
database.
namespace MovieList.Models
{
public class MovieContext : DbContext
{
public MovieContext(DbContextOptions<MovieContext> options)
: base(options)
{ }
namespace MovieList.Models
{
public class Movie
{
// EF Core will configure the database to generate this value
public int MovieId { get; set; }
migrationBuilder.InsertData(
table: "Movies",
columns: new[] { "MovieId", "Name", "Rating", "Year" },
values: new object[] { 1, "Casablanca", 5, 1942 });
namespace MovieList.Controllers
{
public class HomeController : Controller
{
private MovieContext context { get; set; }
<h2>Movie List</h2>
[HttpGet]
public IActionResult Add() {
ViewBag.Action = "Add";
return View("Edit", new Movie());
}
[HttpGet]
public IActionResult Edit(int id) {
ViewBag.Action = "Edit";
var movie = context.Movies.Find(id);
return View(movie);
}
[HttpGet]
public IActionResult Delete(int id) {
var movie = context.Movies.Find(id);
return View(movie);
}
<h2>@ViewBag.Title</h2>
<div class="form-group">
<label asp-for="Name">Name</label>
<input asp-for="Name" class="form-control">
</div>
<div class="form-group">
<label asp-for="Rating">Rating</label>
<input asp-for="Rating" class="form-control">
</div>
<h2>Confirm Deletion</h2>
<h3>@Model.Name (@Model.Year)</h3>
modelBuilder.Entity<Genre>().HasData(
new Genre { GenreId = "A", Name = "Action" },
new Genre { GenreId = "C", Name = "Comedy" },
new Genre { GenreId = "D", Name = "Drama" },
new Genre { GenreId = "H", Name = "Horror" },
new Genre { GenreId = "M", Name = "Musical" },
new Genre { GenreId = "R", Name = "RomCom" },
new Genre { GenreId = "S", Name = "SciFi" }
);
migrationBuilder.CreateTable(
name: "Genres",
columns: table => new {
GenreId = table.Column<string>(nullable: false),
Name = table.Column<string>(nullable: true)
}, constraints: table => {
table.PrimaryKey("PK_Genres", x => x.GenreId);
});
migrationBuilder.UpdateData(
table: "Movies",
keyColumn: "MovieId",
keyValue: 1,
column: "GenreId",
value: "D");
How to manually
test and debug
an ASP.NET Core
web app
Knowledge
1. Describe how to use Visual Studio to change the default browser.
2. Describe some of the debugging features provided by the
developer tools of the major browsers.
3. Describe the conditions under which the Internal Server Error
page is displayed.
4. Describe the conditions under which the Exception Helper dialog
is displayed.
5. Distinguish between a breakpoint and a tracepoint.
How to work
with controllers
and routing
namespace GuitarShop.Controllers
{
public class HomeController : Controller
{
public IActionResult Index()
{
return Content("Home controller, Index action");
}
namespace GuitarShop.Controllers
{
public class ProductController : Controller
{
public IActionResult List(string id = "All")
{
return Content("Product controller, List action, id: "
+ id);
}
[Route("About")]
public IActionResult About()
{
return Content("Home controller, About action");
}
}
[Route("Product/{id}")]
public IActionResult Detail(int id)
{
return Content("Product controller, Detail action, ID: "
+ id);
}
[NonAction]
public string GetSlug(string name)
{
return name.Replace(' ', '-').ToLower();
}
}
With a name
https://www.domain.com/product/fender-special-edition-
standard-stratocaster
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
How to work
with Razor views
Knowledge
1. Distinguish between a Razor code block and inline expressions,
loops, and if statements.
2. Distinguish between an inline conditional statement and an inline
conditional expression.
3. Describe how an MVC web app typically maps its views to the
action methods of its controllers.
4. Describe the use of tag helpers to generate a URL.
A _ViewImports.cshtml file
that enables all ASP.NET Core MVC tag helpers
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
<a asp-action="Details"
asp-route-id="Fender-Stratocaster">
View Fender Stratocaster</a>
<div class="form-group">
<label asp-for="Price"></label>
<input asp-for="Price" class="form-control">
</div>
<h1>Product List</h1>
@if (ViewBag.Categories != null)
{
foreach (Category c in ViewBag.Categories)
{
<a asp-controller="Product" asp-action="List"
asp-route-id="@c.Name">@c.Name</a>
<text> | </text>
}
}
<a asp-controller="Product" asp-action="List"
asp-route-id="All">All</a>
<hr />
<footer>
<hr />
<p>© @DateTime.Now.Year - Guitar Shop</p>
</footer>
</body>
</html>
@RenderBody()
<footer>
<hr />
<p>© @DateTime.Now.Year - Guitar Shop</p>
</footer>
<h1>Product Manager</h1>
@if (ViewBag.Categories != null)
{
foreach (Category c in ViewBag.Categories)
{
<a asp-controller="Product" asp-action="List"
asp-route-id="@c.Name">@c.Name</a><text> | </text>
}
<a asp-controller="Product" asp-action="List"
asp-route-id="All">All</a>
}
<hr />
@RenderBody()
<footer>
<hr />
<p>© @DateTime.Now.Year - Guitar Shop</p>
</footer>
@section scripts {
<script src="~/lib/jquery-validate/jquery.validate.min.js">
</script>
<script src="~/lib/jquery-validation-unobtrusive/
jquery.validate.unobtrusive.min.js">
</script>
}
<h2>Update</h2>
// the HTML elements for the rest of the view body go here
<script src="~/lib/jquery/jquery.min.js"></script>
<script src="~/lib/popper.js/popper.min.js"></script>
<script src="~/lib/bootstrap/js/bootstrap.min.js"></script>
@RenderSection("scripts", false)
</head>
<body>
@RenderBody()
</body>
</html>
How to transfer
data from
controllers
modelBuilder.Entity<Conference>().HasData(
new Conference { ConferenceID = "afc", Name = "AFC"},
...
);
modelBuilder.Entity<Division>().HasData(
new Division { DivisionID = "north", Name = "North" },
...
);
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
<html>
<head>
<meta name="viewport" content="width=device-width" />
<link href="~/lib/bootstrap/dist/css/bootstrap.min.css"
rel="stylesheet" />
<link href="~/css/site.css" rel="stylesheet" />
<title>@ViewBag.Title</title>
</head>
<body>
<div class="container">
<header class="text-center text-white">
<h1 class="bg-primary mt-3 p-3">NFL Teams</h1>
</header>
<main>
@RenderBody()
</main>
</div>
</body>
</html>
TempData["ActiveConf"] = model.ActiveConf;
TempData["ActiveDiv"] = model.ActiveDiv;
return RedirectToAction("Details",
new { ID = model.Team.TeamID });
}
<div class="row">
<div class="col-sm-3">
<!-- Conference and Division ul elements -->
</div>
<div class="col-sm-9">
<ul class="list-inline">
@foreach (Team team in Model.Teams)
{
<li class="list-inline-item">
<form asp-action="Details" method="post">
<button class="bg-white border-0“
type="submit">
<img src="~/images/@team.LogoImage"
alt="@team.Name"
title="@team.Name |
@team.Conference.Name
@team.Division.Name" />
</button>
<h2>Team Details</h2>
<div class="row">
<div class="col-sm-6 col-sm-offset-3">
<ul class="list-group text-center">
<li class="list-group-item">
<img src="~/images/@Model.Team.LogoImage" alt="" />
<h3>@Model.Team.Name</h3>
</li>
<li class="list-group-item">
<h4>Conference: @Model.Team.Conference.Name</h4>
</li>
<li class="list-group-item">
<h4>Division: @Model.Team.Division.Name</h4>
</li>
Knowledge
1. List the six ways that you can handle state with ASP.NET Core
MVC.
2. Describe how session state works with the focus on the session ID.
3. Describe how to enable session state for an ASP.NET Core MVC
web app, including how to change its default settings.
4. Describe the use of session state in controllers and views.
services.AddControllersWithViews();
...
}
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
...
}
services.AddControllersWithViews().AddNewtonsoftJson();
...
}
In a view
@{
var team = Context.Session.GetObject<Team>("team");
}
In a view
@{
var teams = Context.Session.GetObject<List<Team>>("teams");
}
In a view
@{
var session = new MySession(Context.Session);
var teams = session.GetTeams();
}
TempData["message"] =
$"{model.Team.Name} added to your favorites";
return RedirectToAction("Index",
new {
ActiveConf = session.GetActiveConf(),
ActiveDiv = session.GetActiveDiv()
});
}
TempData["message"] =
$"{model.Team.Name} added to your favorites";
return RedirectToAction("Index",
new { ActiveConf = session.GetActiveConf(),
ActiveDiv = session.GetActiveDiv() });
}
session.RemoveMyTeams();
cookies.RemoveMyTeamIds();
Knowledge
1. Describe how to use controller properties to retrieve primitive
types from GET and POST requests.
2. List the order of places where MVC looks for data when it’s
binding a parameter.
3. Describe how to use model binding to retrieve primitive types from
GET and POST requests.
4. Describe how to use model binding to retrieve complex types from
POST requests.
5. Describe how to use the name and value attributes of a submit
button to POST data.
A route parameter
https://localhost:5001/Home/Index/2
The view
<form asp-action="Add" method="post">
<label for="description">Description:</label>
<input type="text" name="description">
<label for="duedate">Due Date:</label>
<input type="text" name="duedate">
<button type="submit">Add</button>
</form>
The view
@model ToDo
...
<form asp-action="Add" method="post">
<label asp-for="Description">Description:</label>
<input asp-for="Description">
<label asp-for="DueDate">Due Date:</label>
<input type="text" asp-for="DueDate">
<button type="submit">Add</button>
</form>
return View(model);
}
return View(model);
}
[BindNever]
public bool IsManager { get; set; }
}
[HttpGet]
public IActionResult Add()
{
ViewBag.Categories = context.Categories.ToList();
ViewBag.Statuses = context.Statuses.ToList();
return View();
}
<input type="hidden"
name="@nameof(ToDo.Id)" value="@task.Id" />
<button type="submit"
name="@nameof(ToDo.StatusId)" value="closed"
class="btn btn-primary btn-sm">Completed
</button>
<button type="submit"
class="btn btn-primary btn-sm">Delete
</button>
</form>
</td>
</tr>
}
</tbody>
</table>
</div>
</div>
<h2>New Task</h2>
<div class="form-group">
<label asp-for="CategoryId">Category:</label>
<select asp-for="CategoryId" class="form-control"
asp-items="@(new SelectList(ViewBag.Categories,
"CategoryId", "Name"))">
<option value=""></option>
</select>
</div>
<div class="form-group">
<label asp-for="StatusId">Status:</label>
<select asp-for="StatusId" class="form-control"
asp-items="@(new SelectList(ViewBag.Statuses,
"StatusId", "Name"))">
<option value=""></option>
</select>
</div>
How to
validate data
Knowledge
1. Describe the default data validation provided by model binding.
2. Describe the use of attributes for data validation.
3. List six data validation attributes.
4. Describe the use of tag helpers for data validation.
5. Describe the use of the IsValid property of a controller’s
ModelState property.
6. Describe the use of CSS to format validation messages.
[Range(1, 5)]
public int Rating { get; set; }
}
[Range(1, 5,
ErrorMessage = "Please enter a rating between 1 and 5.")]
public int Rating { get; set; }
if (ModelState.GetValidationState(key) ==
ModelValidationState.Valid) {
if (customer.DOB > DateTime.Today) {
ModelState.AddModelError(
key, "Date of birth must not be a future date.");
}
}
if (ModelState.IsValid) {
// code that adds customer to database
return RedirectToAction("Welcome");
}
else {
return View(customer);
}
}
if (IsPast) {
from = new DateTime(now.Year, 1, 1);
from = from.AddYears(-numYears);
}
// check date
if (IsPast) {
if (dateToCheck >= from && dateToCheck < now) {
return ValidationResult.Success;
}
}
else {
if (dateToCheck > now && dateToCheck <= from) {
return ValidationResult.Success;
}
}
}
string msg = base.ErrorMessage ??
ctx.DisplayName + " must be a " +
(IsPast ? "past" : "future") + " date within " +
numYears + " years of now.";
return new ValidationResult(msg);
}
}
jQuery.validator.unobtrusive.adapters.addSingleVal(
"pastdate", "numyears");
<script src="~/lib/jquery/jquery.min.js"></script>
<script src="~/lib/jquery-validation/jquery.validate.min.js">
</script>
<script src="~/lib/jquery-validation-unobtrusive/
jquery.validate.unobtrusive.min.js"></script>
<script src="~/js/pastdate.js"></script>
<title>@ViewBag.Title</title>
</head>
The view
<input asp-for="EmailAddress" class="form-control" />
<input asp-for="Username" class="form-control" />
<input type="hidden" name="Region" value="West" />
dateToCheck.setFullYear(dateToCheck.getFullYear() +
minYears);
jQuery.validator.unobtrusive.adapters.addSingleVal(
"minimumage", "years");
[HttpPost]
public IActionResult Index(Customer customer)
{
if (TempData["okEmail"] == null) {
string msg = Check.EmailExists(
context, customer.EmailAddress);
if (!String.IsNullOrEmpty(msg)) {
ModelState.AddModelError(
nameof(Customer.EmailAddress), msg);
}
}
@{
ViewData["Title"] = "Registration";
}
@section scripts {
<script src="~/lib/jquery-
validation/dist/jquery.validate.min.js"></script>
<script src="~/lib/jquery-validation-unobtrusive/
jquery.validate.unobtrusive.min.js"></script>
<script src="~/js/minimum-age.js"></script>
}
How to use
EF Core
Knowledge
1. Distinguish between Code First development and Database First
development.
2. Describe the purpose of a database (DB) context class and entity
classes.
3. Describe the three techniques you can use to configure a database
with Code First development.
4. Describe the use of EF Core commands to work with a database.
[Required]
[StringLength(200)]
public string Title { get; set; } // not nullable
}
modelBuilder.Entity<Book>().HasData(
new Book { ISBN = "1548547298",
Title = "The Hobbit" },
new Book { ISBN = "0312283709",
Title = "Running With Scissors" }
);
}
entity.HasData(
new Book { ISBN = "1548547298",
Title = "The Hobbit" },
new Book { ISBN = "0312283709",
Title = "Running With Scissors" }
);
}
}
[ForeignKey("AuthorId")] // FK property
public Author Author { get; set; } // navigation property
}
modelBuilder.Entity<Author>().ToTable("Authors");
modelBuilder.Entity<AuthorBio>().ToTable("Authors");
}
// navigation properties
public Book Book { get; set; }
public Author Author { get; set; }
}
namespace Bookstore.Models
{
public class Author
{
public int AuthorId { get; set; }
// read-only property
public string FullName => $"{FirstName} {LastName}";
// navigation property
public ICollection<BookAuthor> BookAuthors { get; set; }
}
}
namespace Bookstore.Models
{
public partial class Book
{
public int BookId { get; set; }
// navigation property
public ICollection<BookAuthor> BookAuthors { get; set; }
}
}
// navigation properties
public Author Author { get; set; }
public Book Book { get; set; }
}
}
namespace Bookstore.Models
{
public class Genre
{
[StringLength(10)]
[Required(ErrorMessage = "Please enter a genre id.")]
[Remote("CheckGenre", "Validation", "")]
public string GenreId { get; set; }
[StringLength(25)]
[Required(ErrorMessage = "Please enter a genre name.")]
public string Name { get; set; }
namespace Bookstore.Models
{
public class BookstoreContext : DbContext
{
public BookstoreContext(
DbContextOptions<BookstoreContext> options) : base(options)
{ }
namespace Bookstore.Models
{
internal class SeedAuthors : IEntityTypeConfiguration<Author>
{
public void Configure(EntityTypeBuilder<Author> entity)
{
entity.HasData(
new Author { AuthorId = 1, FirstName = "Michelle",
LastName = "Alexander" },
new Author { AuthorId = 2, FirstName = "Stephen E.",
LastName = "Ambrose" },
...
new Author { AuthorId = 26, FirstName = "Seth",
LastName = "Grahame-Smith" }
);
}
}
}
namespace Bookstore.Models
{
internal class SeedBooks : IEntityTypeConfiguration<Book>
{
public void Configure(EntityTypeBuilder<Book> entity)
{
entity.HasData(
new Book { BookId = 1, Title = "1776",
GenreId = "history", Price = 18.00 },
new Book { BookId = 2, Title = "1984",
GenreId = "scifi", Price = 5.50 },
...
new Book { BookId = 29,
Title = "Harry Potter and the Sorcerer's Stone",
GenreId = "novel", Price = 9.75 }
);
}
}
}
namespace Bookstore.Models
{
internal class SeedBookAuthors :
IEntityTypeConfiguration<BookAuthor>
{
public void Configure(EntityTypeBuilder<BookAuthor> entity)
{
entity.HasData(
new BookAuthor { BookId = 1, AuthorId = 18 },
new BookAuthor { BookId = 2, AuthorId = 20 },
...
new BookAuthor { BookId = 28, AuthorId = 4 },
new BookAuthor { BookId = 28, AuthorId = 26 },
new BookAuthor { BookId = 29, AuthorId = 25 }
);
}
}
namespace Bookstore.Models
{
internal class SeedGenres : IEntityTypeConfiguration<Genre>
{
public void Configure(EntityTypeBuilder<Genre> entity)
{
entity.HasData(
new Genre { GenreId = "novel", Name = "Novel" },
new Genre { GenreId = "memoir", Name = "Memoir" },
new Genre { GenreId = "mystery", Name = "Mystery" },
new Genre { GenreId = "scifi",
Name = "Science Fiction" },
new Genre { GenreId = "history", Name = "History" }
);
}
}
}
[Required]
[StringLength(200)]
public string Title { get; set; }
namespace Bookstore.Models.DataLayer
{
[ModelMetadataType(typeof(BooksMetadata))]
public partial class Books
{
public bool HasTitle =>
!string.IsNullOrEmpty(Title);
}
}
namespace Bookstore.Models.DataLayer
{
public class BooksMetadata
{
[RegularExpression("^[a-zA-Z0-9 _.,!':]+$",
ErrorMessage =
"Title may not contain special characters.")]
public string Title { get; set; }
[Range(0.0, 1000000.0,
ErrorMessage = "Price must be greater than zero.")]
public double Price { get; set; }
}
}
In one statement
var books = context.Books.ToList();
The projection
var authors = context.Authors
.Select(a => new DropdownDTO {
Value = a.AuthorId.ToString(),
Text = a.FirstName + ' ' + a.LastName
})
.ToList();
[Timestamp]
public byte[] RowVersion { get; set; }
}
context.SaveChanges();
try {
context.SaveChanges();
return RedirectToAction("Index");
}
catch (DbUpdateConcurrencyException ex) {
var entry = ex.Entries.Single();
var dbValues = entry.GetDatabaseValues();
if (dbValues == null) {
ModelState.AddModelError("", "Unable to save - "
+ "this book was deleted by another user.");
}
if (dbBook.Title != book.Title)
ModelState.AddModelError("Title",
$"Current db value: {dbBook.Title}");
// read-only properties
public bool HasWhere => Where != null;
public bool HasOrderBy => OrderBy != null;
public bool HasPaging => PageNumber > 0 && PageSize > 0;
}
[HttpPost]
public IActionResult Edit(BookViewModel vm) {
if (ModelState.IsValid) {
data.DeleteCurrentBookAuthors(vm.Book);
data.AddNewBookAuthors(vm.Book,
vm.SelectedAuthors);
data.Books.Update(vm.Book);
data.Save();
...
}
...
}
The bookstore
website
Knowledge
1. Describe why it’s generally considered a good practice to have a
“fat” model and “skinny” controllers.
2. Describe how the Author Catalog page provides for paging and
sorting.
3. Describe how the Book Catalog page provides for paging, sorting,
and filtering.
4. Describe how the Cart page uses session state and cookies to store
its data.
5. Describe how the Manage Books tab of the Admin page provides
a feature for searching for books that’s also used by the Manage
Genres tab.
if (current.SortField.EqualsNoCase(fieldName) &&
current.SortDirection == "asc")
this[nameof(GridDTO.SortDirection)] = "desc";
else
this[nameof(GridDTO.SortDirection)] = "asc";
}
// this constructor used when just need to get route data from the session
public GridBuilder(ISession sess) {
session = sess;
routes = session.GetObject<RouteDictionary>(RouteKey) ??
new RouteDictionary();
}
SaveRouteSegments();
}
@{
// reset to current route values
routes = Model.CurrentRoute.Clone();
// filter flags
string default = BooksGridDTO.DefaultFilter;
public bool IsFilterByAuthor => routes.AuthorFilter != default;
public bool IsFilterByGenre => routes.GenreFilter != default;
public bool IsFilterByPrice => routes.PriceFilter != default;
// sort flags
public bool IsSortByGenre =>
routes.SortField.EqualsNoCase(nameof(Genre));
public bool IsSortByPrice =>
routes.SortField.EqualsNoCase(nameof(Book.Price));
}
@{
ViewData["Title"] = " | Book Catalog";
[JsonIgnore]
public double Subtotal => Book.Price * Quantity;
}
<h1>Your Cart</h1>
<form asp-action="Clear" method="post">
<ul class="list-group mb-4">
<li class="list-group-item">
<div class="row">
<div class="col">Subtotal: @Model.Subtotal.ToString("c")
</div>
<div class="col">
<div class="float-right">
<a asp-action="Checkout" class="btn btn-primary">
Checkout</a>
<button type="submit" class="btn btn-primary">
Clear Cart</button>
<a asp-action="List" asp-controller="Book"
asp-all-route-data="@Model.BookGridRoute">
Back to Shopping</a>
</div>
</div>
</div>
</li>
</ul>
</form>
if (search.HasSearchTerm) {
var vm = new SearchViewModel {
SearchTerm = search.SearchTerm
};
var options = new QueryOptions<Book> {
Include = "Genre, BookAuthors.Author"
};
if (search.IsBook) {
options.Where = b => b.Title.Contains(vm.SearchTerm);
vm.Header =
$"Search results for book title '{vm.SearchTerm}'";
}
if (genre.Books.Count > 0) {
TempData["message"] = $"Can't delete genre {genre.Name} "
+ "because it's associated with these books.";
return GoToBookSearchResults(id);
}
else {
return View("Genre", genre);
}
}
How to use
dependency injection
and unit testing
Knowledge
1. Describe how to configure a web app for dependency injection.
2. List and describe the three dependency life cycles.
3. Distinguish between controllers that are tightly coupled with EF
Core and controllers that are loosely coupled with EF Core.
4. Explain how dependency chaining works with repository objects
and unit of work objects.
5. Describe the use of DI with an HttpContextAccessor object.
Assert.IsType<string>(result); // assert
}
// act
var result = controller.Index();
// assert
Assert.IsType<ViewResult>(result);
}
// act
var result = controller.Edit(new Author());
// assert
Assert.IsType<RedirectToActionResult>(result);
}
// act
var result = controller.Index();
// assert
Assert.IsType<ViewResult>(result);
}
// act
var result = controller.Edit(new Author());
// assert
Assert.IsType<RedirectToActionResult>(result);
}
// act
var result = cart.Subtotal;
// assert
Assert.IsType<double>(result);
}
namespace Bookstore.Tests
{
public class BookControllerTests
{
[Fact]
public void Index_ReturnsARedirectToActionResult() {
// arrange
var unit = new Mock<IBookstoreUnitOfWork>();
var controller = new BookController(unit.Object);
// act
var result = controller.Index();
// assert
Assert.IsType<RedirectToActionResult>(result);
}
// act
var result = controller.Index();
// assert
Assert.Equal("List", result.ActionName);
}
// act
var model =
controller.Details(1).ViewData.Model as Book;
// assert
Assert.IsType<Book>(model);
}
}
}
namespace Bookstore.Tests
{
public class AdminBookControllerTests
{
public IBookstoreUnitOfWork GetUnitOfWork()
{
// set up Book repository
var bookRep = new Mock<IRepository<Book>>();
bookRep.Setup(m => m.Get(It.IsAny<QueryOptions<Book>>()))
.Returns(new Book { BookAuthors = new List<BookAuthor>() });
bookRep.Setup(m => m.List(It.IsAny<QueryOptions<Book>>()))
.Returns(new List<Book>());
bookRep.Setup(m => m.Count).Returns(0);
return unit.Object;
}
[Fact]
public void Edit_GET_ModelIsBookObject()
{
// arrange
var unit = GetUnitOfWork();
var controller = new BookController(unit);
// act
var model = controller.Edit(1).ViewData.Model as BookViewModel;
// assert
Assert.IsType<BookViewModel>(model);
}
// act
var result = controller.Edit(vm);
// assert
Assert.IsType<ViewResult>(result);
}
// act
var result = controller.Edit(vm);
// assert
Assert.IsType<RedirectToActionResult>(result);
}
}
}
namespace Bookstore.Tests
{
public class CartTests
{
private Cart GetCart()
{
// create HTTP context accessor
var accessor = new Mock<IHttpContextAccessor>();
// setup session
var session = new Mock<ISession>();
accessor.Setup(m => m.HttpContext.Session)
.Returns(session.Object);
[Fact]
public void Subtotal_ReturnsADouble()
{
// arrange
Cart cart = GetCart();
cart.Add(new CartItem { Book = new BookDTO() });
// act
var result = cart.Subtotal;
// assert
Assert.IsType<double>(result);
}
// act
var result = cart.Subtotal;
// assert
Assert.Equal(Math.Round(expected, 2), Math.Round(result, 2));
}
}
}
How to work
with tag helpers,
partial views,
and view components
Knowledge
1. Distinguish between tag helper attributes and elements.
2. Explain how to register tag helpers.
3. Explain how to create custom tag helpers for standard and non-
standard HTML elements.
4. Describe the use of the HtmlTarget attribute to control the scope of
a tag helper.
5. Describe the use of the TagHelperOutput class and the TagBuilder
class to add an HTML element before or after the tag helper
element.
[HtmlAttributeName("my-max-number")]
public int Max { get; set; }
output.Content.AppendHtml(option);
}
}
}
[ViewContext]
[HtmlAttributeNotBound]
public ViewContext ViewCtx { get; set; }
// create link
output.BuildLink(url, linkClasses);
output.Content.SetContent(Number.ToString());
}
}
return View(vm);
}
return View(vm);
}
[HtmlAttributeName("my-mark-area-active")]
public bool IsAreaOnly { get; set; }
<main>
<my-temp-message />
@RenderBody()
</main>
</div>
<script src="~/lib/jquery/dist/jquery.min.js"></script>
<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js">
</script>
<script src="~/js/site.js" asp-append-version="true"></script>
@RenderSection("Scripts", required: false)
</body>
</html>
<label>Genre: </label>
<vc:genre-drop-down
selected-value="@Model.CurrentRoute.GenreFilter">
</vc:genre-drop-down>
<label>Price: </label>
<vc:price-drop-down
selected-value="@Model.CurrentRoute.PriceFilter">
</vc:price-drop-down>
How to
authenticate and
authorize users
Knowledge
1. Distinguish between authentication and authorization.
2. Describe how individual user account authentication works.
3. Describe how to use authorization attributes to restrict access to
controllers and actions.
4. List and describe three properties of the IdentityUser class.
5. Describe how to add the Identity tables to the DB context class and
the database.
[Authorize]
[HttpGet]
public IActionResult Add()
{
...
}
[Authorize(Roles = "Admin")]
[HttpGet]
public IActionResult Delete(int id)
{
...
}
...
}
namespace Bookstore.Models
{
public class User : IdentityUser {
// Inherits all IdentityUser properties
}
}
namespace Bookstore.Models
{
public class BookstoreContext : IdentityDbContext<User>
{
public BookstoreContext(
DbContextOptions<BookstoreContext> options)
: base(options) { }
app.UseAuthentication();
app.UseAuthorization();
app.UseSession();
...
}
@using Microsoft.AspNetCore.Identity
@inject SignInManager<User> signInManager
@if (signInManager.IsSignedIn(User))
{
// signed-in user - Log Out button and username
<li class="nav-item">
<form method="post" asp-action="Logout"
asp-controller="Account" asp-area="">
<input type="submit" value="Log Out"
class="btn btn-outline-light" />
<span class="text-light">@User.Identity.Name</span>
</form>
</li>
}
namespace Bookstore.Controllers
{
public class AccountController : Controller
{
private UserManager<User> userManager;
private SignInManager<User> signInManager;
namespace Bookstore.Models
{
public class RegisterViewModel
{
[Required(ErrorMessage = "Please enter a username.")]
[StringLength(255)]
public string Username { get; set; }
<h1>Register</h1>
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<form method="post" asp-action="Register">
<div class="form-group row">
<div class="col-sm-2"><label>Username:</label></div>
<div class="col-sm-4">
<input asp-for="Username" class="form-control" />
</div>
<div class="col">
<span asp-validation-for="Username"
class="text-danger"></span>
</div>
</div>
<div class="form-group row">
<div class="col-sm-2"><label>Password:</label></div>
<div class="col-sm-4">
<input type="password" asp-for="Password"
class="form-control" />
</div>
namespace Bookstore.Models
{
public class LoginViewModel
{
[Required(ErrorMessage = "Please enter a username.")]
[StringLength(255)]
public string Username { get; set; }
<h1>Login</h1>
if (result.Succeeded) {
if (!string.IsNullOrEmpty(model.ReturnUrl) &&
Url.IsLocalUrl(model.ReturnUrl))
{
return Redirect(model.ReturnUrl);
}
else
{
return RedirectToAction("Index", "Home");
}
}
}
ModelState.AddModelError("", "Invalid username/password.");
return View(model);
}
@if (Model.Roles.Count() == 0)
{
<form method="post" asp-action="CreateAdminRole">
<button type="submit" class="btn btn-primary">
Create Admin Role</button>
</form>
}
else
{
<table class="table table-bordered table-striped table-sm">
<thead>
<tr><th>Role</th><th></th></tr>
</thead>
<tbody>
@foreach (var role in Model.Roles)
{
<tr>
<td>@role.Name</td>
[HttpPost]
public async Task<IActionResult> DeleteRole(string id)
{
IdentityRole role = await roleManager.FindByIdAsync(id);
await roleManager.DeleteAsync(role);
return RedirectToAction("Index");
}
[HttpPost]
public async Task<IActionResult> CreateAdminRole()
{
await roleManager.CreateAsync(new IdentityRole("Admin"));
return RedirectToAction("Index");
}
BookstoreContext.CreateAdminUser(app.ApplicationServices)
.Wait();
}
});
namespace Bookstore.Models
{
public class ChangePasswordViewModel
{
public string Username { get; set; }
[NotMapped]
public IList<string> RoleNames { get; set; };
}
How to use
Visual Studio Code
Knowledge
1. Distinguish between Visual Studio Code and Visual Studio.
2. Describe how to use VS Code to open and close a folder for an
existing Visual Studio project.
3. List and describe the three modes Visual Studio Code provides for
viewing and editing files.
4. Describe how to use VS Code to run an ASP.NET Core project.
5. Describe how to use the CLI to execute the EF Core commands.
6. Describe how to use the CLI to create a new ASP.NET Core MVC
web app and add NuGet packages to it.