Posts

....
Technical Blog for .NET Developers ©

Wednesday, July 3, 2024

Angular: HighCharts & SignalR

In this post we make a basic implementation of a graphic library, connected in real time with SignalR hub

High Charts Demos

The application is displaying data on the chart, generated from a SignalR hub, to test the versatility of implementation of this graphic library



The graph component has the next code, with html and scss styles

It is very important to implement the event chartInstance, to obtain a reference to the graphic chart

 
import {Component, EventEmitter, OnInit} from '@angular/core';
import * as Highcharts from 'highcharts';

@Component({
    selector: 'line-chart-component',
    templateUrl: 'line-chart-component.html',
    styleUrls: ['line-chart-component.scss']
})
export class LineChartComponent implements OnInit {

    Highcharts: typeof Highcharts = Highcharts;
    chartOptions!: Highcharts.Options;    
    updateFlag = false;
  
    data: number[] = [];
    categories: string[] = [];

    the_chart!: Highcharts.Chart;

    onChartInstance(chart: Highcharts.Chart)  : void {

        this.the_chart = chart;
    }

    ngOnInit(): void {
  
        this.chartOptions = {     
            title: {
              text: 'Demo'
            } ,                
            xAxis: {
                categories: this.categories
            },            
            series: [{
                type: 'line',
                data: this.data,                
                name: 'demo'
              }],            
          };              
    }
    

    addData(category: string, data: number) {

       this.the_chart.xAxis[0].categories.push(category);
       this.the_chart.series[0].addPoint(data);
    }    
  
}


 
<div class="divChart">
  <highcharts-chart 
    [Highcharts]="Highcharts"
    [options]="chartOptions"
    [(update)]="updateFlag"
    (chartInstance)="onChartInstance($event)">
  </highcharts-chart>    
</div>


 
.divChart {
    display:flex; 
    justify-content: center; 
    align-items: center; 
    flex-direction: column;
}



The SignalR connection and configuration is released in the event ngOnInit of the component

 
    ngOnInit(): void {

        this.signalr.startConnection();
        this.addSignalRListeners();        
    }    

    addSignalRListeners = (): void => {

        this.signalr.addListener('newConnection', this.onNewConnection );
        this.signalr.addListener('broadcastMessage', this.onBroadcastMessage );
    }

    onNewConnection = (connection: any): void  => {
        console.log(connection);
    }

    onBroadcastMessage = (message: any): void => {
        this.lineChart.addData(message.demo, message.total);
        console.log(message);
    }  
    


 
    public startConnection = () => {

        this.hubConnection = new HubConnectionBuilder()
                                .withUrl(environment.hubUrl, {
                                    accessTokenFactory: () => {
                                        return localStorage.getItem('token')!
                                    }
                                })                                
                                .configureLogging(LogLevel.Information)
                                .withStatefulReconnect()
                                .build();

        this.hubConnection
            .start()
            .then(() => console.log('Connection started'))
            .catch(err => console.log('Error while starting connection: ' + err)
            );
    }

    public addListener(evtName: string, fn: (result: any) => void) : void {

        this.hubConnection.on(evtName, fn);                        
    }   
  


METHOD SOFTWARE 2024 ©

Saturday, June 22, 2024

Azure Cosmos DB

Azure Cosmos DB is the Cloud Database for AI Era, its features make it the option for globally and fast access distributed databases: Azure Cosmos DB




In this post we are implementing access from Web Api to Azure Cosmos DB with a Generic Repository. Previously we have set up the resource and filled the containers in the database

The implementation of the library to access the database needs the database name, container, and account data

 
public static class CosmosDbConfiguration
{
    public static void ConfigureCosmosDb(this IServiceCollection services, IConfiguration configuration)
    {
        services.AddSingleton<ICosmosUsersLibrary>(InitializeCosmosClientInstanceAsync(configuration.GetSection("CosmosDbUsers")));
    }

    private static CosmosUsersLibrary InitializeCosmosClientInstanceAsync(IConfigurationSection configurationSection)
    {
        string databaseName = configurationSection.GetSection("DatabaseName")!.Value!;
        string containerName = configurationSection.GetSection("ContainerName")!.Value!;
        string account = configurationSection.GetSection("Account")!.Value!;

        // If key is not set, assume we're using managed identity
        string key = configurationSection.GetSection("Key")!.Value!;
        CosmosClient client;
        if (string.IsNullOrEmpty(key))
        {
            ManagedIdentityCredential miCredential = new ();
            client = new CosmosClient(account, miCredential);
        }
        else
        {
            client = new CosmosClient(account, key);
        }

        CosmosUsersLibrary cosmosDbService = new (client, databaseName, containerName);
        
        //DatabaseResponse database = await client.CreateDatabaseIfNotExistsAsync(databaseName);
        //await database.Database.CreateContainerIfNotExistsAsync(containerName, "/id");

        return cosmosDbService;
    }
}


 
public class CosmosUsersLibrary(
    CosmosClient dbClient,
    string databaseName,
    string containerName) : CosmosLibrary<CosmosUser>, ICosmosUsersLibrary
{
    public override Container Container 
    {
        get
        {
            return dbClient.GetContainer(databaseName, containerName);
        }
    }
}  



The Generic Repository is implemented in a base class, the abstract pattern is important to override operations such as logical delete or updates with dependencies

   
using Microsoft.Azure.Cosmos;

namespace Cosmos.Infrastructure;

public abstract class CosmosLibrary<T> : ICosmosLibrary<T> where T : ICosmosItem
{
    public abstract Container Container { get; }

    public async Task AddItemAsync(T item)
    {
        await this.Container.CreateItemAsync<T>(item, new PartitionKey(item.Id));
    }

    public async Task DeleteItemAsync(string id)
    {
        await this.Container.DeleteItemAsync<CosmosUser>(id, new PartitionKey(id));
    }

    public async Task<T> GetItemAsync(string id)
    {
        try
        {
            ItemResponse<T> response = await this.Container.ReadItemAsync<T>(id, new PartitionKey(id));
            return response.Resource;
        }
        catch (CosmosException ex) when (ex.StatusCode == System.Net.HttpStatusCode.NotFound)
        {
            return default;
        }
    }

    public async Task<IEnumerable<T>> GetItemsAsync(string queryString)
    {
        var query = this.Container.GetItemQueryIterator<T>(new QueryDefinition(queryString));
        List<T> results = [];
        while (query.HasMoreResults)
        {
            FeedResponse<T> response = await query.ReadNextAsync();

            results.AddRange([.. response]);
        }

        return results;
    }

    public async Task UpdateItemAsync(string id, T item)
    {
        await this.Container.UpsertItemAsync<T>(item, new PartitionKey(id));
    }
}



METHOD SOFTWARE ©©

Saturday, May 25, 2024

JWT Token Validation

There are different ways and technologies to validate a JWT Token, depending on the needs of securization of the infrastructure

This article deepens on theories and techniques of JWT Token validation: How to Validate JWTs in .NET

In this example we implement automatic validation with ASP.NET Core middleware, making emphasis on validate these pieces of the token: issuer, audience, and expiration time, which are the basic pieces to be validated



With this code we generate a JWT Token, codified in a string composed by header, payload, and signature

 

    public string GenerateToken(string user)
    {
        JwtSecurityTokenHandler tokenHandler = new ();
        byte[] key = Encoding.ASCII.GetBytes("B88CF37BEEE14F9DAA10DA3BDF23D9CA6EBD06E27A6D49C2867A211685A41E88");
        SecurityTokenDescriptor tokenDescriptor = new ()
        {
            Subject = new ClaimsIdentity(new[] { new Claim("id", "user_Id") }),
            Expires = DateTime.UtcNow.AddMinutes(1),
            SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature),
            Issuer = "https://authdomain",
            Audience = "the_audience",            
            IssuedAt = DateTime.UtcNow,
            Claims = new Dictionary<string, object> { ["claim1"] = "test" }
        };
        var token = tokenHandler.CreateToken(tokenDescriptor);
        return tokenHandler.WriteToken(token);
    }





To validate the token from the api, configure the token at IoC to validate it as it is created originally from the source

 
  
        public static void ConfigureJWTToken(this IServiceCollection services, IConfiguration configuration)
        {
            byte[] key = Encoding.ASCII.GetBytes("B88CF37BEEE14F9DAA10DA3BDF23D9CA6EBD06E27A6D49C2867A211685A41E88");

            services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
              .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options =>
              {
                  options.Audience = configuration["Auth:Audience"];
                  //options.Authority : Gets or sets the Authority to use when making OpenIdConnect calls.
                  options.TokenValidationParameters =
                    new TokenValidationParameters
                    {
                        ValidateAudience = true,
                        AudienceValidator = new AudienceValidator((audiences, token, options) =>
                        {
                            // audience validator logic
                            return true;
                        }),
                        ValidateLifetime = true,
                        ClockSkew = TimeSpan.Zero,
                        ValidateIssuer = true,
                        ValidIssuer = "https://authdomain",
                        ValidateIssuerSigningKey = true,
                        IssuerSigningKey = new SymmetricSecurityKey(key),
                    };
              });
        }  
  


METHOD SOFTWARE 2024

Friday, May 10, 2024

.NET Maui

.NET Multi-platform App UI (.NET MAUI) is a cross-platform framework for creating native mobile and desktop apps with C# and XAML.

Using .NET MAUI, you can develop apps that can run on Android, iOS, macOS, and Windows from a single shared code-base.

Microsoft .NET Maui

In this post we set up the environment to develop Maui Apps with Visual Studio 2022

First create a new project of type .NET Maui App, and configure the emulator of Android devices




The emulator installs a serie of tools, you can type the next command to list the Android Virtual Devices installed on the machine



Ensure that Hypervisor is operational on the machine



Start the virtual device to debug the application, notice you can configure different devices to test the application





<METHOD SOFTWARE 2024>

Saturday, April 27, 2024

Redis Service Stack

ServiceStack's C# Redis Client is a simple, high-performance and feature-rich C# Client for Redis with native support and high-level abstractions for serializing POCOs and Complex Types supporting both native Sync and Async APIs

In this post we are installing Redis Server on WSL for Windows 11 OS, and using it from .NET Core Web Application

As a first step we are installing WSL from PowerShell with Admin privileges elevated

     
    wsl --install    
    




Now we have installed Wsl we need to update apt-get

 
  	sudo apt-get update
	sudo apt-get upgrade
  


Then we can install Redis server database

 
  sudo apt-get install redis-server
  




We have already installed Redis Server, now we are using it from our web application demo

 
    using ServiceStack.Redis;
  


 
        private IRedisClient GetRedisClient()
        {
            string conString = "redis://localhost:6379?ConnectTimeout=5000&IdleTimeOutSecs=180";
            RedisManagerPool manager = new(conString);
            return manager.GetClient();
        }  
    


 
            var client = GetRedisClient();
            var bookId = int.Parse(Request.Form["bookId"]);

            if (!client.GetAllItemsFromList("cart").Contains(bookId.ToString()))
            {
                var book = _context.Books.Find(bookId);
                book.InStock--;
                _context.SaveChanges();

                client.AddItemToList("cart", bookId.ToString());
            } 
    





<METHOD SOFTWARE 2024>