Skip to content

Instantly share code, notes, and snippets.

@JaimeStill
Last active May 17, 2017 17:23
Show Gist options
  • Save JaimeStill/2466e51aafe0b74f6201226f3bc060de to your computer and use it in GitHub Desktop.
Save JaimeStill/2466e51aafe0b74f6201226f3bc060de to your computer and use it in GitHub Desktop.
MVA Introduction to ASP.NET Core 1.0

Contents

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

Links

Downloads

.Net
Visual Studio

References

Windows Command Line
C#
dotnet
Visual Studio Code Reference
.Net Core
ASP.NET Core
Data Annotations

Back to Top

Basic Setup

  1. Install .Net Core
  2. Install Visual Studio Code
  3. Install Visual Studio Community

Basic Console App

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:

build-asset-prompt

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

Implementing ASP.NET Core Kestrel Server

Directory Structure

  • Program.cs
  • Startup.cs
  • project.json

File Contents

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"
        }
    }
}

Startup.cs

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();
        }
    }
}

Execution

{app-dir}>dotnet restore
{app-dir}>dotnet run

dotnet run

Running App

Configure dotnet-watch

dotnet-watch on GitHub

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

dotnet-watch

Modify Startup.cs during execution, then save

return context.Response.WriteAsync($"Hello {name}, you are being watched from the web");

dotnet-watch-update

Refresh the browser
dotnet-watch-refresh

Back to Top

Setup

Create Project in Visual Studio

web-app-new-project

web-app-template

Alternatively, from the Command Line

{dir}>md {app-dir}
{dir}>cd {app-dir}
{app-dir}>dotnet new -t web

Solution File Structure

  • 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)
        • wwwroot
        • Dependencies
        • Program.cs
        • project.json
          • project.lock.json
        • Startup.cs
        • web.config

Project Files

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();
        }
    }
}

Configure to Serve Static Files

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>

Execute

web-app-debug

web-app-index-html

web-app-debug-console

Enable Default Document

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();
        }
    }
}

Enable Directory Browser

  1. Rename index.html something else
  2. Add additional static files to wwwroot
  3. 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();
        }
    }
}

Execute

web-apps-directory-browser

launchSettings.json Create Multiple Launch Profiles

"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"
    }
  }
}

Startup.cs Environment-based Middleware Registration

if (env.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
}

Back to Top

project.json Configuration Dependency

"dependencies": {
  "Microsoft.NETCore.App": {
    "version": "1.0.0",
    "type": "platform"
  },
  "Microsoft.Extensions.Configuration.Json": "1.0.0",
  ...

Startup.cs Configuration Setup

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

web-apps-configuration

Back to Top

Create a .Net Core Class Library

Add a new Project to the WebAppsAndMiddleware Solution

web-apps-class-lib

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

web-app-class-lib-render

Back to Top

Routing

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

web-app-routing

Back to Top

Setup MVC

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

web-app-controller

Configure MVC Route Rules

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

Implement MVC View

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:

web-app-view-error

Create the following file structure in the web app project root:

  • Views
    • Home
      • Index.cshtml

Index.cshtml

<h1>Index</h1>
<p>
    Rendered from the Index() action method from the Home controller.
</p>

Execute

web-app-view

Using MVC Models

Create the following file structure in the web app project root:

  • Models
    • Home
      • IndexModel.cs

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

web-app-mvc

Back to Top

Setup

Create new ASP.NET Core Web App Project

forms-new-project

Web Application with Individual User Accounts Authentication

forms-project-template

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

add-controller-scaffolding

add-controller-scaffolding-details

Modifications made from adding a controller

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

pending-migration-error

Configure EF Database

{app-dir}>dotnet ef migrations add initial

add-migration

{app-dir}>dotnet ef database update

apply-migrations

Execute

movies-app

appsettings.json database connection string

"ConnectionStrings": {
  "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=aspnet-mvc_form-980d7c59-581b-4d28-bcd5-96fb385aa42c;Trusted_Connection=True;MultipleActiveResultSets=true"
}

Adding Data

Movies/Create GET

movies-app-create-get

Create.cshtml

<!-- Specifies to call the Create POST action method in the current controller context -->
<form asp-action="Create">

Movies/Create POST

movies-app-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);
}

Model Attributes

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

movies-app-details

Manage SQL Database

View -> SQL Server Object Explorer or Ctrl + , Ctrl + S

sql-server-object-explorer

Back to Top

Model Setup

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

movies-app-input-validation

See how ASP.NET Core tag helpers expand to provide input validation when rendered in the DOM

movies-app-view-source

Back to Top

A lot of information is logged as output when debugging an ASP.NET Core app

movies-app-middleware-logging

Add Logging

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

movies-app-debug-logging

Available log level options, by increasing severity:

  1. Debug
  2. Information
  3. Warning
  4. Error
  5. Critical

Add a New Logger

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

movies-app-custom-logging

Set LogLevel in Code Rather Than Configuration

Startup.cs

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
    loggerFactory.AddConsole(LogLevel.Warning);
    
    // Additional configuration
}

Creating a Log File

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

movies-app-added-log

movies-app-log-file

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment