Tag Archives: .net

Local RAG with .NET, Postgres, and Ollama: Postgres with pgvector Setup (Part 1)

Step 1: Set Up Postgres Locally with the pgvector Extension

In this series, we’ll build a RAG (Retrieval-Augmented Generation) application that runs completely on your local machine. RAG systems use AI to answer questions based on your specific documents or data. The first component we need is a vector database to store and search through our document embeddings1. We’ll use Postgreswith the pgvector extension, which adds support for vector similarity search.

To make things easy to start, I am going to use a Docker container to run PostgreSQL with the pgvector extension instead of installing and configuring Postgres locally. This makes sure we all have the same setup and avoids configuration issues across different operating systems.

To make it easy to start, I am going to use a Docker container to run Postgres with the pgvector extension instead of installing and configuring it locally.

In a powershell prompt, I pull the image:

docker pull pgvector/pgvector:pg16

This pulled the code from docker hub here : https://hub.docker.com/r/pgvector/pgvector

At the time or this post, I got an image ‘pgvector/pgvector’ with tag pg16. You can see this by running this command in PowerShell:

docker images

Once you have the image, you can run it – starting the container with this command:

docker run -d --name postgres__with_pgvector -e POSTGRES_PASSWORD=password99 -e POSTGRES_USER=postgres -e POSTGRES_DB=vectordb -p 5432:5432 pgvector/pgvector:pg16

You can also do this start-up visually using Docker Desktop if you prefer:

Click on the images link on the left menu on Docker Desktop and then click the run triangle next to the image you just downloaded. You will put the parameters in the command line above into environment variables like this:

Click on the Containers cube link on the left and you will see your container is running.

Next, you can connect to postgres in the container with the command:

 docker exec -it postgres_with_pgvector psql -U postgres

That should give you a postgres prompt ‘postgres=#’

Let’s test that everything is connected and working now with these commands (hitting enter after each line):

CREATE EXTENSION IF NOT EXISTS vector;
CREATE TABLE items (
    id bigserial PRIMARY KEY,
    embedding vector(3)
);
INSERT INTO items (embedding) VALUES ('[1,2,3]');
SELECT * FROM items;

The select should return:

 id | embedding
----+-----------
  1 | [1,2,3]
(1 row)

To get out of the sql prompt, type ‘\q’

Let’s review what we’ve accomplished:

  • Postgres is running in a Docker container
  • The pgvector extension is installed and working
  • We’ve verified we can store and retrieve vector data

In Part 2, we’ll set up Ollama to run an AI model locally. This will allow us to generate the vector embeddings that we’ll store in our Postgres database. Then in Part 3, we’ll create a .NET application that brings these components together into a complete RAG system.

If you need to stop the container, you can use:

docker stop postgres__with_pgvector

Or use Docker Desktop to stop it. Your data will persist for next time.

See you in Part 2!
-Jim

  1. Embeddings are mathematical representations of objects like text, images, and audio. They are used by machine learning (ML) and artificial intelligence (AI) systems to understand complex relationships in data.  ↩︎

Extension method to trim all string fields in a C# object

/// <summary>
/// This does NOT go into sub objects, only the top level object
/// i.e. if you have a class with a string field, it will trim that string field value
/// but not trim any string fields on sub-objects inside the containing class
/// </summary>
/// <param name="currentObject"></param>
        public static void SafeTrimAllStringFields(this object currentObject)
        {
            if (currentObject == null)
            {
                return;
            }

            var type = currentObject.GetType();
            var stringFields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
                .Where(f => f.FieldType == typeof(string));

            foreach (var field in stringFields)
            {
                var value = (string)field.GetValue(currentObject);
                if (value != null)
                {
                    field.SetValue(currentObject, value.SafeTrim());
                }
            }
        }
 }

   public static string SafeTrim(this string value)
        {
            return value == null ? string.Empty : value.Trim();
        }

Selenium test .net webforms with data-attributes

I was struggling to E2E Selenium test a repeater, in a user control, in an old .net webforms page – tall ask right?

Here is the solution I came up with:

I added a custom attribute (“data-serviceid”) to the element the repeater is drawing – inside the code behind of the user control (a link button in this case):

var lbNewReport = (LinkButton)args.Item.FindControl("lbNewReport");
lbNewReport.Attributes.Add("data-serviceid", myServiceId);

Then I used this static helper method for selenium:

public static By SelectorByAttributeValue(string attributeName, string attributeValue)
{
return (By.XPath($"//*[@{attributeName} = '{attributeValue}']"));
}

With This call:

var linkIWant = _driver.FindElement(SelectorByAttributeValue("data-serviceid", myServiceId));

And voila – I have the control in hand that I want to interact with(clicking in this case for me).

Implementing reCAPTCHA in a Razor MVC view

Setup in your Google account

You will have to have a Google account to use reCAPTCHA.

Log into the google account you want to tie your reCAPTCHA keys to and navigate to:

https://www.google.com/recaptcha/admin#list

Under the ‘Register a new site’ section of that page,follow the instructions and set up a separate key set for each of your development,test, and production environments – including one specifically for ‘localhost’ or 127.0.0.1 so you can test locally.

Web config changes

Add the public and private keys you just created on the Google site to your web.config:

<add key="ReCaptchaPrivateKey" value="yourPrivateKey"/> 
<add key="ReCaptchaPublicKey" value="yourPublicKey"/>

HttpGet action in the controller

Add a property for yourPublicKey to your viewmodel and load it from the web.config in the get controller action, passing it into the view.

Changes in the head section

Add this script in the head section of your layout or view page:

<script src="https://www.google.com/recaptcha/api.js?render=@layoutModel.yourPublicKey"></script>

Changes on the .cshtml view page

There are two steps for the view page where you want to display the reCaptcha box:

Add this to the view where you want the reCaptcha box to display:

<div class="g-recaptcha" data-sitekey="@Model.YourPublicKey">

And add this script at the bottom of that view:

<script src='https://www.google.com/recaptcha/api.js'></script>

HttpPost action in the controller

You will need some code in the post action of your controller to hit the Google reCaptcha service and get a response if it appears this is a valid person – something like this:

var response = Request["g-recaptcha-response"];
var reCaptchaSecretKey = ConfigurationManager.AppSettings["yourPrivateKey"];
var webClient = new WebClient();
var resultFromGoogle = webClient.DownloadString(string.Format("https://www.google.com/recaptcha/api/siteverify?secret={0}&response={1}", reCaptchaSecretKey , response));
var parsedResponseFromGoogle = JObject.Parse(resultFromGoogle);
var thisIsARealPersonNotARobot = (bool)parsedResponseFromGoogle.SelectToken("success");

With that result in hand, you can decide how to handle a success or failure.

Gotchas:

I noticed that reCAPTCHA tried to send it’s request through TLS1.1 and our site will not allow it – we require TLS 1.2, so I had to add a directive to force it to only use 1.2 with this setting at the top of the HTTPPost controller action:

  ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;

Thanks for reading and happy coding,
-Jim

Disable Html.DropDownListFor if only one item in dropdown


@Html.DropDownListFor(m => m.SelectedValue,
new SelectList(Model.CollectionOfItemsForDropdown, "ValueField", "NameField"),
@Model.CollectionOfItemsForDropdown.Count > 1
? (object)new { @class = "form-control", required = "true", }
: new { @class = "form-control", required = "true", disabled = "disabled" })

You have to use the conditional operator here for the anonymous objects because the dropdown list will be disabled if the word ‘disabled’ is rendered in the tag in any way.

Why are Non-microsoft conferences so much better?

I am a .Net coder. I like it, I really like C#. Not kidding at all – I really do.

But DAMN man! I went to Ruby Midwest this last weekend and it was so much better than any Microsoft technology based conference I have ever been to.

Two things were different

The first thing

Probably the most important is so simple. I do not need someone show me where menu items are in the tools. How many times have you been to a Microsoft conference to spend 45 minutes at a time listening to this:

Go to Tools –> SubItem –> SubSubItem and click this to increase the font…

Jeesh man, that is what Google is for, manuals are for, or in new Visual Studio, they added the quick launch box. Holy cheese batman, that thing is gonna KILL all the Microsoft conferences. what will they ever talk about now? (Maybe 15 min on how to use the quick launch box?)

If I want to learn how to use an IDE or other visual tool, I can do that without paying a conference fee and giving up time with my family. I also realize that this is not the only thing that goes on at Microsoft conferences – there is a lot of good at these conferences too. Seem like though, the MS conferences weigh a lot heavier on the Black And Decker how to manual side of things.

Second Thing

One track conference. I am sorry all you nerds, but you need the soft skills talks if you want to max out the possibilities in your life (and coding). The best code writer in the world is probably hiding somewhere and typing this beautiful, pristine code out by him/herself somewhere right now. It is code so tight and so clean that it could squeeze a tear out of a rock. But,we will never see this beautiful code. Indiana Jones couldn’t find this code with three bullwhips and six trained monkeys. Ever. It takes talking to people, to humans, to get your code out in the wild; either through open source or your job. Sorry.

One track conferences like Ruby Midwest force you to sit through soft skills talks as well as straight on nerd fare. Unless you leave (one entire company I know did…) the conference altogether, you are going to learn something about how to work with other folks. This is crucial stuff my friends. Really crucial.

Write code better and get that code to other people!

Unless the Microsoft community starts to teach about how to write code better, and how to get that code to other people at their conferences, we will not move forward as a community.

Don’t get me wrong. I really value the work, thought, and sacrifice that Microsoft based conference organizers put in. Organizers like Lee Brandt work a TON to put on conferences like KCDC. The folks in Iowa do an amazing job with Iowa Code camp. They can’t offer talks that aren’t proposed though.

I am not sure of the exact next step to get this better but would like to throw this volley out to start the conversation.

Thanks for hanging in this long,

Happy coding,

Jim