Introduction to ASP.NET Core and Tooling
Web Apps and Middleware
Application Configurations and NuGet Packages
ASP.NET Core and Middleware
Intro to Routing and MVC
ASP.NET Core and the MVC Pattern
Creating a Form
Form Validation
Logging and Diagnostics
Windows Command Line
C#
dotnet
Visual Studio Code Reference
.Net Core
ASP.NET Core
Data Annotations
- Install .Net Core
- Install Visual Studio Code
- Install Visual Studio Community
Directory Setup
{dir}>md {app-dir}
{dir}>cd {app-dir}
{app-dir}>dotnet new
{app-dir}>dotnet restore
Files contained in {app-dir}:
- Program.cs
- project.json
Open in VS Code
{app-dir}>code .
Add build assets required to debug in Visual Studio Code:
Program.cs
using System;
namespace ConsoleApplication
{
public class Program
{
public static void Main(string[] args)
{
Console.WriteLine("What's your name?");
var name = Console.ReadLine();
Console.WriteLine($"Hello {name}, welcome to C#!");
}
}
}
Execute the Console App
{app-dir}>dotnet run
Program.cs
Startup.cs
project.json
{
"version": "1.0.0-*",
"buildOptions": {
"debugType": "portable",
"emitEntryPoint": true
},
"dependencies": {},
"frameworks": {
"netcoreapp1.1": {
"dependencies": {
"Microsoft.NETCore.App": {
"type": "platform",
"version": "1.1.0"
},
"Microsoft.AspNetCore.Server.Kestrel": "1.1.0"
},
"imports": "dnxcore50"
}
}
}
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
namespace ConsoleApplication
{
public class Startup
{
public void Configure(IApplicationBuilder app)
{
app.Run(
context => {
var name = "human";
if (context.Request.Query.Keys.Contains("name"))
{
var nameValue = new Microsoft.Extensions.Primitives.StringValues();
if (context.Request.Query.TryGetValue("name", out nameValue))
{
name = nameValue[0];
}
}
return context.Response.WriteAsync($"Hello from the web, {name}");
}
);
}
}
}
Program.cs
using System;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
namespace ConsoleApplication
{
public class Program
{
public static void Main(string[] args)
{
var host = new WebHostBuilder()
.UseKestrel()
.UseStartup<Startup>()
.Build();
host.Run();
}
}
}
{app-dir}>dotnet restore
{app-dir}>dotnet run
Configure dotnet-watch
project.json
{
"version": "1.0.0-*",
"buildOptions": {
"debugType": "portable",
"emitEntryPoint": true
},
"dependencies": {},
"frameworks": {
"netcoreapp1.1": {
"dependencies": {
"Microsoft.NETCore.App": {
"type": "platform",
"version": "1.1.0"
},
"Microsoft.AspNetCore.Server.Kestrel": "1.1.0"
},
"imports": "dnxcore50"
}
},
"tools": {
"Microsoft.DotNet.Watcher.Tools": {
"version": "1.0.0-*",
"imports": "portable-net46+win10"
}
}
}
Restore Dependencies
{app-dir}>dotnet restore
Execute with dotnet-watch
{app-dir}>dotnet watch run
Modify Startup.cs during execution, then save
return context.Response.WriteAsync($"Hello {name}, you are being watched from the web");
Create Project in Visual Studio
Alternatively, from the Command Line
{dir}>md {app-dir}
{dir}>cd {app-dir}
{app-dir}>dotnet new -t web
- Solution 'WebAppsAndMiddleware'
- Solution Items
global.json
- src
- WebAppsAndMiddleware
- Properties
launchSettings.json
- References
- .NETCoreApp,Version=v1.0
- Microsoft.AspNetCore.Diagnostics (1.0.0)
- Microsoft.AspNetCore.Server.IISIntegration (1.0.0)
- Microsoft.AspNetCore.Server.Kestrel (1.0.0)
- Microsoft.Extensions.Logging.Console (1.0.0)
- Microsoft.NETCore.App (1.0.0)
- .NETCoreApp,Version=v1.0
- wwwroot
- Dependencies
Program.cs
project.json
project.lock.json
Startup.cs
web.config
- Properties
- WebAppsAndMiddleware
- Solution Items
launchSettings.json
Debug Profiles
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"Kestrel": {
"commandName": "Project",
"launchBrowser": true,
"launchUrl": "http://localhost:8081",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
Program.cs
using System.IO;
using Microsoft.AspNetCore.Hosting;
namespace WebAppsAndMiddleware
{
public class Program
{
public static void Main(string[] args)
{
var host = new WebHostBuilder()
.UseKestrel()
.UseUrls("http://localhost:8081")
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseStartup<Startup>()
.Build();
host.Run();
}
}
}
project.json StaticFiles Dependency
"dependencies": {
"Microsoft.NETCore.App": {
"version": "1.0.0",
"type": "platform"
},
"Microsoft.AspNetCore.StaticFiles": "1.0.0"
}
Startup.cs Configure UseStaticFiles
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
// Order is important! Place before app.Run()
app.UseStaticFiles();
app.Run(async (context) =>
{
await context.Response.WriteAsync("Hello World!");
});
}
Add index.html to wwwroot
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title></title>
</head>
<body>
<h1>ASP.NET Core</h1>
<hr />
<p>
Served from wwwroot using StaticFileMiddleware.
</p>
</body>
</html>
Startup.cs
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace WebAppsAndMiddleware
{
public class Startup
{
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit http://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseFileServer();
}
}
}
- Rename index.html something else
- Add additional static files to wwwroot
- Configure
Startup.cs
Startup.cs
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace WebAppsAndMiddleware
{
public class Startup
{
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit http://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
services.AddDirectoryBrowser();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseDirectoryBrowser();
app.UseFileServer();
}
}
}
"profiles": {
"IIS Express - Dev": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IIS Express - Prod": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Production"
}
},
"Kestrel - Dev": {
"commandName": "Project",
"launchBrowser": true,
"launchUrl": "http://localhost:8081",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"Kestrel - Prod": {
"commandName": "Project",
"launchBrowser": true,
"launchUrl": "http://localhost:8081",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Production"
}
}
}
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
"dependencies": {
"Microsoft.NETCore.App": {
"version": "1.0.0",
"type": "platform"
},
"Microsoft.Extensions.Configuration.Json": "1.0.0",
...
using Microsoft.Extensions.Configuration
// Additional References
public class Startup
{
public IConfigurationRoot Configuration { get; set; }
public Startup(IHostingEnvironment env)
{
Configuration = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appSettings.json")
.Build();
}
// Additional Startup configuration
}
appSettings.json Create in Project Root
{
"message": "Hello from Configuration"
}
Debug and check Configuration
Create a .Net Core Class Library
Add a new Project to the WebAppsAndMiddleware Solution
Alternatively, Create from Command Line
{dir}>md {app-dir}
{dir}>cd {app-dir}
{app-dir}>dotnet new -t lib
Rename Class1.cs to something else (in this case, I've used DemoClass.cs)
DemoClass.cs
namespace DemoLib
{
public class DemoClass
{
public static string Greeting()
{
return "Hello from DemoLib";
}
}
}
project.json Register DemoLib Dependency
"dependencies": {
"DemoLib": "1.0.0-*",
...
}
Startup.cs Call Greeting() from app.Run()
using DemoLib;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace WebAppsAndMiddleware
{
public class Startup
{
public IConfigurationRoot Configuration { get; set; }
public Startup(IHostingEnvironment env)
{
Configuration = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appSettings.json")
.Build();
}
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit http://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
services.AddDirectoryBrowser();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.Run(async (context) =>
{
await context.Response.WriteAsync(DemoClass.Greeting());
});
//app.UseDirectoryBrowser();
//app.UseFileServer();
}
}
}
Execute
project.json Routing Dependency
"dependencies": {
"Microsoft.AspNetCore.Routing": "1.0.0"
}
Startup.cs Configure Routing
public void ConfigureServices(IServiceCollection services)
{
services.AddRouting();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
var routeBuilder = new RouteBuilder(app);
routeBuilder.MapGet("", context => context.Response.WriteAsync("Hello from Default Route"));
routeBuilder.MapGet("demo", context => context.Response.WriteAsync("Hello from Demo Route"));
routeBuilder.MapGet("demo/deep", context => context.Response.WriteAsync("Hello from Deep Route"));
routeBuilder.MapGet("post/{postNumber:int}", context => context.Response.WriteAsync($"Blog post id: {context.GetRouteValue("postNumber")}"));
routeBuilder.MapGet("post/{postNumber}", context => context.Response.WriteAsync($"Blog post string: {context.GetRouteValue("postNumber")}"));
app.UseRouter(routeBuilder.Build());
app.Run(async (context) =>
{
await context.Response.WriteAsync("Hello from Catch All Route");
});
}
Execute
project.json MVC Dependency
"dependencies": {
"Microsoft.AspNet.Mvc": "1.0.1"
}
Startup.cs MVC Configuration
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace WebAppsAndMiddleware
{
public class Startup
{
public IConfigurationRoot Configuration { get; set; }
public Startup(IHostingEnvironment env)
{
Configuration = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appSettings.json")
.Build();
}
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit http://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseMvc();
}
}
}
Create Controllers
folder in the root of the project directory and add a HomeController.cs
class.
HomeController.cs
using Microsoft.AspNetCore.Mvc;
namespace WebAppsAndMiddleware.Controllers
{
public class HomeController
{
[HttpGet("/")]
public string Index() => "Hello from MVC!";
}
}
Execute
Startup.cs MVC Route Definitions
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
HomeController.cs Remove Route Attribute
public class HomeController
{
public string Index() => "Hello from MVC!";
}
Still executes the same, but provides a convention-based route structure
HomeController.cs
using Microsoft.AspNetCore.Mvc;
namespace WebAppsAndMiddleware.Controllers
{
public class HomeController : Controller
{
public IActionResult Index()
{
return View();
}
}
}
Trying to run the app at this point will produce the following error because no Index
view has been created for the Home
controller:
Create the following file structure in the web app project root:
- Views
- Home
- Index.cshtml
- Home
Index.cshtml
<h1>Index</h1>
<p>
Rendered from the Index() action method from the Home controller.
</p>
Execute
Create the following file structure in the web app project root:
- Models
- Home
- IndexModel.cs
- Home
IndexModel.cs
public class IndexModel
{
public string Name { get; set; }
public int Age { get; set; }
}
HomeController.cs
using Microsoft.AspNetCore.Mvc;
using WebAppsAndMiddleware.Models.Home;
namespace WebAppsAndMiddleware.Controllers
{
public class HomeController : Controller
{
public IActionResult Index()
{
var model = new IndexModel
{
Name = "Jaime",
Age = 31
};
return View(model);
}
}
}
Home/Index.cshtml
@model WebAppsAndMiddleware.Models.Home.IndexModel
<h1>Person!</h1>
<p>Name: @Model.Name </p>
<p>Age: @Model.Age</p>
Execute
Create new ASP.NET Core Web App Project
Web Application with Individual User Accounts Authentication
Create Movie.cs
in Models
folder
Movie.cs
using System;
namespace mvc_form.Models
{
public class Movie
{
public int Id { get; set; }
public string Title { get; set; }
public DateTime ReleaseDate { get; set; }
public string Genre { get; set; }
public decimal Price { get; set; }
}
}
Create MoviesController.cs
in Controllers
folder
ApplicationDbContext.cs
updated to include the Movie.cs
entity model
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using mvc_form.Models;
namespace mvc_form.Data
{
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
// Customize the ASP.NET Identity model and override the defaults if needed.
// For example, you can rename the ASP.NET Identity table names and more.
// Add your customizations after calling base.OnModelCreating(builder);
}
public DbSet<Movie> Movie { get; set; }
}
}
MoviesController.cs
preconfigured with CRUD action methods
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using mvc_form.Data;
using mvc_form.Models;
namespace mvc_form.Controllers
{
public class MoviesController : Controller
{
private readonly ApplicationDbContext _context;
public MoviesController(ApplicationDbContext context)
{
_context = context;
}
// GET: Movies
public async Task<IActionResult> Index()
{
return View(await _context.Movie.ToListAsync());
}
// GET: Movies/Details/5
public async Task<IActionResult> Details(int? id)
{
if (id == null)
{
return NotFound();
}
var movie = await _context.Movie.SingleOrDefaultAsync(m => m.Id == id);
if (movie == null)
{
return NotFound();
}
return View(movie);
}
// GET: Movies/Create
public IActionResult Create()
{
return View();
}
// POST: Movies/Create
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("Id,Genre,Price,ReleaseDate,Title")] Movie movie)
{
if (ModelState.IsValid)
{
_context.Add(movie);
await _context.SaveChangesAsync();
return RedirectToAction("Index");
}
return View(movie);
}
// GET: Movies/Edit/5
public async Task<IActionResult> Edit(int? id)
{
if (id == null)
{
return NotFound();
}
var movie = await _context.Movie.SingleOrDefaultAsync(m => m.Id == id);
if (movie == null)
{
return NotFound();
}
return View(movie);
}
// POST: Movies/Edit/5
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("Id,Genre,Price,ReleaseDate,Title")] Movie movie)
{
if (id != movie.Id)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
_context.Update(movie);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!MovieExists(movie.Id))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction("Index");
}
return View(movie);
}
// GET: Movies/Delete/5
public async Task<IActionResult> Delete(int? id)
{
if (id == null)
{
return NotFound();
}
var movie = await _context.Movie.SingleOrDefaultAsync(m => m.Id == id);
if (movie == null)
{
return NotFound();
}
return View(movie);
}
// POST: Movies/Delete/5
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DeleteConfirmed(int id)
{
var movie = await _context.Movie.SingleOrDefaultAsync(m => m.Id == id);
_context.Movie.Remove(movie);
await _context.SaveChangesAsync();
return RedirectToAction("Index");
}
private bool MovieExists(int id)
{
return _context.Movie.Any(e => e.Id == id);
}
}
}
Action method views generated in Views/Movies
folder
Create.cshtml
@model mvc_form.Models.Movie
@{
ViewData["Title"] = "Create";
}
<h2>Create</h2>
<form asp-action="Create">
<div class="form-horizontal">
<h4>Movie</h4>
<hr />
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Genre" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="Genre" class="form-control" />
<span asp-validation-for="Genre" class="text-danger" />
</div>
</div>
<div class="form-group">
<label asp-for="Price" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="Price" class="form-control" />
<span asp-validation-for="Price" class="text-danger" />
</div>
</div>
<div class="form-group">
<label asp-for="ReleaseDate" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="ReleaseDate" class="form-control" />
<span asp-validation-for="ReleaseDate" class="text-danger" />
</div>
</div>
<div class="form-group">
<label asp-for="Title" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="Title" class="form-control" />
<span asp-validation-for="Title" 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-default" />
</div>
</div>
</div>
</form>
<div>
<a asp-action="Index">Back to List</a>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
Delete.cshtml
@model mvc_form.Models.Movie
@{
ViewData["Title"] = "Delete";
}
<h2>Delete</h2>
<h3>Are you sure you want to delete this?</h3>
<div>
<h4>Movie</h4>
<hr />
<dl class="dl-horizontal">
<dt>
@Html.DisplayNameFor(model => model.Genre)
</dt>
<dd>
@Html.DisplayFor(model => model.Genre)
</dd>
<dt>
@Html.DisplayNameFor(model => model.Price)
</dt>
<dd>
@Html.DisplayFor(model => model.Price)
</dd>
<dt>
@Html.DisplayNameFor(model => model.ReleaseDate)
</dt>
<dd>
@Html.DisplayFor(model => model.ReleaseDate)
</dd>
<dt>
@Html.DisplayNameFor(model => model.Title)
</dt>
<dd>
@Html.DisplayFor(model => model.Title)
</dd>
</dl>
<form asp-action="Delete">
<div class="form-actions no-color">
<input type="submit" value="Delete" class="btn btn-default" /> |
<a asp-action="Index">Back to List</a>
</div>
</form>
</div>
Details.cshtml
@model mvc_form.Models.Movie
@{
ViewData["Title"] = "Details";
}
<h2>Details</h2>
<div>
<h4>Movie</h4>
<hr />
<dl class="dl-horizontal">
<dt>
@Html.DisplayNameFor(model => model.Genre)
</dt>
<dd>
@Html.DisplayFor(model => model.Genre)
</dd>
<dt>
@Html.DisplayNameFor(model => model.Price)
</dt>
<dd>
@Html.DisplayFor(model => model.Price)
</dd>
<dt>
@Html.DisplayNameFor(model => model.ReleaseDate)
</dt>
<dd>
@Html.DisplayFor(model => model.ReleaseDate)
</dd>
<dt>
@Html.DisplayNameFor(model => model.Title)
</dt>
<dd>
@Html.DisplayFor(model => model.Title)
</dd>
</dl>
</div>
<div>
<a asp-action="Edit" asp-route-id="@Model.Id">Edit</a> |
<a asp-action="Index">Back to List</a>
</div>
Edit.cshtml
@model mvc_form.Models.Movie
@{
ViewData["Title"] = "Edit";
}
<h2>Edit</h2>
<form asp-action="Edit">
<div class="form-horizontal">
<h4>Movie</h4>
<hr />
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<input type="hidden" asp-for="Id" />
<div class="form-group">
<label asp-for="Genre" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="Genre" class="form-control" />
<span asp-validation-for="Genre" class="text-danger" />
</div>
</div>
<div class="form-group">
<label asp-for="Price" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="Price" class="form-control" />
<span asp-validation-for="Price" class="text-danger" />
</div>
</div>
<div class="form-group">
<label asp-for="ReleaseDate" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="ReleaseDate" class="form-control" />
<span asp-validation-for="ReleaseDate" class="text-danger" />
</div>
</div>
<div class="form-group">
<label asp-for="Title" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="Title" class="form-control" />
<span asp-validation-for="Title" 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-default" />
</div>
</div>
</div>
</form>
<div>
<a asp-action="Index">Back to List</a>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
Index.cshtml
@model IEnumerable<mvc_form.Models.Movie>
@{
ViewData["Title"] = "Index";
}
<h2>Index</h2>
<p>
<a asp-action="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.Genre)
</th>
<th>
@Html.DisplayNameFor(model => model.Price)
</th>
<th>
@Html.DisplayNameFor(model => model.ReleaseDate)
</th>
<th>
@Html.DisplayNameFor(model => model.Title)
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.Genre)
</td>
<td>
@Html.DisplayFor(modelItem => item.Price)
</td>
<td>
@Html.DisplayFor(modelItem => item.ReleaseDate)
</td>
<td>
@Html.DisplayFor(modelItem => item.Title)
</td>
<td>
<a asp-action="Edit" asp-route-id="@item.Id">Edit</a> |
<a asp-action="Details" asp-route-id="@item.Id">Details</a> |
<a asp-action="Delete" asp-route-id="@item.Id">Delete</a>
</td>
</tr>
}
</tbody>
</table>
Attempting to navigate within MoviesController
generates an error
Configure EF Database
{app-dir}>dotnet ef migrations add initial
{app-dir}>dotnet ef database update
Execute
appsettings.json
database connection string
"ConnectionStrings": {
"DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=aspnet-mvc_form-980d7c59-581b-4d28-bcd5-96fb385aa42c;Trusted_Connection=True;MultipleActiveResultSets=true"
}
Movies/Create GET
Create.cshtml
<!-- Specifies to call the Create POST action method in the current controller context -->
<form asp-action="Create">
Movies/Create POST
MoviesController.cs
Create POST Action Method
// POST: Movies/Create
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("Id,Genre,Price,ReleaseDate,Title")] Movie movie)
{
if (ModelState.IsValid)
{
_context.Add(movie);
await _context.SaveChangesAsync();
return RedirectToAction("Index");
}
return View(movie);
}
Movie.cs
public class Movie
{
public int Id { get; set; }
public string Title { get; set; }
[Display(Name ="Release Date")]
[DisplayFormat(DataFormatString ="{0:dd MMM yyyy}", ApplyFormatInEditMode =true)]
public DateTime ReleaseDate { get; set; }
public string Genre { get; set; }
[DisplayFormat(DataFormatString ="{0:C}", ApplyFormatInEditMode =true)]
public decimal Price { get; set; }
}
Movies/Details
View -> SQL Server Object Explorer
or Ctrl + , Ctrl + S
Movie.cs
public class Movie
{
public int Id { get; set; }
[StringLength(60, MinimumLength =3)]
public string Title { get; set; }
[Display(Name ="Release Date")]
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString ="{0:dd MMM yyyy}", ApplyFormatInEditMode =true)]
public DateTime ReleaseDate { get; set; }
[RegularExpression(@"^[A-Z]+[a-zA-Z''-'\s]*$")]
[Required]
[StringLength(30)]
public string Genre { get; set; }
[Range(1, 100)]
[DataType(DataType.Currency)]
[DisplayFormat(DataFormatString ="{0:C}", ApplyFormatInEditMode =true)]
public decimal Price { get; set; }
}
Execute and attempt to submit an invalid Movie entity
See how ASP.NET Core tag helpers expand to provide input validation when rendered in the DOM
A lot of information is logged as output when debugging an ASP.NET Core app
Startup.cs
configures logging
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
// Additional configuration
}
appsettings.json
modify Logging
from Information
to Debug
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Debug",
"System": "Debug",
"Microsoft": "Debug"
}
}
Many more details become available
Available log level options, by increasing severity:
- Debug
- Information
- Warning
- Error
- Critical
Startup.cs
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
var startupLogger = loggerFactory.CreateLogger<Startup>();
startupLogger.LogCritical("Some seriously critical stuff happened");
startupLogger.LogError("Erroneous task is running rampant!");
startupLogger.LogWarning("If actions are not taken, we are doomed to destruction");
startupLogger.LogInformation("Erroneous task successfully eradicated :)");
startupLogger.LogDebug("Hopefully, these tasks no longer get out of hand");
// Additional configuration
}
Execute
Startup.cs
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(LogLevel.Warning);
// Additional configuration
}
project.json
add the Serilog
dependency
"dependencies": {
"Serilog": "2.4.0",
"Serilog.Sinks.File": "3.2.0",
"Serilog.Extensions.Logging": "1.3.1"
}
Startup.cs
configure
using Serilog;
// Additional usings
// ctor
public Startup(IHostingEnvironment env)
{
var logFile = Path.Combine(env.ContentRootPath, "auto-log.txt");
Log.Logger = new LoggerConfiguration().WriteTo.File(logFile).CreateLogger();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddSerilog();
}
Execute