Instalación

Visual Studio Code:

https://code.visualstudio.com/

Sesión #6

La Walking Skeleton

En un contexto de desarrollo de software, es una implementación inicial mínima de la arquitectura de una aplicación que incluye y conecta los componentes básicos del sistema.

 

Como su nombre lo indica, la estructura es funcional de una manera rudimentaria. El sistema  no proporciona el nivel de servicio requerido para el producto final. Los subsistemas están incompletos pero conectados.

La Walking Skeleton

Objetivos:

  

  • Implementar la funcionalidad básica de la API del proyecto de ejemplo.

 

  • Entender: uso de donet CLI, API Controller y Endpoints, Entity Framework, la estructura de una API, variables de entorno, control de versiones.

Crear el proyecto

mkdir DatingApp
cd DatingApp

dotnet --info
dotnet -help

#Crear una solución
dotnet new sln

#Crear la API
dotnet new webapi -o API

#Ver los archivo creados
ls

#Agregar el proyecto API a la solución
dotnet sln add API

Configurar VS Code

Instalar las siguiente extensiones:

  • C#
  • C# extensions
  • Material Icon Theme

Activar Auto save

 

Conociendo los archivos de la API

cd API
dotnet watch run

dotnet dev-certs https --trust

¿Qué es Entity Framework?

  • Object Relational Mapper ORM
  • Traducir nuestros comandos a SQL para actualizar las tablas de la BD

AppUser

Entity Framework

DbContext

Entity Framework

  • Consultar (Querying)  
  • Seguimiento de cambios (change tracking)
  • Guardar
  • Concurrencias (Concurrency)
  • Transacciones (Transactions)
  • Cacheo
  • Convenciones Built-in
  • Configurar
  • Migraciones

Agregar Entity Framework

Instalar en vscode la extensión NuGet Gallery

Ctrl + Shift + P

Escribir NuGet: Open NuGet Gallery

 

Agregar DBContext

Crear la carpeta Data y el archivo DataContext.cs

using API.Entities;
using Microsoft.EntityFrameworkCore;

namespace API.Data
{
    public class DataContext : DbContext
    {
        public DataContext(DbContextOptions options) : base(options)
        {

        }

        public DbSet<AppUser> Users { get; set; }
    }
}

Agregar DBContext

En Startup.cs

// ...        
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddDbContext<DataContext> (options => {
                options.UseSqlite("Connection string");
            });
            services.AddControllers();
        }
// ...

Agregar el connection string

En appSettings.Development.json

// ...        
  "ConnectionStrings": {
    "DefaultConnection": "Data source=datingapp.db"
  },
// ...
// ...
		private readonly IConfiguration _config;
        public Startup(IConfiguration config)
        {
            _config = config;

        }
// ....

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddDbContext<DataContext>(options =>
            {
                options.UseSqlite(_config.GetConnectionString("DefaultConnection"));
            });
            services.AddControllers();
        }

En Startup.cs

Agregar el connection string

Con Nuget, instalar: Microsoft.EntityFrameworkCore.Design

// Hacer la migración desde la terminal 
dotnet ef migrations add InitialCreate -o Data/Migrations

Instalar desde la terminal: dotnet tool install --global dotnet-ef --version 5.0.1 (Buscar la versión en nuget.org)

Crear la BD usando ef

// Desde la terminal
dotnet ef database update

Para la administración de la BD se debe instalar la extesión SQLite en Visual Studio Code

 

Ctrl + Shift + P, escribe SQLite y selecciona la opción Open Database y luego especifica la ruta de la aplicación.

 

Listo!, ya se puede gestionar la base de datos.

Agregar una API Controller

using System.Collections.Generic;
using System.Linq;
using API.Data;
using API.Entities;
using Microsoft.AspNetCore.Mvc;

namespace API.Controllers
{
    [ApiController]
    [Route("api/[controller]")]
    public class UsersController : ControllerBase
    {
        private readonly DataContext _context;
        public UsersController(DataContext context)
        {
            _context = context;
        }
        [HttpGet]
        public ActionResult<IEnumerable<AppUser>> GetUsers()
        {
            return _context.Users.ToList();
        }

        [HttpGet("{id}")]
        public ActionResult<AppUser> GetUser(int id)
        {
            return _context.Users.Find(id);
        }
    }
}

Crear UsersController.cs

Agregar una API Controller

Habiendo creado el controlador es momento de probar las rutas creadas a través de Postman.

Hacer nuestro código asíncrono

using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using API.Data;
using API.Entities;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;

namespace API.Controllers
{
    [ApiController]
    [Route("api/[controller]")]
    public class UsersController : ControllerBase
    {
        private readonly DataContext _context;
        public UsersController(DataContext context)
        {
            _context = context;
        }
        [HttpGet]
        public async Task<ActionResult<IEnumerable<AppUser>>> GetUsers()
        {
            return await _context.Users.ToListAsync();
        }

        [HttpGet("{id}")]
        public async Task<ActionResult<AppUser>> GetUser(int id)
        {
            return await _context.Users.FindAsync(id);
        }
    }
}

Control de versiones

// Desde la terminal en la raíz del proyecto 
git status
git init
dotnet new gitignore

// Agregar en gitignore

appsettings.json

// Continuamos con GIT
git add .
git commit -m "Users entity finished"
git remote add origin https://github.com/meneseswill/DatingApp.git
git push -u origin master

Descargar GIT: https://git-scm.com/downloads

Crear cuenta en GitHub: https://github.com/l

La Walking Skeleton

Objetivos:

  

  • Implementar la funcionalidad básica de la API del proyecto de ejemplo.

 

  • Entender: uso de donet CLI, API Controller y Endpoints, Entity Framework, la estructura de una API, variables de entorno, control de versiones.

Sesión #7

La Walking Skeleton (Parte 2)

Objetivos:

Completar esa sesión requiere entender:

  • Usar Angular CLI
  • Cómo crear una aplicación Angular
  • Los archivos de un proyecto Angular
  • El proceso de Angular Bootstrap
  • Realizar consultas HTTP con Angular
  • Ejecutar las aplicaciones Angular con HTTPS
  • Agregar paquetes con npm

Crear la aplicación Angular

Visite el siguiente sitio para ver el proceso de instalación de Angular CLI: https://cli.angular.io/

// Verificar que se cumple con los requerimientos mínimos
node --version
npm --version

// Instalar Angular CLI
npm install -g @angular/cli
ng new client

Ejecutar la aplicación Angular

cd client
ng serve

Agregar extensiones a VS Code para trabajar Angular

  • Angular Language Service
  • Angular Snippets
  • Bracket Pair Colorizer 2

Realizar solicitudes HTTP con Angular (I)

Agregar el módulo en src/app/app.module.ts

// ...
import {HttpClientModule} from '@angular/common/http';

// ...

  imports: [
    BrowserModule,
    AppRoutingModule,
    HttpClientModule
  ],

Realizar solicitudes HTTP con Angular (II)

En src/app/app.component.ts

import { HttpClient } from '@angular/common/http';
import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
  title = 'The Dating App';
  users: any;

  constructor(private http: HttpClient){}
  ngOnInit(){
    this.getUsers();
  }

  getUsers() {
    this.http.get('https://localhost:5001/api/users').subscribe(response => {
      this.users = response;
    }, error => {
      console.log(error);
    })
  }
}

Agregar CORS

Para arreglar el problema de los CORS, vamos a la API, en Startup.cs

// ....

// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
  // ...
  services.AddCors();
}

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
  // ...

  app.UseRouting();

  app.UseCors(x => x.AllowAnyHeader().AllowAnyMethod().WithOrigins("http://localhost:4200"));

  app.UseAuthorization();

  // ...
}

// ...

Mostrar los usuarios

En app.component.html

<h1>{{title}}</h1>
<ul>
    <li *ngFor='let user of users'>
  		{{user.id}} - {{user.userName}}
    </li>
</ul>

Agregar bootstrap y fontawesome

cd client
ng add ngx-bootstrap 
npm install font-awesome

// Se debe reiniciar Angular

Usar HTTPS en Angular (MAC)

OS X

1. Double click on the certificate (server.crt)

2. Select your desired keychain (login should suffice)

3. Add the certificate

4. Open Keychain Access if it isn’t already open

5. Select the keychain you chose earlier

6. You should see the certificate localhost

7. Double click on the certificate

8. Expand Trust

9. Select the option Always Trust in When using this certificate

10. Close the certificate window

Usar HTTPS en Angular (WIN)

Windows 10

 

1. Double click on the certificate (server.crt)

2. Click on the button “Install Certificate …”

3. Select whether you want to store it on user level or on machine level

4. Click “Next”

5. Select “Place all certificates in the following store”

6. Click “Browse”

7. Select “Trusted Root Certification Authorities”

8. Click “Ok”

9. Click “Next”

10. Click “Finish”

Usar HTTPS en Angular

Copiar los certificados server.crt y server.key en una carpeta dentro de client llamada ssl, luego en angular.json

// ...
"serve": {
  "builder": "@angular-devkit/build-angular:dev-server",
    "options": {
      "sslCert": "./ssl/server.crt",
        "sslKey": "./ssl/server.key",
          "ssl": true,
            "browserTarget": "client:build"
    },
//...
//
//Hay que reiniciar los servidores, antes debe agregar en el CORS la ruta actual
//Esto es en Startup.cs

app.UseCors(x => x.AllowAnyHeader()
            .AllowAnyMethod()
            .WithOrigins("https://localhost:4200"));

Guardar en source control

git add .
git commit -m "End of section 3"
git push origin master

En el gitignore del client, agregue la exclusión de la carpeta ssl

La Walking Skeleton (Parte 2)

Objetivos:

Completar esa sesión requiere entender:

  • Usar Angular CLI
  • Cómo crear una aplicación Angular
  • Los archivos de un proyecto Angular
  • El proceso de Angular Bootstrap
  • Realizar consultas HTTP con Angular
  • Ejecutar las aplicaciones Angular con HTTPS
  • Agregar paquetes con npm

Sesión #8

La Walking Skeleton (Parte 2)

Objetivo:

Implementar autenticación en nuestra aplicación.

Entender:

  • Cómo almacenar Pass en BD
  • Uso de herencia en C#, DRY
  • Uso del C# Debugger
  • Uso de Data Transfer Objects (DTOs)
  • Validaciones
  • Jason Web Tokens (JWTs)
  • Uso de servicios en C#
  • Middleware
  • Extender métodos, DRY

¿Por dónde iniciar?

Requerimientos:

  • El usuario debería ser capaz de hacer login
  • El usuario debe ser capaz de registrarse
  • El usuario debe ser capaz de ver a otros usuarios
  • El usuario debe ser capaz de enviar mensajes privados a otros usuarios.

Todo comienza por el usuario

¿Cómo almacenar la contraseña de los usuarios?

Opción 1: en texto simple

¿Cómo almacenar la contraseña de los usuarios?

Opción 2: encriptar la contraseña (hash)

¿Cómo almacenar la contraseña de los usuarios?

Opción 2: Aplicar a la contraseña hash y salt

Actualizar la entidad User

namespace API.Entities
{
    public class AppUser
    {
        public int Id { get; set; }
        public string UserName { get; set; }
        public byte[] PasswordHash { get; set; }
        public byte[] PasswordSalt { get; set; }
    }
}
// Desde la terminal

dotnet ef migrations add UserPasswordAdded
dotnet ef database update

Crear a Base API Controller

// En Controllers, crear la clase BaseApiController.cs

using Microsoft.AspNetCore.Mvc;

namespace API.Controllers
{
    [ApiController]
    [Route("api/[controller]")]
    public class BaseApiController : ControllerBase
    {

    }
}
// Se puede extender de BaseApiController y omitir los atributos iniciales
// 
namespace API.Controllers
{
    public class UsersController : BaseApiController
    
    // ...

Crear el controlador Account

using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using API.Data;
using API.Entities;
using Microsoft.AspNetCore.Mvc;

namespace API.Controllers
{
    public class AccountController : BaseApiController
    {
        private readonly DataContext _context;
        public AccountController(DataContext context)
        {
            _context = context;
        }

        [HttpPost("register")]
        public async Task<ActionResult<AppUser>> Register(string username, string password){
            using var hmac = new HMACSHA512();

            var user = new AppUser {
                UserName = username,
                PasswordHash = hmac.ComputeHash(Encoding.UTF8.GetBytes(password)),
                PasswordSalt = hmac.Key,
            };

            _context.Users.Add(user);
            await _context.SaveChangesAsync();

            return user;
        }
    }
}

Usando en Debugger

Ctrl + Shift + P => .NET Generate Assets for Build and Debug

En Windows

En MAC

Usando DTOs

(Data Transfer Object)

Crear el archivo DTOs/RegisterDto.cs

namespace API.DTOs
{
    public class RegisterDto
    {
        public string Username { get; set; }
        public string Password { get; set; }
    }
}

Usando DTOs

(Data Transfer Object)

Usar el DTO en AccountController.cs y validar si el usuario ya existe

using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using API.Data;
using API.DTOs;
using API.Entities;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;

namespace API.Controllers
{
    public class AccountController : BaseApiController
    {
        private readonly DataContext _context;
        public AccountController(DataContext context)
        {
            _context = context;
        }

        [HttpPost("register")]
        public async Task<ActionResult<AppUser>> Register(RegisterDto registerDto){
            if(await UserExists(registerDto.Username)) return BadRequest("Username is taken");
            
            using var hmac = new HMACSHA512();

            var user = new AppUser {
                UserName = registerDto.Username.ToLower(),
                PasswordHash = hmac.ComputeHash(Encoding.UTF8.GetBytes(registerDto.Password)),
                PasswordSalt = hmac.Key,
            };

            _context.Users.Add(user);
            await _context.SaveChangesAsync();

            return user;
        }

        private async Task<bool> UserExists(string username)
        {
            return await _context.Users.AnyAsync(x => x.UserName == username.ToLower());
        }
    }
}

Agregar validación

using System.ComponentModel.DataAnnotations;

namespace API.DTOs
{
    public class RegisterDto
    {
        [Required]
        public string Username { get; set; }
        [Required]
        public string Password { get; set; }
    }
}

Agregar el endpoint login

// ...
[HttpPost("login")]
public async Task<ActionResult<AppUser>> Login(LoginDto loginDto) 
{
  var user = await _context.Users
  .SingleOrDefaultAsync(x => x.UserName == loginDto.Username);

  if(user == null) return Unauthorized("Invalid username");

  using var hmac = new HMACSHA512(user.PasswordSalt);

  var computeHash = hmac.ComputeHash(Encoding.UTF8.GetBytes(loginDto.Password));

  for(int i = 0; i < computeHash.Length; i++)
  {
    if(computeHash[i] != user.PasswordHash[i]) return Unauthorized("Invalid password");
  }

  return user;
}
// ...

En AccountController.cs

namespace API.DTOs
{
    public class LoginDto
    {
        public string Username { get; set; }
        public string Password { get; set; }
    }
}

Crear LoginDto.cs en DTOs

Borrar registros de la VD

dotnet ef database drop
dotnet ef database update
dotnet watch run

Desde la terminal

JSON Web Tokens (JWT)

Beneficios del JWT

  • No se manejan sesiones. El JWT está autocontenido en el token
  • Portable. Un token puede ser usado con múltiples Backends
  • No se requieren Cookies (Mobile friendly)
  • Rendimiento. Una vez que se emite el token, ya no es necesario hacer una solicitud a la base de datos para verificar la autenticación del usuario.

Agregar lógica a CreateToken

        public string CreateToken(AppUser user)
        {
            var claims = new List<Claim> 
            {
                new Claim(JwtRegisteredClaimNames.NameId, user.UserName)
            };

            var creds = new SigningCredentials(_key, SecurityAlgorithms.HmacSha512Signature);

            var tokenDescriptor = new SecurityTokenDescriptor
            {
                Subject = new ClaimsIdentity(claims),
                Expires = DateTime.Now.AddDays(7),
                SigningCredentials = creds
            };

            var tokenHandler = new JwtSecurityTokenHandler();

            var token = tokenHandler.CreateToken(tokenDescriptor);

            return tokenHandler.WriteToken(token);
        }

Cttr + shift + p => nugget Gallery

Desarrollo de Aplicaciones III

By Wilfredo Meneses

Desarrollo de Aplicaciones III

  • 1,232