Development Guide
Guide for building, testing, and running the Virtufin API locally.
Table of Contents
- Prerequisites
- Project Structure
- Building
- Running Locally
- Testing
- Debugging
- Generating Protobuf Files
Prerequisites
Required Software
| Software | Version | Purpose |
|---|---|---|
| .NET SDK | 10.0+ | Runtime and build |
| Dapr CLI | 1.18+ | Local development sidecar |
| Git | Latest | Version control |
Install .NET SDK
# macOS
brew install dotnet
# Linux (Ubuntu)
wget https://packages.microsoft.com/config/ubuntu/22.04/packages-microsoft-prod.deb
sudo dpkg -i packages-microsoft-prod.deb
sudo apt update
sudo apt install dotnet-sdk-10.0
# Windows
winget install Microsoft.DotNet.SDK.10
Install Dapr CLI
# macOS
brew install dapr/tap/dapr-cli
# Linux
wget -q https://raw.githubusercontent.com/dapr/cli/master/install/install.sh -O - | /bin/bash
# Windows
winget install Dapr.CLI
Project Structure
virtufin-api/
├── src/
│ ├── Virtufin.Api/ # Main API project
│ │ ├── Configuration/ # Configuration models
│ │ │ ├── Models.cs
│ │ │ ├── PortConstants.cs
│ │ │ └── ServicesConfigurationLoader.cs
│ │ ├── HealthChecks/ # Health check implementations
│ │ ├── Protos/ # Protocol buffer definitions
│ │ │ ├── buf.yaml
│ │ │ ├── gateway.proto
│ │ │ ├── config.proto
│ │ │ ├── state.proto
│ │ │ ├── pubsub.proto
│ │ │ ├── google/api/ # Google API protos
│ │ │ └── dapr/ # Dapr proto definitions
│ │ ├── Services/ # Business logic
│ │ │ ├── ConfigService.cs
│ │ │ ├── Constants.cs
│ │ │ ├── DaprPubSubBridgeService.cs
│ │ │ ├── GatewayService.cs
│ │ │ ├── GrpcChannelPool.cs
│ │ │ ├── GrpcReflectionService.cs
│ │ │ ├── PubSubService.cs
│ │ │ ├── StateService.cs
│ │ │ └── SubscriptionManagerBase.cs
│ │ ├── Constants.cs
│ │ ├── GrpcModels.cs
│ │ ├── Program.cs
│ │ ├── Version.props
│ │ └── Virtufin.Api.csproj
│ ├── Virtufin.Api.Client/ # C# client library
│ ├── python/ # Python client
│ └── typescript/ # TypeScript client
├── tests/
│ └── Virtufin.Api.Tests/ # Test project
├── docs/ # Documentation
├── scripts/ # Utility scripts
├── examples/ # Example code
└── versions.env # Version configuration
Building
Build Commands
# Restore dependencies
dotnet restore
# Build the solution
dotnet build
# Build a specific project
dotnet build src/Virtufin.Api/Virtufin.Api.csproj
# Build in Release mode
dotnet build --configuration Release
# Build without restoring
dotnet build --no-restore
Build Output
After building, the API binaries are located at:
src/Virtufin.Api/bin/Debug/net10.0/
Running Locally
1. Start Dapr
# Initialize Dapr (first time only)
dapr init
# Verify Dapr is running
dapr status
2. Configure Services
Edit src/Virtufin.Api/config/services.json:
{
"services": [
{
"name": "workmanager",
"grpc": {
"host": "localhost",
"port": 25002
},
"pubsub": {
"pubsubName": "pubsub"
},
"state": {
"storeName": "statestore"
},
"jobs": {
"crons": []
}
}
// ... additional services
]
}
3. Start the API
cd src/Virtufin.Api
dotnet run
4. Verify Endpoints
# Health check
curl http://localhost:5001/health
# Swagger UI
open http://localhost:5001/swagger
# List services
curl http://localhost:5001/v1/gateway/list-services
# List methods
curl "http://localhost:5001/v1/gateway/list-methods?service=workmanager"
Custom Ports
dotnet run -- --http-port 5001 --grpc-port 5002
With Dapr
# Run with Dapr sidecar
dapr run --app-id virtufin-api --app-protocol grpc --app-port 5002 -- dotnet run
# Dapr uses gRPC app-protocol via app-port 5002 for all communication
# Or use the script
./scripts/start-dapr.sh
Testing
Run All Tests
dotnet test
Run Tests with Coverage
dotnet test --collect:"XPlat Code Coverage"
Run Specific Tests
# By project
dotnet test tests/Virtufin.Api.Tests/
# By filter
dotnet test --filter "FullyQualifiedName~GrpcIntegrationTests"
Test Categories
| Test File | Description |
|---|---|
GrpcIntegrationTests.cs |
gRPC integration tests |
GrpcStreamingTests.cs |
Streaming functionality tests |
ServicesTests.cs |
Service layer tests |
ConfigServiceTests.cs |
Configuration tests |
Writing Tests
// Example test
[Fact]
public async Task ListServices_ReturnsConfiguredServices()
{
// Arrange
using var channel = GrpcChannel.ForAddress("http://localhost:5002");
var client = new Gateway.GatewayClient(channel);
// Act
var response = await client.ListServicesAsync(new ListServicesRequest());
// Assert
Assert.NotNull(response.Services);
Assert.Contains("workmanager", response.Services);
}
Debugging
Visual Studio Code
Create .vscode/launch.json:
{
"version": "0.2.0",
"configurations": [
{
"name": ".NET Core Launch (web)",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
"program": "${workspaceFolder}/src/Virtufin.Api/bin/Debug/net10.0/Virtufin.Api.dll",
"args": [],
"cwd": "${workspaceFolder}/src/Virtufin.Api",
"env": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
]
}
JetBrains Rider
Rider automatically detects .NET projects. Use the run configuration dropdown to start the API.
CLI Debugging
# Enable verbose logging
dotnet run --verbosity detailed
# With launch profile
dotnet run --launch-profile "Development"
gRPC Client Debugging
Use grpc_cli for testing:
# List services
grpc_cli ls localhost:5002
# List methods for a service
grpc_cli ls localhost:5002 virtufin.Gateway
# Invoke a method
grpc_cli call localhost:5002 virtufin.Gateway.ListServices ""
Generating Protobuf Files
Proto Generation
The project uses MSBuild protobuf compilation. During build:
.protofiles inProtos/are processed- C# classes are generated in
obj/folder - Files are linked to
Virtufin.Api.Protosnamespace
Manual Generation
If needed, regenerate manually:
# Install protobuf compiler
brew install protobuf
# Generate C# from proto
protoc --csharp_out=. --grpc_out=. \
--plugin=protoc-gen-grpc=$(which grpc_csharp_plugin) \
Protos/gateway.proto
# Or use the dotnet tool
dotnet tool install --global protoc-gen-grpccsharp
Proto Structure
// Protos/gateway.proto
syntax = "proto3";
option csharp_namespace = "Virtufin.Api.Protos";
package virtufin;
import "Protos/google/api/annotations.proto";
service Gateway {
rpc Invoke (InvokeRequest) returns (InvokeResponse);
// ...
}
Generated files:
- Protos/Gateway.cs - Message classes
- Protos/GatewayGrpc.cs - gRPC client/service stubs
Development Workflow
1. Create a Feature Branch
git checkout -b feature/my-feature
2. Make Changes
Edit source files in src/Virtufin.Api/.
3. Run Tests
dotnet test
4. Commit
git add .
git commit -m "Add my feature"
5. Push and Create PR
git push origin feature/my-feature
Troubleshooting
Port Already in Use
Unable to bind to http://localhost:5001
Find and kill the process:
# macOS/Linux
lsof -i :5001
lsof -i :5002
kill -9 <PID>
# Windows
netstat -ano | findstr :5001
netstat -ano | findstr :5002
taskkill /PID <PID> /F
Dapr Not Responding
# Restart Dapr
dapr init
dapr status
# Check Dapr logs
docker logs dapr_placement
Proto Compilation Errors
# Clean and rebuild
dotnet clean
rm -rf src/Virtufin.Api/obj
dotnet build
gRPC Reflection Not Working
Ensure the backend service is reachable:
services:
- name: myservice
host: myservice-host
port: 5002
protocol: grpc
Client Connection Refused
- Verify API is running:
curl http://localhost:5001/health - HTTP (REST, Swagger, gRPC-Web) runs on port 5001; native gRPC runs on port 5002
- In Docker/containers, defaults are also 5001 (HTTP) and 5002 (gRPC)
Code Style
C# Conventions
- Use file-scoped namespaces
- Prefer
async/awaitover blocking calls - Use nullable reference types
- Follow .NET naming conventions
Example
namespace Virtufin.Api.Services;
public class MyService
{
private readonly ILogger<MyService> _logger;
public MyService(ILogger<MyService> logger)
{
_logger = logger;
}
public async Task<Result> ProcessAsync(Request request, CancellationToken ct)
{
_logger.LogInformation("Processing request: {Id}", request.Id);
try
{
var result = await DoWorkAsync(request, ct);
return Result.Success(result);
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to process request: {Id}", request.Id);
return Result.Failure(ex.Message);
}
}
}