The DicomProxyCore SDK provides libraries and tools for integrating directly with the DICOM Router/PACS system from .NET applications.
SDK components
DicomProxyCore SDK
├── DicomProxyCore.dll
├── DicomProxyCore.Models.dll
├── DicomProxyCore.Services.dll
├── DicomProxyCore.Dicom.dll
├── DicomProxyCore.Commands.dll
└── DicomProxyCore.Imaging.SkiaSharp.dll
Dependencies: .NET 9.0, FellowOak DICOM 5.2.2, SkiaSharp, SQLite, NLog, ASP.NET Core, Jint, LazyCache.
NuGet installation
<PackageReference Include="DicomProxyCore" Version="1.09.1" />
<PackageReference Include="DicomProxyCore.Models" Version="1.09.1" />
<PackageReference Include="DicomProxyCore.Services" Version="1.09.1" />
Core classes
ConfigurationBase
Configuration.Setup("C:\\MyApp", false);
var config = Configuration.Instance;
string storagePath = config.StoragePath;
int dicomPort = config.DicomPort;
string aeTitle = config.ThisModality.AE_Title;
config.DicomPort = 11112;
config.Save();
DicomProxyService
public class MyDicomService
{
private DicomProxyService _proxyService;
public void Initialize()
{
_proxyService = new DicomProxyService("C:\\MyApp", false);
_proxyService.Start();
}
public void Shutdown() => _proxyService?.Stop();
}
Custom CStoreSCP
public class CustomCStoreSCP : CStoreSCP
{
public override DicomCStoreResponse OnCStoreRequest(DicomCStoreRequest request)
{
var dataset = request.Dataset;
var studyUID = dataset.GetSingleValue<string>(DicomTag.StudyInstanceUID);
var patientName = dataset.GetSingleValueOrDefault(DicomTag.PatientName, "Unknown");
// Custom processing...
return new DicomCStoreResponse(request, DicomStatus.Success);
}
}
Data models
// Connection (PACS server)
var server = new Connection
{
Id = "pacs1",
AE_Title = "MAINPACS",
Hostname = "192.168.1.100",
Port = 11112,
Enabled = true,
Priority = 1
};
// DICOM query
var query = new DicomQuery
{
Level = QueryLevel.Study,
PatientName = "DOE^JOHN",
StudyDate = "20240315",
Modality = "CT"
};
Service interfaces
IStorageManager
public class CustomStorageManager : IStorageManager
{
public string GetInstancePath(string studyUID, string seriesUID, string instanceUID)
=> Path.Combine(Configuration.Instance.StoragePath, "studies", studyUID, seriesUID, $"{instanceUID}.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 path = GetInstancePath(studyUID, seriesUID, instanceUID);
Directory.CreateDirectory(Path.GetDirectoryName(path));
await new DicomFile(dataset).SaveAsync(path);
return true;
}
}
IMessaging
public class CustomMessaging : IMessaging
{
public void SendMessage(string message, MessageType type = MessageType.Info)
{
MessageReceived?.Invoke(this, new MessageEventArgs
{
Message = message, Type = type, Timestamp = DateTime.Now
});
}
public void Start() { }
public void Stop() { }
}
Dependency injection
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IStorageManager, CustomStorageManager>();
services.AddSingleton<IMessaging, CustomMessaging>();
services.AddSingleton<ILicenseManager, CustomLicenseManager>();
services.AddSingleton(Configuration.Instance);
services.AddLogging(b => b.AddNLog("nlog.config"));
}
DICOM processing
public async Task ProcessDicomFile(string filePath)
{
var dicomFile = await DicomFile.OpenAsync(filePath);
var dataset = dicomFile.Dataset;
var studyUID = dataset.GetSingleValue<string>(DicomTag.StudyInstanceUID);
var modality = dataset.GetSingleValueOrDefault(DicomTag.Modality, "");
// Convert to JSON
var json = new DicomJson2().WriteToString(dataset);
// Image processing (if pixel data present)
if (dataset.Contains(DicomTag.PixelData))
{
var image = new DicomImage(dataset);
using var bitmap = image.RenderImage().AsClonedBitmap();
// ...
}
}
Image processing (SkiaSharp)
public async Task<byte[]> ConvertToJpegAsync(DicomDataset dataset, int quality = 90)
{
using var image = new DicomImage(dataset);
using var bitmap = image.RenderImage().AsClonedBitmap();
using var stream = new MemoryStream();
using var skBitmap = SKBitmap.FromImage(bitmap);
skBitmap.Encode(SKEncodedImageFormat.Jpeg, quality).SaveTo(stream);
return stream.ToArray();
}
JavaScript scripting (Jint)
public async Task<bool> ValidateDataAsync(DicomDataset dataset)
{
var engine = new Engine();
engine.SetValue("getTag", new Func<string, string>(tagName =>
dataset.GetSingleValueOrDefault(DicomTag.Parse(tagName), "")));
var result = engine.Evaluate(@"
var patientName = getTag('00100010');
var studyUID = getTag('0020000D');
patientName && patientName.trim() !== '' &&
studyUID && studyUID.trim() !== '';
");
return Convert.ToBoolean(result.ToObject());
}
Best practices
- Configuration — use strongly-typed config objects; support environment-specific overrides; keep secrets out of JSON.
- Error handling — global exception handling; structured logging with correlation IDs; retry policies for transient failures.
- Performance — connection pooling; async/await throughout; cache frequently accessed data; monitor memory and disposal.
- Security — validate all inputs; use TLS; authenticate API calls; log security events.
- Testing — unit-test core logic; integration-test DICOM operations; load-test high-throughput paths.
Docker
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 && dotnet build -c Release -o /app/build
FROM build AS publish
RUN dotnet publish -c Release -o /app/publish
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "MyApp.dll"]
See also: Examples & Tutorials