Curso

ASP.NET Core

Web Api

Sobre mi

.NET Core

.NET Frameworks

.NET Frameworks

Diferencias con .NET Core

  • .NET vs .NET Core
    • .NET Core no soporta todos los tipos de applicaciones .Net
    • .NET está enfocado solo a entornos Windows
    • Mayoría de APIs comunes entre ambos
  • ​Mono vs .NET Core
    • Mono está enfocado a desarrollo móvil
    • .NET Core está enfocado a cloud y apps de escritorio

.NET Core

Caracteríaticas principales

 

  • Multiplataforma
  • Consistente entre diferentes arquitecturas
  • Soporte Command-Line 
  • Flexibilidad de despliegue con Docker
  • Lenguajes (C#, F# y VisualBasic.Net)
  • Performance
  • Modular

.NET Core

Cuando usar .NET Core

 

  • Siempre que se trate de un proyecto nuevo que no tenga dependencia de frameworks o servicios basados en .Net tradicional
  • Aplicaciones multiplataforma
  • Microservicios (.NET Core es modular)
  • Performance
  • Aplicaciones Universales de Windows (UWP)

.NET Core

Cuando no usar .NET Core

 

  • Aplicaciones que ya se encuentren en producción
  • Cuando estamos obligados a utilizar librerías de terceros incompatibles
  • Utilización de soluciones que aun no han sido portadas a .NET Core como WinForms o ASP.NET Web Forms

.NET Core

Performance

.NET Core

Demo 1

 

  • Instalar la última versión de .NET Core
  • Crear una aplicación de consola a partir de la linea de comandos
dotnet new console --output demo1
dotnet run --project demo1

.NET Core tools

.NET Core tools

  • dotnet new -> crear proyectos, soluciones, etc
  • dotnet sln -> Añadir, quitar o listar proyectos de una solución
  • dotnet add -> Añadir paquetes o referencias a un proyecto.
  • dotnet restore -> Restaurar referencias y paquetes nugets de una solución o proyecto
  • dotnet build -> Compilar proyecto o solución
  • dotnet test -> Ejecutar tests unitarios
  • dotnet run -> Ejecutar un proyecto

 

.NET Core tools

Demo 2.

Creación de un proyecto .NET Core con linea de comandos

 

  1. Crear una solución
  2. Crear un proyecto de tipo library y añadirlo a la solución
  3. Añadir el proyecto library a nuestra solución
  4. Añadir el nuget package Json.Net al proyecto library via linea de comandos
  5. Crear un método que haga uso de Json.Net en nuestro proyecto library

.NET Core tools

Demo 2.

Creación de un proyecto .NET Core con linea de comandos

  1. Crear un proyecto de tipo test  
  2. Añadir el proyecto a nuestra solución
  3. Añadir a nuestro proyecto test, el proyecto library como referencia.
  4. Crear un test para probar el método anteriormente creado con el proyecto library
  5. Ejecutar los tests haciendo uso del comando dotnet test
  6. ​Crear un proyecto de consola que tenga library como referencia y haga uso del método que antes hemos testeado
  7. Ejecutar el proyecto de consola recientemente creado

API REST

Se trata de cualquier interfaz que utilice el protocolo HTTP para intercambiar información

API REST

Características

 

  1. Comunicación Cliente/Servidor sin estado
  2. POST, GET, PUT, DELETE (CRUD)
  3. URI como identificación única de recursos
  4. Facilidad de integración entre sistemas
  5. Escalabilidad (al no compartir estado)

Principios SOLID

Principios SOLID

Principios SOLID

 

Single Responsibility Principle

 

El principio de Responsabilidad Única viene a decirnos que un objeto debe de realizar un solo propósito

Principios SOLID

 

¿Cuando sabemos que estamos violando el Principio de Responsabilidad Única?

 

  • Cuando se aplican varias capas de arquitectura en la misma clase
  • Según el número de métodos públicos
  • Cada vez que escribes nueva funcionalidad la clase se ve afectada y hay que modificarla

Principios SOLID

 

Open / Closed Principle

 

Nuestros objetos deben de estar abiertos a ser extendidos y no a ser modificados. Nuevos requisitos o funcionalidad debe implicar la creación de nuevo código, no la alteración del ya existente

Principios SOLID

 

¿Como detectamos que estamos violando el Principio Open / Closed?

 

Si cada vez que añadimos un nuevo requisito, se ven afectadas a modificación las mismas clases

Principios SOLID

Ejemplo viola el Principio Open / Closed

public enum TipoVehiculo
{
    Coche,
    Moto
}
public class Vehiculo()
{
    public TipoVehiculo Type { get; set; }
    public Draw()
    {
        switch (Type)
        {
            case Coche:
                DrawCoche();
                break;
            case Moto:
                DrawMoto();
                break;
        }
    }
}

Principios SOLID

Ejemplo que cumple el Principio Open / Closed

public abstract class Vehiculo()
{
    public virtual void Draw() { }
}
public class Coche : Vehiculo
{
    public override void Draw()
    {
        DrawCoche();
    }
}
public class Moto : Vehiculo
{
    public override void Draw()
    {
        DrawMoto();
    }
}

Principios SOLID

 

Liskov Substitution Principle

 

El principio de sustitución de Liskov viene a decir que si extendemos una clase, nuestro código debe de funcionar de igual forma con la clase hija que con su padre.

Este principio nos ayudará a utilizar la herencia de forma correcta.

Principios SOLID

 

¿Cómo podemos identificar que estamos violando el Principio de Sustitución de Liskov?

 

  • Cuando al extender una clase alguno de los métodos que sobre-escribimos no hace nada o lanza una excepción
  • A través de nuestros Tests. Las clases tanto padres como hijas deben de poder pasar los mismos tests.

Principios SOLID

 

Interface Segregation Principle

 

Cuando creamos interfaces que definan los contratos de nuestras clases, todas las clases que las implementen, deberán de agregar comportamientos a todos los métodos del contrato

Principios SOLID

 

¿Cuando sabemos que estamos violando el principio de segregación de interfaces?

 

Si al implementar nuestra interfaz en una clase, vemos que alguno de los métodos a implementar no tienen sentido en nuestra clase. Como solución, debemos dividir la interfaz inicial a varias interfaces más específicas

Principios SOLID

Ejemplo que viola el principio de segregación de interfaces

public interface IProduct
{
    int GetRecommendedAge();
}
public class DVD : IProduct
{
    public int GetRecommendedAge()
    {
    }
}
public class CD : IProduct
{
    public int GetRecommendedAge()
    {
        // No tiene sentido este método
    }
}

Principios SOLID

Ejemplo que cumple el principio de segregación de interfaces

public interface IProduct
{
    int GetRecommendedAge();
}
public interface IAgeRecommender
{
    int GetRecommendedAge();
}
public class DVD : IProduct, IAgeRecommender
{
    public int GetRecommendedAge()
    {
    }
}
public class CD : IProduct
{
}

Principios SOLID

 

Dependency Inversion

 

Debemos abstraer a través de interfaces la implementación concreta de nuestros servicios, de forma, que el núcleo de nuestro código no dependa de las implementaciones, sino únicamente de contratos  

Principios SOLID

 

¿Cómo saber que estamos violando el principio de inversión de dependencias?

 

Cualquier instancia de una clase compleja dentro de otra, hace que violemos este principio. La mejor manera de darse cuenta es cuando escribimos tests. Cuando no podemos probar una clase con facilidad, ya que depende de otra clase, es un claro ejemplo de violación del principio de inversión de dependencias

Principios SOLID

Ejemplo que no cumple el principio Dependency Inversion

public class CestaCompra
{
    public class CheckOut(Producto producto)
    {
        var baseDeDatos = new BaseDeDatos();
        baseDeDatos.Guardar(producto);

        var pago = new CreditCard();
        pago.Pagar(producto);

    }
}
public class CreditCard
{
    public void Pagar(Producto producto)
    {

    }
}
public class BaseDeDatos()
{
    public class Guardar(Producto producto)
    {

    }
}

Principios SOLID

Ejemplo que cumple el principio Dependency Inversion

// El núcleo de nuestra app es independiente de las implementaciones de los contratos
public class CestaCompra
{
    private readonly IDataAcces _dataAccess;
    private readonly IPaymentMethod _paymentMethod;
    public CestaCompra(IDataAccess dataAccess, IPaymentMethod payment)
    {
        _dataAccess = dataAccess;
        _paymentMethod = paymentMethod;
    }

    public class CheckOut(Producto producto)
    {
        _dataAccess.Guardar(producto);
        _paymentMethod.Pagar(producto);
    }
}

public interface IPaymentMethod
{
    void Pagar(Producto producto);
}
public interface IDataAcces
{
    void Guardar(Producto producto);
}

Git

Git vs TFS

 

  1. Branches mucho más rápidos y flexibles que Git
  2. Pull Requests para revisión de código
  3. Mucho más rápido que TFS
  4. Todo se hace en local, nunca en servidor
  5. Puede manejarse por linea de comandos

 

Git desventajas

 

 

 

La única desventaja es que al principio es difícil de adoptar sobre todo por la gran cantidad de comandos y operaciones que puedes hacer

 

Git Comandos básicos descarga código

  1. Fetch: Esto es comunicar con el servidor si existe algún cambio en nuestra rama. No se descarga código, sino que solo te muestra los cambios que tiene tu código con el servidor
  2. Pull: Es la operación que descarga la última versión del código que haya en el servidor. Es el comando a ejecutar después de un Fetch
  3. Check out: Simplemente cambiamos de rama. Esta operación no descarga ningún código del servidor, a no ser que la rama no exista en local 

Git Comandos básicos subida de código

  1. Commit: Marcamos un cambio en nuestro código en local, pero sin subir este código al servidor. Podemos acumular varios Commits antes de subir los cambios al servidor. Es obligatorio comentar cada Commit.
  2. Push: Sube a servidor los commits acumulados por el usuario.

Git Merge

  1. Merge: En Git es normal crear ramas por cada funcionalidad nueva que vamos a desarrollar. Este comando se utiliza para copiar los cambios de una rama a otra.
  2. Merge Conflict: Cuando después de hacer Merge Git se da cuenta de que existen conflictos.

Git Herramientas

Es recomendable utilizar un cliente de Git para la gestión del código. Veamos algunas recomendaciones:

 

  1. No utilizar la extensión de Git de Visual Studio. Conlleva a confusión de como realmente funciona Git y empeora su aceptación más que mejorarla.
  2. Es recomendable utilizar un IDE aparte para gestionar Git, al menos al principio. Estas son algunas de ellas:
    1. SourceTree
    2. Fork
    3. Git Kraken
    4. Git client
    5. ...

Git buenas prácticas

 

  1. Realizar una estrategia de Git a seguir antes de comenzar un proyecto
  2. Crear siempre una rama (Branch) nueva, cada vez que vayamos a trabajar sobre el código
  3. Utilizar Pull Requests para revisión de código
  4. Crear ramas diferentes según entorno para facilitar integraciones continuas 

Git Demo

 

 

 

 

Instalar un cliente de Git y realizar un pequeño ejemplo que revise los comandos básicos que hemos utilizado

Introducción ASP.NET Core MVC

 ASP.NET Core MVC

 

 

 

Framework basado en la arquitectura MVC para compilar aplicaciones web y Web APIs

 Arquitectura MVC

Model-View-Controller

  1. Model: Lógica de negocio de nuestra app
  2. Controller: Los requests http son redirigidos a los controladores, los cuales hacen uso de la capa Model para devolver el resultado a la capa View en forma de Html o devolviendo datos en formato Json o XML en el caso de un WebAPI
  3. View: Capa de presentación de resultados después de haber sido procesado un request por los controladores

 Componentes

ASP.NET Core MVC

  1. Routing: Mecanismo de redirección a una acción de un controlador a partir de un request Http.
  2. Model Binding: Transforma la información proporcionada en la URI de un Http en objetos que un controlador pueda procesar.
  3. Model Validation: Usar DataAnnotations para hacer validaciones sobre nuestras entidades
  4. Dependency Injection: Inyección de dependencias
  5. Filters: Pre ejecuciones de procesos antes de la ejecución de una acción o un controlador.
  6. Areas: Sub aplicaciones MVC dentro de un mismo proyecto
  7. Web APIs
  8. Testability
  9. Razor view engine
  10. Tag Helpers
  11. ​View Components (Partial Views)

Demo

ASP.NET Core MVC

 

 

Vamos a crear un proyecto nuevo de ASP.NET Core Web Api y vamos a subirlo a un repositorio de Git

 ASP.NET Core MVC Startup class

 Program.Main

public class Program
{
    public static void Main(string[] args)
    {
        CreateWebHostBuilder(args).Build().Run();
    }

    public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
        WebHost.CreateDefaultBuilder(args)
            .UseStartup<Startup>();
}
  1. WebHost.CreateDefaultBuilder: Crea el WebHost de nuestra app
  2. UseStartup: Indica a nuestra app la primera clase a ejecutar

Krestel

Se trata del WebHost (servidor) multi-plataforma que ofrece ASP.NET Core para desplegar nuestro site

 

Podemos utilizar Krestel por si solo o configurar un reverse proxy

Krestel

Ventajas de utilizar un Reverse Proxy Server

 

  1. Podemos ejecutar varias web apps a través de la misma IP y puerto. Krestel no soporta este escenario.
  2. Nos da una capa de seguridad adicional sobre nuestras apps.
  3. Puede integrarse mejor con infraestructuras ya montadas.
  4. Configuración SSL y balances de carga más simplificados

Startup class

Esta clase es en la que definimos el pipeline de las peticiones Http y la configuración de los servicios de nuestra app

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

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

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

        app.UseHttpsRedirection();
        app.UseMvc();
    }
}

Startup class

Método ConfigureServices

 

  1. Se trata de un método opcional
  2. El web host llama a este método antes que el método Configure
  3. El patrón típico a aplicar en este método es usar los métodos Add{Service} para añadir servicios a nuestro contenedor de dependencias y después llamar a service.Configure{Service} en el caso de que sean necesarios
  4. Existen además métodos de extensión de IServiceCollection para añadir multiples servicios que estén relacionados entre si

Startup class

Método ConfigureServices

public void ConfigureServices(IServiceCollection services)
{
    // Add framework services.
    services.AddDbContext<ApplicationDbContext>(options =>
        options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

    services.AddIdentity<ApplicationUser, IdentityRole>()
        .AddEntityFrameworkStores<ApplicationDbContext>()
        .AddDefaultTokenProviders();

    services.AddMvc();

    // Add application services.
    services.AddTransient<IEmailSender, AuthMessageSender>();
    services.AddTransient<ISmsSender, AuthMessageSender>();
}

Startup class

Método Configure

Se trata del método que define como vamos a tratar los requests Http que reciba nuestra app.

Middlewares: se tratan de requests delegates concatenados que manipulan nuestro Http Request. Podemos crear nuestros propios Middlewares si fuera necesario

ASP.NET Core MVC Dependency Injection

 

Dependency Injection

 

 

  • Integrado en ASP.NET Core
  • IoC que nos permite inyectar dependencias en los constructores de nuestras clases
  • Se trata de la aplicación práctica del Principio de Inversión de Dependencias (SOLID)

Dependency Injection

Ejemplo de una clase con dependencias

public class MiDependencia
{
    public MiDependencia()
    {
    }

    public Task WriteMessage(string message)
    {
        Console.WriteLine(
            $"MiDependencia.WriteMessage called. Message: {message}");

        return Task.FromResult(0);
    }
}

public class Modelo : PageModel
{
    MiDependencia _dependency = new MiDependencia();

    public async Task OnGetAsync()
    {
        await _dependency.WriteMessage(
            "IndexModel.OnGetAsync created this message.");
    }
}

Dependency Injection

Ejemplo de una clase aplicando dependency injection

public interface IMyDependency
{
    Task WriteMessage(string message);
}

public class Modelo : PageModel
{
    private readonly IMiDependencia _dependency;
    public Modelo(IMiDependencia dependency)
    {
        _dependency = dependency;
    }

    public async Task OnGetAsync()
    {
        await _dependency.WriteMessage(
            "IndexModel.OnGetAsync created this message.");
    }
}

Dependency Injection

Registro de Servicios en la clase Startup -> ConfigureServices

public void ConfigureServices(IServiceCollection services)
{
    services.AddScoped<IMiDependencia, MiDependencia>();
}

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<ApplicationDbContext>(options =>
        options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

    services.AddIdentity<ApplicationUser, IdentityRole>()
        .AddEntityFrameworkStores<ApplicationDbContext>()
        .AddDefaultTokenProviders();

    services.AddMvc();
}

IServiceCollection es nuestro contenedor de dependencias

Dependency Injection

Ciclo de vida de nuestros servicios

 

  • Transient: Se creará una nueva instancia de nuestro servicio cada vez que lo solicitemos.
  • Scoped: Se creará un servicio por cada Http Request. Durante el procesamiento de un request no se generaran nuevas instancias de este servicio.
  • Singleton: El servicio se crea la primera vez que se solicita y las subsecuentes llamadas al mismo servicio, devolverán la misma instancia previamente creada.

ASP.NET Core MVC Routing

Routing

Responsable de mapear la URI de entrada en un request y procesarla adecuadamente. Este es el Routing por defecto configurado:

{controller=Home}/{action=Index}/{id?}

ASP.NET Core MVC Gestión Errores

Gestión Errores

 

Tenemos 2 formas de gestionar errores a nivel global en una app ASP.NET Core webapi

 

  • Haciendo uso del Middleware existente ExceptionHandler
  • O creando nuestro propio Middleware que gestione los errores

Gestión Errores

ExceptionHandler

app.UseExceptionHandler(options =>
{
    options.Run(async httpContext =>
    {
        httpContext.Response.ContentType = "application/json";
        httpContext.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
        var ex = httpContext.Features.Get<IExceptionHandlerFeature>();
        await httpContext.Response.WriteAsync(
            JsonConvert.SerializeObject(new ErrorModel()
            {
                StatusCode = httpContext.Response.StatusCode,
                Message = ex.Error.Message
            }));
    });
});

Gestión Errores

Custom ExceptionHandler Middleware

 

1. Crear la clase que acepte por constructor un RequestDelegate e implemente el método Invoke o InvokeAsync

 

2. Registrar el nuevo Middleware en el método Configure de la clase Startup

Gestión Errores

Custom ExceptionHandler Middleware

public class ErrorHandlerMiddleware
{
    private readonly RequestDelegate _requestDelegate;
    private readonly ILogger _logger;

    public ErrorHandlerMiddleware(RequestDelegate requestDelegate, ILogger<ErrorHandlerMiddleware> logger)
    {
        _requestDelegate = requestDelegate;
        _logger = logger;
    }

    public async Task InvokeAsync(HttpContext httpContext)
    {
        try
        {
            await _requestDelegate(httpContext);
        }
        catch (Exception e)
        {
            _logger.LogError($"Something went wrong: {e}");
            httpContext.Response.ContentType = "application/json";
            httpContext.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
            
            await httpContext.Response.WriteAsync(
                JsonConvert.SerializeObject(new ErrorModel()
                {
                    StatusCode = httpContext.Response.StatusCode,
                    Message = e.Message
                }));
        }
    }
}

ASP.NET Core

API Versioning

 

API Versioning

Los tipos de versionado que ofrece ASP.NET Core son los siguientes:

 

 

  1. Versión por QueryString (Default)
  2. Versión por URL
  3. Versión a través de Headers 

API Versioning

Setup

 

Instalar el nuget package Microsoft.AspNetCore.Mvc.Versioning

public void ConfigureServices( IServiceCollection services )
{
    services.AddMvc();
    services.AddApiVersioning();
 
    // remaining other stuff omitted for brevity
}
services.AddApiVersioning(
    o =>
    {
        o.AssumeDefaultVersionWhenUnspecified = true );
        o.DefaultApiVersion = new ApiVersion( new DateTime( 2016, 7, 1 ) );
    } );

API Versioning

QueryString versioning

[ApiVersion( "2.0" )]
[Route( "api/helloworld" )]
public class HelloWorld2Controller : Controller {
    [HttpGet]
    public string Get() => "Hello world!";
}

api/helloworld?api-version=2.0

API Versioning

URL Path versioning

[ApiVersion( "1" )]
[Route( "api/v{version:apiVersion}/[controller]" )]
public class HelloWorldController : Controller {
    public string Get() => "Hello world!";
}
 
[ApiVersion( "2" )]
[ApiVersion( "3" )]
[Route( "api/v{version:apiVersion}/helloworld" )]
public class HelloWorld2Controller : Controller {
    [HttpGet]
    public string Get() => "Hello world v2!";
 
    [HttpGet, MapToApiVersion( "3.0" )]
    public string GetV3() => "Hello world v3!";
}

api/v1/helloworld

api/v2/helloworld

api/v3/helloworld

API Versioning

Header versioning

public void ConfigureServices( IServiceCollection services )
{
    services.AddMvc();
    services.AddApiVersioning(o => 
        o.ApiVersionReader = new HeaderApiVersionReader("api-version"));
}

API Versioning

Separar Versiones de Controladores por namespace

namespace My.Services.V1
{
    [ApiVersion( "1.0" )]
    public class HelloWorldController : Controller
    {
        public string Get() => "Hello world v1.0!";
    }
}
namespace My.Services.V2
{
    [ApiVersion( "2.0" )]
    public class HelloWorldController : Controller
    {
        public string Get() => "Hello world v2.0!";
    }
}

ASP.NET Core Configuration Patrón Option

 

Configuration Patrón Option

 

Se trata de un patrón para recuperar Settings en nuestro web app y poder utilizarlos a través de Dependency Injection en nuestros servicios 

Es decir, crear clases Settings que almacenan constantes a utilizar por nuestro proyecto

Options Configuración Básica

 

 

  • Creamos una clase que almacenará nuestros settings

 

public class MyOptions
{
    public MyOptions()
    {
        // Set default value.
        Option1 = "value1_from_ctor";
    }
    
    public string Option1 { get; set; }
    public int Option2 { get; set; } = 5;
}

Options Configuración Básica

 

  • Añadimos nuestra clase como una configuración al contenedor de dependencias dentro del método ConfigureServices en la clase Startup:
// Example #1: Basic options
// Register the Configuration instance which MyOptions binds against.
services.Configure<MyOptions>(Configuration);
  • Hay que indicar en el archivo appsettings.json las propiedades que queremos informar:
{
  "option1": "this is a test",
  "option2" : 500,
  "Logging": {
    "LogLevel": {
      "Default": "Warning"
    }
  },
  "AllowedHosts": "*"
}

Options Configuración Básica

 

  • Podemos resolver la estos settings ya en cualquiera de nuestros servicios de la siguiente forma:
private readonly MyOptions _options;
public UnaClaseCualquiera(IOptions<MyOptions> optionsAccessor)
{
    _options = optionsAccessor.Value;
}

Options Configuración de una Sección

 

  • Declaramos nuestra clase que almacenará los Settings:
public class AppSettings
{
    public string Secret { get; set; }
}
  • La forma de hacer el registro es lo que cambia en este caso:
// Settings
var appSettingsConfig = Configuration.GetSection("AppSettings");
services.Configure<AppSettings>(appSettingsConfig);

Options Configuración de una Sección

 

  • Y así quedaría nuestro appsettings.json:
{
  "AppSettings": {
    "Secret": "THIS IS USED TO SIGN AND VERIFY JWT TOKENS"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Warning"
    }
  },
  "AllowedHosts": "*"
}

ASP.NET Core Settings by Environment

 

Settings by Environment

 

  • Nuestras apps de ASP.NET Core contienen el archivo appsettings.json, del cual podemos crear uno por entorno
  • Los valores de entorno que se utilizan por defecto son: Production, Development y Staging. El valor por defecto si no lo indicamos al ejecutar nuestra app es Production
  • Podemos crear los entornos que queramos y para poder usarlos debemos establecer la variable de entorno: ASPNETCORE_ENVIRONMENT

Settings by Environment

 

  • La variable de entorno ASPNETCORE_ENVIRONMENT es completamente independiente de la configuración de compilación (Debug o Release)
  • Podemos establecer en nuestro IDE de forma automática el entorno sobre el que ejecutar nuestra app
  • También podemos establecer el entorno del servidor de destino de despliegue

Settings by Environment

Ejemplo de como establecer la variable de entorno ASPNETCORE_ENVIRONMENT en Azure

Settings by Environment

Para poder habilitar los archivos appsettings.json por variable de entorno tendremos que indicarlo en la clase Startup a nuestra interfaz IConfiguration:

var builder = new ConfigurationBuilder()
                .SetBasePath(env.ContentRootPath)
                .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
                .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true)
                .AddEnvironmentVariables();
Configuration = builder.Build();

Settings by Environment

Lo único que queda ahora entonces es crear un archivo por entorno de la siguiente forma:

{
  "AppSettings": {
    "Secret": "PRODUCTION SETTINGS"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Error",
      "System": "Information",
      "Microsoft": "Information"
    }
  }
}

appsettings.Production.json

{
  "Logging": {
    "LogLevel": {
      "Default": "Error",
      "System": "Information",
      "Microsoft": "Information"
    }
  }
}

appsettings.Development.json

{
  "AppSettings": {
    "Secret": "DEFAULT SETTINGS"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Error",
      "System": "Information",
      "Microsoft": "Information"
    }
  }
}

appsettings.json

ASP.NET Core Jwt Authentication

 

Jwt Authentication

SETUP

 

  • En el método Configure de la clase Startup añadimos el middleware que se encarga de la autentificación:

 

  • Lo primero que debemos de hacer es añadir el servicio de Authentication indicando que los Schemas a utilizar son los de JwtBearer
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme);
app.UseAuthentication();

Jwt Authentication

  • Seguidamente tendremos que añadir la configuración del JwtBearer, como la secret a utilizar, como validar el token, etc
var key = Encoding.ASCII.GetBytes(appSettingsConfig.Get<AppSettings>().Secret);

services.AddJwtBearer(options => {
    options.RequireHttpsMetadata = false;
    options.SaveToken = true;
    options.TokenValidationParameters = new TokenValidationParameters()
    {
        ValidateIssuerSigningKey = true,
        IssuerSigningKey = new SymmetricSecurityKey(key),
        ValidateIssuer = false,
        ValidateAudience = false
    };
});

Jwt Authentication

  • Por último, nos quedaría informar el token de autentificación al usuario que lo solicite después de haberse identificado:
// authentication successful so generate jwt token
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_appSettings.Secret);
var tokenDescriptor = new SecurityTokenDescriptor
{
    Subject = new ClaimsIdentity(new Claim[] 
    {
        new Claim(ClaimTypes.Name, user.Id.ToString())
    }),
    Expires = DateTime.UtcNow.AddDays(7),
    SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), 
                                                SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
user.Token = tokenHandler.WriteToken(token);

ASP.NET Core Entity Framework

Entity Framework

Conceptos básicos


  • DbContext: Representa una sesión con la base de datos que indiquemos. Para crear un contexto de base de datos debemos de heredar de DbContext y definir en él mismo las tablas a partir de DbSet.
  • DbSet<TEntity>: Representa una entidad de domino o una tabla en base de datos.

Entity Framework

SETUP

 

  • Instalaremos los siguientes nuget packages:
    • Microsoft.EntityFrameworkCore.SQLite
    • Microsoft.EntityFrameworkCore.Tools

Entity Framework

Definiendo nuestro DbContext

public class KriboDbContext : DbContext
{
    public KriboDbContext(DbContextOptions<KriboDbContext> options) : base(options)
    {
    }
    // Tables
    public DbSet<User> Users { get; set; }
}
  • Como parámetro del constructor recibe un DbContextOptions<T>. Estas opciones las indicaremos en el ConfigureServices de la clase Startup
  • Definimos nuestras tablas con DbSet. Estas pueden ser consultadas con LINQ sin problema.
  • Cualquier modificación de esta clase o en alguna de las entidades requieren de una migración.

Entity Framework

Registro de nuestro DbContext en ConfigureServices

// Database connection
services.AddDbContext<KriboDbContext>(options => 
    options.UseSqlite(dbSettingsConfig.Get<DbSettings>().ConnectionString)
);
  • En este caso, estamos indicando las opciones anteriormente mencionadas con la cadena de conexión a nuestra base de datos SQLite
private readonly AppSettings _appSettings;
private readonly KriboDbContext _dbContext;
public UserService(IOptions<AppSettings> appSettings, KriboDbContext dbContext)
{
    _appSettings = appSettings.Value;
    _dbContext = dbContext;
}
  • Una vez registrado, podemos utilizarlo en cualquiera de nuestros servicios

Migrations

Las migraciones las realizaremos por lineas de comandos. Veamos los comandos de los que disponemos:

  • dotnet ef migrations add <Nombre-Migración> (Add-Migration): Crea una migración a aplicar
  • dotnet ef database update (Update-Database): Actualiza la base de datos
  • dotnet ef migrations remove (Remove-Migration): Borra la última migración que tuviéramos creada
  • dotnet ef database update <Nombre-Migración> (Update-Database): Hace un rollback de nuestra base de datos a la migración que indiquemos
  • dotnet ef migrations script (Script-Migration): Crea un script de SQL desde la migración 0 a la última que tengamos en cola. Podemos cambiar estos parámetros from y to
  • myDbContext.Database.Migrate(): Podemos crear migraciones en runtime, por ejemplo, cuando ejecutamos el código por primera vez

Migrations

Insertar datos iniciales en nuestras tablas

 

  • Haciendo override en nuestra clase DbContext del método OnModelCreating
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    base.OnModelCreating(modelBuilder);
    modelBuilder.Entity<User>().HasData(
        new User
        {
            Id = 1,
            FirstName = "",
            LastName = "",
            Password ="test",
            Username = "test",
            Token = ""
        },
        new User
        {
            Id = 2,
            FirstName = "",
            LastName = "",
            Password = "test2",
            Username = "test2",
            Token = ""
        }
    );
}

Entity Framework

DEMO

 

  1. Crear la clase DbContext
  2. Registrar el DbContext en Dependency Injection
  3. Crear la migración inicial
  4. Actualizar la base de datos
  5. Utilizar nuestro DbContext en un servicio
  6. Insertar datos iniciales de una tabla
  7. Añadir migración nueva
  8. Actualizar base de datos
  9. Comprobar que todo funciona correctamente

ASP.NET Core Entity Framework - Repository Pattern & Unit of Work

Repository Pattern & Unit of Work

 

  • El objetivo de estos patrones de diseño es desacoplar Entity Framework (o cualquier otro ORM) de nuestra lógica de negocio, haciendo posible cambiar de un ORM a otro con tan solo la implementación de un par de interfaces
  • Unit of Work: Se trata del patrón que nos permite tener un solo contexto para multiples operaciones sobre la base de datos aunque estén accediendo desde diferentes servicios.

Repository Pattern

 

El objetivo de este patrón de diseño es ocultar los detalles de como se acceden a los datos de nuestra base de datos, también evitamos lógica de acceso a datos a lo largo de nuestra aplicación

 

Si partimos de la siguiente interfaz:

public interface IEmployeeRepository
{
    Task<Employee> Get(Guid? id);
    Task Save(Employee employee);
    Task Delete(Employee employee);
    Task Update(Employee employee);
    Task<IEnumerable<Employee>> FindAll();
}

Repository Pattern

Y su implementación:

public class EmployeeRepository : IEmployeeRepository
{
    private EmployeeContext _employeeContext;
    public EmployeeRepository()
    {
        _employeeContext = new EmployeeContext();
    }
    public async Task<Employee> Get(Guid? id)
    {
        return await _employeeContext.Employees.FirstOrDefaultAsync(x => x.Id == id);
    }
    
    public async Task Save(Employee employee)
    {
        _employeeContext.Employees.Add(employee);
        await _employeeContext.SaveChangesAsync();
    }
    
    public async Task Delete(Employee employee)
    {
        _employeeContext.Employees.Remove(employee);
        await _employeeContext.SaveChangesAsync();
    }
    
    public async Task Update(Employee employee)
    {
        _employeeContext.Employees.Update(employee);
        await _employeeContext.SaveChangesAsync();
    }
    
    public async Task<IEnumerable<Employee>> FindAll()
    {
        return await _employeeContext.Employees.ToListAsync();
    }
}

Repository Pattern

Y lo que quedaría sería registrar el repositorio en nuestra inyección de dependencias. De la siguiente forma:

services.AddScoped<IEmployeeRepository, EmployeeRepository>();

De esta forma ya estamos desacoplando como se accede a la base de datos, pero tendríamos que estar registrando un repositorio por entidad de base de datos. Podemos crear un Repositorio genérico para cualquier entidad a partir de la siguiente intefaz:

public interface IGenericRepository<T> where T: class, IEntity
{
    Task<T> Get(Guid? id);
    Task Save(T employee);
    Task Delete(T employee);
    Task Update(T employee);
    Task<IEnumerable<T>> FindAll();
}
public interface IEntity
{
    Guid Id { get; set; }
}

Repository Pattern

La implementación de IGenericRepository<T> sería de la siguiente forma:

public class GenericRepository<T> : IGenericRepository<T> where T: class, IEntity
{
    private DbContext _dbContext;
    public GenericRepository(DbContext dbContext)
    {
        _dbContext = dbContext;
    }

    public async Task Delete(T employee)
    {
        _dbContext.Set<T>().Remove(employee);
        await _dbContext.SaveChangesAsync();
    }

    public async Task<IEnumerable<T>> FindAll()
    {
        return await _dbContext.Set<T>().ToListAsync();
    }

    public async Task<T> Get(Guid? id)
    {
        return await _dbContext.Set<T>().FirstOrDefaultAsync(x => x.Id == id);
    }

    public async Task Save(T employee)
    {
        _dbContext.Set<T>().Add(employee);
        await _dbContext.SaveChangesAsync();
    }

    public async Task Update(T employee)
    {
        _dbContext.Set<T>().Update(employee);
        await _dbContext.SaveChangesAsync();
    }
}

Unit of Work

El Repository Pattern tiene el problema que no podemos realizar acumular transacciones y después actualizar la base de datos, sino que cada vez que llamamos por ejemplo a Delete<T>, siempre estamos ejecutando _dbContext.SaveChangesAsync()

El patrón Unit of Work nos ayuda a poder ejecutar sentencias de base de datos de forma transaccional y después enviar los cambios acumulados a la base de datos. Como siempre partimos de una interfaz:

public interface IUnitOfWork
{
    IGenericRepository<Blog> BlogRepository { get; }
    IGenericRepository<Post> PostRepository { get; }
    void Save();
}

Unit of Work

La implementación de la interfaz sería de la siguiente forma:

public class UnitOfWork : IUnitOfWork
{
    private readonly BloggingContext _bloggingContext;
    private IGenericRepository<Blog> _blogRepository;
    private IGenericRepository<Post> _postRepository;
    public UnitOfWork(BloggingContext bloggingContext)
    {
        _bloggingContext = bloggingContext;
    }

    public IGenericRepository<Blog> BlogRepository
    {
        get
        {
            return _blogRepository = _blogRepository ?? new GenericRepository<Blog>(_bloggingContext);
        }
    }

    public IGenericRepository<Post> PostRepository
    {
        get
        {
            return _postRepository = _postRepository ?? new GenericRepository<Post>(_bloggingContext);
        }
    }

    public void Save()
    {
        _bloggingContext.SaveChanges();
    }
}

Unit of Work

El registro en la inyección de dependencias seria:

 

 

 

Y la utilización del Unit of Work en nuestro código quedaría:

var connection = @"Server=(localdb)\mssqllocaldb;Database=EFGetStarted.AspNetCore.NewDb;Trusted_Connection=True;";
services.AddDbContext<BloggingContext>(options => options.UseSqlServer(connection));
services.AddScoped<IUnitOfWork, UnitOfWork>();
_unitOfWork.BlogRepository.Create(new Blog()
{
    Url = "https://dotnetthoughts.net"
});

_unitOfWork.PostRepository.Create(new Post()
{
    Title = "Hello World",
    Content = "This is a sample blog content",
    BlogId = 1
});

_unitOfWork.Save();

ASP.NET Core Unit Test

Unit Test

 

Un Unit Test es la forma de comprobar el correcto funcionamiento de una unidad de código. Esto sirve para asegurar que cada unidad funcione correctamente y eficientemente por separado.

 

En el momento que probemos la integración de los distintos módulos de nuestro sistema, estaríamos hablado de Tests de Integración. 

Unit Test

Ventajas

  • Refactorización: Las pruebas unitarias facilitan que el programador cambie el código para mejorar su estructura.
  • Simplifica la integración: Probando por separado cada unidad de código, aseguramos que llegaremos a la fase de integración con gran seguridad en nuestro código.
  • Documentación: Las pruebas unitarias son en si mismas los requisitos de nuestro código.
  • Separación: Fuerza a la utilización del principio de Inversión de dependencias. 
  • Errores: Los errores son más fáciles de identificar de antemano con pruebas unitarias

Unit Test

Mocks

  • Utilizaremos el siguiente Nuget package para generar Mocks de nuestras interfaces para poder testear nuestro código: Moq 
// Creamos el Mock sobre nuestra interfaz
var genericUserRepository = new Mock<IGenericRepository<User>>();

// Después definimos el comportamiento sobre el método de la interfaz que vamos a utilizar
genericUserRepository
    .Setup(x => x.FirstOrDefault(It.IsAny<Expression<Func<User, bool>>>()))
    .Returns(Task.FromResult<User>(null)); 

// La forma de utilizar nuestra interfaz "mock"
var userService = new UserService(new genericUserRepository.Object);

ASP.NET Core Integration Test

Integration Test

  • Se tratan de los tipos de tests que prueban la integración de los distintos módulos de nuestra aplicación.
  • Estos tests de integración de nuestro web api los podemos identificar:
    • Tests de bases de datos
    • Tests de sistemas de archivos
    • Request-Response pipeline
    • Etc
  • Al contrario que los Tests Unitarios (Unit Tests) se identifican por:
    • Usar los componentes que utilizamos en producción
    • Requieren más proceso de datos
    • Llevan más tiempo en su ejecución

Integration Tests

SETUP

  • ASP.NET Core ofrece un Test Web Host para llevar a cabo los tests de integarción
  • Crearemos un proyecto de Test con .Net Core e instalaremos los nuget packages:
    • Microsoft.AspNetCore.App
    • Microsoft.AspNetCore.Mvc.Testing
    • Modificar el SDK del Proyecto de testing:
<Project Sdk="Microsoft.NET.Sdk.Web">

Integration Tests

Implementación


  • Utilizaremos xUnit para implementar los tests de integración
  • Nuestra clase de test debe de heredar de IClassFixture<WebApplicationFactory<Startup>>
  • Inicializamos nuestro factory y el cliente para hacer las llamadas

Integration Tests

Implementación

public class ValuesControllerTest : IClassFixture<WebApplicationFactory<Startup>>
{
    private readonly WebApplicationFactory<Startup> _factory;
    private readonly HttpClient _client;
    private User user = null;
    public ValuesControllerTest(WebApplicationFactory<Startup> factory)
    {
        _factory = factory;
        _client = _factory
            .WithWebHostBuilder(builder =>
            {
                builder.ConfigureTestServices((IServiceCollection services) =>
                {
                    services.AddScoped<IUnitOfWork>(sp => new UnitOfWorkMock(new GenericUserRepositoryMock()));
                });
            })
        .CreateClient();
    }

    [Fact]
    public async Task get_values_unauthorized()
    {
        var response = await _client.GetAsync("/api/values");
        Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
    }

    [Fact]
    public async Task get_values_ok()
    {
        if (user == null)
            user = await _client.LogInUser(new User { Username = "test", Password = "test" });

        _client.DefaultRequestHeaders.Add("Authorization", $"Bearer {user.Token}");

        var response = await _client.GetAsync("/api/values");
        Assert.Equal(HttpStatusCode.OK, response.StatusCode);
    }

}

Integration Tests

Implementación

Podríamos crear nuestro propio WebApplicationFactory

public class CustomWebApplicationFactory<TStartup> 
        : WebApplicationFactory<TStartup> where TStartup : class
{
    public CustomWebApplicationFactory()
    {
    }

    protected override void ConfigureWebHost(IWebHostBuilder builder)
    {
        builder.ConfigureServices((webHostBuilder, services) => 
        {
            var configuration = GetConfiguration(webHostBuilder.HostingEnvironment);

            services.AddMvc()
                .SetCompatibilityVersion(CompatibilityVersion.Version_2_1);

            // Settings
            var appSettingsConfig = configuration.GetSection("AppSettings");
            services.Configure<AppSettings>(appSettingsConfig);
            var dbSettingsConfig = configuration.GetSection("DbSettings");
            services.Configure<DbSettings>(dbSettingsConfig);

            // Authentication Jwt
            services.AddJwtAuthentication(appSettingsConfig.Get<AppSettings>().Secret,
                                          false);

            services.AddTransient<IValuesService, ValuesService>();
            services.AddTransient<IUserService, UserService>();
            services.AddScoped<IUnitOfWork, UnitOfWorkMock>();
            services.AddTransient<IJwtTokenService, JwtTokenService>();
        });
    }
}

ASP.NET Core ApiController attribute

ApiController attribute

  • En las primeras versiones de ASP.NET Core bastaba con heredar de Controller para montar nuestros controladores

 

  • En ASP.NET Core 2.1 han introducido ControllerBase y ApiAttribute

ApiController Attribute

  • ControllerBase complementa a ApiAttribute
  • Con ApiAttribute la validación de ModelState no es necesaria hacerla. Es decir, el siguiente código lo ejecuta por defecto:

 

 

  • Con ApiController el atributo Route es obligatorio
  • Etc
if (!ModelState.IsValid)
{
    return BadRequest(ModelState);
}

aspnet-core-1

By Ramón Esteban

aspnet-core-1

  • 251
Loading comments...

More from Ramón Esteban