# Utilisation de scripts CSX pour l'essai rapide C#

Besoin de tester un fragment du code C# sans faire tourner le projet complet`.csx`) vous permet d'écrire et de faire fonctionner le code CM SK1 comme une langue de scripting. Non `Program.cs`, non `.csproj`, pas d'étape de construction - simplement écrire et exécuter. parfait pour tester les APIsM SK3 validation de la logiqueMSC4 ou prototypage avant de s'engager à une mise en œuvre complète

Puisque j’ai lancé la fonctionnalité complète de recherche semanti c, je pensais que j’aimerais partager comment je l’utilise. `.csx` Dossiers pour les essais spéciaux dans ce projet et d’autres projets.

<datetime class="hidden">2025-11-26T20:00</datetime>

<!--category-- C#, Testing, Scripting, dotnet-script -->
[TOC]

## CSX vs .NET 10 File-Apps basés

Avant de plonger dans , laissez 's adresser l’éléphant dans la salle: .NET 10 maintenant a des applications fondées sur les fichiers natifs, qui vous permettent d’exploiter `.cs` les fichiers directement avec `dotnet run app.cs`. Comment cela se compare-t-il à CSX?

### .NET 10 Fichier-Apps basés MSC3Actuellement disponibleMSc4

Avec .NET 10, vous pouvez exécuter directement un seul fichier CM SK3

```bash
# .NET 10 - available now!
dotnet run app.cs
```

**Caractéristiques:**

- Natif du SDK - Aucun outil supplémentaire n'est nécessaire
- Utilisation connue `.cs` prolongation
- Références NuGet via `#:package` Directive
- Soutien complet au débugger depuis le premier jour
- Le même compilateur que les projets réguliers

```csharp
// app.cs - .NET 10 style
#:package Newtonsoft.Json@13.0.3

using Newtonsoft.Json;

var obj = new { Name = "Test", Value = 42 };
Console.WriteLine(JsonConvert.SerializeObject(obj));
```

### Scripts CSX (Available maintenant)

CSX par l'intermédiaire `dotnet-script` est disponible depuis 2017:

```bash
# Available today
dotnet script app.csx
```

**Caractéristiques:**

- Travaille avec .NET 6, 7, | | 3 | 4 | 5 | 6
- Écosystème et outils riches
- Mode REPL pour l'exploration interactive
- Prove et bataille-testé

```csharp
// app.csx - CSX style
#r "nuget: Newtonsoft.Json, 13.0.3"

using Newtonsoft.Json;

var obj = new { Name = "Test", Value = 42 };
Console.WriteLine(JsonConvert.SerializeObject(obj));
```

### Quelles méthodes utiliser?

| Caractéristiques | CSX MSC2dotnet-scriptM SK4 S| | | .NET | MSC7 Applications de fichiers |MSC8
|---------|---------------------|-------------------|
| **Disponibilité** | .NET 6+ | | 3 | 4 | 5 | 6
| **Installation** | `dotnet tool install -g dotnet-script` | Conçu dans le SDK |
| **Extension de fichiers** | `.csx` | `.cs` |
| **Syntaxe NuGet** | `#r "nuget: Pkg, Ver"` | `#:package Pkg@Ver` |
| **Mode REPL** | Oui | N'est pas encore possible |
| **Soutien de l'IDE** | Bon ( Code de V.S.
| **Débugging** | Oui | Oui

**Ma recommandation:**

- **Essayez d'abord les applications de fichier .NET 10** - soutien natif signifie moins de frais d’outils
- **Retourner à la CSX** si vous avez besoin du mode REPL ou si vous êtes sur une version .NET plus ancienne
- Les concepts sont presque identiques - transferts de connaissances facilement entre les deux

Le reste de cet article couvre la CSX qui fonctionne toujours très bien et a certaines fonctionnalités (comme REPL) que .NET 10 les applications de fichier n'ont pas encore

## Qu'est-ce que CSX?

CSX (CM SK1 Script) les fichiers sont des fichiers de code qui peuvent être exécutés directement sans compilation dans un projet.

```csharp
// hello.csx
Console.WriteLine("Hello from C# Script!");
```

Run it:

```bash
dotnet script hello.csx
```

Ce n’est pas le cas. `Main()` méthode, aucun nom de domaine, aucune enveloppe de classe requise .

## Installer le site dotnet-script

La façon la plus populaire d'exécuter les fichiers CSX est par [dotnet-script](https://github.com/dotnet-script/dotnet-script):

```bash
dotnet tool install -g dotnet-script
```

Vérifier l’installation:

```bash
dotnet script --version
```

## Où la CSX s'inscrit dans le cycle d'essai

Avant de plonger dans la "comment ", laissez's comprendre les CSX3lorsque les scripts CSX occupent une place unique dans la pyramide d’essaiM SK5

```
                    ┌─────────────────┐
                    │   E2E Tests     │  ← Full system, slow, expensive
                    │   (Playwright)  │
                   ─┼─────────────────┼─
                   │ Integration Tests │  ← Multiple components, database
                   │    (xUnit + DB)   │
                  ─┼───────────────────┼─
                 │   CSX Scripts        │  ← Quick validation, exploration
                 │   (Ad-hoc testing)   │     ★ YOU ARE HERE ★
                ─┼─────────────────────┼─
               │      Unit Tests         │  ← Single class, mocked deps
               │   (xUnit, NUnit, etc)   │
              ─┴─────────────────────────┴─
```

### Scripts CSX comme "Tests mini d'intégration"

Les scripts CSX ne sont pas un remplacement pour les essais formels. **Compte tenu de l'importance que revêtent les investissements dans le secteur privé, il est important d'établir des conditions favorables à la mise en œuvre du programme.**. Pensez à ceux-ci comme aux

- **Pre-Spikes de mise en œuvre** - Vérifier le fonctionnement d’une API avant de construire un service autour de celle-ci
- **Aides de débugging** - Isoler et reproduire les problèmes sans reconstruire votre app entière
- **Essais exploratoires** - Comprendre comment se comporte une bibliothèque avant de rédiger des tests d'unité
- **Essais de fumée** - Contrôle rapide de l'hygiène par rapport aux services réels

### Le processus de travail du développement

Voici' comment le CSX s’inscrit dans un cycle typique de développement des caractéristiquesM SK1

```
1. EXPLORE (CSX Script)
   └─→ "Does this API even work? What's the response format?"
   └─→ Write a quick script to call the API and see the output

2. PROTOTYPE (CSX Script)
   └─→ "How should I structure this service?"
   └─→ Test different approaches without project scaffolding

3. IMPLEMENT (Production Code)
   └─→ Build the actual service with proper error handling, DI, etc.
   └─→ You already know the API works from step 1!

4. TEST (xUnit/NUnit)
   └─→ Write formal unit tests with mocks
   └─→ Write integration tests against test database

5. DEBUG (CSX Script)
   └─→ Production issue? Write a script to reproduce it
   └─→ Faster than adding logging, rebuilding, deploying
```

### Exemple réelle: Établissement de l’intégration Umami

Lorsque j’ai construit l’intégration des analyses Umami pour ce blog, mon flux de travail était:

1. **CSX: Tester l'API brut** - L'authentification fonctionne-t-elle? Comment les réponses ressemblent-elles?
2. **CSX: Conversion du temps d'essai** - Found a bug here before writing any production code
3. **Implement: Build UmamiClient** - Avec confiance car I'd a déjà validé l’API
4. **xUnit: Essais d'unité de rédaction** - Mock HttpClient, logique de serialisation d'essai
5. **CSX: Problème de production de débugging** - Metrique retournant vide? Script pour isoler le problème

Les scripts CSX n’ont pas remplacé les tests de mon unité. **m’a empêché de rédiger un code qui ne fonctionnerait pas** et m'a aidé **supprimer les problèmes plus rapidement** quand ils se sont produits.

## Pourquoi utiliser CSX pour l'essai?

### 1. Cérémonie de zéro

Approche traditionnelle pour tester un appel API:

1. Créer un nouveau projet de console
2. Ajouter des paquets NuGet
3. Écrire `Program.cs`
4. Établissement
5. Faire courir
6. Supprimer le projet lorsqu'il est terminé

Approche CSX:

1. Écrire des scripts
2. Exécuter le script

### 2. Références NuGet en ligne

Besoin d’un paquet? Reférer directement dans votre script:

```csharp
#r "nuget: Newtonsoft.Json, 13.0.3"
#r "nuget: RestSharp, 110.2.0"

using Newtonsoft.Json;
using RestSharp;

var client = new RestClient("https://api.github.com");
var request = new RestRequest("users/scottgal", Method.Get);
request.AddHeader("User-Agent", "CSX-Test");

var response = await client.ExecuteAsync(request);
Console.WriteLine(JsonConvert.SerializeObject(
    JsonConvert.DeserializeObject(response.Content),
    Formatting.Indented));
```

Démarrer le téléchargement des paquets. Suivre l'utilisation du cache.

### 3. DLLs locaux de référence

Évaluation de votre propre bibliothèque? Référence directement à celle-ci:

```csharp
#r "bin/Debug/net9.0/MyLibrary.dll"

using MyLibrary;

var result = MyClass.DoSomething();
Console.WriteLine(result);
```

### 4. Référence Autres scripts

Split complex scripts into reusable parts:

```csharp
#load "helpers.csx"
#load "config.csx"

// Use functions/classes from loaded scripts
var config = LoadConfig();
var result = ProcessData(config);
```

## Exemples réels de ce projet

Il s’agit de scripts réels que j’utilise pour débuger et tester ce blog.

### Tester les Timestamps de l'API

**Le problème:** Mon intégration d'analyse Umami retournait des données vides. Après quelques heures de débuggingM SK1 J'ai soupçonné que la conversion du timbre était erronée - l'API Umami attend les timbrex Unix en millisecondes, mais je n'étais pas certaineMST4 si mon code MSC5NET produisait le bon formatMSL6

**Pourquoi CSX?** J'aurais pu ajouter un enregistrement au code de production, reconstruire, déployerM SK2 et vérifier les registresMSC3 Ou je pouvais écrire un script rapide pour vérifier ma hypothèse en MST4 secondes MST5

```csharp
#!/usr/bin/env dotnet-script

// This script helped debug an issue where the Umami API was returning empty data.
// The API expects Unix timestamps in milliseconds, and I suspected my conversion was wrong.

// Start with known values we can verify
var now = DateTime.UtcNow;
var yesterday = now.AddHours(-24);

// The "O" format specifier gives us ISO 8601 format - precise and unambiguous
// Example output: "2025-11-24T10:30:45.1234567Z"
Console.WriteLine($"Now: {now:O}");
Console.WriteLine($"Yesterday: {yesterday:O}");

// The Umami API expects Unix timestamps in MILLISECONDS (not seconds!)
// DateTimeOffset is the safest way to convert - it handles time zones correctly.
// Always use ToUniversalTime() first to ensure we're working with UTC.
var nowOffset = new DateTimeOffset(now.ToUniversalTime());
var yesterdayOffset = new DateTimeOffset(yesterday.ToUniversalTime());

// ToUnixTimeMilliseconds() returns milliseconds since 1970-01-01 00:00:00 UTC
var nowMs = nowOffset.ToUnixTimeMilliseconds();
var yesterdayMs = yesterdayOffset.ToUnixTimeMilliseconds();

Console.WriteLine($"\nNow in milliseconds: {nowMs}");
Console.WriteLine($"Yesterday in milliseconds: {yesterdayMs}");

// IMPORTANT: Verify the conversion is reversible!
// This catches off-by-one errors and timezone issues
var nowConverted = DateTimeOffset.FromUnixTimeMilliseconds(nowMs);
var yesterdayConverted = DateTimeOffset.FromUnixTimeMilliseconds(yesterdayMs);

Console.WriteLine($"\nConverted back (should match above):");
Console.WriteLine($"Now: {nowConverted:O}");
Console.WriteLine($"Yesterday: {yesterdayConverted:O}");

// THE ACTUAL BUG: I found this timestamp in my application logs
// Let's see what date it actually represents...
var suspiciousTimestamp = 1763440087664L;
var suspiciousDate = DateTimeOffset.FromUnixTimeMilliseconds(suspiciousTimestamp);
Console.WriteLine($"\nSuspicious timestamp {suspiciousTimestamp} = {suspiciousDate:O}");

// Output showed this timestamp was in the year 2025... but it should have been in 2024!
// Tracing back, I found I was using DateTime.Now instead of DateTime.UtcNow,
// causing the local timezone offset to be applied incorrectly.
```

**Résultats** Ce script a prouvé que le timbre était 1 année dans l’avenir. J’ai tracé le bug en utilisant `DateTime.Now` au lieu de `DateTime.UtcNow` dans le code de production. Fixé en M SK1 minutes au lieu d’une durée potentielle de 5 heures de débugging.

### Évaluation de la génération des chaînes de recherche

**Le problème:** J'ai besoin de vérifier que ASP.NET's `QueryHelpers` class génère des chaînes de demande dans le format exact que l'API Umami attend.

**Pourquoi CSX?** La lecture de la documentation est une chose, mais voir le résultat réel vous indique exactement ce que votre code produira.

```csharp
#!/usr/bin/env dotnet-script

// Pull in ASP.NET's WebUtilities package - this is the same package
// that ASP.NET Core uses internally for query string manipulation
#r "nuget: Microsoft.AspNetCore.WebUtilities, 9.0.0"

using Microsoft.AspNetCore.WebUtilities;

// These are the exact parameters I need to send to the Umami metrics API
// Using a Dictionary makes it easy to see all parameters at once
var queryParams = new Dictionary<string, string>
{
    {"startAt", "1730000000000"},   // Unix timestamp in milliseconds
    {"endAt", "1730086400000"},     // 24 hours later
    {"type", "url"},                // Type of metric to fetch
    {"unit", "day"},                // Aggregation unit
    {"limit", "500"}                // Maximum results to return
};

// QueryHelpers.AddQueryString builds a properly formatted query string
// First parameter: base URL (empty string = just the query string portion)
// Second parameter: dictionary of key-value pairs
var queryString = QueryHelpers.AddQueryString(string.Empty, queryParams);

Console.WriteLine($"Generated query string:");
Console.WriteLine(queryString);
// Output: ?startAt=1730000000000&endAt=1730086400000&type=url&unit=day&limit=500

// Now let's verify we can parse it back - this catches encoding issues
// that might not be obvious in the generated string
Console.WriteLine($"\nParsed back (verifying round-trip):");
var parsed = QueryHelpers.ParseQuery(queryString);
foreach (var kvp in parsed)
{
    // Note: parsed values are StringValues, not string
    // StringValues can hold multiple values for the same key (e.g., ?tag=a&tag=b)
    Console.WriteLine($"  {kvp.Key} = {kvp.Value}");
}

// What I learned: QueryHelpers properly handles URL encoding for special characters
// This became important when I later added search terms with spaces and unicode
```

### Tester les appels de l'API Raw HTTP

**Le problème:** Avant de construire une classe de services complets avec injection de dépendance, manipulation des erreurs, logique d'essai de nouveauM SK2 et essais unitaires , J'ai voulu vérifier que l'API fonctionne réellement et comprendre son format de réponseMSC4

**Pourquoi CSX?** Il est plus rapide d’écrire 50 des lignes de code exploratoire que d’établir une infrastructure de service appropriée.

```csharp
#!/usr/bin/env dotnet-script

// System.Net.Http.Json provides extension methods like PostAsJsonAsync and GetFromJsonAsync
// This is the same package ASP.NET Core uses internally
#r "nuget: System.Net.Http.Json, 9.0.0"

using System.Net.Http.Json;
using System.Text.Json;

// Configuration - in a real app these would come from appsettings.json
var websiteId = "32c2aa31-b1ac-44c0-b8f3-ff1f50403bee";
var umamiPath = "https://umami.mostlylucid.net";
var username = "admin";

// SECURITY: Never hardcode passwords! Use environment variables instead.
// Set before running: $env:UMAMI_PASSWORD = "your-password" (PowerShell)
//               or:   export UMAMI_PASSWORD="your-password" (bash)
var password = Environment.GetEnvironmentVariable("UMAMI_PASSWORD") ?? "";

if (string.IsNullOrEmpty(password))
{
    // Provide helpful instructions when the password is missing
    Console.WriteLine("ERROR: Set UMAMI_PASSWORD environment variable");
    Console.WriteLine("  PowerShell: $env:UMAMI_PASSWORD = 'your-password'");
    Console.WriteLine("  Bash:       export UMAMI_PASSWORD='your-password'");
    return;  // In CSX, 'return' at top level exits the script
}

// Create a single HttpClient instance - never create multiple instances in a loop!
// BaseAddress means all subsequent requests can use relative URLs
var httpClient = new HttpClient { BaseAddress = new Uri(umamiPath) };

// === STEP 1: Authenticate ===
// PostAsJsonAsync automatically serializes our anonymous object to JSON
// and sets the Content-Type header to application/json
Console.WriteLine("Step 1: Logging in...");
var loginPayload = new { username, password };
var loginResponse = await httpClient.PostAsJsonAsync("/api/auth/login", loginPayload);

// Always check for errors before trying to read the response body
if (!loginResponse.IsSuccessStatusCode)
{
    Console.WriteLine($"Login failed: {loginResponse.StatusCode}");
    var error = await loginResponse.Content.ReadAsStringAsync();
    Console.WriteLine($"Error body: {error}");
    return;
}

Console.WriteLine("Login successful!");

// === STEP 2: Extract JWT Token ===
// Use JsonDocument for one-off JSON parsing without creating dedicated DTOs
// This is perfect for exploratory testing when we don't know the exact schema
var loginContent = await loginResponse.Content.ReadAsStringAsync();
var loginJson = JsonDocument.Parse(loginContent);
var token = loginJson.RootElement.GetProperty("token").GetString();

// Add the JWT token to all future requests via the Authorization header
httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {token}");

// === STEP 3: Build the API Request ===
// Always use UTC for API calls to avoid timezone confusion
var now = DateTime.UtcNow;
var yesterday = now.AddHours(-24);
var nowMs = ((DateTimeOffset)now).ToUnixTimeMilliseconds();
var yesterdayMs = ((DateTimeOffset)yesterday).ToUnixTimeMilliseconds();

var testUrl = $"/api/websites/{websiteId}/metrics?startAt={yesterdayMs}&endAt={nowMs}&type=url&unit=day&limit=10";

Console.WriteLine($"\nStep 2: Testing metrics endpoint...");
Console.WriteLine($"URL: {testUrl}");

// === STEP 4: Make the Request ===
var response = await httpClient.GetAsync(testUrl);
Console.WriteLine($"Status: {response.StatusCode}");

// Pretty-print the JSON response so we can understand the structure
var responseBody = await response.Content.ReadAsStringAsync();
try
{
    var formatted = JsonSerializer.Serialize(
        JsonSerializer.Deserialize<JsonElement>(responseBody),
        new JsonSerializerOptions { WriteIndented = true });
    Console.WriteLine($"Response:\n{formatted}");
}
catch
{
    // If it's not valid JSON, just print raw
    Console.WriteLine($"Response (raw):\n{responseBody}");
}

// What I learned from this script:
// 1. The API returns an array of objects with 'x' (url) and 'y' (count) properties
// 2. Empty results return [] not null
// 3. The JWT token expires after 24 hours
```

### Essais avec injection de dépendance

**Le problème:** I' j’ai publié un paquet NuGet (UmamiM SK2Net) et je veux l’essayer exactement comme le consommateur l’utiliserait

**Pourquoi CSX?** Créer un projet de console d'essai, ajouter ma référence NuGet , écrire toutes les plaques de boiler DI M SK2 que's 15+ minutes de cérémonieMSC5 Avec CSXMST6 Je peux vérifier l'expérience du consommateur en moins de MST7 minutesM ST8

```csharp
#!/usr/bin/env dotnet-script

// Reference my published NuGet package - this tests the ACTUAL PUBLISHED VERSION,
// not my local source code. This is crucial for verifying releases work correctly!
#r "nuget: Umami.Net, 0.1.0"

// Standard Microsoft DI packages - the same ones ASP.NET Core uses
#r "nuget: Microsoft.Extensions.DependencyInjection, 9.0.0"
#r "nuget: Microsoft.Extensions.Logging.Console, 9.0.0"

using Umami.Net;
using Umami.Net.UmamiData;
using Umami.Net.UmamiData.Models.RequestObjects;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

// Configuration
var websiteId = "32c2aa31-b1ac-44c0-b8f3-ff1f50403bee";
var umamiPath = "https://umami.mostlylucid.net";
var password = Environment.GetEnvironmentVariable("UMAMI_PASSWORD") ?? "";

if (string.IsNullOrEmpty(password))
{
    Console.WriteLine("ERROR: Set UMAMI_PASSWORD environment variable");
    return;
}

// === BUILD THE DI CONTAINER ===
// This mimics exactly what happens in a real ASP.NET Core app's Program.cs

var services = new ServiceCollection();

// Add logging so we can see what the library is doing internally
// Debug level will show HTTP requests, retries, token refreshes, etc.
services.AddLogging(builder =>
{
    builder.AddConsole();
    builder.SetMinimumLevel(LogLevel.Debug);  // Show everything
});

// This is my library's extension method - this is the public API that users call
// I want to verify this works correctly without any hidden dependencies
services.AddUmamiData(umamiPath, websiteId);

// Build the container and resolve our service
var serviceProvider = services.BuildServiceProvider();
var umamiDataService = serviceProvider.GetRequiredService<UmamiDataService>();

Console.WriteLine("=== Testing Umami.Net Package via DI ===\n");

// === TEST THE LOGIN FLOW ===
Console.WriteLine("Testing login...");
var loginSuccess = await umamiDataService.LoginAsync("admin", password);
if (!loginSuccess)
{
    Console.WriteLine("ERROR: Login failed - check credentials");
    return;
}
Console.WriteLine("Login successful!\n");

// === TEST THE METRICS API ===
Console.WriteLine("Testing metrics API...");
var metricsResult = await umamiDataService.GetMetrics(new MetricsRequest
{
    StartAtDate = DateTime.UtcNow.AddHours(-24),
    EndAtDate = DateTime.UtcNow,
    Type = MetricType.url,   // Get URL metrics (most visited pages)
    Unit = Unit.day,
    Limit = 10
});

// Display results
Console.WriteLine($"API returned status: {metricsResult?.Status}");
if (metricsResult?.Data?.Length > 0)
{
    Console.WriteLine($"\nTop {Math.Min(5, metricsResult.Data.Length)} URLs in the last 24 hours:");
    foreach (var metric in metricsResult.Data.Take(5))
    {
        // metric.x = the URL path, metric.y = the view count
        Console.WriteLine($"  {metric.y,5} views - {metric.x}");
    }
}
else
{
    Console.WriteLine("No data returned - check date range or website ID");
}

// What I verified with this script:
// 1. The NuGet package installs correctly
// 2. The DI registration extension method works
// 3. The service can be resolved from the container
// 4. Login and API calls work as expected
```

### Évaluation de la base de données du vecteur Qdrant

**Le problème:** I'j'intègre une base de données vectorale Qdrant pour la recherche sémantique . Avant d'écrire le service de production, Je dois comprendre comment fonctionne le client gRPCM SK3 quel est l'aspect de l'APIMSC4 et vérifier si ma instance locale Qdran fonctionne correctementMNK5

**Pourquoi CSX?** Les bases de données vectorielles sont un nouveau domaine pour beaucoup de développeurs. Le CSX m’autorise à expérimenter interactivement , à essayer différentes opérations et à voir des résultats immédiats avant d’entreprendre une architecture

```csharp
#!/usr/bin/env dotnet-script

// Qdrant.Client is the official .NET client for the Qdrant vector database
#r "nuget: Qdrant.Client, 1.12.0"

using Qdrant.Client;
using Qdrant.Client.Grpc;

// === CRITICAL: Windows gRPC HTTP/2 Fix ===
// By default, .NET on Windows doesn't allow unencrypted HTTP/2 connections (used by gRPC)
// Without this line, you'll get cryptic "Protocol error" exceptions
// This must be called BEFORE creating the QdrantClient!
AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true);

// Connect to Qdrant running locally
// Note: Port 6334 is gRPC (faster), port 6333 is REST API
// The .NET client uses gRPC for better performance
var client = new QdrantClient("localhost", 6334);

Console.WriteLine("=== Qdrant Vector Database Testing ===\n");

// === STEP 1: List Existing Collections ===
// A "collection" in Qdrant is like a table - it holds vectors with the same dimensionality
Console.WriteLine("Step 1: Checking existing collections...");
var collections = await client.ListCollectionsAsync();

if (!collections.Any())
{
    Console.WriteLine("No collections found. This is a fresh Qdrant instance.\n");
}
else
{
    foreach (var collection in collections)
    {
        var info = await client.GetCollectionInfoAsync(collection);
        Console.WriteLine($"  Collection: {collection}");
        Console.WriteLine($"    Points (vectors): {info.PointsCount}");
        Console.WriteLine($"    Status: {info.Status}");
    }
    Console.WriteLine();
}

// === STEP 2: Create a Test Collection ===
// Vector databases store "points" - each point has a vector and optional metadata (payload)
var testCollection = "csx_demo";

Console.WriteLine($"Step 2: Creating test collection '{testCollection}'...");
try
{
    await client.CreateCollectionAsync(
        collectionName: testCollection,
        vectorsConfig: new VectorParams
        {
            // Vector size MUST match your embedding model!
            // all-MiniLM-L6-v2 produces 384-dimensional vectors
            // text-embedding-ada-002 produces 1536-dimensional vectors
            Size = 384,

            // Cosine similarity is standard for text embeddings
            // Alternatives: Distance.Dot (dot product), Distance.Euclid (euclidean)
            Distance = Distance.Cosine
        });
    Console.WriteLine("Collection created successfully!\n");
}
catch (Exception ex) when (ex.Message.Contains("already exists"))
{
    Console.WriteLine("Collection already exists, continuing...\n");
}

// === STEP 3: Insert Test Data ===
// In production, vectors come from an embedding model (BERT, OpenAI, etc.)
// For testing, we'll use random vectors
Console.WriteLine("Step 3: Inserting test point...");

var testVector = Enumerable.Range(0, 384)
    .Select(_ => (float)Random.Shared.NextDouble())
    .ToArray();

// Payload = metadata attached to the vector
// This is what you filter on and return in search results
var payload = new Dictionary<string, Value>
{
    ["title"] = "Understanding Vector Databases",
    ["slug"] = "understanding-vector-databases",
    ["language"] = "en",
    ["created"] = DateTime.UtcNow.ToString("O")
};

await client.UpsertAsync(
    collectionName: testCollection,
    points: new[]
    {
        new PointStruct
        {
            Id = Guid.NewGuid(),  // Unique identifier for this point
            Vectors = testVector,
            Payload = { payload }
        }
    });
Console.WriteLine("Point inserted!\n");

// === STEP 4: Search for Similar Vectors ===
// In production, you'd embed a search query and find similar documents
Console.WriteLine("Step 4: Searching for similar vectors...");

var searchVector = Enumerable.Range(0, 384)
    .Select(_ => (float)Random.Shared.NextDouble())
    .ToArray();

var results = await client.SearchAsync(
    collectionName: testCollection,
    vector: searchVector,
    limit: 5,
    scoreThreshold: 0.0f  // Return all results (random vectors won't have high similarity)
);

Console.WriteLine($"Found {results.Count} results:");
foreach (var result in results)
{
    // Score: 0 to 1 for cosine similarity (higher = more similar)
    Console.WriteLine($"  Score: {result.Score:F4}");
    Console.WriteLine($"    Title: {result.Payload["title"].StringValue}");
    Console.WriteLine($"    Slug: {result.Payload["slug"].StringValue}");
}

// === STEP 5: Clean Up ===
Console.WriteLine($"\nStep 5: Deleting test collection...");
await client.DeleteCollectionAsync(testCollection);
Console.WriteLine("Done! Test collection cleaned up.");

// What I learned from this script:
// 1. The gRPC client is fast but needs the HTTP/2 switch on Windows
// 2. Collection creation requires specifying vector dimensions upfront
// 3. Payloads can be arbitrary key-value pairs
// 4. Search returns results sorted by similarity score
```

## Autres exemples pratiques

### Évaluation d'un point final HTTP

```csharp
#r "nuget: System.Net.Http.Json, 9.0.0"

using System.Net.Http.Json;

var http = new HttpClient();
http.DefaultRequestHeaders.Add("User-Agent", "CSX-Test");

// Test a GET endpoint
var response = await http.GetFromJsonAsync<JsonElement>(
    "https://api.github.com/repos/dotnet/runtime");

Console.WriteLine($"Stars: {response.GetProperty("stargazers_count")}");
Console.WriteLine($"Forks: {response.GetProperty("forks_count")}");
```

### Examen de la Serialisation JSON

```csharp
#r "nuget: System.Text.Json, 8.0.0"

using System.Text.Json;
using System.Text.Json.Serialization;

public record Person(
    string Name,
    int Age,
    [property: JsonPropertyName("email_address")] string Email);

var person = new Person("Scott", 50, "scott@example.com");

var options = new JsonSerializerOptions
{
    WriteIndented = true,
    PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};

var json = JsonSerializer.Serialize(person, options);
Console.WriteLine(json);

// Deserialize back
var parsed = JsonSerializer.Deserialize<Person>(json, options);
Console.WriteLine($"Parsed: {parsed}");
```

### Examen des demandes de base de données

```csharp
#r "nuget: Npgsql, 8.0.0"
#r "nuget: Dapper, 2.1.24"

using Npgsql;
using Dapper;

var connectionString = "Host=localhost;Database=test;Username=postgres;Password=secret";

await using var conn = new NpgsqlConnection(connectionString);

// Quick query test
var results = await conn.QueryAsync<dynamic>(
    "SELECT * FROM users WHERE created_at > @date",
    new { date = DateTime.UtcNow.AddDays(-7) });

foreach (var row in results)
{
    Console.WriteLine($"{row.id}: {row.name}");
}
```

### Évaluation des modèles de Regex

```csharp
using System.Text.RegularExpressions;

var patterns = new[]
{
    @"^\d{4}-\d{2}-\d{2}$",           // Date
    @"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$", // Email
    @"^https?://[\w\-]+(\.[\w\-]+)+", // URL
};

var testCases = new[]
{
    "2025-11-24",
    "test@example.com",
    "https://mostlylucid.net",
    "not-a-date",
    "invalid-email",
};

foreach (var test in testCases)
{
    Console.WriteLine($"\n{test}:");
    foreach (var pattern in patterns)
    {
        var match = Regex.IsMatch(test, pattern);
        if (match) Console.WriteLine($"  ✓ Matches: {pattern}");
    }
}
```

### Examen des demandes de LINQ

```csharp
var data = new[]
{
    new { Name = "Alice", Age = 30, Department = "Engineering" },
    new { Name = "Bob", Age = 25, Department = "Marketing" },
    new { Name = "Charlie", Age = 35, Department = "Engineering" },
    new { Name = "Diana", Age = 28, Department = "Engineering" },
};

// Test complex LINQ query
var result = data
    .Where(x => x.Department == "Engineering")
    .GroupBy(x => x.Age >= 30)
    .Select(g => new
    {
        Senior = g.Key,
        Count = g.Count(),
        Names = string.Join(", ", g.Select(x => x.Name))
    });

foreach (var group in result)
{
    Console.WriteLine($"Senior: {group.Senior}, Count: {group.Count}, Names: {group.Names}");
}
```

### Examen de la recherche par vecteur Qdrant

```csharp
#r "nuget: Qdrant.Client, 1.12.0"

using Qdrant.Client;
using Qdrant.Client.Grpc;

var client = new QdrantClient("localhost", 6334);

// Test collection exists
var collections = await client.ListCollectionsAsync();
Console.WriteLine("Collections:");
foreach (var collection in collections)
{
    Console.WriteLine($"  - {collection}");
}

// Test a search (assuming you have embeddings)
var testVector = Enumerable.Range(0, 384).Select(_ => (float)Random.Shared.NextDouble()).ToArray();

try
{
    var results = await client.SearchAsync(
        collectionName: "blog_posts",
        vector: testVector,
        limit: 5);

    foreach (var result in results)
    {
        Console.WriteLine($"Score: {result.Score}, Id: {result.Id}");
    }
}
catch (Exception ex)
{
    Console.WriteLine($"Search failed: {ex.Message}");
}
```

## Soutien de l'IDE

### Code d'atelier visuel

Installer le [C# Kit de développement](https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.csdevkit) extension. Vous obtiendrez

- Éclaircissement de la syntaxe
- IntelliSense
- Run/Debug via CodeLens

Créer `.vscode/launch.json`:

```json
{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Run CSX",
            "type": "coreclr",
            "request": "launch",
            "program": "dotnet",
            "args": ["script", "${file}"],
            "cwd": "${workspaceFolder}"
        }
    ]
}
```

### JetBrains Rider

Rider a construit-dans le support CSX.RightM SK2cliquer n’importe quel `.csx` et sélectionner "Run".

## Conseils et trucs

### Utiliser le Shebang

Ajouter une barre pour rendre les scripts exécutables directement sur Linux/Mac:

```csharp
#!/usr/bin/env dotnet-script

Console.WriteLine("Runs directly with ./script.csx");
```

### Arguments par défaut

Commande d'accès-arguments de ligne par l'intermédiaire du global `Args` variable:

```csharp
// run: dotnet script test.csx -- arg1 arg2 "arg with spaces"
Console.WriteLine($"Arguments: {Args.Count}");
foreach (var (arg, index) in Args.Select((a, i) => (a, i)))
{
    Console.WriteLine($"  [{index}]: {arg}");
}

// Common pattern: use args with defaults
var environment = Args.ElementAtOrDefault(0) ?? "development";
var verbose = Args.Contains("--verbose");

Console.WriteLine($"Environment: {environment}, Verbose: {verbose}");
```

### Variables environnementales pour les secrets

N'utilisez jamais les secrets de code dur - des variables environnementales:

```csharp
var apiKey = Environment.GetEnvironmentVariable("API_KEY");
var dbPassword = Environment.GetEnvironmentVariable("DB_PASSWORD");

if (string.IsNullOrEmpty(apiKey))
{
    Console.Error.WriteLine("ERROR: API_KEY not set");
    Console.Error.WriteLine("Run: $env:API_KEY='your-key' (PowerShell)");
    Console.Error.WriteLine(" or: export API_KEY='your-key' (bash)");
    Environment.Exit(1);
}

// Safely log partial key for debugging
Console.WriteLine($"Using API key: {apiKey[..4]}...{apiKey[^4..]}");
```

### Mode interactif (REPL)

Démarrer une session interactive pour l’exploration:

```bash
dotnet script
```

Vous obtiendrez un C# REPL:

```
> var x = 42;
> x * 2
84
> #r "nuget: Newtonsoft.Json, 13.0.3"
> using Newtonsoft.Json;
> JsonConvert.SerializeObject(new { foo = "bar" })
"{"foo":"bar"}"
```

### Débugging

Débugging avec le code VS en ajoutant un point de rupture et en utilisant F5, ou:

```bash
dotnet script test.csx --debug
```

### Utiliser des dossiers pour les DTO rapides

Aucun fichier de classe n'est nécessaire - définir en ligne:

```csharp
// Records are perfect for CSX - single line definitions
public record Person(string Name, int Age, string Email);
public record ApiResponse<T>(bool Success, T? Data, string? Error);
public record SearchResult(string Title, string Slug, float Score);

var person = new Person("Scott", 50, "scott@example.com");
var response = new ApiResponse<Person>(true, person, null);
```

### Imprimer avec Dumpify

```csharp
#r "nuget: Dumpify, 0.6.5"

using Dumpify;

var data = new
{
    Name = "Test",
    Items = new[] { 1, 2, 3 },
    Nested = new { Foo = "bar" }
};

data.Dump();  // Pretty console output with colors
```

## Questions courantes & Gotchas

### Édition: M SK1NuGet package not found"

Le premier essai est lente - téléchargement des paquets dans l'arrière-plan

```csharp
#r "nuget: SomePackage, 1.0.0"  // First run: downloads
                                  // Second run: uses cache
```

**Fixer**: Attendez pour que le premier essai soit terminé , ou avant-downloadM SK3

```bash
dotnet script init  # Creates omnisharp.json
dotnet script       # Downloads packages in REPL
```

### Enjeu: M SK1Type ou nom de domaine non trouvé"

La version du paquet peut être incorrecte ou incompatible:

```csharp
// Bad - version doesn't have the type you need
#r "nuget: Microsoft.Extensions.Http, 6.0.0"

// Good - use matching version for your .NET SDK
#r "nuget: Microsoft.Extensions.Http, 9.0.0"
```

### Issue: gRPC sur Windows

Les services Qdrant et autres gRPC échouent avec les erreurs HTTP/2

```csharp
// Add this BEFORE creating gRPC clients
AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true);

var client = new QdrantClient("localhost", 6334);  // Now works
```

### Issue: HttpClient Socket Exhaustion

Don't créer plusieurs instances HttpClient dans un cercle

```csharp
// Bad - creates socket exhaustion
foreach (var url in urls)
{
    using var client = new HttpClient();  // DON'T do this
    await client.GetAsync(url);
}

// Good - reuse HttpClient
using var client = new HttpClient();
foreach (var url in urls)
{
    await client.GetAsync(url);
}
```

### Édition: Async au niveau supérieur

Haut de la page-level async fonctionne seulement dans CSX - pas d'async Main requisM SK2

```csharp
// This works - no async Main needed
var response = await httpClient.GetAsync("https://example.com");
var content = await response.Content.ReadAsStringAsync();
Console.WriteLine(content);
```

### Numéro: Conflicts de chargement d’assemblage

Lors de la référence aux DLL locaux qui ont des dépendances:

```csharp
// Order matters - load dependencies first
#r "Mostlylucid.Shared/bin/Debug/net9.0/Mostlylucid.Shared.dll"
#r "Mostlylucid.Services/bin/Debug/net9.0/Mostlylucid.Services.dll"

// Or use NuGet for dependencies, local for your code
#r "nuget: Microsoft.Extensions.Logging, 9.0.0"
#r "MyLibrary/bin/Debug/net9.0/MyLibrary.dll"
```

### Issue: Script Won't Run After Edit

Le cache IntelliSense peut être bloqué:

```bash
# Clear the cache
rm -rf ~/.dotnet-script/          # Linux/Mac
rd /s /q %USERPROFILE%\.dotnet-script\  # Windows
```

### Issue: Types de référence non négligeables

CSX utilise différents paramètres par défaut - active explicitement si nécessaire

```csharp
#nullable enable

string? nullableString = null;  // OK
string nonNullable = null;      // Warning
```

## Quand utiliser CSX contre le projet complet

**Utiliser CSX lorsque:**

- Essais rapides
- Exploration de l'API
- Algorithmes de prototypage
- Tester les paquets NuGet avant d'ajouter au projet
- Validation de la serialisation regex, LINQ, JSON
- Examen de la demande de base de données
- Apprendre/expérimenter

**Utiliser un projet complet lorsque:**

- Plusieurs fichiers ayant des dépendances complexes
- Essai d'unité (utiliser xUnit/NUnitM SK2
- Code de production
- Collaboration d'équipe
- Pipelines CI/CD

## Real-Exemple mondiale: Essai de mon blog API

Voici' est un script que j'utilise pour tester le point final de recherche Mostlylucid.

```csharp
#r "nuget: System.Net.Http.Json, 8.0.0"

using System.Net.Http.Json;

var baseUrl = Args.Length > 0 ? Args[0] : "https://www.mostlylucid.net";
var searchTerm = Args.Length > 1 ? Args[1] : "docker";

var http = new HttpClient { BaseAddress = new Uri(baseUrl) };

Console.WriteLine($"Searching {baseUrl} for '{searchTerm}'...\n");

var results = await http.GetFromJsonAsync<JsonElement>(
    $"/api/search?term={Uri.EscapeDataString(searchTerm)}");

if (results.TryGetProperty("results", out var items))
{
    foreach (var item in items.EnumerateArray().Take(5))
    {
        var title = item.GetProperty("title").GetString();
        var slug = item.GetProperty("slug").GetString();
        Console.WriteLine($"- {title}");
        Console.WriteLine($"  /{slug}\n");
    }
}
```

Run it:

```bash
dotnet script search-test.csx -- https://localhost:5001 "entity framework"
```

## Résumé

Les scripts CSX sont le milieu parfait entre la C# REPL et un projet complet.

- **Accroître la vitesse**: Rédiger et fonctionner en secondes
- **Simplicitité**: Aucune cérémonie de projet
- **Puissance**: Cplôme C# avec support NuGet
- **Portabilité**: Partagez un fichier unique

La prochaine fois que vous aurez besoin de tester quelque chose rapidement dans C#, reporter `dotnet new console` et s’adresser à `dotnet script` plutôt.

**Ressources:**

- [dotnet-script GitHub](https://github.com/dotnet-script/dotnet-script)
- [Documentation de scriptage C#](https://docs.microsoft.com/en-us/archive/msdn-magazine/2016/january/essential-net-csharp-scripting)