Skip to content

Development Guide

Guide for building, testing, and running the Virtufin API locally.

Table of Contents


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:

  1. .proto files in Protos/ are processed
  2. C# classes are generated in obj/ folder
  3. Files are linked to Virtufin.Api.Protos namespace

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

  1. Verify API is running: curl http://localhost:5001/health
  2. HTTP (REST, Swagger, gRPC-Web) runs on port 5001; native gRPC runs on port 5002
  3. In Docker/containers, defaults are also 5001 (HTTP) and 5002 (gRPC)

Code Style

C# Conventions

  • Use file-scoped namespaces
  • Prefer async/await over 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);
        }
    }
}