Saltar a contenido

Andreani.ARQ.WebHost

Resumen

Esta librería tiene el objetivo de contener las configuraciones estándar e iniciales para cualquier proyecto .Net que requiera un Host.

Configuración WebHost

// Program.cs
var builder = WebApplication.CreateBuilder(args);

builder.Host.ConfigureAndreaniWebHost(args);

Danger

DEPRECATED:

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
            .ConfigureAndreaniWebHost<Startup>(args);

el método ConfigureAndreaniWebHost realiza las siguientes configuraciones:

  1. Configuration File: configura el host para recibir las configuraciones por archivo appsettings.json o appsettings.yml
  2. Logging: configura el serilog. Dispone de la configuración enviada a través de el archivo appsettigs.
  3. Configuración WebHost: tiene la configuración estándar para levantar un web host
  4. Elastic APM: Configura por defecto Elastic APM

Configure Services

Web API

builder.Services.ConfigureAndreaniServices();
//...

var app = builder.Build();

app.ConfigureAndreani();

Danger

**DEPRECATED:**
```csharp

    public void ConfigureServices(IServiceCollection services)
    {
        services.ConfigureAndreaniServices();
    }
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IApiVersionDescriptionProvider provider)
    {
        app.ConfigureAndreani(env, provider);
    }

```

Los método ConfigureAndreaniServices y ConfigureAndreani realizan las siguientes configuraciones:

  1. Configuración de Controllers ASPNET
  2. Configuración de Compresión GZIP a las respuestas de las API.
  3. Configuración Swagger - Autodocumentación
  4. Configuración de HealthCheck - Urls: /health y /healthcheck
  5. Configuración de Versionado de APIs.
  6. Configuración de Middleware para manejo de error de validación fluentValidation.
  7. Configuración Routing, Authenticación y Authorization.

Host Worker

builder.Services.ConfigureAndreaniWorkerServices();
//...

var app = builder.Build();

app.ConfigureAndreaniWorker();

Danger

**DEPRECATED**
```csharp

    public void ConfigureServices(IServiceCollection services)
    {
        services.ConfigureAndreaniWorkerServices();
    }
    public void Configure(IApplicationBuilder app)
    {
        app.ConfigureAndreaniWorker();
    }

```

Para un worker no necesitamos de configuraciones extras, por lo que los métodos solo configuran:

  1. Configuración de HealthCheck - Urls: /health y /healthcheck

Endpoints:

El metodo ConfigureAndreani permite configurar endpoints extras fuera de los controllers de ASPNET.

app.ConfigureAndreani(configEndopint: (endpoint) => endpoint.MapGet("/test", async context =>
{
    await context.Response.WriteAsync(JsonSerializer.Serialize(new
    {
        Test = "esto es una api de test"
    }));
}));
En este ejemplo tendremos disponible el endpoint "/test".

Warning

Esta funcionalidad no debe ser usada para exponer endpoints de negocio.

Middleware:

El método ConfigureAndreani nos permite configurar middleware. Para eso podemos seguir las especificaciones de Microsoft de configuración de middleware

app.ConfigureAndreani(
    middleware: (opt) => {
        opt.Use(async (context, next) =>
        {
            // Do work that can write to the Response.
            await next.Invoke();
            // Do logging or other work that doesn't write to the Response.
        });
    });

HealthChecks:

Como crear CustomHealthChecks en un proyecto de platform:

Para crear un custom HealthCheck que apunte a otro servicio ( por ejemplo una API ) se debe crear una nueva clase en Infrastructure dentro de una carpeta que se va a llamar HealtChecks, la misma clase tiene que implementar la interfaz IHealthCheck, luego a la clase le creamos un constructor que reciba un string (este se crea para recibir la url del servicio del que dependemos, y que se pueda configurar en el appsettings), y dentro del metodo que nos pide implementar la interfaz CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default) , hacemos un request al "/health" del servicio y en función del StatusCode que devuelva, retornamos Healthy o Unhealthy, como en el ejemplo:

public class HealthCheckApiWithArgs : IHealthCheck
    {
        private readonly string _url;

        public HealthCheckApiWithArgs(string url)
        {
            _url = url;
        }

        public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
        {
            var client = new HttpClient();

            client.BaseAddress = new Uri(_url);

            HttpResponseMessage response = await client.GetAsync("");

            return response.StatusCode == HttpStatusCode.OK ?
                await Task.FromResult(new HealthCheckResult(
                      status: HealthStatus.Healthy,
                      description: "The API is healthy :)"
                       )) :
                await Task.FromResult(new HealthCheckResult(
                      status: context.Registration.FailureStatus,
                      description: $"API does not work because {response.ReasonPhrase} :("));
        }
    }

Una vez creado el custom Health Check pasamos a configurarlo, creamos dentro de Infrastructure una carpeta que se va a llamar Extensions, dentro de esa carpeta creamos una clase static con el nombre HealthChecksExtensions, luego creamos un metodo static void AddHealthChecks y dentro del mismo configuramos el healthcheck como se muestra en el ejemplo:

 public static class HealthChecksExtensions
    {
        public static void AddHealthChecks(this IServiceCollection services, IConfiguration configuration)
        {
            var url = configuration["HealthChecks:Url"];

            services.AddHealthChecks()
                .AddTypeActivatedCheck<HealthCheckApiWithArgs>(
                "Custom Health Check with Args",
                failureStatus: HealthStatus.Unhealthy,
                tags: new[] { $"EndPoint Services: {url}" },
                args: new object[] { url }
                );
        }
    }

Warning

No nos podemos olvidar de agregar el valor de "HealthChecks:Url" en el appsettings.Development.yml o en el appsettings.yml

y por ultimo vamos a DependencyInjection de Infrastructure y agregamos la linea services.AddHealthChecks(configuration);.

Tip

El parametro args de `AddTypeActivatedCheck` puede recibe un array de object, pero cada parametro que se pase por la función tiene que estar en el constructor de nuestra clase HealthCheck.

Para más información de creación de custom Health Checks ver Create health checks.

Danger

**DEPRECATED:**
### Configuración AzureB2C

Para configurar la autenticación en nuestra app, simplemente debemos agregar el siguiente servicio:

=== "Program.cs"

    ```csharp
        // Ejemplo con nueva defición de Program

        // Configuramos el Services para las DI

        var builder = WebApplication.CreateBuilder(args);

        builder.Host.ConfigureAndreaniWebHost(args);
        builder.Services.ConfigureAndreaniServices();
        builder.Services.AddApplication();
        builder.Services.AddInfrastructure(builder.Configuration);
        builder.Services.AddAzureADAuthentication(builder.Configuration);

        var app = builder.Build();

        // Configuramos el middleware
        app.ConfigureAndreani();

    ```

=== "Startup.cs"

    ```csharp
        public void ConfigureServices(IServiceCollection services)
        {
            //...
            services.AddAzureB2CAuthentication(Configuration);
            }
    ```

El método `AddAzureB2CAuthentication` recibe `Configuration` para poder realizar la configuración de la especificación del protocolo openid de Azure AD B2C

En el archivo `appsettings.development.yml` debemos configurar lo siguiente:
```yaml
AzureB2C:
Tenant: "andreanib2cdev"
Flow: "B2C_1A_SIGNUP_SIGNIN"
```

En caso de que queramos permitir solo el token de una aplicación especifica, debemos configurar el campo ClientId.

```yaml
AzureB2C: 
ClientId: "58fb34da-0a3e-45f6-aa35-f806a684433a"
Tenant: "andreanib2cdev"
Flow: "B2C_1A_SIGNUP_SIGNIN"
```

En caso de que necesitemos trabajar con otro identity provider con openid podemos configurar la ruta de configuración de especificación. Esto es la definición que tiene el identity provider para validar un JWT. Para esto podemos configurar el campo connection.

```yaml
AzureB2C: 
    Connection: "url"
```

Danger

**DEPRECATED:** El metodo `AddAzureB2CAuthentication` recibe dos parametros `envB2C` y `flow` (flujo de las Custom Policy), por defecto, se configura con un flujo, lo que significa que solo va a validar token creados por algún flujo de las Custom Policy y no de los flujos por defecto de Azure.

En caso de necesitar cambiar alguna configuración, se debe especificar con el nombre en el parámetro del método de la siguiente manera:   
**Custom Policy** : 
```csharp   
    //Por defecto "B2C_1A_SIGNUP_SIGNIN"
    services.AddAzureB2CAuthentication(flow: "b2c_flow_example");`
```
**Env** : 
```csharp 
    //Por defecto "andreanib2cdev"
    services.AddAzureB2CAuthentication(envB2c: "andreanib2cExampleEnv");`
```
Otro detalle de la configuración es que en el caso de que la env `ASPNETCORE_ENVIRONMENT` esté en `Development` la configuración va a apuntar al tenant de dev, caso contrario apunta al tenant de prod.

Danger

**DEPRECATED:**
### Configuración AzureAD

En el caso de Azure AD se van a tener que establecer algunas variables de entorno en el appsettings:

```yaml
AzureAd:
Instance: "https://login.microsoftonline.com/"
ClientId: "{ClientId}"
TenantId: "{TenantId}"
```
Por defecto el **SectionName** es AzureAd, pero lo puede cambiar en AddAzureADAuthentication, pasándole como parámetro un string con el nombre.

```csharp
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }
        public IConfiguration Configuration { get; }

        public void ConfigureServices(IServiceCollection services)
        {
            //...
            services.AddAzureADAuthentication(Configuration, {{Optional Section Name}} );
        }
```
En caso de que se le asigne la Key `ClientId` se autenticará tokens provenientes solo de ese client, en caso contrario se autenticara cualquier token del dominio de Azure ad de Andreani.

WebHost Authentication

La librería WebHost ofrece un módulo de autenticación personalizable y flexible que permite autenticar a los usuarios utilizando diferentes tokens y proveedores de identidad. Esta funcionalidad de autenticación ha sido mejorada para que ya no dependa de ninguna tecnología en particular. Ahora es posible autenticar con diferentes Tokens de diferentes Identity Providers o Custom Tokens, y todo se configura mediante las variables de entorno.

Configuracion:

Para configurar la autenticación en nuestra app, simplemente debemos agregar el siguiente servicio:

builder.Services.AddOpenIdConnectAuthentication(builder.Configuration);

La configuración de la autenticación se realiza a través de la sección "OpenIdConnect" del archivo de configuración de la aplicación. En esta sección se pueden especificar diferentes proveedores de autenticación y sus configuraciones correspondientes. A continuación, se muestra un ejemplo de cómo podría verse la sección "OpenIdConnect" en el archivo de configuración:

OpenIdConnect:
  - AuthScheme: "Cognito"
    Issuer: "https://test-issuer1.com"
    ClientID: "testclientid1"
    ValidateAudience: true
    ValidateIssuer: true
    ValidateIssuerSingingKey: true
  - AuthScheme: "AzureADB2C"
    Issuer: "https://test-issuer2.com"
    ClientID: "testclientid2"
    ValidateAudience: false
    ValidateIssuer: true
    ValidateIssuerSingingKey: false
    Flow: "B2C_A1_SignIn_SignUpAAD"
  - AuthScheme: "CustomToken"
    ClientID: "testclientid3"
    ValidateAudience: false
    ValidateIssuer: true
    ValidateIssuerSingingKey: true
    UseCustomScheme: true
    SecretKey: "test_secret_key"

AuthScheme: Este atributo especifica el nombre del esquema de autenticación que se usará.

Issuer: Este atributo especifica la URL del proveedor de identidad que se utilizará para autenticar a los usuarios. (Opcional)

ClientID: Este atributo especifica el identificador del cliente que se utilizará para autenticar a los usuarios. (Opcional)

ValidateAudience: Este atributo especifica si se deben validar los valores del token de audiencia (aud). Si este valor es verdadero, el valor de la audiencia del token debe coincidir con el valor especificado en el atributo "ClientID". Este valor por defecto es verdadero. (Opcional)

ValidateIssuer: Este atributo especifica si se deben validar los valores del token del emisor (iss). Si este valor es verdadero, el valor del emisor del token debe coincidir con el valor especificado en el atributo "Issuer". Este valor por defecto es verdadero. (Opcional)

ValidateIssuerSingingKey: Este atributo especifica si se deben validar las SigninKeys del proveedor de identidad. Si este valor es verdadero, se valida que la SigninKey en el token corresponda a una de las claves de firma proporcionadas por el proveedor de identidad. Este valor por defecto es verdadero. (Opcional)

Flow: Este atributo especifica el flujo de autenticación que se utilizará. Es un atributo opcional y solo se utiliza en algunos proveedores de identidad. Por ejemplo, en Azure AD B2C se puede especificar el flujo B2C_A1_SignIn_SignUpAAD para habilitar el registro de nuevos usuarios. (Opcional)

UseCustomScheme: Este atributo especifica si se utilizará un esquema de autenticación personalizado en lugar de un proveedor de identidad. Si este valor es verdadero, se utilizará el atributo "SecretKey" como clave secreta para firmar y verificar tokens. Este valor por defecto es falso. (Opcional)

SecretKey: Este atributo especifica la secret key que se utilizará para firmar y verificar tokens si se utiliza un esquema de autenticación personalizado. Este valor solo se utiliza si "UseCustomScheme" es verdadero. (Opcional)

Desactivar Swagger UI:

Con esta funcionalidad existe la posiblidad de desactivar la UI de Swagger a través de variables de entorno, la variable va a ser un boolean con valor true o false, como a continuación:

Swagger:
  Enabled: false

En caso de no usar esta variable, Swagger sigue funcionando.

Configuración de Cors

La configuración de Control de Acceso HTTP (CORS) en una aplicación web es esencial para permitir que los navegadores restrinjan las solicitudes desde diferentes orígenes. Esta librería ofrece una forma de configurar CORS de manera personalizada en una aplicación web construida en C# .NET 6 a partir de la clase CorsOption y las environment en nuestro appsettings. A continuación, se muestra una documentación paso a paso sobre cómo configurar CORS en tu aplicación.

    public class CorsOption
    {
        public List<string> Origins { get; set; }
        public List<string> Headers { get; set; }
        public List<string> Methods { get; set; }
        public List<string> ExposedHeaders { get; set; }
        public TimeSpan? MaxAge { get; set; }

        public CorsOption()
        {
            Origins = new List<string>();
            Headers = new List<string>();
            ExposedHeaders = new List<string>();
            Methods = new List<string>();
        }
    }

Configuración básica

Para configurar CORS correctamente, debemos seguir estos pasos:

  1. Usar builder.Services.AddDefaultCors(builder.Configuration) antes de llamar a builder.Build().
  2. Llamar a .WithOrigins() y .Build() en la configuración CORS.
  3. Agregar app.UseCors() después de la creación de la aplicación.

A continuación se muestra un ejemplo:

using Andreani.ARQ.WebHost.Extension;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using ProjectName.Application;
using ProjectName.Infrastructure;

var builder = WebApplication.CreateBuilder(args);

builder.Host.ConfigureAndreaniWebHost(args);

builder.Services.ConfigureAndreaniServices();
builder.Services.AddApplication();
builder.Services.AddInfrastructure(builder.Configuration);

builder.Services.AddDefaultCors(builder.Configuration).WithOrigins().Build();

var app = builder.Build();

app.UseCors();
app.ConfigureAndreani();

app.Run();

Además, aquí tienes un ejemplo de cómo configurar los orígenes en tu archivo de configuración (ya sea YAML o JSON):

Cors: 
  Origins: 
  - "localhost:3000"
  - "andreani.com.ar" 
"Cors": {
    "Origins": [
        "localhost:3000",
        "andreani.com.ar"
    ]
}

Warning

- **La configuración los Origins es obligatoría, en caso de no tenerlo en las environment no dejará levantar la aplicación hasta que esté configurado correctamente**
- **También es necesario el uso del `.Buil()` para terminar la configuración**

Notas

-**El método `WithOrigins()` se encarga de configurar los Cors para que acepten todos los origenes que terminen en los valores que le pasamos en las environments**
- **Por defecto si no se configuran los `Headers` o `Methods`, los origenes configurados van a aceptar cualquier header y cualquier método**

Configuración con Headers

Si deseas configurar los Headers CORS, sigue los pasos de la configuración básica y agrega .WithHeaders() antes de .Build(). A continuación, se muestra un ejemplo:

using Andreani.ARQ.WebHost.Extension;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using ProjectName.Application;
using ProjectName.Infrastructure;

var builder = WebApplication.CreateBuilder(args);

builder.Host.ConfigureAndreaniWebHost(args);

builder.Services.ConfigureAndreaniServices();
builder.Services.AddApplication();
builder.Services.AddInfrastructure(builder.Configuration);

builder.Services.AddDefaultCors(builder.Configuration).WithOrigins().WithHeaders().Build();

var app = builder.Build();

app.UseCors();
app.ConfigureAndreani();

app.Run();
Cors: 
  Origins: 
  - "localhost:3000"
  - "andreani.com.ar"
  Headers:
  - "Content-Type"
  - "Accept"  
"Cors": {
    "Origins": [
        "localhost:3000",
        "andreani.com.ar"
    ],
    "Headers": [
        "Content-Type",
        "Accept"
    ]
}

Configuración de Methods

Para configurar los Methods CORS, sigue los pasos de la configuración básica y agrega .WithMethods() antes de .Build(). Aquí tienes un ejemplo:

using Andreani.ARQ.WebHost.Extension;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using ProjectName.Application;
using ProjectName.Infrastructure;

var builder = WebApplication.CreateBuilder(args);

builder.Host.ConfigureAndreaniWebHost(args);

builder.Services.ConfigureAndreaniServices();
builder.Services.AddApplication();
builder.Services.AddInfrastructure(builder.Configuration);

builder.Services.AddDefaultCors(builder.Configuration).WithOrigins().WithMethods().Build();

var app = builder.Build();

app.UseCors();
app.ConfigureAndreani();

app.Run();
Cors: 
  Origins: 
  - "localhost:3000"
  - "andreani.com.ar"
  Methods:
  - "DELETE"
  - "PUT"  
"Cors": {
    "Origins": [
        "localhost:3000",
        "andreani.com.ar"
    ],
    "Methods": [
        "DELETE",
        "PUT"
    ]
}

Notas

**Por defecto, los métodos ``POST`` y ``GET`` estarán habilitados aunque no se configuren explícitamente.**

Configuración de ExposedHeaders

Si deseas configurar los ExposedHeaders CORS, sigue los pasos de la configuración básica y agrega .WithExposedHeaders() antes de .Build(). Aquí tienes un ejemplo:

using Andreani.ARQ.WebHost.Extension;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using ProjectName.Application;
using ProjectName.Infrastructure;

var builder = WebApplication.CreateBuilder(args);

builder.Host.ConfigureAndreaniWebHost(args);

builder.Services.ConfigureAndreaniServices();
builder.Services.AddApplication();
builder.Services.AddInfrastructure(builder.Configuration);

builder.Services.AddDefaultCors(builder.Configuration).WithOrigins().WithExposedHeaders().Build();

var app = builder.Build();

app.UseCors();
app.ConfigureAndreani();

app.Run();
Cors: 
  Origins: 
  - "localhost:3000"
  - "andreani.com.ar"
  ExposedHeaders:
  - "Content-Type"
  - "Accept"  
"Cors": {
    "Origins": [
        "localhost:3000",
        "andreani.com.ar"
    ],
    "ExposedHeaders": [
        "Content-Type",
        "Accept"
    ]
}

Configuración de MaxAge

Si deseas configurar la propiedad MaxAge de CORS, sigue los pasos de la configuración básica y agrega .WithMaxAge() antes de .Build(). Aquí tienes un ejemplo:

using Andreani.ARQ.WebHost.Extension;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using ProjectName.Application;
using ProjectName.Infrastructure;

var builder = WebApplication.CreateBuilder(args);

builder.Host.ConfigureAndreaniWebHost(args);

builder.Services.ConfigureAndreaniServices();
builder.Services.AddApplication();
builder.Services.AddInfrastructure(builder.Configuration);

builder.Services.AddDefaultCors(builder.Configuration).WithOrigins().WithMaxAge().Build();

var app = builder.Build();

app.UseCors();
app.ConfigureAndreani();

app.Run();
Cors: 
  Origins: 
  - "localhost:3000"
  - "andreani.com.ar"
  MaxAge: "00:00:30"  
"Cors": {
    "Origins": [
        "localhost:3000",
        "andreani.com.ar"
    ],
    "MaxAge": "00:00:30"
}

Configuración con ActionCorsConfiguration

Para configurar los CORS utilizando el método ActionCorsConfiguration, sigue estos pasos:

  1. Utiliza builder.Services.ActionCorsConfiguration(options => ...) antes de llamar a builder.Build().
  2. Llama a app.UseCors("CustomPolicy") después de crear la aplicación.

Aquí tienes un ejemplo:

using Andreani.ARQ.WebHost.Extension;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using ProjectName.Application;
using ProjectName.Infrastructure;

var builder = WebApplication.CreateBuilder(args);

builder.Host.ConfigureAndreaniWebHost(args);

builder.Services.ConfigureAndreaniServices();
builder.Services.AddApplication();
builder.Services.AddInfrastructure(builder.Configuration);

builder.Services.AddDefaultCors(options =>
options.AddPolicy("CustomPolicy", policy =>
policy.WithOrigins("http://localhost:3000").AllowAnyHeader().AllowAnyMethod()
));

var app = builder.Build();

app.UseCors("CustomPolicy");
app.ConfigureAndreani();

app.Run();

Esta configuración te permite definir una política CORS personalizada llamada CustomPolicy que acepta solicitudes desde http://localhost:3000, permite cualquier encabezado y cualquier método.