Wednesday, January 30, 2019

Personal Notes from Udemy Course "ASP.NET Core 2.0"

Here are my notes for my future reference from Jonas Fagerberg's excellent Udemy Course. I would highly recommend you go buy the course: "ASP.NET Core 2.0"

Chapter 3 - MVC

Startup.cs - Let's get wired up!

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using AspNetCoreVideoCourse.Services;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

namespace AspNetCoreVideoCourse
{
    public class Startup
    {
        public IConfiguration Configuration { get; set; }
    
        public Startup()
        {
            Configuration = new ConfigurationBuilder().SetBasePath(Directory.GetCurrentDirectory())
                .AddJsonFile("appsettings.json")
                .Build();
        }
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();
            services.AddSingleton(provider => Configuration);
            services.AddSingleton();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env, IMessageService messageService)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseMvc(routes =>
            {
                routes.MapRoute(
                    name: "default",
                    template: "{controller=Home}/{action=Index}/{id?}");
            });
            app.Run(async (context) =>
            {
                await context.Response.WriteAsync(messageService.GetMessage());
            });
        }
    }
}

Explicit Attribute Routing

namespace AspNetCoreVideoCourse.Controllers
{
    [Route("[controller]")]
    public class EmployeeController
    {
        [Route("[action]")]
        public string Name()
        {
            return "Jonas";
        }
        [Route("[action]")]
        public string Country()
        {
            return "Sweden";
        }
        [Route("")]
        [Route("[action]")]
        public string Index()
        {
            return "Hello from Employee";
        }
    }
}

Generalized Attribute Routing:

namespace AspNetCoreVideoCourse.Controllers
{
    [Route("company/[controller]/[action]")]
    public class EmployeeController
    {
        public string Name()
        {
            return "Jonas";
        }
        public string Country()
        {
            return "Sweden";
        }
        [Route("")]
        public string Index()
        {
            return "Hello from Employee";
        }
    }
}

To return json data directly to the browser:

public class HomeController : Controller
    {
        public ObjectResult Index()
        {
            var model = new Video {Id = 1, Title = "Shreck"};
            return new ObjectResult(model);
        }
    }

To display an IEnumerable in Razor

@model IEnumerable<AspNetCoreVideoCourse.Models.Video>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>Video</title>
</head>
<body>
    <table>
        @foreach (var video in Model)
        {
            <tr>
                <td>@video.Id</td>
                <td>@video.Title</td>
            </tr>
        }
    </table>

</body>
</html>

Chapter 5: Models

Creating an object through POST Example

Data annotations are found in System.ComponentModel.DataAnnotations.

The most common are:
MinLength,MaxLength,Range, RegularExpression, Display, DataType, Required, Compare

Example

in Video.cs
public class Video
{
    public int Id { get; set; }
    [Required, MinLength(3)]
    [DataType(DataType.Password)]
    public string Title { get; set; }
    [Display(Name = "Film Genre")]
    public Genres Genre { get; set; }
}
in ViewModels/ViedoEditViewModel.cs:
namespace AspNetCoreVideoCourse.ViewModels
{
    public class VideoEditViewModel
    {
        public int Id { get; set; }
        [Required, MinLength(3), MaxLength(80)]
        public string Title { get; set; }
        public Genres Genre { get; set; }
    }
}

in HomeController.cs:
HttpGet]
public IActionResult Create()
{
    return View();
}
[HttpPost]
public IActionResult Create(VideoEditViewModel model)
{
    if (ModelState.IsValid)
    {
        var video = new Video
        {
            Title = model.Title,
            Genre = model.Genre
        };
        _videoData.Add(video);

        return RedirectToAction("Details", new {id = video.Id});
    }

    return View();
}
in Views/Home/Create.cshtml:
@using AspNetCoreVideoCourse.Models
@model AspNetCoreVideoCourse.Entities.Video
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<h2>Create Video</h2>
<form asp-action="Create" method="post">
    <div asp-validation-summary="All"></div>
    <table>
        <tr>
            <td><label asp-for="Title"></label></td>
            <td><input asp-for="Title"/></td>
            <td><span asp-validation-for="Title"></span></td>
        </tr>
        <tr>
            <td><label asp-for="Genre"></label></td>
            <td><select asp-for="Genre" asp-items="Html.GetEnumSelectList<Genres>()"></select></td>
            <td><span asp-validation-for="Genre"></span></td>
        </tr>
    </table>
    <input type="submit" value="Create"/>
</form>
<div><a asp-action="Index">Back to List</a></div>

Entity Framework

in package manager:

PM> add-Migration
cmdlet Add-Migration at command pipeline position 1
Supply values for the following parameters:
Name: Initial
PM> update-database

Additional Files:

In Startup.cs:
public class Startup

public IConfiguration Configuration { get; set; }

public Startup(IHostingEnvironment env)
{
    var builder = new ConfigurationBuilder().SetBasePath(Directory.GetCurrentDirectory())
        .AddJsonFile("appsettings.json", optional: true);

    if (env.IsDevelopment())
    {
        builder.AddUserSecrets<Startup>();

    }

    Configuration = builder.Build();

}
public void ConfigureServices(IServiceCollection services)
{
    var conn = Configuration.GetConnectionString("DefaultConnection");
    services.AddDbContext<VideoDbContext>(options => options.UseSqlServer(conn));
    services.AddMvc();
    services.AddSingleton(provider => Configuration);
    services.AddSingleton<IMessageService, ConfigurationMessageService>();
    services.AddScoped<IVideoData, SqlVideoData>();
}

In SqlVideoData.cs:
:
using System.Collections.Generic;
using AspNetCoreVideoCourse.Data;
using AspNetCoreVideoCourse.Entities;

namespace AspNetCoreVideoCourse.Services
{
    public class SqlVideoData : IVideoData
    {
        private VideoDbContext _db;

        public SqlVideoData(VideoDbContext db)
        {
            _db = db;
        }

        public IEnumerable<Video> GetAll()
        {
            return _db.Videos;
        }

        public Video Get(int id)
        {
            return _db.Find<Video>(id);
        }

        public void Add(Video newVideo)
        {
            _db.Add(newVideo);
            _db.SaveChanges();
        }
    }
}
in VideoDbContext.cs:
using AspNetCoreVideoCourse.Entities;
using Microsoft.EntityFrameworkCore;

namespace AspNetCoreVideoCourse.Data
{
    public class VideoDbContext : DbContext
    {
        public DbSet<Video> Videos { get; set; }

        public VideoDbContext(DbContextOptions<VideoDbContext> options) : base(options)
        {
            
        }

        protected override void OnModelCreating(ModelBuilder builder)
        {
            base.OnModelCreating(builder);
        }
    }
}
       

To Add an "Edit" View

in Edit.cshtml:
<form asp-action="Edit" method="post">
    <div asp-validation-summary="All"></div>
    <table>
        <tr>
            <td><label asp-for="Title"></label></td>
            <td><input asp-for="Title" /></td>
            <td><span asp-validation-for="Title"></span></td>
        </tr>
        <tr>
            <td><label asp-for="Genre"></label></td>
            <td><select asp-for="Genre" asp-items="Html.GetEnumSelectList<Genres>()"></select></td>
            <td><span asp-validation-for="Genre"></span></td>
        </tr>
    </table>
    <input type="submit" value="Edit" />
</form>

in HomeController.cs:
[HttpGet]
public IActionResult Edit(int id)
{
    var video = _videoData.Get(id);
    if (video == null)
    {
        return RedirectToAction("Index");
    }

    return View(video);
}
[HttpPost]
public IActionResult Edit(int id, VideoEditViewModel model)
{
    var video = _videoData.Get(id);
    if (video == null || !ModelState.IsValid) return View(model);

    video.Title = model.Title;
    video.Genre = model.Genre;

    _videoData.Commit();

    return RedirectToAction("Details", new {id = video.Id});
}
in SqlVideoData.cs:
using System.Collections.Generic;
using AspNetCoreVideoCourse.Data;
using AspNetCoreVideoCourse.Entities;

namespace AspNetCoreVideoCourse.Services
{
    public class SqlVideoData : IVideoData
    {
        private VideoDbContext _db;

        public SqlVideoData(VideoDbContext db)
        {
            _db = db;
        }

        public IEnumerable<Video> GetAll()
        {
            return _db.Videos;
        }

        public Video Get(int id)
        {
            return _db.Find<Video>(id);
        }

        public void Add(Video newVideo)
        {
            _db.Add(newVideo);
            _db.SaveChanges();
        }

        public int Commit()
        {
            return _db.SaveChanges();
        }
    }
}

Section 7 Razor Views

_ViewStart.cshtml will define variables for all pages in lower directories
_ViewImports.cshtml will define import statements for all lower pages

Partial Views

  @Html.Partial("_Video", video);
  @await Html.PartialAsync("_Video", video);

Components

Components allow you to inject html from code.

in Views/Shared/Components/Message/Default.cshtml:
using AspNetCoreVideoCourse.Services;
using Microsoft.AspNetCore.Mvc;

namespace AspNetCoreVideoCourse.ViewComponents
{
    public class Message : ViewComponent
    {
        private readonly IMessageService _message;

        public Message(IMessageService message)
        {
            _message = message;
        }

        public IViewComponentResult Invoke()
        {
            var model = _message.GetMessage();
            return View("Default", model);
        }
    }
}
in Views/Shared/_Layout.cs:
<footer>
    @RenderSection("footer",false)
    @await Component.InvokeAsync("Message")
</footer>

Section 8: Forms Authentication / Identity Management

in AccountController:
using System.Threading.Tasks;
using AspNetCoreVideoCourse.Entities;
using AspNetCoreVideoCourse.ViewModels;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;

namespace AspNetCoreVideoCourse.Controllers
{
    public class AccountController : Controller
    {
        private readonly UserManager<User> _userManager;
        private readonly SignInManager<User> _signInManager;

        public AccountController(UserManager<User> userManager, SignInManager<User> signInManager)
        {
            _userManager = userManager;
            _signInManager = signInManager;
        }
        [HttpGet]
        public IActionResult Register()
        {
            return View();
        }

        [HttpPost]
        public async Task<IActionResult> Register(RegisterViewModel model)
        {
            if (!ModelState.IsValid) return View();

            var user = new User {UserName = model.Username};
            var result = await _userManager.CreateAsync(user, model.Password);
            if (result.Succeeded)
            {
                await _signInManager.SignInAsync(user, false);
                return RedirectToAction("Index", "Home");
            }
            else
            {
                foreach (var error in result.Errors)
                {
                    ModelState.AddModelError("",error.Description);
                }
            }

            return View();
        }

        [HttpPost]
        public async Task<IActionResult> Logout()
        {
            await _signInManager.SignOutAsync();
            return RedirectToAction("Index", "Home");
        }

        [HttpGet]
        public IActionResult Login(string returnUrl = "")
        {
            var model = new LoginViewModel {ReturnUrl = returnUrl};
            return View(model);
        }
        [HttpPost]
        public async Task<IActionResult> Login(LoginViewModel model)
        {
            if (!ModelState.IsValid) return View(model);
            var result =
                await _signInManager.PasswordSignInAsync(model.Username, model.Password, model.RememberMe, false);

            if (result.Succeeded)
            {
                if (!string.IsNullOrEmpty(model.ReturnUrl) &&
                    Url.IsLocalUrl(model.ReturnUrl))
                {
                    return Redirect(model.ReturnUrl);
                }
                else
                {
                    return RedirectToAction("Index", "Home");
                }
            }
            ModelState.AddModelError("","Login failed");
            return View(model);
        }
    }
}  
in Views/Account/Login:
@model LoginViewModel

@{ ViewBag.Title = "Login";}

<h2>Login</h2>

<form method="post" asp-controller="Account" asp-action="Login" asp-route-returnurl="@Model.ReturnUrl">
    <div asp-validation-summary="ModelOnly"></div>
    
    <div>
        <label asp-for="Username"></label>
        <input asp-for="Username"/>
        <span asp-validation-for="Username"></span>
    </div>

    <div>
        <label asp-for="Password"></label>
        <input asp-for="Password"/>
        <span asp-validation-for="Password"></span>
    </div>

    <div>
        <label asp-for="RememberMe"></label>
        <input asp-for="RememberMe"/>
        <span asp-validation-for="RememberMe"></span>
    </div>
    
    <div>
        <input type="submit" value="Register"/>
    </div>

</form>
in Views/Account/Register:
@model RegisterViewModel

@{ ViewBag.Title = "Register";}

<h1>Register</h1>
<form method="post" asp-controller="Account" asp-action="Register">
    <div asp-validation-summary="ModelOnly"></div>
    
    <div>
        <label asp-for="Username"></label>
        <input asp-for="Username"/>
        <span asp-validation-for="Username"></span>
    </div>

   <div>
        <label asp-for="Password"></label>
        <input asp-for="Password"/>
        <span asp-validation-for="Password"></span>
    </div>

   <div>
        <label asp-for="ConfirmPassword"></label>
        <input asp-for="ConfirmPassword"/>
        <span asp-validation-for="ConfirmPassword"></span>
    </div>
    
    <div>
        <input type="submit" value="Register"/>
    </div>
</form>
in ViewModels/LoginViewModel.cs:
using System.ComponentModel.DataAnnotations;

namespace AspNetCoreVideoCourse.ViewModels
{
    public class LoginViewModel
    {
        [Required]
        public string Username { get; set; }
        [Required, DataType(DataType.Password)]
        public string Password { get; set; }
        public string ReturnUrl { get; set; }
        [Display(Name = "Remember Me")]
        public bool RememberMe { get; set; }
    }
}
in ViewModels/RegisterViewModel.cs:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;

namespace AspNetCoreVideoCourse.ViewModels
{
    public class RegisterViewModel
    {
        [Required, MaxLength(255)]
        public string Username { get; set; }
        [Required, DataType(DataType.Password)]
        public string Password { get; set; }
        [Required, DataType(DataType.Password), Compare(nameof(Password))]
        public string ConfirmPassword { get; set; }
    }
}
in Views/Shared/_LoginLinks.cshtml:
@using Microsoft.AspNetCore.Identity

@inject SignInManager<User> SignInManager
@inject UserManager<User> UserManger

@if (SignInManager.IsSignedIn(User))
{
    <div>@User.Identity.Name</div>
    <form method="post" asp-controller="Account" asp-action="Logout">
        <input type="submit" value="Logout"/>
    </form>
}
else
{
    <a asp-controller="Account" asp-action="Login">Login</a>
    <a asp-controller="Account" asp-action="Register">Register</a>
}


No comments: