Author Archives: Berend de Jong

LetsEncrypt certificate renewal behind proxy

When deploying a .NET Core website on your domain, you likely utilize a reverse proxy to route traffic from ports 443 and 80 to your Kestrel web server. The configuration for this in Apache is as follows.

The line ProxyPass /.well-known/acme-challenge ! is included to ensure that Let’s Encrypt can successfully renew the certificate.

The other ProxyPass lines are for blazor to connect to the server.

ProxyRequests On
ProxyPreserveHost On
ProxyPass /.well-known/acme-challenge !
ProxyPassMatch ^/_blazor/(.*) http://0.0.0.0:5003/_blazor/$1
ProxyPass /_blazor ws://localhost:5003/_blazor
ProxyPass / http://0.0.0.0:5001/
ProxyPassReverse / http://0.0.0.0:5001/

Share

Ubuntu journalctl

If you want to view the log for a systemd service you can use the journalctl command. Basic Log Viewing: To view the logs for your prog-app service, you can use the following command:

sudo journalctl -u service-app

Tail Logs: If you want to follow the log in real-time, as new entries are added, use the -f flag:

sudo journalctl -fu service-app

Filter by Time: If you’re interested in logs from a specific time period, you can use --since and --until options. For example:

sudo journalctl -u service-app --since "2023-11-27" --until "2023-11-28"

Viewing the Most Recent Entries: To see the most recent entries, you can combine journalctl with other commands like tail. For example:

sudo journalctl -u service-app | tail -n 20
Share

Mollie Payments with C# / ASP.NET MVC Net Core

This post describes how to implement Mollie payments in your ASP.NET MVC Web application.

Begin by setting up an account at my.mollie.com. Ensure that you fully complete the registration process, which includes adding at least one payment method.

Following this, launch Visual Studio (or Visual Studio Code) and create a new MVC web application. The next step is to integrate the Mollie API. Do this by adding the NuGet package ‘Mollie.Api‘. As of the writing of this post, the current version is 3.3.0.

Continue reading
Share

LetsEncrypt request certificate error

Invalid host in redirect target “subdomain.domain.io.well-known”

When you get this error while requesting a new certificate you probably have a wrong redirect statement in your apache configuration.

The “redirect permanent” in the port 80 section should end with a / (or remove this entry while requesting a new certificate)

Share

Multilangual MVC application

Create a new ASP.Net MVC application

Add a Resources folder and add two files with this folder:
Views.Home.Index.en.resx
Views.Home.Index.nl.resx

Add an entry WelcomeText to both files. With a dutch value in the nl.resx file and an english value in the en.resx file.

Now update your startup.cs to support multiple languages. Edit your Program.cs in the root of the project file. Add this code right before var app = builder.Build();

// Add services to the container.
builder.Services.AddLocalization(options => options.ResourcesPath = "Resources");
builder.Services.AddControllersWithViews()
    .AddViewLocalization(LanguageViewLocationExpanderFormat.Suffix)
    .AddDataAnnotationsLocalization();

builder.Services.AddLocalization(options => options.ResourcesPath = "Resources");

builder.Services.Configure<RequestLocalizationOptions>(options =>
{
    var supportedCultures = new[] { new CultureInfo("en"), new CultureInfo("nl") };
    options.DefaultRequestCulture = new RequestCulture("en");
    options.SupportedCultures = supportedCultures;
    options.SupportedUICultures = supportedCultures;
});

And this code right before app.Run();

var supportedCultures = new[] { new CultureInfo("en"), new CultureInfo("nl") };
var localizationOptions = new RequestLocalizationOptions
{
    DefaultRequestCulture = new RequestCulture("en"),
    SupportedCultures = supportedCultures,
    SupportedUICultures = supportedCultures
};

app.UseRequestLocalization(localizationOptions);

That’s it. Now you can switch between languages by adding ?culture=en or ?culture-nl to your url.

Share

LangChain – Create Chroma vector database

See also the LangChain cookbook.

Below is an example of creating a Chroma vector database using a TextSplitter with a simple text string.

from dotenv import load_dotenv
from langchain.vectorstores import Chroma
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.embeddings import OpenAIEmbeddings

load_dotenv()

# https://platform.openai.com/docs/guides/embeddings/use-cases
embeddings = OpenAIEmbeddings(model="text-embedding-ada-002")

text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=10,  # default: 4000
    chunk_overlap=2  # default: 200
)

texts = text_splitter.create_documents(
    texts=['Often times your document is too long (like a book) for your LLM. You need to split it up into chunks. Text splitters help with this.'])

vectordb = Chroma.from_documents(
    documents=texts, embedding=embeddings, persist_directory="./dbtest")

You can also include PDF documents in your Chroma database. See the code below.

import os
from dotenv import load_dotenv
from langchain.vectorstores import Chroma
from langchain.document_loaders import DirectoryLoader
from langchain.document_loaders import PyPDFLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.embeddings import OpenAIEmbeddings

load_dotenv()

if not any(file.endswith('.pdf') for file in os.listdir('.')):
    exit(1)

# https://platform.openai.com/docs/guides/embeddings/use-cases
embeddings = OpenAIEmbeddings(model="text-embedding-ada-002")

text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=2000,  # default: 4000
    chunk_overlap=100  # default: 200
)

loader = DirectoryLoader(".", glob='./*.pdf', loader_cls=PyPDFLoader)
documents = loader.load()

texts = text_splitter.split_documents(documents)

vectordb = Chroma.from_documents(
    documents=texts, embedding=embeddings, persist_directory="./dbtest")
Share

Decorator pattern

The Decorator pattern allows you to dynamically add additional behavior or features to an object at runtime. In the context of a car, let’s create an example with a base Car class, a SportsCar class, and a LuxuryCar class. We’ll use decorators to add extra features to the cars.

First, let’s define the base Car class:

public interface ICar
{
    string GetDescription();
    double GetCost();
}

public class Car : ICar
{
    public string GetDescription()
    {
        return "Basic Car";
    }

    public double GetCost()
    {
        return 20000.0;
    }
}

Next, we’ll create the SportsCar class, which will act as a decorator and add sports car features:

public class SportsCar : ICar
{
    private readonly ICar _car;

    public SportsCar(ICar car)
    {
        _car = car;
    }

    public string GetDescription()
    {
        return _car.GetDescription() + ", Sports Car";
    }

    public double GetCost()
    {
        return _car.GetCost() + 15000.0;
    }
}

Now, let’s create the LuxuryCar class, which will be another decorator to add luxury features:

public class LuxuryCar : ICar
{
    private readonly ICar _car;

    public LuxuryCar(ICar car)
    {
        _car = car;
    }

    public string GetDescription()
    {
        return _car.GetDescription() + ", Luxury Car";
    }

    public double GetCost()
    {
        return _car.GetCost() + 30000.0;
    }
}

Finally, we can use the decorators to create different car configurations:

// Creating a basic car
ICar basicCar = new Car();
Console.WriteLine("Description: " + basicCar.GetDescription());
Console.WriteLine("Cost: $" + basicCar.GetCost());

// Adding sports car features to the basic car
ICar sportsCar = new SportsCar(basicCar);
Console.WriteLine("Description: " + sportsCar.GetDescription());
Console.WriteLine("Cost: $" + sportsCar.GetCost());

// Adding luxury features to the sports car
ICar luxuryCar = new LuxuryCar(sportsCar);
Console.WriteLine("Description: " + luxuryCar.GetDescription());
Console.WriteLine("Cost: $" + luxuryCar.GetCost());

Output:

Description: Basic Car
Cost: $20000
Description: Basic Car, Sports Car
Cost: $35000
Description: Basic Car, Sports Car, Luxury Car
Cost: $65000
Share

LangChain – Use a PromptTemplate with Chaining

See also the LangChain cookbook.

Below is an example of using a PromptTemplate in LangChain

from dotenv import load_dotenv
from langchain.chat_models import ChatOpenAI
from langchain.chains import LLMChain
from langchain.prompts import PromptTemplate
from langchain.chains import SimpleSequentialChain

load_dotenv()

template = """
You are a senior programmer with an expertise in {lang}. 
Implement the function below in the {lang} programming language:

Find the first four prime numbers

"""

prompt = PromptTemplate(
    input_variables=["lang"],
    template=template,
)

second_prompt = PromptTemplate(
    input_variables=["lang"],
    template="Convert the function {lang} below to a textual representation",
)

llm = ChatOpenAI(model_name="gpt-4")

chain = LLMChain(llm=llm, prompt=prompt)

chain_two = LLMChain(llm=llm, prompt=second_prompt)

overall_chain = SimpleSequentialChain(chains=[chain, chain_two], verbose=True)

print(overall_chain.run("c#"))
Share