diff --git a/Common/Common.csproj b/Common/Common.csproj index 993e89d..2be1f2a 100644 --- a/Common/Common.csproj +++ b/Common/Common.csproj @@ -5,7 +5,9 @@ + + diff --git a/Common/Model/ClientModel.cs b/Common/Model/ClientModel.cs index 8758a17..1a86972 100644 --- a/Common/Model/ClientModel.cs +++ b/Common/Model/ClientModel.cs @@ -2,6 +2,8 @@ using Microsoft.AspNetCore.Mvc; using System; using System.Collections.Generic; +using Microsoft.AspNetCore.Mvc.Rendering; +using Hl7.Fhir.Model; namespace FHIRcastSandbox.Model { public class ClientModel : ModelBase { @@ -9,16 +11,36 @@ public ClientModel() { ActiveSubscriptions = new List(); SubscriptionsToHub = new List(); + PatientSearchOptions = new Dictionary(); + SearchPatients = new SelectList(new List()); } public string UserIdentifier { get; set; } public string PatientIdentifier { get; set; } public string PatientIdIssuer { get; set; } - public string AccessionNumber { get; set; } + public string AccessionNumberGroup { get; set; } - public string StudyId { get; set; } + public string Event { get; set; } public string Topic { get; set; } public List ActiveSubscriptions { get; set; } public List SubscriptionsToHub { get; set; } + + //Patient Info + public Patient Patient { get; set; } + public string PatientName { get; set; } + public string PatientDOB { get; set; } + public string PatientOpenErrorDiv { get; set; } + + public string SelectedPatientID { get; set; } + public IEnumerable SearchPatients { get; set; } + public Dictionary PatientSearchOptions { get; set; } + + //Study Info + public string StudyId { get; set; } + public string AccessionNumber { get; set; } + public string StudyOpenErrorDiv { get; set; } + + //FHIR Server Info + public string FHIRServer { get; set; } } } diff --git a/WebSubClient/Controllers/WebSubClientController.cs b/WebSubClient/Controllers/WebSubClientController.cs index ec913af..83fa06a 100644 --- a/WebSubClient/Controllers/WebSubClientController.cs +++ b/WebSubClient/Controllers/WebSubClientController.cs @@ -1,3 +1,10 @@ +using FHIRcastSandbox.Model; +using Hl7.Fhir.Model; +using Hl7.Fhir.Rest; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Rendering; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Linq; @@ -5,12 +12,9 @@ using System.Security.Cryptography; using System.Text; using System.Threading.Tasks; -using FHIRcastSandbox.Model; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; -using Newtonsoft.Json; -namespace FHIRcastSandbox.Controllers { +namespace FHIRcastSandbox.Controllers +{ [Route("")] public class HomeController : Controller { public IActionResult Index() { @@ -23,34 +27,58 @@ public IActionResult Index() { [Route("client")] public class WebSubClientController : Controller { - private readonly ILogger logger; + #region Constructors public WebSubClientController(ILogger logger) { this.logger = logger; this.UID = Guid.NewGuid().ToString("n"); + + if (internalModel == null) + { + internalModel = new ClientModel(); + internalModel.FHIRServer = DEFAULT_FHIR_SERVER; + createFHIRClient(); + } + } + + private void createFHIRClient(string fhirServer = DEFAULT_FHIR_SERVER) + { + client = new FhirClient(fhirServer); + client.PreferredFormat = ResourceFormat.Json; } + + #endregion #region Properties public static ClientModel internalModel; - private static Dictionary pendingSubs = new Dictionary(); - private static Dictionary activeSubs = new Dictionary(); + private static Dictionary pendingSubs = new Dictionary(); + private static Dictionary activeSubs = new Dictionary(); + + private readonly ILogger logger; + private static FhirClient client; + private static Patient _patient; + private static ImagingStudy _study; + const string DEFAULT_FHIR_SERVER = "http://test.fhir.org/r3"; + private const string VIEW_NAME = "WebSubClient"; public string UID { get; set; } #endregion [HttpGet] - public IActionResult Get() => View("WebSubClient", new ClientModel()); + public IActionResult Get() + { + return View(VIEW_NAME, internalModel); + } #region Client Events public IActionResult Refresh() { if (internalModel == null) { internalModel = new ClientModel(); } internalModel.ActiveSubscriptions = activeSubs.Values.ToList(); - - return View("WebSubClient", internalModel); + return View(VIEW_NAME, internalModel); } /// @@ -68,7 +96,183 @@ public IActionResult Post([FromForm] ClientModel model) { //var response = httpClient.PostAsync(this.Request.Scheme + "://" + this.Request.Host + "/api/hub/notify", new StringContent(JsonConvert.SerializeObject(model), Encoding.UTF8, "application/json")).Result; var response = httpClient.PostAsync(this.Request.Scheme + "://localhost:5000/api/hub/notify", new StringContent(JsonConvert.SerializeObject(model), Encoding.UTF8, "application/json")).Result; - return View("WebSubClient", model); + return View(VIEW_NAME, model); + } + + [Route("searchPatients")] + [HttpPost] + public IActionResult SearchPatients(string patientID, string patientName) + { + SearchParams pars = new SearchParams(); + if (patientID != null) { pars.Add("_id", patientID); } + if (patientName != null) { pars.Add("name", patientName); } + + Bundle bundle = client.Search(pars); + + List patients = new List(); + foreach (Bundle.EntryComponent entry in bundle.Entry) + { + Patient pat = (Patient)entry.Resource; + patients.Add(new SelectListItem + { + Value = pat.Id, + Text = pat.Name[0].ToString() + }); + } + + internalModel.SearchPatients = new SelectList(patients, "Value", "Text"); + + return View(VIEW_NAME, internalModel); + } + + [Route("openPatient")] + [HttpPost] + public IActionResult OpenPatient(string patientSelect) + { + if (internalModel == null) { internalModel = new ClientModel(); } + if (patientSelect != null) + { + _patient = GetPatient(patientSelect); + if (_patient != null) + { + _study = null; + ClearPatientInfo(); + return UpdateClientModel(); + } + } + else + { + internalModel.PatientOpenErrorDiv = "

No patient ID given.

"; + } + + return View(VIEW_NAME, internalModel); + } + + [Route("openStudy")] + [HttpPost] + public IActionResult OpenStudy(string studyID) + { + if (internalModel == null) { internalModel = new ClientModel(); } + if (studyID != null) + { + _study = GetStudy(studyID); + if (_study != null) + { + ClearStudyInfo(); + ClearPatientInfo(); + return UpdateClientModel(); + } + else + { + internalModel.StudyOpenErrorDiv = "

No study ID given.

"; + } + } + + return View(VIEW_NAME, internalModel); + } + + [Route("saveSettings")] + [HttpPost] + public IActionResult SaveSettings(string fhirServer) + { + //return PartialView("ErrorModal"); + if (fhirServer != internalModel.FHIRServer) + + { + internalModel.FHIRServer = fhirServer; + createFHIRClient(internalModel.FHIRServer); + } + + return View(VIEW_NAME, internalModel); + } + + private IActionResult UpdateClientModel() + { + //Study is nothing just use patient + //Otherwise get patient from study + if (_study != null) + { + _patient = GetPatient("", _study.Patient.Reference); + + internalModel.AccessionNumber = (_study.Accession != null) ? _study.Accession.Value : ""; + internalModel.StudyId = _study.Uid; + } + else + { + ClearStudyInfo(); + } + + if (_patient == null) + { + ClearPatientInfo(); + } + else + { + internalModel.PatientName = $"{_patient.Name[0].Family}, {_patient.Name[0].Given.FirstOrDefault()}"; + internalModel.PatientDOB = _patient.BirthDate; + } + + + internalModel.Patient = _patient; + return View(VIEW_NAME, internalModel); + } + + private void ClearPatientInfo() + { + internalModel.PatientName = ""; + internalModel.PatientDOB = ""; + internalModel.PatientOpenErrorDiv = ""; + } + + private void ClearStudyInfo() + { + internalModel.StudyId = ""; + internalModel.AccessionNumber = ""; + internalModel.StudyOpenErrorDiv = ""; + } + + private Patient GetPatient(string id, string patientURI = "") + { + Uri uri = new Uri(client.Endpoint + "Patient/" + id); + + try + { + if (patientURI.Length > 0) { return client.Read(patientURI); } + else { return client.Read(uri); } + } + catch (FhirOperationException ex) + { + foreach (var item in ex.Outcome.Children) + { + Narrative nar = item as Narrative; + if (nar != null) + { + internalModel.PatientOpenErrorDiv = nar.Div; + } + } + return null; + } + } + + private ImagingStudy GetStudy(string id) + { + Uri uri = new Uri(client.Endpoint + "ImagingStudy/" + id); + try + { + return client.Read(uri); + } + catch (FhirOperationException ex) + { + foreach (var item in ex.Outcome.Children) + { + Narrative nar = item as Narrative; + if (nar != null) + { + internalModel.StudyOpenErrorDiv = nar.Div; + } + } + return null; + } } #endregion @@ -87,7 +291,7 @@ public IActionResult Get(string subscriptionId, [FromQuery] SubscriptionVerifica //Received a verification request for non-pending subscription, return a NotFound response if (!pendingSubs.ContainsKey(subscriptionId)) { return NotFound(); } - Subscription sub = pendingSubs[subscriptionId]; + Model.Subscription sub = pendingSubs[subscriptionId]; //Validate verification subcription with our subscription. If a match return challenge //otherwise return NotFound response. @@ -135,7 +339,7 @@ public async Task Subscribe(string subscriptionUrl, string topic, var secret = Encoding.UTF8.GetString(buffer, 0, buffer.Length); var httpClient = new HttpClient(); string subUID = Guid.NewGuid().ToString("n"); - var data = new Subscription() + var data = new Model.Subscription() { UID = subUID, Callback = new Uri(this.Request.Scheme + "://" + this.Request.Host + "/client/" + subUID), @@ -168,15 +372,15 @@ public async Task Subscribe(string subscriptionUrl, string topic, var result = await httpClient.PostAsync(subscriptionUrl, httpcontent); if (internalModel == null) { internalModel = new ClientModel(); } - return View("WebSubClient", internalModel); + return View(VIEW_NAME, internalModel); } [Route("unsubscribe/{subscriptionId}")] [HttpPost] public async Task Unsubscribe(string subscriptionId) { this.logger.LogDebug($"Unsubscribing subscription {subscriptionId}"); - if (!activeSubs.ContainsKey(subscriptionId)) { return View("WebSubClient", internalModel); } - Subscription sub = activeSubs[subscriptionId]; + if (!activeSubs.ContainsKey(subscriptionId)) { return View(VIEW_NAME, internalModel); } + Model.Subscription sub = activeSubs[subscriptionId]; sub.Mode = SubscriptionMode.unsubscribe; var httpClient = new HttpClient(); @@ -194,7 +398,7 @@ public async Task Unsubscribe(string subscriptionId) { activeSubs.Remove(subscriptionId); - return View("WebSubClient", internalModel); + return View(VIEW_NAME, internalModel); } #endregion diff --git a/WebSubClient/Views/WebSubClient/WebSubClient.cshtml b/WebSubClient/Views/WebSubClient/WebSubClient.cshtml index c0f1d01..fb8bc0d 100644 --- a/WebSubClient/Views/WebSubClient/WebSubClient.cshtml +++ b/WebSubClient/Views/WebSubClient/WebSubClient.cshtml @@ -10,6 +10,7 @@ + View @@ -22,13 +23,13 @@

Client info

@* Title row *@
-

Subscription info

-

User session info

+

Subscription info

+

User session info

@* Content row *@
@* Subscription column *@ -
+
@* Create new subscriptions form *@
@@ -96,82 +97,123 @@
- @* User session column *@ -
+ + @* Session Info Column *@ +
-
- @{ - using (Html.BeginForm("Post", "WebSubClient", FormMethod.Post)) + @* Current status row *@ +
+ @* Patient column *@ +
+
Patient Context
+ @if (Model.Patient != null) { -
-
-
- -
- @Html.TextBoxFor(m => m.UserIdentifier, new { @class = "form-control" }) -
-
- -
- @Html.TextBoxFor(m => m.PatientIdentifier, new { @class = "form-control" }) -
-
- -
- @Html.TextBoxFor(m => m.PatientIdIssuer, new { @class = "form-control" }) -
-
-
-
- -
- @Html.TextBoxFor(m => m.AccessionNumber, new { @class = "form-control" }) -
-
- -
- @Html.TextBoxFor(m => m.AccessionNumberGroup, new { @class = "form-control" }) -
-
- -
- @Html.TextBoxFor(m => m.StudyId, new { @class = "form-control" }) -
-
-
+
+ @Html.Label("Name: " + Model.Patient.Name[0].ToString()) @*Model.PatientName)*@ +
+ @Html.Label("ID: " + Model.Patient.Id) +
+ @Html.Label("DOB: " + Model.Patient.BirthDate) + + } else + { +
No patient selected.
+ } +
+ @* Study column *@ +
+ +
+ @Html.Label("Accession number: " + Model.AccessionNumber) +
+ @Html.Label("Study ID: " + Model.StudyId) -
- +
+
+ @* Selection list row *@ +
+ @* Patient column *@ +
+ +
+ + @{ + using (Html.BeginForm("openPatient", "WebSubClient", FormMethod.Post)) + { + +
+
+
+ } + } +
+ @* Study column *@ +
+ +
+
+ @* Search criteria row *@ +
+ @* Patient column *@ +
+
Patient search
+ + @{ + using (Html.BeginForm("searchPatients", "WebSubClient", FormMethod.Post)) + { +
- @Html.TextBoxFor(m => m.Topic, new { @class = "form-control" }) -
-
- +
+ +

- @Html.TextBoxFor(m => m.Event, new { @class = "form-control" }) -
-
- -
+ +
+
+ +
+ +
+
+
+ } } - } -
-
-
-
- @* Miscellaneous row *@ -
-
-
- @{ - using (Html.BeginForm("Refresh", "WebSubClient", FormMethod.Post)) - { -
- +
+ @* Study column *@ +
+
Study search
+
+
+ @* Settings row *@ +
+
+
+
+ @{ + using (Html.BeginForm("saveSettings", "WebSubClient", FormMethod.Post)) + { +
+

+ Settings +

+
+
+ @*
@Html.TextBoxFor(m => m.FHIRServer, new { @class = "form-control" })
*@ + + +
+ } + } +
- } - } +
+
@@ -212,10 +254,27 @@
+ + @* Miscellaneous row *@ +
+
+
+ @{ + using (Html.BeginForm("Refresh", "WebSubClient", FormMethod.Post)) + { +
+ +
+ } + } +
+
+
+ - + diff --git a/WebSubClient/WebSubClient.csproj b/WebSubClient/WebSubClient.csproj index 9f7fdf2..325f7d4 100644 --- a/WebSubClient/WebSubClient.csproj +++ b/WebSubClient/WebSubClient.csproj @@ -6,8 +6,11 @@ + + +