This is a viewer only at the moment see the article on how this works.
To update the preview hit Ctrl-Alt-R (or ⌘-Alt-R on Mac) or Enter to refresh. The Save icon lets you save the markdown file to disk
This is a preview from the server running through my markdig pipeline
使用 CSX 脚本进行快速 C\ @ #测试
Wednesday, 26 November 2025
需要测试 C#}代码的片段而不旋转一个完整的工程@ ? {C#} 脚本文件 @ MS K3} *.csx) 允許您寫入並執行 C# 編碼如腳本語言@. no Program.cs# , 否# .csproj@, @ no building step@-}只要寫作並執行 *.# 完全可以測試 APIs,} 校正邏輯_,} 或原型
因为我要推出完整的Semanti C搜索功能 我以为I'd 分享我如何使用 .csx 在此及其他工程中用于临时测试的文件@. @%
CSX vs @ .NET @ MPK1文件@ MS K2}基于 Apps
@._NET@10}现在已有本地的 "files @MS K6}基于应用程序="#让您运行 .cs 直接与 dotnet run app.cs这跟CSX?有什么比?
.NET {10} 檔案-基于應用程式( 現在已可用MS K4
您可以使用“ . @ net@ MS K1 @ ” 来运行单一的 C-_ file C#}\ 直接 _ :}
# .NET 10 - available now!
dotnet run app.cs
功能@: @%
- SDK 本地端SDK @ -}无需额外工具
- 熟悉使用
.cs扩展扩展号 - 通过 NuGet 引用
#:package指令 - 从第一天起提供全部调试器支持
- 与经常项目相同的汇编器
// app.cs - .NET 10 style
#:package [email protected]
using Newtonsoft.Json;
var obj = new { Name = "Test", Value = 42 };
Console.WriteLine(JsonConvert.SerializeObject(obj));
CSX 脚本@( @AVilable Now @)
CSX通过 dotnet-script 自「2017:」至今,
# Available today
dotnet script app.csx
功能@: @%
- 与“.NET”合作
- 富丰富的生态系统和工具
- 互动勘探的REPL模式
- 验证和战斗@-_测试
// 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));
您应该使用什么@ ? @
| +Field Apps & MSSK8+ | |||
|---|---|---|---|
| 可用程度 @ | @ .#NET @ MPK2 @ @ I | @ MS K4 @NET$10# | # |
| 安装装置 | dotnet tool install -g dotnet-script 建在SDK |
里 | |
| 文件扩展名 | .csx |
.cs |
|
| NuGet 语法 | #r "nuget: Pkg, Ver" |
#:package Pkg@Ver |
|
| REPL 模式 # | 是 @ MS K1尚未# | ||
| IDE 支持 @ | }好的 (#VS 代码=,#Rider)}# | }改善的 | # |
| 调试 | 是 | 是(notive) MSC4 |
我的建议#:
- 先尝试“ .@NET @ MS K1}文件应用程序 @- 本地支持意指较少工具管理费
- 返回到 CSX 如果您需要 REPL 模式或使用旧的 @.NET 版本
- 这些概念几乎完全相同,在这两个概念之间很容易进行知识转让。
本篇文章的其余部分包括CSX,它仍然运作良好,并具有一些特点: “(”类似于 REPLQ)}“.NET 10File Appsds doñZ'}尚未达到.}
CSX?是什么东西?
CSX (C#ScriptMS K2文件是可直接执行的代码文件, 而无需编译为项目@.Python}"PYthonMSKOK6Stystele"C{#Q-您写入了编码,你运行它=, 你可以看到结果 @.
// hello.csx
Console.WriteLine("Hello from C# Script!");
运行它:
dotnet script hello.csx
不对,不对 Main() 方法@,}没有命名空间 @,}不需要类包装 *.}
正在安装 dotnet@ - static
运行 CSX 文件最常用的方法就是通过 点网@ - @ scrip:
dotnet tool install -g dotnet-script
校验安裝@: @%
dotnet script --version
CSX 适合测试周期的地方
在潜入“"”之前,请让“MS K1”来理解 “" ” 当“".” CSX脚本在测试金字塔中占据一个独特的位置时
┌─────────────────┐
│ 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) │
─┴─────────────────────────┴─
CSX 脚本作为 @ " @ mini 集成测试@ MS K1
CSX 脚本是 @'}% 1 取代正式测试@ -}它们='re a 补充@. 把它们当做#:
- Pre- 实施激增 @-_View a API 在围绕它建立服务前工作
- 调试辅助工具 @- 孤立和复制问题 不重建您的整个应用程序
- 探索试验 @- @ 理解在写作单位测试前图书馆的行为方式@ info/ plain
- 烟雾测试 @-_快速理智检查真实服务@(=,}APIS},}队列)}
发展工作流量
CSX如何融入典型特征开发周期 :
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
建立乌马米融合
当我为这个博客建立 Umami 分析集成时,
- CSX: 测试生API @ -_ 验证工作@ MS K1} 什么反应看起来像 @ MPK2
- CSX: 测试时间戳转换 {-}在写入任何生产代码@!}% 1}之前在这里发现了一个错误
- 建立 UmamiClient @-# 充满信心, 因为 I'_#d 已验证 API
- X Unit:写单位测试 - mock HttpClient,测试序列逻辑
- CSX:调试生产问题 - 返回空的度量@? 用于孤立问题的脚本
CSX 脚本替换了我的单位测试@- @它们 阻止我写代码 并助我, 更快调试问题 当它们发生时 .
为什么使用 CSX 测试@? @%
1. 零盛宴
测试API的传统做法
- 创建新控制台工程
- 添加 NuGet 软件包
- 写入
Program.cs - 构建构建
- 运行运行中
- 完成后删除项目
CSX 方法@: @%
- 写入脚本
- 运行脚本
2. 内嵌 Nuget 引用
需要套件@ ? @% 1 在您的脚本中直接引用\ :}
#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));
首运行的下载软件包@. 以后使用缓存@ .
3.参考本地 DLLS
测试您自己的库@? @ 直接引用@ :}
#r "bin/Debug/net9.0/MyLibrary.dll"
using MyLibrary;
var result = MyClass.DoSomething();
Console.WriteLine(result);
4. 参考其它脚本
将复杂脚本拆分成可再使用部件@: @%
#load "helpers.csx"
#load "config.csx"
// Use functions/classes from loaded scripts
var config = LoadConfig();
var result = ProcessData(config);
此工程的真实实例
我用来调试和测试这个博客“'”代码库=. 每一个都解决了我在开发过程中遇到的一个真正的问题@.
API 测试时戳
问题: 我的Umami 分析集成正在返回空数据@. @% 经过几小时调试后, 我怀疑时间戳转换是错误的 @ - @ @ @ umami API 期待 Unix 时标在毫秒= @ MPK3}(但我不确定我的 &.NET 代码是否生成了正确的格式) @. @
为什么CSX? 我本可以在生产代码中加上伐木记录 重建了, 已部署 , 并检查logs." 或者我可以写一个快速脚本来验证我的假设 在“30”秒
#!/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.
结果:* 此脚本验证了时间戳是 @ 1_ 未来年份@ MS K1_ I track the bug back to use DateTime.Now 代替 DateTime.UtcNow 在生产代码中.固定在 5\ 分钟,而不是可能的"MS K2"小时调试@.}
测试查询字符串生成
问题: 我需要核实ASP.NETIS1 QueryHelpers 类以 Umami API 的准确格式生成查询字符串@. @ does it URL\ -_encode 特殊字符@MSSK2}{什么顺序是%?}中的参数
为什么CSX? 读取文档是一回事 @,,但看到实际输出会准确显示您的代码将生成什么@.
#!/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
RW HTTP API电话测试
问题: 在建立有依赖性注射的完整服务级前,我想验证API的实际效果并理解其响应格式@.}{回答错误处理,}重试逻辑,}和单位测试_,}我还想核实API是否有效 并且了解它的反应格式>MS K4}*
为什么CSX? 如果 API能用我预期的#,-I'+浪费了 @5分钟而不是@5Qhours_.+#
#!/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
使用依赖性注射测试
问题: I'@ve 发表一个 Nuget 软件包 @ ( @ umami @ I. @ Net) @ 并想要测试它是否精确, 因为消费者会使用它 @ MPK4+和适当的依赖性注射设置@ MS K5+不是通过即时课程直接拨打=.+#
为什么CSX? 正在创建测试控制台工程@, @ 添加我的 NuGet reference@ ,} 写入所有 DI 锅炉牌 * @ MS K2} '\ {15+}\ 仪式的分钟=.}CSX,}我可以校验在 < MSKO7\ minutes\ MPK8> 下的消费经验
#!/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
测试 Qdrant 矢量数据库
问题: I'm 整合了用于语义搜索的 Qdrant 矢量数据库@. 在写入 producing service\ ,我需要理解 gRPC 用戶端是如何工作的 @,}#API 长什么样 @MS K4, 并校验我的本地 Qdrant 实例正在正确运行 #.}
为什么CSX? 矢量数据库是许多开发者的新区域 @. CSX 让我尝试交互性#,}尝试不同的操作并看到直接结果,然后承诺建立架构@.
#!/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
更实用实例
HTTP 端点测试
#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")}");
测试 JSON 序列化
#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, "[email protected]");
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}");
测试数据库查询
#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}");
}
测试 Regex 模式
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",
"[email protected]",
"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}");
}
}
LINQ 测试查询
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}");
}
测试 Qdrant 矢量搜索
#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}");
}
IDE 支持
视觉工作室代码
安装 C#Dev包 扩展@. 你得到 @:
- 语法突出
- Intelli 感知
- 通过代码日志运行\ / debug
创建创建 .vscode/launch.json:
{
"version": "0.2.0",
"configurations": [
{
"name": "Run CSX",
"type": "coreclr",
"request": "launch",
"program": "dotnet",
"args": ["script", "${file}"],
"cwd": "${workspaceFolder}"
}
]
}
喷气车列车骑士
Rider 在 CSX 支持下创建了 @ - @ in CS X 支持@ MS K1} 右键 @ MPK2单击任何 .csx 并选择文件@"Run".}
提示和诡计
使用 Shebang
添加一个 shebang 使脚本可以在 Linux\ /\ mac:\ 上直接执行
#!/usr/bin/env dotnet-script
Console.WriteLine("Runs directly with ./script.csx");
默认参数
通过 Global 访问命令@-}线性参数 Args 變數@: @%
// 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}");
秘密的环境变量
使用环境变量@:
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..]}");
交互式模式 @(_REPL})
启动互动会话以探索@: @%
dotnet script
你得到一个C# REPLMSSK1
> var x = 42;
> x * 2
84
> #r "nuget: Newtonsoft.Json, 13.0.3"
> using Newtonsoft.Json;
> JsonConvert.SerializeObject(new { foo = "bar" })
"{"foo":"bar"}"
调试
用 VS 代码调试, 添加一个中断点并用 F5, @ 或 :} 运行
dotnet script test.csx --debug
使用快速 DTO 记录
没有需要的类文件@ - 定义内嵌 @ MS K1\ {
// 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, "[email protected]");
var response = new ApiResponse<Person>(true, person, null);
漂亮打印与压缩
#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
共同问题
找不到您的套件@ " @ label
第一运行是慢速的 @ - package 在背景中下载@ MS K1
#r "nuget: SomePackage, 1.0.0" // First run: downloads
// Second run: uses cache
修整@ :_ 等待完成首跑@ MS K1} 或 pre -_ download @ MPK3}
dotnet script init # Creates omnisharp.json
dotnet script # Downloads packages in REPL
找不到 " 或命名空间@ MSK2 @
软件包版本可能错误或不匹配@: @%
// 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"
在 Windows 上发行@: @ gRPC
Qdrant 和其他 GRPC 服务因 HTTP/2}错误@: 失败
// Add this BEFORE creating gRPC clients
AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true);
var client = new QdrantClient("localhost", 6334); // Now works
问题@: HttpClient Socket 解析
Donñ't 在循环中创建多个 HttpClient 实例@ :}
// 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);
}
最高层的自动同步
顶端的-级的音频在 CSX 工作 @ - none async main 需要@ MS K2
// This works - no async Main needed
var response = await httpClient.GetAsync("https://example.com");
var content = await response.Content.ReadAsStringAsync();
Console.WriteLine(content);
Q: 组装加载冲突
当查找有依赖关系的本地 DLL 时@: @%
// 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"
Q:脚本元@'}在编辑后运行
IntelliSensense 缓存可以获取 stale:
# Clear the cache
rm -rf ~/.dotnet-script/ # Linux/Mac
rd /s /q %USERPROFILE%\.dotnet-script\ # Windows
无效参考类型
CSX 使用不同的默认值@ - @ 如果需要, 明确启用@ MPK1} *
#nullable enable
string? nullableString = null; // OK
string nonNullable = null; // Warning
何时使用 CSX 对完整工程
当 : CSX 时使用 CS X
- 快速测试@ - @ off 测试
- APP 勘探
- 原型算法
- 在添加到工程前测试 NuGet 软件包
- 校验 regexM, LIN,JSON序列化
- 数据库查询测试
- 学习/ 实验
使用完整工程时@ : @
- 复杂依赖的多个文件
- 单位测试 * @(_use x United/}N Unitime{)}%
- 生产守则
- 团队协作
- CD管道
测试我的博客API
这里% ' @ a 用于测试 Mostlylucid 搜索端点的脚本@ MS K1}
#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");
}
}
运行它:
dotnet script search-test.csx -- https://localhost:5001 "entity framework"
摘要摘要
CSX 脚本是C#}REPL与一个完整的计划之间完美的中间地带 它们是:的理想
- 速度速度: 写入并运行在秒内
- 简单化: 没有工程仪式
- 电力由 NuGet 支持的“ :” 完整 C#
- 便捷性: 共享一个单一文件
下次你要在 C#, 跳过时快速测试某个东西 dotnet new console 所达到的 dotnet script 取代.
资源@: