① 开发环境快速搭建与依赖配置

基于Dicom.Core 4.0.4.0

② WorklistServer

public class WorklistServer
{

 private static IDicomServer _server;
 private static Timer _itemsLoaderTimer;


 protected WorklistServer()
 {
 }

 public static string AETitle { get; set; }


 public static IWorklistItemsSource CreateItemsSourceService => new WorklistItemsProvider();

 public static List<WorklistItem> CurrentWorklistItems { get; private set; }

 public static void Start(int port, string aet)
 {
    AETitle = aet;
    _server = DicomServer.Create<WorklistService>(port);
 }


 public static void Stop()
 {
    _itemsLoaderTimer?.Dispose();
    _server.Dispose();
 }

}

③ WorklistService代码实现

public class WorklistService : DicomService, IDicomServiceProvider, IDicomCEchoProvider, IDicomCFindProvider, IDicomNServiceProvider
{

   private static readonly DicomTransferSyntax[] AcceptedTransferSyntaxes = new DicomTransferSyntax[]
      {
           DicomTransferSyntax.ExplicitVRLittleEndian,
           DicomTransferSyntax.ExplicitVRBigEndian,
           DicomTransferSyntax.ImplicitVRLittleEndian
      };

   private IMppsSource _mppsSource;
   private IMppsSource MppsSource
   {
       get
       {
           if (_mppsSource == null)
           {
               _mppsSource = new MppsHandler(Logger);
           }

           return _mppsSource;
       }
   }
   public static IWorklistItemsSource CreateItemsSourceService => new WorklistItemsProvider();

   public static List<WorklistItem> CurrentWorklistItems { get; private set; }

   public WorklistService(INetworkStream stream, Encoding fallbackEncoding, Dicom.Log.Logger log) : base(stream, fallbackEncoding, log)
   {
   }


   public DicomCEchoResponse OnCEchoRequest(DicomCEchoRequest request)
   {
       LogWrite.mwlLog($"Received verification request from AE {Association.CallingAE} with IP: {Association.RemoteHost}");
       return new DicomCEchoResponse(request, DicomStatus.Success);
   }


   public IEnumerable<DicomCFindResponse> OnCFindRequest(DicomCFindRequest request)
   {
       LogWrite.mwlLog("WorklistSCP收到工作列表查询请求\r\n");
       foreach (var element in request.Dataset)
       {
           // 输出标签的键(Tag)和值
           LogWrite.mwlLog("Tag: " + element.ToString() + " 数值 " + request.Dataset.GetSingleValueOrDefault(element.Tag, string.Empty));
       }
       var newWorklistItems = CreateItemsSourceService.GetAllCurrentWorklistItems();
       foreach (DicomDataset result in WorklistHandler.FilterWorklistItems(request.Dataset, newWorklistItems))
       {
           yield return new DicomCFindResponse(request, DicomStatus.Pending) { Dataset = result };
       }
       yield return new DicomCFindResponse(request, DicomStatus.Success);
   }


   public void OnConnectionClosed(Exception exception)
   {
       Clean();
   }


   public void OnReceiveAbort(DicomAbortSource source, DicomAbortReason reason)
   {
       LogWrite.mwlLog($"Received abort from {source}, reason is {reason}");
   }


   public System.Threading.Tasks.Task OnReceiveAssociationReleaseRequestAsync()
   {
       Clean();
       return SendAssociationReleaseResponseAsync();
   }


   public System.Threading.Tasks.Task OnReceiveAssociationRequestAsync(DicomAssociation association)
   {
       LogWrite.mwlLog($"Received association request from AE: {association.CallingAE} with IP: {association.RemoteHost} ");

       if (WorklistServer.AETitle != association.CalledAE)
       {
           Logger.Error($"Association with {association.CallingAE} rejected since called aet {association.CalledAE} is unknown");
           return SendAssociationRejectAsync(DicomRejectResult.Permanent, DicomRejectSource.ServiceUser, DicomRejectReason.CalledAENotRecognized);
       }

       foreach (var pc in association.PresentationContexts)
       {
           if (pc.AbstractSyntax == DicomUID.Verification
               || pc.AbstractSyntax == DicomUID.ModalityWorklistInformationModelFIND
               || pc.AbstractSyntax == DicomUID.ModalityPerformedProcedureStepSOPClass
               || pc.AbstractSyntax == DicomUID.ModalityPerformedProcedureStepNotificationSOPClass
               || pc.AbstractSyntax == DicomUID.ModalityPerformedProcedureStepNotificationSOPClass)
           {
               pc.AcceptTransferSyntaxes(AcceptedTransferSyntaxes);
           }
           else
           {
               LogWrite.mwlLog($"Requested abstract syntax {pc.AbstractSyntax} from {association.CallingAE} not supported");
               pc.SetResult(DicomPresentationContextResult.RejectAbstractSyntaxNotSupported);
           }
       }

       LogWrite.mwlLog($"Accepted association request from {association.CallingAE}");
       return SendAssociationAcceptAsync(association);
   }


   public void Clean()
   {
       // cleanup, like cancel outstanding move- or get-jobs
   }


   public DicomNCreateResponse OnNCreateRequest(DicomNCreateRequest request)
   {
       if (request.SOPClassUID != DicomUID.ModalityPerformedProcedureStepSOPClass)
       {
           return new DicomNCreateResponse(request, DicomStatus.SOPClassNotSupported);
       }
       var affectedSopInstanceUID = request.Command.GetSingleValue<string>(DicomTag.AffectedSOPInstanceUID);
       Logger.Log(LogLevel.Info, $"reeiving N-Create with SOPUID {affectedSopInstanceUID}");
       var procedureStepId = request.Dataset
           .GetSequence(DicomTag.ScheduledStepAttributesSequence)
           .First()
           .GetSingleValue<string>(DicomTag.ScheduledProcedureStepID);
       var ok = MppsSource.SetInProgress(affectedSopInstanceUID, procedureStepId);

       return new DicomNCreateResponse(request, ok ? DicomStatus.Success : DicomStatus.ProcessingFailure);
   }


   public DicomNSetResponse OnNSetRequest(DicomNSetRequest request)
   {
       if (request.SOPClassUID != DicomUID.ModalityPerformedProcedureStepSOPClass)
       {
           return new DicomNSetResponse(request, DicomStatus.SOPClassNotSupported);
       }
       var requestedSopInstanceUID = request.Command.GetSingleValue<string>(DicomTag.RequestedSOPInstanceUID);
       Logger.Log(LogLevel.Info, $"receiving N-Set with SOPUID {requestedSopInstanceUID}");

       var status = request.Dataset.GetSingleValue<string>(DicomTag.PerformedProcedureStepStatus);
       if (status == "COMPLETED")
       {
           var doseDescription = request.Dataset.GetSingleValueOrDefault(DicomTag.CommentsOnRadiationDose, string.Empty);
           var listOfInstanceUIDs = new List<string>();
           foreach (var seriesDataset in request.Dataset.GetSequence(DicomTag.PerformedSeriesSequence))
           {
               foreach (var instanceDataset in seriesDataset.GetSequence(DicomTag.ReferencedImageSequence))
               {
                   var instanceUID = instanceDataset.GetSingleValueOrDefault(DicomTag.ReferencedSOPInstanceUID, string.Empty);
                   if (!string.IsNullOrEmpty(instanceUID)) listOfInstanceUIDs.Add(instanceUID);
               }
           }
           var ok = MppsSource.SetCompleted(requestedSopInstanceUID, doseDescription, listOfInstanceUIDs);

           return new DicomNSetResponse(request, ok ? DicomStatus.Success : DicomStatus.ProcessingFailure);
       }
       else if (status == "DISCONTINUED")
       {
           var ok = MppsSource.SetDiscontinued(requestedSopInstanceUID, string.Empty);
           return new DicomNSetResponse(request, ok ? DicomStatus.Success : DicomStatus.ProcessingFailure);
       }
       else
       {
           return new DicomNSetResponse(request, DicomStatus.InvalidAttributeValue);
       }
   }


   #region not supported methods but that are required because of the interface

   public DicomNDeleteResponse OnNDeleteRequest(DicomNDeleteRequest request)
   {
       Logger.Log(LogLevel.Info, "receiving N-Delete, not supported");
       return new DicomNDeleteResponse(request, DicomStatus.UnrecognizedOperation);
   }

   public DicomNEventReportResponse OnNEventReportRequest(DicomNEventReportRequest request)
   {
       Logger.Log(LogLevel.Info, "receiving N-Event, not supported");
       return new DicomNEventReportResponse(request, DicomStatus.UnrecognizedOperation);
   }

   public DicomNGetResponse OnNGetRequest(DicomNGetRequest request)
   {
       Logger.Log(LogLevel.Info, "receiving N-Get, not supported");
       return new DicomNGetResponse(request, DicomStatus.UnrecognizedOperation);
   }

   public DicomNActionResponse OnNActionRequest(DicomNActionRequest request)
   {
       Logger.Log(LogLevel.Info, "receiving N-Action, not supported");
       return new DicomNActionResponse(request, DicomStatus.UnrecognizedOperation);
   }

   #endregion

}

④ 分步实操:CreateItemsSourceService.GetAllCurrentWorklistItems() 通过配置sql和数据库连接查询数据

public List<WorklistItem> GetAllCurrentWorklistItems()
{
    List<WorklistItem> worklistItems = new List<WorklistItem>();
    try
    {
            var appSettings = ConfigurationManager.AppSettings;
            BaseType = appSettings["BaseType"] ?? null;
            BasedbUrl = appSettings["BasedbUrl"] ?? null;
            QuerySql = appSettings["QuerySql"] ?? null;
            NameType = appSettings["NameType"] ?? "1";
            Encode = appSettings["Encode"] ?? null;
            LogWrite.mwlLog("数据库类型:" + BaseType);
            LogWrite.mwlLog("数据库链接字符串:" + BasedbUrl);
            LogWrite.mwlLog("查询数据库SQL语句:" + QuerySql);
            LogWrite.mwlLog("姓名显示形式:" + NameType);
            LogWrite.mwlLog("字符集:" + Encode);
            bool IsConnected = true;
            myds = GetDataValue.GetData(BaseType, BasedbUrl, QuerySql, ref IsConnected);
            if (!IsConnected)
            {
                LogWrite.mwlLog("第三方数据库链接失败!");
                return worklistItems;
            }
            if (myds.Rows.Count == 0)
            {
                LogWrite.mwlLog("Worklist未查询到数据集合");
                return worklistItems;
            }
            else
            {
                for (int i = 0; i < myds.Rows.Count; i++)
                {
                    WorklistItem worklistItem = new WorklistItem();
                    string PatientName = myds.Rows[i]["PatientName"].ToString();
                    if (NameType.Equals("1"))
                    {
                        string[] spell = new string[] { null };
                        new Hanzi2PinyinConverter().Convert(PatientName, "", out spell);
                        PatName = PatientName;
                }
                    else if (NameType.Equals("2"))
                {
                        PatName = PinYinConverter.Get(PatientName);
                    }
                    else if (NameType.Equals("3"))
                {
                        PatName = PatientName + PinYinConverter.Get(PatientName);
                    }
                    worklistItem.Surname = PatName;
                    worklistItem.PatientID = myds.Rows[i]["PatientID"].ToString();
                    worklistItem.Modality = myds.Rows[i]["Modality"].ToString();
                    worklistItem.AccessionNumber = myds.Rows[i]["AccessionNumber"].ToString();
                    worklistItem.PatientAge = myds.Rows[i]["PatientAge"].ToString();
                    worklistItem.PatientWeight = myds.Rows[i]["PatientWeight"].ToString();
                    worklistItem.ExamDateAndTime = DateTime.Parse(myds.Rows[i]["StudyTime"].ToString());
                    worklistItem.DateOfBirth = DateTime.Parse(myds.Rows[i]["PatientBirthDate"].ToString());
                    worklistItem.Sex = myds.Rows[i]["Sex"].ToString();
                    worklistItem.PregnancyStatus = myds.Rows[i]["PregnancyStatus"].ToString();
                    worklistItem.HospitalName = myds.Rows[i]["InstitutionName"].ToString();
                    worklistItem.ReferringPhysician = myds.Rows[i]["RequestingPhysician"].ToString();
                    worklistItem.PerformingPhysician = myds.Rows[i]["PerformingPhysician"].ToString();
                    worklistItem.StudyUID = myds.Rows[i]["StudyUuid"].ToString();
                    worklistItem.ProcedureID = myds.Rows[i]["ProcedureID"].ToString();
                    worklistItem.ProcedureStepID = myds.Rows[i]["ProcedureStepID"].ToString();
                    worklistItem.ScheduledAET = myds.Rows[i]["ScheduledAET"].ToString();
                    worklistItem.Encode = Encode;
                    worklistItem.ExamRoom = myds.Rows[i]["ExamRoom"].ToString();
                    worklistItem.Forename = "";
                    worklistItem.AdmissionID = myds.Rows[i]["AdmissionID"].ToString();
                    worklistItem.ScheduledStationName = myds.Rows[i]["ScheduledStationName"].ToString();
                    worklistItem.ExamDescription = PinYinConverter.GetFirst(myds.Rows[i]["ExamDescription"].ToString());
                worklistItems.Add(worklistItem);
                }
            }
    }
    catch (Exception ex)
    {
        LogWrite.mwlLog("Worklist数据库集合转化异常:" + ex.ToString());
        return new List<WorklistItem>();
    }
    return worklistItems;
}

⑤ 分步实操: WorklistHandler 实现

public class WorklistHandler
{
    private static readonly System.Text.RegularExpressions.Regex reg = new System.Text.RegularExpressions.Regex("\\d+$");
    public static IEnumerable<DicomDataset> FilterWorklistItems(DicomDataset request, List<WorklistItem> allWorklistItems)
    {
        var exams = allWorklistItems.AsQueryable();

        // 从请求中获取字符集
        var requestedCharacterSet = "ISO_IR 100";  // 默认值
        if (request.Contains(DicomTag.SpecificCharacterSet))
        {
            var charsets = request.GetValues<string>(DicomTag.SpecificCharacterSet);
            if (charsets != null && charsets.Length > 0)
            {
                requestedCharacterSet = charsets[0];
                LogWrite.mwlLog("WorklistSCP请求的字符集: " + requestedCharacterSet);
            }
        }
        if ( request.TryGetSingleValue(DicomTag.PatientID, out string patientId))
        {
            exams = exams.Where(x => x.PatientID.Equals(patientId));
        }

        var patName = request.GetSingleValueOrDefault(DicomTag.PatientName, string.Empty);
        if (!string.IsNullOrEmpty(patName))
        {
            exams = AddNameCondition(exams, patName);
        }

        DicomDataset procedureStep = null;
        if (request.Contains(DicomTag.ScheduledProcedureStepSequence))
        {
            procedureStep = request.GetSequence(DicomTag.ScheduledProcedureStepSequence).First();

            // Required Matching keys
            var scheduledStationAET = procedureStep.GetSingleValueOrDefault(DicomTag.ScheduledStationAETitle, string.Empty);
            if (!string.IsNullOrEmpty(scheduledStationAET))
            {
                exams = exams.Where(x => x.ScheduledAET == scheduledStationAET);
            }

            var performingPhysician = procedureStep.GetSingleValueOrDefault(DicomTag.PerformingPhysicianName, string.Empty);
            if (!string.IsNullOrEmpty(performingPhysician))
            {
                exams = exams.Where(x => x.PerformingPhysician == performingPhysician);
            }
            var modality = procedureStep.GetSingleValueOrDefault(DicomTag.Modality, string.Empty);
            if (!string.IsNullOrEmpty(modality))
            {
                LogWrite.mwlLog("WorklistSCP从 ScheduledProcedureStep 获取到 Modality:" + modality);
                exams = exams.Where(x => x.Modality == modality);
            }
            // if only date is specified, then using standard matching
            // but if both are specified, then MWL defines a combined match
            var scheduledProcedureStepStartDateTime = procedureStep.GetSingleValueOrDefault(DicomTag.ScheduledProcedureStepStartDate, string.Empty);
            if (!string.IsNullOrEmpty(scheduledProcedureStepStartDateTime))
            {
                exams = AddDateCondition(exams, scheduledProcedureStepStartDateTime);
            }
            var procedureStepLocation = procedureStep.GetSingleValueOrDefault(DicomTag.ScheduledProcedureStepLocation, string.Empty);
            if (!string.IsNullOrEmpty(procedureStepLocation))
            {
                exams = exams.Where(x => x.ExamRoom.Equals(procedureStepLocation));
            }

            var procedureDescription = procedureStep.GetSingleValueOrDefault(DicomTag.ScheduledProcedureStepDescription, string.Empty);
            if (!string.IsNullOrEmpty(procedureDescription))
            {
                exams = exams.Where(x => x.ExamDescription.Equals(procedureDescription));
            }
        }

        var results = exams.ToList();

        //  Parsing result 
        foreach (var result in results)
        { 
            var resultingSPS = new DicomDataset();
            var resultingSPCS = new DicomDataset();
            var resultDataset = new DicomDataset();
            var resultingSPSSequence = new DicomSequence(DicomTag.ScheduledProcedureStepSequence, resultingSPS);
            if (procedureStep != null)
            {
                resultDataset.Add(resultingSPSSequence);
            }
            // 判断是否需要转换中文名
            bool needConvertName = true;
            string patientName = null;
            // 根据请求的字符集设置响应的字符集
            switch (requestedCharacterSet.ToUpperInvariant())
            {
                case "ISO_IR 100":  // Latin1
                    AddIfExistsInRequest(resultDataset, request, DicomTag.SpecificCharacterSet, "ISO_IR 100");
                    needConvertName = true;  // Latin1 不支持中文,需要转换
                    break;
                case "GB18030":     // 中文简体
                case "GBK":         // 中文简体
                case "GB2312":      // 中文简体
                    AddIfExistsInRequest(resultDataset, request, DicomTag.SpecificCharacterSet, "GB18030");
                    needConvertName = false;  // GB18030 支持中文,不需要转换
                    break;
                case "ISO_IR 192":  // UTF-8
                    AddIfExistsInRequest(resultDataset, request, DicomTag.SpecificCharacterSet, "ISO_IR 192");
                    needConvertName = false;  // UTF-8 支持中文,不需要转换
                    break;
                default:            // 其他未知字符集,使用 Latin1 作为安全选项
                    AddIfExistsInRequest(resultDataset, request, DicomTag.SpecificCharacterSet, "ISO_IR 100");
                    needConvertName = true;  // 使用拼音
                    LogWrite.mwlLog("WorklistSCP未知的字符集:" + requestedCharacterSet + ", 使用 Latin1");
                    break;
            }
            // 根据字符集决定是否需要转换中文名
            if (needConvertName)
            {
                patientName = ConvertToDeviceName(result.Surname);
                LogWrite.mwlLog("WorklistSCP转换患者姓名 - 原始名:" + result.Surname + "转换后: " + patientName + "字符集:" + requestedCharacterSet);
            }
            else
            {
                patientName = result.Surname;
                LogWrite.mwlLog("WorklistSCP使用原始中文名 - 患者姓名: {PatientName}:" + result.Surname + "字符集:" + requestedCharacterSet);
            }
            //add results to "main" dataset
            //检查号
            AddIfExistsInRequest(resultDataset, request, DicomTag.AccessionNumber, result.AccessionNumber);    // T2
            //医院名称
            AddIfExistsInRequest(resultDataset, request, DicomTag.InstitutionName, result.HospitalName);
            //推荐医师名称
            AddIfExistsInRequest(resultDataset, request, DicomTag.ReferringPhysicianName, result.ReferringPhysician); // T2
            //姓名
            AddIfExistsInRequest(resultDataset, request, DicomTag.PatientName, patientName); //T1
            AddIfExistsInRequest(resultDataset, request, DicomTag.InstitutionalDepartmentName, string.Format("DEPT{0}", "放射科")); //T1
            //患者id
            AddIfExistsInRequest(resultDataset, request, DicomTag.PatientID, result.PatientID); // T1
            //出生日期
            AddIfExistsInRequest(resultDataset, request, DicomTag.PatientBirthDate, result.DateOfBirth); // T2
            //年龄
            AddIfExistsInRequest(resultDataset, request, DicomTag.PatientAge, RegulateAge(result.PatientAge)); // T2
            //性别
            AddIfExistsInRequest(resultDataset, request, DicomTag.PatientSex, RegulateSex(result.Sex)); //T2
            //检查号
            AddIfExistsInRequest(resultDataset, request, DicomTag.StudyID, result.AccessionNumber);
            //StudyUID
            AddIfExistsInRequest(resultDataset, request, DicomTag.StudyInstanceUID, result.StudyUID); // T1
            AddIfExistsInRequest(resultDataset, request, DicomTag.SeriesInstanceUID, result.StudyUID); // T1
            AddIfExistsInRequest(resultDataset, request, DicomTag.StudyDescription, ""); // T1
            AddIfExistsInRequest(resultDataset, request, DicomTag.SpecialNeeds, "CC"); // T1
            AddIfExistsInRequest(resultDataset, request, DicomTag.PatientState, ""); // T1
            //
            AddIfExistsInRequest(resultDataset, request, DicomTag.BodyPartExamined, result.ExamDescription);
            //请求医生
            AddIfExistsInRequest(resultDataset, request, DicomTag.RequestingPhysician, result.ReferringPhysician); //T2
            //请求描述
            AddIfExistsInRequest(resultDataset, request, DicomTag.RequestedProcedureDescription, result.ExamDescription); //T1C
            //请求过程ID
            AddIfExistsInRequest(resultDataset, request, DicomTag.RequestedProcedureID, result.ProcedureID); // T1

            // Scheduled Procedure Step sequence T1
            // add results to procedure step dataset
            // Return if requested
            if (procedureStep != null)
            {
                //设备ae
                AddIfExistsInRequest(resultingSPS, procedureStep, DicomTag.ScheduledStationAETitle, result.ScheduledAET); // T1
                //检查时间
                AddIfExistsInRequest(resultingSPS, procedureStep, DicomTag.ScheduledProcedureStepStartDate, result.ExamDateAndTime.ToString("yyyyMMdd")); //T1
                AddIfExistsInRequest(resultingSPS, procedureStep, DicomTag.ScheduledProcedureStepStartTime, result.ExamDateAndTime); //T1
                //检查时间
                AddIfExistsInRequest(resultingSPS, procedureStep, DicomTag.StudyDate, GetDate(DateTime.Now)); //T1
                AddIfExistsInRequest(resultingSPS, procedureStep, DicomTag.StudyTime, GetTime(DateTime.Now)); //T1
                //检查类型
                AddIfExistsInRequest(resultingSPS, procedureStep, DicomTag.Modality, result.Modality); // T1
                //请求医生
                AddIfExistsInRequest(resultingSPS, procedureStep, DicomTag.ScheduledPerformingPhysicianName, result.PerformingPhysician); //T2
                //步骤描述
                AddIfExistsInRequest(resultingSPS, procedureStep, DicomTag.ScheduledProcedureStepDescription, result.PerformingPhysician); // T1C
                //步骤ID
                AddIfExistsInRequest(resultingSPS, procedureStep, DicomTag.ScheduledProcedureStepID, result.ProcedureStepID); //
                //检查项目名称                                                                                                          ////T1
                AddIfExistsInRequest(resultingSPS, procedureStep, DicomTag.ScheduledStationName, result.ScheduledStationName); //T2
                AddIfExistsInRequest(resultingSPS, procedureStep, DicomTag.ScheduledProcedureStepLocation, result.ExamRoom); //T2
 
            }

            
            var procedureDescription = procedureStep.GetSingleValueOrDefault(DicomTag.RequestedProcedureCodeSequence, string.Empty);
            if (!string.IsNullOrEmpty(procedureDescription))
            {
                DicomDataset resultingrRPCS = new DicomDataset();
                AddIfExistsInRequest(resultingrRPCS, procedureStep, DicomTag.CodeValue, "");
                AddIfExistsInRequest(resultingrRPCS, procedureStep, DicomTag.CodingSchemeDesignator, "");
                AddIfExistsInRequest(resultingrRPCS, procedureStep, DicomTag.CodingSchemeVersion, "");
                AddIfExistsInRequest(resultingrRPCS, procedureStep, DicomTag.CodeMeaning, "");
                var resultingRPCSSequence = new DicomSequence(DicomTag.RequestedProcedureCodeSequence, resultingrRPCS);
                resultDataset.Add(resultingRPCSSequence);
            }

            // Put blanks in for unsupported fields which are type 2 (i.e. must have a value even if NULL)
            // In a real server, you may wish to support some or all of these, but they are not commonly supported
            AddIfExistsInRequest(resultDataset, request, DicomTag.ReferencedStudySequence, new DicomDataset());         // Ref//d Study Sequence
            AddIfExistsInRequest(resultDataset, request, DicomTag.Priority, "");
            //怀孕状态
            AddIfExistsInRequest(resultDataset, request, DicomTag.PregnancyStatus, result.PregnancyStatus);// Transport Arrangements// Priority
            AddIfExistsInRequest(resultDataset, request, DicomTag.MedicalAlerts, "");
            AddIfExistsInRequest(resultDataset, request, DicomTag.Allergies, "");
            AddIfExistsInRequest(resultDataset, request, DicomTag.PatientTransportArrangements, "");
           
            //承认ID
            AddIfExistsInRequest(resultDataset, request, DicomTag.AdmissionID, result.AdmissionID);                               // Admission ID
            AddIfExistsInRequest(resultDataset, request, DicomTag.CurrentPatientLocation, "");                    // Patient Location
            AddIfExistsInRequest(resultDataset, request, DicomTag.ReferencedPatientSequence, new DicomDataset());       // Ref//d Patient Sequence
            //体重
            AddIfExistsInRequest(resultDataset, request, DicomTag.PatientWeight, result.PatientWeight);                             // Weight
            AddIfExistsInRequest(resultDataset, request, DicomTag.ConfidentialityConstraintOnPatientDataDescription, "");// Confidentiality Constraint
            //sop
            AddIfExistsInRequest(resultDataset, request, DicomTag.ImplementationClassUID, DicomUIDGenerator.GenerateDerivedFromUUID());
            AddIfExistsInRequest(resultDataset, request, DicomTag.TransferSyntaxUID, DicomTS.Explicit_VR_Little_Endian);

            // Send Reponse 
            yield return resultDataset;
        }
    }


    //Splits patient name into 2 separte strings surname and forename and send then to the addstringcondition subroutine.
    internal static IQueryable<WorklistItem> AddNameCondition(IQueryable<WorklistItem> exams, string dicomName)
    {
        if (string.IsNullOrEmpty(dicomName) || dicomName == "*")
        {
            return exams;
        }

        var personName = new DicomPersonName(DicomTag.PatientName, dicomName);
        if (dicomName.Contains("*"))
        {
            var firstNameRegex = new Regex("^" + Regex.Escape(personName.First).Replace("\\*", ".*") + "$");
            var lastNameRegex = new Regex("^" + Regex.Escape(personName.Last).Replace("\\*", ".*") + "$");
            exams = exams.Where(x => firstNameRegex.IsMatch(x.Forename) || lastNameRegex.IsMatch(x.Surname));
        }
        else
        {
            exams = exams.Where(x => (x.Forename.Equals(personName.First) && x.Surname.Equals(personName.Last)));
        }

        return exams;
    }


    internal static IQueryable<WorklistItem> AddDateCondition(IQueryable<WorklistItem> exams, string dateCondition)
    {
        if (!string.IsNullOrEmpty(dateCondition) && dateCondition != "*")
        {
            var range = new DicomDateTime(DicomTag.ScheduledProcedureStepStartDate, dateCondition).Get<DicomDateRange>();
            exams = exams.Where(x => range.Contains(x.ExamDateAndTime));
        }
        return exams;
    }


    internal static void AddIfExistsInRequest<T>(DicomDataset result, DicomDataset request, DicomTag tag, T value)
    {
        // Only send items which have been requested
        if (request.Contains(tag))
        {
            if (value == null)
            {
                value = default;
            }
            result.AddOrUpdate(tag, value);
        }
    }

    internal class DicomTS
    {
        public const string Implicit_VR_Little_Endian = "1.2.840.10008.1.2";
        public const string Explicit_VR_Little_Endian = "1.2.840.10008.1.2.1";
        public const string Explicit_VR_Big_Endian = "1.2.840.10008.1.2.2";
    }

    protected static object GetDate(DateTime dt)
    {
        return dt.ToString("yyyyMMdd");
    }
    protected static object GetTime(DateTime dt)
    {
        return dt.ToString("HHmmss");
    }
    public static string RegulateSex(string sex)
    {
        switch (sex)
        {
            case "男":
            case "M":
            case "m":
                return "M";
            case "女":
            case "F":
            case "f":
                return "F";
            default:
                return "O";
        }
    }

    protected static string RegulateAge(string age)
    {
        SortedList<string, string> replaces = new SortedList<string, string>();
        replaces.Add("零", "0");
        replaces.Add("一", "1");
        replaces.Add("二", "2");
        replaces.Add("三", "3");
        replaces.Add("四", "4");
        replaces.Add("五", "5");
        replaces.Add("六", "6");
        replaces.Add("七", "7");
        replaces.Add("八", "8");
        replaces.Add("九", "9");
        replaces.Add("壹", "1");
        replaces.Add("贰", "2");
        replaces.Add("叁", "3");
        replaces.Add("肆", "4");
        replaces.Add("伍", "5");
        replaces.Add("陆", "6");
        replaces.Add("柒", "7");
        replaces.Add("捌", "8");
        replaces.Add("玖", "9");
        replaces.Add(" ", "");
        age = WorklistHandler.Replace(age, replaces);
        int? year = WorklistHandler.GetAgeValueByUnit(ref age, new string[] { "年", "岁", "Y", "y" });
        int? month = WorklistHandler.GetAgeValueByUnit(ref age, new string[] { "月", "M", "m" });
        int? week = WorklistHandler.GetAgeValueByUnit(ref age, new string[] { "周", "星期", "礼拜", "W", "w" });
        int? day = WorklistHandler.GetAgeValueByUnit(ref age, new string[] { "日", "天", "D", "d" });
        int? hour = WorklistHandler.GetAgeValueByUnit(ref age, new string[] { "时", "小时", "H", "h" });
        StringBuilder sb = new StringBuilder();
        if (year.HasValue) sb.Append(year.Value.ToString().PadLeft(3, '0')).Append("Y");
        if (month.HasValue) sb.Append(month.Value.ToString().PadLeft(3, '0')).Append("M");
        if (week.HasValue) sb.Append(week.Value.ToString().PadLeft(3, '0')).Append("W");
        if (day.HasValue) sb.Append(day.Value.ToString().PadLeft(3, '0')).Append("D");
        if (hour.HasValue) sb.Append(hour.Value.ToString().PadLeft(3, '0')).Append("H");
        return sb.Length < 1 ? null : sb.ToString();
    }
    private static string Replace(string s, SortedList<string, string> replaces)
    {
        if (s == null) return null;
        foreach (KeyValuePair<string, string> kv in replaces)
        {
            s = s.Replace(kv.Key, kv.Value);
        }
        return s;
    }
    private static int? GetAgeValueByUnit(ref string s, string[] units)
    {
        if (s == null) return null;
        foreach (string unit in units)
        {
            try
            {
                string[] ss = s.Split(new string[] { unit }, StringSplitOptions.RemoveEmptyEntries);
                if (ss.Length < 1) continue;
                System.Text.RegularExpressions.Match m = reg.Match(ss[0]);
                if (!m.Success) continue;
                int v;
                if (int.TryParse(m.Value, out v))
                {
                    int start = ss[0].Length - m.Value.Length;
                    int end = ss[0].Length + unit.Length - 1;
                    if (start > 0)
                    {
                        if (s.Length - end - 1 > 0)
                        {
                            s = s.Substring(0, start) + s.Substring(end + 1);
                        }
                        else
                        {
                            s = s.Substring(0, start);
                        }
                    }
                    else
                    {
                        if (s.Length - end - 1 > 0)
                        {
                            s = s.Substring(end + 1);
                        }
                        else
                        {
                            s = "";
                        }
                    }
                    return v;
                }
            }
            catch
            { }
        }
        return null;
    }

    private static string ConvertToDeviceName(string chineseName)
    {
        try
        {
            if (string.IsNullOrEmpty(chineseName))
                return "";
            var result = new StringBuilder();
            var isFirst = true;
            foreach (var c in chineseName)
            {
                if (PinyinHelper.IsChinese(c))
                {
                    var pinyin = PinyinHelper.GetPinyin(c);
                    // 首字大写
                    if (isFirst)
                    {
                        result.Append(char.ToUpper(pinyin[0]) + pinyin.Substring(1).ToLower());
                        isFirst = false;
                    }
                    else
                    {
                        result.Append(pinyin.ToLower());
                    }
                    result.Append('^'); // DICOM中姓名分隔符
                }
                else
                {
                    // 如果是非文字符,直接添加
                    result.Append(c);
                }
            }

            // 移除最后一个分隔符(如果存在)
            if (result.Length > 0 && result[result.Length - 1] == '^')
            {
                result.Length--;
            }

            return result.ToString();
        }
        catch (Exception ex)
        {
            LogWrite.mwlLog("WorklistSCP" + ex + "转换患者姓名失败:" + chineseName);
            return chineseName; // 转换失败时返回原始名字
        }
    }
}

⑥ Main 方法

static void Main(string[] args)
{
var appSettings = ConfigurationManager.AppSettings;
String Prot = appSettings[“Port”] ?? “1004”;
String Ae = appSettings[“Ae”] ?? null;
WorklistServer.Start(int.Parse(Prot), Ae);
LogWrite.mwlLog(Prot+ Ae);
Console.WriteLine($"Starting WORKLIST SCP server with AET: "+ Ae + " port "+ Prot);
Console.WriteLine(“Press any key to stop the service”);
Console.Read();
}

⑦ AppSettings核心配置参考

<appSettings>
	<!--数据库类型  SQLSERVER /ORACLE /MYSQL/-->
	<add key="BaseType" value="SQLSERVER"/>
	<!--数据库链接字符串-->
	<add key="BasedbUrl" value="server=127.0.0.1,1433; database=pacs; uid=sa;pwd="/>
	<!--数据库查询sql-->
	<add key="QuerySql" value="SELECT PatientId
  ,PatientName
  ,Modality
  ,Sex
  ,AccessionNumber
  ,PatientWeight
  ,StudyTime
  ,PatientBirthDate
  ,PregnancyStatus
  ,PatientAge
  ,StudyUuid
  ,InstitutionName
  ,RequestingPhysician
  ,PerformingPhysician
  ,ProcedureID
  ,ProcedureStepID
  ,ScheduledAET
  ,ExamRoom
  ,AdmissionID
  ,ScheduledStationName
  ,ExamDescription
   FROM WRKLIST_XQXQWDDR"/>
	<!--姓名显示形式 1中文 2英文 3中文+英文-->
	<add key="NameType" value="1"/>
	<!--字符集 ISO_IR 192/ISO_IR 100/GB18030可以空/实际以仪器交互字符集为准-->
	<add key="Encode" value="ISO_IR 100"/>
	<!--服务端口-->
	<add key="Port" value="104"/>
	<!--服务AE名称-->
	<add key="Ae" value="Bluesky"/>
</appSettings>

更多推荐