DICOM Router - SDK Documentation

SDK Documentation

Overview

The DICOM Proxy SDK provides comprehensive libraries and tools for integrating with the DICOM Proxy/Router/PACS system. This document covers the core SDK components, integration patterns, and development guidelines.

SDK Architecture

Core Components

DicomProxyCore SDK
├── DicomProxyCore.dll              (Main library)
├── DicomProxyCore.Models.dll       (Data models)
├── DicomProxyCore.Services.dll     (Service interfaces)
├── DicomProxyCore.Dicom.dll        (DICOM utilities)
├── DicomProxyCore.Commands.dll     (Command processors)
└── DicomProxyCore.Imaging.SkiaSharp.dll (Image processing)

Dependency Stack

  • .NET 9.0 - Target framework
  • FellowOak DICOM 5.2.2 - DICOM implementation
  • SkiaSharp - Image processing
  • SQLite - Local database
  • NLog - Logging framework
  • ASP.NET Core - Web framework
  • Jint - JavaScript engine
  • LazyCache - Caching framework

Installation

NuGet Packages

<PackageReference Include="DicomProxyCore" Version="1.09.1" />
<PackageReference Include="DicomProxyCore.Models" Version="1.09.1" />
<PackageReference Include="DicomProxyCore.Services" Version="1.09.1" />

Manual Installation

# Copy SDK libraries to your project
cp DicomProxyCore*.dll YourProject/libs/
# Add references in your project file

Core SDK Classes

ConfigurationBase

Central configuration management class.

using DicomProxyCore;

// Initialize configuration
Configuration.Setup("C:\\MyApp", false);
var config = Configuration.Instance;

// Access configuration properties
string storagePath = config.StoragePath;
int dicomPort = config.DicomPort;
string aeTitle = config.ThisModality.AE_Title;

// Update configuration
config.DicomPort = 11112;
config.Save();

DicomProxyService

Main service orchestrator for DICOM proxy functionality.

using DicomProxyCore;

public class MyDicomService
{
    private DicomProxyService _proxyService;

    public void Initialize()
    {
        // Set base path and service mode
        _proxyService = new DicomProxyService("C:\\MyApp", false);
        
        // Start the service
        _proxyService.Start();
    }

    public void Shutdown()
    {
        _proxyService?.Stop();
    }
}

CStoreSCP

DICOM Service Class Provider for handling incoming connections.

using DicomProxyCore.CStoreSCP;
using FellowOakDicom.Network;

public class CustomCStoreSCP : CStoreSCP
{
    public override DicomCStoreResponse OnCStoreRequest(DicomCStoreRequest request)
    {
        // Custom C-STORE handling
        var dataset = request.Dataset;
        var studyUID = dataset.GetSingleValue<string>(DicomTag.StudyInstanceUID);
        
        // Process the DICOM data
        ProcessDicomData(dataset);
        
        // Return success response
        return new DicomCStoreResponse(request, DicomStatus.Success);
    }

    private void ProcessDicomData(DicomDataset dataset)
    {
        // Custom processing logic
        var patientName = dataset.GetSingleValueOrDefault(DicomTag.PatientName, "Unknown");
        var modality = dataset.GetSingleValueOrDefault(DicomTag.Modality, "");
        
        Console.WriteLine($"Received: {patientName} - {modality}");
    }
}

Connection Management

using DicomProxyCore.CStoreSCP.ConnectionManager;
using DicomProxyCore.Models;

public class ConnectionService
{
    private readonly FreqUsedPcs _connectionManager;

    public ConnectionService()
    {
        _connectionManager = new FreqUsedPcs();
    }

    public async Task<bool> TestConnectionAsync(Connection server)
    {
        try
        {
            var client = _connectionManager.GetClient(server);
            await client.AddRequestAsync(new DicomCEchoRequest());
            await client.SendAsync();
            return true;
        }
        catch
        {
            return false;
        }
    }
}

Data Models

Connection Model

using DicomProxyCore.Models;

var server = new Connection
{
    Id = "pacs1",
    Name = "Main PACS Server",
    AE_Title = "MAINPACS",
    Hostname = "192.168.1.100",
    Port = 11112,
    Enabled = true,
    MaxConnections = 10,
    TimeoutSeconds = 30,
    Priority = 1
};

// Add to configuration
Configuration.Instance.Servers.Add(server);

DICOM Query Models

using DicomProxyCore.Models;

// Study-level query
var studyQuery = new DicomQuery
{
    Level = QueryLevel.Study,
    StudyInstanceUID = "1.2.3.4.5.6.7.8.9",
    PatientName = "DOE^JOHN",
    StudyDate = "20240315",
    Modality = "CT"
};

// Series-level query
var seriesQuery = new DicomQuery
{
    Level = QueryLevel.Series,
    StudyInstanceUID = "1.2.3.4.5.6.7.8.9",
    SeriesInstanceUID = "1.2.3.4.5.6.7.8.9.10",
    Modality = "CT"
};

Service Interfaces

IStorageManager

using DicomProxyCore.Services;

public class CustomStorageManager : IStorageManager
{
    public string GetStudyPath(string studyInstanceUID)
    {
        return Path.Combine(Configuration.Instance.StoragePath, "studies", studyInstanceUID);
    }

    public string GetSeriesPath(string studyInstanceUID, string seriesInstanceUID)
    {
        return Path.Combine(GetStudyPath(studyInstanceUID), seriesInstanceUID);
    }

    public string GetInstancePath(string studyInstanceUID, string seriesInstanceUID, string sopInstanceUID)
    {
        return Path.Combine(GetSeriesPath(studyInstanceUID, seriesInstanceUID), $"{sopInstanceUID}.dcm");
    }

    public async Task<bool> StoreInstanceAsync(DicomDataset dataset)
    {
        var studyUID = dataset.GetSingleValue<string>(DicomTag.StudyInstanceUID);
        var seriesUID = dataset.GetSingleValue<string>(DicomTag.SeriesInstanceUID);
        var instanceUID = dataset.GetSingleValue<string>(DicomTag.SOPInstanceUID);

        var filePath = GetInstancePath(studyUID, seriesUID, instanceUID);
        Directory.CreateDirectory(Path.GetDirectoryName(filePath));

        var file = new DicomFile(dataset);
        await file.SaveAsync(filePath);
        
        return true;
    }
}

IMessaging

using DicomProxyCore.Services;

public class CustomMessaging : IMessaging
{
    public event EventHandler<MessageEventArgs> MessageReceived;

    public void SendMessage(string message, MessageType type = MessageType.Info)
    {
        var args = new MessageEventArgs
        {
            Message = message,
            Type = type,
            Timestamp = DateTime.Now
        };

        MessageReceived?.Invoke(this, args);
    }

    public void Start()
    {
        // Initialize messaging system
        Console.WriteLine("Messaging system started");
    }

    public void Stop()
    {
        // Cleanup messaging system
        Console.WriteLine("Messaging system stopped");
    }
}

ILicenseManager

using DicomProxyCore.Services;

public class CustomLicenseManager : ILicenseManager
{
    public LicenseStatus GetLicenseStatus()
    {
        return new LicenseStatus
        {
            IsValid = true,
            ExpirationDate = DateTime.Now.AddYears(1),
            LicenseType = "Commercial",
            Features = new[] { "DICOM", "REST_API", "PROXY" }
        };
    }

    public bool ValidateLicense(string licenseKey)
    {
        // Custom license validation logic
        return !string.IsNullOrEmpty(licenseKey) && licenseKey.Length > 10;
    }
}

Dependency Injection

Service Registration

using DicomProxyCore.Services;
using Microsoft.Extensions.DependencyInjection;

public void ConfigureServices(IServiceCollection services)
{
    // Register core services
    services.AddSingleton<IStorageManager, CustomStorageManager>();
    services.AddSingleton<IMessaging, CustomMessaging>();
    services.AddSingleton<ILicenseManager, CustomLicenseManager>();
    services.AddSingleton<ICStoreConnectionsManager, CStoreConnectionsManager>();

    // Register configuration
    services.AddSingleton(Configuration.Instance);

    // Register logging
    services.AddLogging(builder =>
    {
        builder.AddNLog("nlog.config");
    });
}

Service Resolution

using DicomProxyCore.Services;

// Using dependency service (existing pattern)
var messaging = DependencyService.Get<IMessaging>();
var storage = DependencyService.Get<IStorageManager>();

// Using standard DI container
var services = new ServiceCollection();
ConfigureServices(services);
var serviceProvider = services.BuildServiceProvider();

var messaging2 = serviceProvider.GetRequiredService<IMessaging>();

DICOM Utilities

DICOM Processing

using DicomProxyCore.Dicom;
using FellowOakDicom;

public class DicomProcessor
{
    public async Task ProcessDicomFile(string filePath)
    {
        // Load DICOM file
        var dicomFile = await DicomFile.OpenAsync(filePath);
        var dataset = dicomFile.Dataset;

        // Extract metadata
        var studyUID = dataset.GetSingleValue<string>(DicomTag.StudyInstanceUID);
        var patientName = dataset.GetSingleValueOrDefault(DicomTag.PatientName, "Unknown");
        var modality = dataset.GetSingleValueOrDefault(DicomTag.Modality, "");

        // Convert to JSON
        var jsonConverter = new DicomJson2();
        var jsonString = jsonConverter.WriteToString(dataset);

        // Process image data
        if (dataset.Contains(DicomTag.PixelData))
        {
            await ProcessImageData(dataset);
        }
    }

    private async Task ProcessImageData(DicomDataset dataset)
    {
        // Extract image
        var image = new DicomImage(dataset);
        
        // Get image properties
        var width = dataset.GetSingleValue<ushort>(DicomTag.Columns);
        var height = dataset.GetSingleValue<ushort>(DicomTag.Rows);
        var bitsAllocated = dataset.GetSingleValue<ushort>(DicomTag.BitsAllocated);

        Console.WriteLine($"Image: {width}x{height}, {bitsAllocated} bits");

        // Render to bitmap (requires DicomProxyCore.Imaging.SkiaSharp)
        using var bitmap = image.RenderImage().AsClonedBitmap();
        
        // Save as JPEG
        await bitmap.SaveAsync("output.jpg");
    }
}

Query Builder

using DicomProxyCore.Helper;
using FellowOakDicom;

public class QueryBuilder
{
    public DicomDataset BuildStudyQuery(string patientName = null, string studyDate = null, string modality = null)
    {
        var query = new DicomDataset
        {
            { DicomTag.QueryRetrieveLevel, "STUDY" }
        };

        // Add search criteria
        if (!string.IsNullOrEmpty(patientName))
            query.AddOrUpdate(DicomTag.PatientName, patientName);

        if (!string.IsNullOrEmpty(studyDate))
            query.AddOrUpdate(DicomTag.StudyDate, studyDate);

        if (!string.IsNullOrEmpty(modality))
            query.AddOrUpdate(DicomTag.ModalitiesInStudy, modality);

        // Add return attributes
        query.AddOrUpdate(DicomTag.StudyInstanceUID, "");
        query.AddOrUpdate(DicomTag.StudyDescription, "");
        query.AddOrUpdate(DicomTag.StudyDate, "");
        query.AddOrUpdate(DicomTag.StudyTime, "");
        query.AddOrUpdate(DicomTag.AccessionNumber, "");
        query.AddOrUpdate(DicomTag.PatientID, "");

        return query;
    }
}

Command Processing

Custom Commands

using DicomProxyCore.Commands;

public class ImportFolderCommand : ICommand
{
    private readonly string _folderPath;
    private readonly IStorageManager _storageManager;

    public ImportFolderCommand(string folderPath, IStorageManager storageManager)
    {
        _folderPath = folderPath;
        _storageManager = storageManager;
    }

    public async Task ExecuteAsync(CancellationToken cancellationToken = default)
    {
        var dicomFiles = Directory.GetFiles(_folderPath, "*.dcm", SearchOption.AllDirectories);
        
        foreach (var filePath in dicomFiles)
        {
            if (cancellationToken.IsCancellationRequested)
                break;

            try
            {
                var dicomFile = await DicomFile.OpenAsync(filePath);
                await _storageManager.StoreInstanceAsync(dicomFile.Dataset);
                Console.WriteLine($"Imported: {filePath}");
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Failed to import {filePath}: {ex.Message}");
            }
        }
    }
}

// Usage
var command = new ImportFolderCommand("C:\\DICOM\\Import", storageManager);
await command.ExecuteAsync();

Background Services

Custom Background Service

using DicomProxyCore.Services.HostedServices;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

public class CustomBackgroundService : BackgroundService
{
    private readonly ILogger<CustomBackgroundService> _logger;
    private readonly IStorageManager _storageManager;

    public CustomBackgroundService(
        ILogger<CustomBackgroundService> logger,
        IStorageManager storageManager)
    {
        _logger = logger;
        _storageManager = storageManager;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            try
            {
                // Perform background task
                await PerformMaintenanceAsync();
                
                // Wait for next cycle (1 hour)
                await Task.Delay(TimeSpan.FromHours(1), stoppingToken);
            }
            catch (OperationCanceledException)
            {
                break;
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error in background service");
                await Task.Delay(TimeSpan.FromMinutes(5), stoppingToken);
            }
        }
    }

    private async Task PerformMaintenanceAsync()
    {
        _logger.LogInformation("Performing maintenance tasks");
        
        // Cleanup old files
        await CleanupOldFilesAsync();
        
        // Update statistics
        await UpdateStatisticsAsync();
    }

    private async Task CleanupOldFilesAsync()
    {
        // Implementation for cleaning up old files
        _logger.LogInformation("Cleanup completed");
    }

    private async Task UpdateStatisticsAsync()
    {
        // Implementation for updating statistics
        _logger.LogInformation("Statistics updated");
    }
}

Image Processing

SkiaSharp Integration

using DicomProxyCore.Imaging.SkiaSharp;
using FellowOakDicom.Imaging;
using SkiaSharp;

public class ImageProcessor
{
    public async Task<byte[]> ConvertToJpegAsync(DicomDataset dataset, int quality = 90)
    {
        using var dicomImage = new DicomImage(dataset);
        using var bitmap = dicomImage.RenderImage().AsClonedBitmap();
        
        using var stream = new MemoryStream();
        using var skBitmap = SKBitmap.FromImage(bitmap);
        using var skData = skBitmap.Encode(SKEncodedImageFormat.Jpeg, quality);
        
        skData.SaveTo(stream);
        return stream.ToArray();
    }

    public async Task<byte[]> ApplyWindowingAsync(DicomDataset dataset, double center, double width)
    {
        using var dicomImage = new DicomImage(dataset);
        
        // Apply windowing
        var windowedImage = dicomImage.RenderImage(center, width);
        using var bitmap = windowedImage.AsClonedBitmap();
        
        using var stream = new MemoryStream();
        using var skBitmap = SKBitmap.FromImage(bitmap);
        using var skData = skBitmap.Encode(SKEncodedImageFormat.Png, 100);
        
        skData.SaveTo(stream);
        return stream.ToArray();
    }

    public async Task<byte[]> ResizeImageAsync(DicomDataset dataset, int width, int height)
    {
        using var dicomImage = new DicomImage(dataset);
        using var originalBitmap = dicomImage.RenderImage().AsClonedBitmap();
        
        using var skBitmap = SKBitmap.FromImage(originalBitmap);
        using var resizedBitmap = skBitmap.Resize(new SKImageInfo(width, height), SKFilterQuality.High);
        using var resizedImage = SKImage.FromBitmap(resizedBitmap);
        using var data = resizedImage.Encode(SKEncodedImageFormat.Jpeg, 90);
        
        using var stream = new MemoryStream();
        data.SaveTo(stream);
        return stream.ToArray();
    }
}

JavaScript Scripting

Script Engine Integration

using DicomProxyCore.Scripting;
using Jint;

public class ScriptingService
{
    private readonly JSScriptingService _scriptingService;

    public ScriptingService()
    {
        _scriptingService = new JSScriptingService();
    }

    public async Task<object> ExecuteScriptAsync(string script, DicomDataset dataset)
    {
        var engine = new Engine();
        
        // Add DICOM dataset to script context
        engine.SetValue("dataset", dataset);
        engine.SetValue("console", new { log = new Action<object>(Console.WriteLine) });

        // Add helper functions
        engine.SetValue("getTag", new Func<string, string>(tagName =>
        {
            var tag = DicomTag.Parse(tagName);
            return dataset.GetSingleValueOrDefault(tag, "");
        }));

        // Execute script
        var result = engine.Evaluate(script);
        return result.ToObject();
    }

    public async Task<bool> ValidateDataAsync(DicomDataset dataset)
    {
        var script = @"
            var patientName = getTag('00100010');
            var studyUID = getTag('0020000D');
            var modality = getTag('00080060');
            
            if (!patientName || patientName.trim() === '') {
                console.log('Missing patient name');
                return false;
            }
            
            if (!studyUID || studyUID.trim() === '') {
                console.log('Missing study instance UID');
                return false;
            }
            
            return true;
        ";

        var result = await ExecuteScriptAsync(script, dataset);
        return Convert.ToBoolean(result);
    }
}

Database Operations

SQLite Integration

using DicomProxyCore.Database;
using SQLite;

public class DatabaseService
{
    private readonly SQLiteAsyncConnection _database;

    public DatabaseService(string dbPath)
    {
        _database = new SQLiteAsyncConnection(dbPath);
        InitializeDatabaseAsync().Wait();
    }

    private async Task InitializeDatabaseAsync()
    {
        await _database.CreateTableAsync<StudyRecord>();
        await _database.CreateTableAsync<SeriesRecord>();
        await _database.CreateTableAsync<InstanceRecord>();
    }

    public async Task<int> InsertStudyAsync(StudyRecord study)
    {
        return await _database.InsertAsync(study);
    }

    public async Task<List<StudyRecord>> GetStudiesAsync(string patientName = null, string studyDate = null)
    {
        var query = _database.Table<StudyRecord>();

        if (!string.IsNullOrEmpty(patientName))
            query = query.Where(s => s.PatientName.Contains(patientName));

        if (!string.IsNullOrEmpty(studyDate))
            query = query.Where(s => s.StudyDate == studyDate);

        return await query.ToListAsync();
    }

    public async Task DeleteStudyAsync(string studyInstanceUID)
    {
        await _database.DeleteAsync<StudyRecord>(studyInstanceUID);
        await _database.Table<SeriesRecord>()
            .Where(s => s.StudyInstanceUID == studyInstanceUID)
            .DeleteAsync();
        await _database.Table<InstanceRecord>()
            .Where(i => i.StudyInstanceUID == studyInstanceUID)
            .DeleteAsync();
    }
}

[Table("Studies")]
public class StudyRecord
{
    [PrimaryKey]
    public string StudyInstanceUID { get; set; }
    
    public string PatientName { get; set; }
    public string PatientID { get; set; }
    public string StudyDate { get; set; }
    public string StudyTime { get; set; }
    public string StudyDescription { get; set; }
    public string AccessionNumber { get; set; }
    public string Modality { get; set; }
    public DateTime CreatedDate { get; set; }
}

Error Handling and Logging

Structured Logging

using NLog;
using System.Diagnostics;

public class LoggingService
{
    private static readonly Logger logger = LogManager.GetCurrentClassLogger();

    public static void LogDicomOperation(string operation, string aeTitle, TimeSpan duration, bool success)
    {
        logger.Info("DICOM {Operation} from {AETitle} took {Duration}ms - {Status}",
            operation, aeTitle, duration.TotalMilliseconds, success ? "Success" : "Failed");
    }

    public static void LogApiRequest(string method, string path, int statusCode, TimeSpan duration)
    {
        logger.Info("API {Method} {Path} returned {StatusCode} in {Duration}ms",
            method, path, statusCode, duration.TotalMilliseconds);
    }

    public static void LogError(Exception ex, string context)
    {
        logger.Error(ex, "Error in {Context}: {Message}", context, ex.Message);
    }
}

// Usage in your code
public async Task<DicomCStoreResponse> OnCStoreRequest(DicomCStoreRequest request)
{
    var stopwatch = Stopwatch.StartNew();
    var success = false;

    try
    {
        // Process the request
        await ProcessStoreRequest(request);
        success = true;
        return new DicomCStoreResponse(request, DicomStatus.Success);
    }
    catch (Exception ex)
    {
        LoggingService.LogError(ex, "C-STORE Processing");
        return new DicomCStoreResponse(request, DicomStatus.ProcessingFailure);
    }
    finally
    {
        stopwatch.Stop();
        LoggingService.LogDicomOperation("C-STORE", request.CalledAE, stopwatch.Elapsed, success);
    }
}

Testing

Unit Testing Framework

using Xunit;
using DicomProxyCore;
using DicomProxyCore.Services;
using Microsoft.Extensions.DependencyInjection;

public class DicomProxyTests
{
    private readonly ServiceProvider _serviceProvider;

    public DicomProxyTests()
    {
        var services = new ServiceCollection();
        services.AddSingleton<IStorageManager, TestStorageManager>();
        services.AddSingleton<IMessaging, TestMessaging>();
        _serviceProvider = services.BuildServiceProvider();
    }

    [Fact]
    public async Task TestDicomStore()
    {
        // Arrange
        var storageManager = _serviceProvider.GetService<IStorageManager>();
        var dataset = new DicomDataset
        {
            { DicomTag.PatientName, "TEST^PATIENT" },
            { DicomTag.StudyInstanceUID, "1.2.3.4.5" },
            { DicomTag.SeriesInstanceUID, "1.2.3.4.5.6" },
            { DicomTag.SOPInstanceUID, "1.2.3.4.5.6.7" }
        };

        // Act
        var result = await storageManager.StoreInstanceAsync(dataset);

        // Assert
        Assert.True(result);
    }

    [Fact]
    public void TestConfigurationSetup()
    {
        // Arrange
        var basePath = Path.GetTempPath();

        // Act
        Configuration.Setup(basePath, false);

        // Assert
        Assert.NotNull(Configuration.Instance);
        Assert.Equal(basePath, Configuration.Instance.ConfigRoot);
    }
}

public class TestStorageManager : IStorageManager
{
    public async Task<bool> StoreInstanceAsync(DicomDataset dataset)
    {
        // Mock implementation for testing
        return await Task.FromResult(true);
    }

    public string GetStudyPath(string studyInstanceUID) => "/test/path";
    public string GetSeriesPath(string studyInstanceUID, string seriesInstanceUID) => "/test/path";
    public string GetInstancePath(string studyInstanceUID, string seriesInstanceUID, string sopInstanceUID) => "/test/path";
}

Performance Optimization

Connection Pooling

using DicomProxyCore.CStoreSCP.ConnectionManager;
using System.Collections.Concurrent;

public class OptimizedConnectionManager
{
    private readonly ConcurrentDictionary<string, DicomClient> _connectionPool;
    private readonly SemaphoreSlim _poolSemaphore;
    private readonly int _maxConnections;

    public OptimizedConnectionManager(int maxConnections = 50)
    {
        _maxConnections = maxConnections;
        _connectionPool = new ConcurrentDictionary<string, DicomClient>();
        _poolSemaphore = new SemaphoreSlim(maxConnections, maxConnections);
    }

    public async Task<DicomClient> GetConnectionAsync(Connection server)
    {
        await _poolSemaphore.WaitAsync();
        
        var key = $"{server.Hostname}:{server.Port}:{server.AE_Title}";
        
        if (_connectionPool.TryGetValue(key, out var existingClient) && 
            existingClient.IsConnected)
        {
            return existingClient;
        }

        var client = DicomClientFactory.Create(server.Hostname, server.Port, false, server.AE_Title, server.AE_Title);
        client.Timeout = TimeSpan.FromSeconds(server.TimeoutSeconds);
        
        _connectionPool.AddOrUpdate(key, client, (k, v) => client);
        
        return client;
    }

    public void ReleaseConnection(DicomClient client)
    {
        _poolSemaphore.Release();
    }
}

Caching Strategies

using LazyCache;

public class CachingService
{
    private readonly IAppCache _cache;
    
    public CachingService(IAppCache cache)
    {
        _cache = cache;
    }

    public async Task<T> GetOrAddAsync<T>(string key, Func<Task<T>> factory, TimeSpan? expiry = null)
    {
        return await _cache.GetOrAddAsync(key, factory, expiry ?? TimeSpan.FromHours(1));
    }

    public async Task<List<StudyRecord>> GetStudiesWithCaching(string patientName)
    {
        var cacheKey = $"studies:{patientName}";
        return await GetOrAddAsync(cacheKey, async () =>
        {
            // Expensive database operation
            return await DatabaseService.GetStudiesAsync(patientName);
        }, TimeSpan.FromMinutes(15));
    }
}

Deployment and Distribution

Creating NuGet Packages

<!-- SDK.csproj -->
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net9.0</TargetFramework>
    <PackageId>MyCompany.DicomProxy.SDK</PackageId>
    <Version>1.0.0</Version>
    <Authors>My Company</Authors>
    <Description>SDK for DICOM Proxy integration</Description>
    <PackageLicenseExpression>MIT</PackageLicenseExpression>
    <GeneratePackageOnBuild>true</GeneratePackageOnBuild>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="DicomProxyCore" Version="1.09.1" />
  </ItemGroup>
</Project>

Docker Integration

# Dockerfile for SDK-based application
FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base
WORKDIR /app
EXPOSE 8080
EXPOSE 11112

FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
WORKDIR /src
COPY ["MyApp.csproj", "."]
RUN dotnet restore "./MyApp.csproj"
COPY . .
WORKDIR "/src/."
RUN dotnet build "MyApp.csproj" -c Release -o /app/build

FROM build AS publish
RUN dotnet publish "MyApp.csproj" -c Release -o /app/publish

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "MyApp.dll"]

Best Practices

1. Configuration Management

  • Use strongly-typed configuration objects
  • Implement configuration validation
  • Support environment-specific overrides
  • Use secrets management for sensitive data

2. Error Handling

  • Implement global exception handling
  • Use structured logging with correlation IDs
  • Provide meaningful error messages
  • Implement retry policies for transient failures

3. Performance

  • Use connection pooling for DICOM connections
  • Implement caching for frequently accessed data
  • Use async/await patterns consistently
  • Monitor memory usage and implement disposal patterns

4. Security

  • Validate all inputs
  • Use secure communication channels
  • Implement proper authentication and authorization
  • Log security events

5. Testing

  • Write unit tests for core functionality
  • Implement integration tests for DICOM operations
  • Use mock objects for external dependencies
  • Perform load testing for high-throughput scenarios

This SDK documentation provides comprehensive coverage of the DicomProxyCore SDK components and integration patterns. For specific implementation examples, refer to the Examples & Tutorials section.