The SampleVNA example demonstrates how to use the DCF to provide multiple services on the same port.
Classes
Class | Description | |
---|---|---|
Database |
Performs all the communication to/from the database.
| |
DatabaseRecord |
Parent class representing the various database record types.
| |
ImageRecord |
A simple wrapper class that represents an image record in the database.
| |
PatientRecord |
A simple wrapper class that represents a patient record in the database.
| |
Program |
The SampleVNA server is an example of providing services that would be required for a vendor neutral archive implementation.
| |
ProgramSampleVNAServer |
SampleVNAServer handles associations by creating and registering SCPs for the various types of DIMSE messages that will arrive.
| |
RemoteDevice |
A simple wrapper class that represents remote devices that are allowed to communicate with the SampleVNA server, or
destinations that the SampleVNA server will communicate with as a function of a C-Move request.
| |
SampleVNAQuerySCP |
Sample VNA Query implementation.
| |
SampleVNAStoreSCP |
SampleVNAStoreSCP writes received datasets to disk at the location specified by Program.SampleVNAServer.ImagePath, and inserts relevant
(see the (Patient|Study|Series|Image)Record classes for what is relevant) tags into a four table patient/study/series/image database.
| |
SampleVNAVerificationSCP |
Sample VNA Verification SCP implementation.
| |
SeriesRecord |
A simple wrapper class that represents a series record in the database.
| |
StudyRecord |
A simple wrapper class that represents a study record in the database.
|
Examples
SampleVNA Sample Code
public class Program { /// <summary> /// Main entry point for SampleVNA. /// </summary> [STAThread] public static void Main() { try { // Create an SampleVNAServer that listens for associations on port 106 SampleVNAServer sampleVNAServer = new SampleVNAServer(106); sampleVNAServer.BeginListening(); Console.WriteLine("listening on {0}...", 106); } catch (Exception e) { Console.WriteLine("Error during execution: {0}", e); Environment.ExitCode = 1; } } /// <summary> /// SampleVNAServer handles associations by creating and registering SCPs for the various types of DIMSE messages that will arrive. /// </summary> public class SampleVNAServer : AssociationListenerAdapter, IAssociationConfigPolicyManager { private readonly AssociationManager _manager; private readonly IList<AllowedPresentationContext> _verificationPresentationContexts; private readonly IList<AllowedPresentationContext> _storePresentationContexts; private readonly IList<AllowedPresentationContext> _queryPresentationContexts; /// <summary> /// Gets the path to the images. /// </summary> public static string ImagePath { get { return Path.Combine(Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly().Location) ?? ".", "SampleVNAImages"); } } /// <summary> /// Constructor with port only /// </summary> /// <param name="port">The port to listen for connections.</param> public SampleVNAServer(int port) : this(port, null, null, null) { Database.ConnectionDictionary = new Dictionary<string, SqlCeConnection>(); if (!Directory.Exists(ImagePath)) Directory.CreateDirectory(ImagePath); } /// <summary> /// Constructor with port and allowed presentation contexts. /// </summary> /// <param name="port">Server port</param> /// <param name="allowedVerificationPresentationContexts">list of allowed verification presentation contexts</param> /// <param name="allowedStorePresentationContexts">list of allowed verification presentation contexts</param> /// <param name="allowedQueryPresentationContexts">list of allowed verification presentation contexts</param> public SampleVNAServer(int port, IList<AllowedPresentationContext> allowedVerificationPresentationContexts, IList<AllowedPresentationContext> allowedStorePresentationContexts, IList<AllowedPresentationContext> allowedQueryPresentationContexts) { _verificationPresentationContexts = allowedVerificationPresentationContexts; _storePresentationContexts = allowedStorePresentationContexts; _queryPresentationContexts = allowedQueryPresentationContexts; _manager = new AssociationManager(); _manager.ServerTcpPort = port; _manager.AddAssociationListener(this); _manager.AssociationConfigPolicyMgr = this; } /// <summary> /// Returns a DicomSessionSettings object to be used for the association. /// </summary> /// <param name="assoc">The AssociationAcceptor for the given association</param> /// <returns>the DicomSessionSettings for the association</returns> public DicomSessionSettings GetSessionSettings(AssociationAcceptor assoc) { Console.WriteLine("Received request from {0}:{1} calling={2} called={3}", assoc.AssociationInfo.GetCallingHost(), assoc.AssociationInfo.GetCallingPort(), assoc.AssociationInfo.CallingTitle, assoc.AssociationInfo.CalledTitle); Exception retval; try { SqlCeConnection conn = new SqlCeConnection(Database.ConnectionString); conn.Open(); Database.ConnectionDictionary[assoc.ConnectionID] = conn; IList<RemoteDevice> acceptableDevices = Database.GetRemoteDevices(conn); RemoteDevice device = acceptableDevices.FirstOrDefault( x => x.CalledAE.Equals(assoc.AssociationInfo.CalledTitle) && x.CallingAE.Equals(assoc.AssociationInfo.CallingTitle) && x.Ip.Equals(assoc.AssociationInfo.GetCallingHost())); if (device != null) return new DicomSessionSettings(); if (!acceptableDevices.Any(x => x.CalledAE.Equals(assoc.AssociationInfo.CalledTitle))) // we didn't have an exact match, so see if any of the devices from the db match the called AE title retval = new AssociationRejectedException(PduAssociateReject.Result.Permanent, PduAssociateReject.Source.ServiceUser, PduAssociateReject.Reason.BadCalled); else if (!acceptableDevices.Any(x => x.CallingAE.Equals(assoc.AssociationInfo.CallingTitle))) // see if any of the devices match the calling AE title retval = new AssociationRejectedException(PduAssociateReject.Result.Permanent, PduAssociateReject.Source.ServiceUser, PduAssociateReject.Reason.BadCalling); else // the problem must be the ip address. Note that "NoReason" is the only acceptable reason when rejecting based on the ip address. retval = new AssociationRejectedException(PduAssociateReject.Result.Transient, PduAssociateReject.Source.ServiceUser, PduAssociateReject.Reason.NoReason); } catch (SqlCeException e) { Console.WriteLine("Trouble trying to talk to the database: {0}{1}", Environment.NewLine, e); retval = e; } if (Database.ConnectionDictionary.ContainsKey(assoc.ConnectionID)) Database.ConnectionDictionary.Remove(assoc.ConnectionID); throw retval; } /// <summary> /// Start listening for associations via the AssociationManager. /// </summary> public void BeginListening() { Thread t = new Thread(_manager.Run); t.Start(); if (!_manager.WaitForRunning(2000)) { throw new TimeoutException("AssociationManager did not start in an acceptable amount of time"); } } /// <summary> /// Stop receiving any new associations. /// </summary> public void Stop() { _manager.Stop(); } /// <summary> /// Callback for beginning the association. /// </summary> /// <param name="assoc">the AssociationAcceptor</param> public override void BeginAssociation(AssociationAcceptor assoc) { assoc.RegisterServiceClassProvider(new SampleVNAVerificationSCP(assoc, _verificationPresentationContexts)); assoc.RegisterServiceClassProvider(new SampleVNAStoreSCP(assoc, _storePresentationContexts)); assoc.RegisterServiceClassProvider(new SampleVNAQuerySCP(assoc, _queryPresentationContexts)); } /// <summary> /// Callback for ending the association. /// </summary> /// <param name="assoc">the AssociationAcceptor</param> public override void EndAssociation(AssociationAcceptor assoc) { Database.ConnectionDictionary[assoc.ConnectionID].Close(); } } }