Reading & Development: 2 hours
Quick Intro
This guide will walk through the process to get patient clinical documents from the My Health Record system and display them to your users using a generic style sheet. To retrieve documents you must already have conducted a GainPCEHRAccess call as conducted in the previous guide.
At the end of this guide you will have a windows form (based on the My Health Record Landing Page created in the previous guide) which will display a document list on the form load, and from that list each document can be viewed in a browser control using an XSL stylesheet.
Step 1: Design the windows form
1. Modify the My Health Record Landing Page form created in previous guide as per the screenshot below.
The form contains a Windows ListBox control to load the list of documents and an Open button to load a selected document.
More Information
For the sake of simplicity, we are using a Windows ListBox to display the document list. In real applications, Grid View or more advance controls will likely be used to allow filtering, highlight relevant content and promote usability.
2. Add the Testing input and output fields on the form where SOAP request and response are displayed. Rename the fields appropriately.
Step: 2 Add code to load the document list
1. Right click on the form, click view code, and add the following additional namespaces
using System.Net;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
using System.ServiceModel;
using Nehta.VendorLibrary.Common;
using Nehta.VendorLibrary.PCEHR;
using Nehta.VendorLibrary.PCEHR.DocumentRegistry;
using Nehta.VendorLibrary.PCEHR.DocumentRepository;
2. Add the following code on the class level to get the certificate and create the request header.
// Obtain the certificate for use with TLS and signing
X509Certificate2 cert = PCEHRHelper.GetCertificate();
// Create PCEHR header
CommonPcehrHeader header = PCEHRHelper.CreateHeader();
3. Copy the following code to add the GetDocumentList method in the class. This code calls the getDocumentList web service which returns a response with a list of documents, these are then mapped with a local model class PatientDocument. We will create the PatientDocument class in step 4 below.
private void GetDocumentList(string ihiNumber)
{
// Override this value to the current patient's IHI.
header.IhiNumber = ihiNumber;
// Instantiate the client
// SVT endpoint is "https://services.svt.gw.myhealthrecord.gov.au/getDocumentList"
GetDocumentListClient documentListClient = new GetDocumentListClient(new Uri("https://services.svt.gw.myhealthrecord.gov.au/getDocumentList"), cert, cert);
// Add server certificate validation callback
ServicePointManager.ServerCertificateValidationCallback += ValidateServiceCertificate;
// Create a query
AdhocQueryBuilder adhocQueryBuilder = new AdhocQueryBuilder(ihiNumber, new[] { DocumentStatus.Approved });
// To further filter documents, build on the adhocQueryBuilder helper functions
// For example, filtering on document type
// adhocQueryBuilder.ClassCode = new List<ClassCodes>() {ClassCodes.DischargeSummary};
// See Table 3 XDSDocumentEntry Document Type and Class Code value set from
// the Document Exchange Service Technical Service Specification
// Create the request using the query
AdhocQueryRequest queryRequest = adhocQueryBuilder.BuildRequest();
try
{
// Invoke the service
AdhocQueryResponse queryResponse = documentListClient.GetDocumentList(header, queryRequest);
// Process data into a more simple model
XdsRecord[] data = XdsMetadataHelper.ProcessXdsMetadata(queryResponse.RegistryObjectList.ExtrinsicObject);
List<PatientDocument> docList = new List<PatientDocument>();
// For displaying the data in a list
foreach (var row in data)
{
// Convert dates from UTC to local time
//row.creationTimeUTC.ToLocalTime();
//row.serviceStopTimeUTC.ToLocalTime();
// Document name
//row.classCodeDisplayName
// Organisation
//row.authorInstitution.institutionName
// Organisation Type
//row.healthcareFacilityTypeCodeDisplayName
// Identifiers to retrieve the document
//row.repositoryUniqueId;
//row.documentId
docList.Add(new PatientDocument
{
DocumentId = row.documentId,
Name = row.classCodeDisplayName,
RepositoryUniqueId = row.repositoryUniqueId,
Organization = row.authorInstitution.institutionName,
OrganizationType = row.healthcareFacilityTypeCodeDisplayName,
CreationDate = row.creationTimeUTC
});
}
lstDocumentList.DataSource = docList;
// Get the soap request and response
string soapRequest = documentListClient.SoapMessages.SoapRequest;
string soapResponse = documentListClient.SoapMessages.SoapResponse;
txtSOAPRequest.Text = soapRequest;
txtSOAPResponse.Text = soapResponse;
}
catch (FaultException e)
{
// Handle any errors
txtError.Text = e.Message;
}
}
4. Let’s create the PatientDocument class as per the code below.
public class PatientDocument
{
public string DocumentId { get; set; }
public string Name { get; set; }
public string RepositoryUniqueId { get; set; }
public string Organization { get; set; }
public string OrganizationType { get; set; }
public string DocumentType { get; set; }
public DateTime CreationDate { get; set; }
public override string ToString()
{
// choose any format that suits you and display what you like
return String.Format("{0} | {1} | {2} | {3}", CreationDate, Name, Organization, OrganizationType);
}
}
5. Add the following method in the class.
private bool ValidateServiceCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
// Checks can be done here to validate the service certificate.
// If the service certificate contains any problems or is invalid, return false. Otherwise, return true.
// This example returns true to indicate that the service certificate is valid.
return true;
}
6. In this step, we will modify the GainAccess method created in a previous guide and pass the IHI to the My Health Record Landing Page form constructor and call getDocumentList from the class constructor using the IHI.
a. Modify the Gain Access method created in previous guide.
// When Gain Access is successfully granted, call My Health Record Landing page.
if (responseStatus != null && responseStatus.code == "PCEHR_SUCCESS")
{
MyHealthRecordForm myHRForm = new MyHealthRecordForm(IHINumber);
myHRForm.Show();
}
b. Modify the My Health Record Form constructor.
public MyHealthRecordForm(string _IHINumber)
{
InitializeComponent();
txtIHINumber.Text = _IHINumber; // Testing Input/Outfield field
GetDocumentList(_IHINumber);
}
7. Run the application and access the My Health Record Landing Page using the My Health Record button. The document list will populate.
More Information
The web service returns extensive information about the clinical document. For demonstration purposes we have only included a subset of these properties in our document list.
Step 3: Prepare your form
We will now load the selected CDA clinical document and display it using the Windows form Browser control and a generic style sheet.
1. Modify the My Health Record Landing Page form to add a Windows Browser control as per the screenshot below. Rename the browser control.
More Information
The HL7 Clinical Document Architecture (CDA) is an XML-based markup standard intended to specify the encoding, structure and semantics of clinical documents for exchange. Further reading can be found here: http://www.hl7.org/implement/standards/product_section.cfm?section=10.
Step 4: Add code to display a clinical document
The getDocument web service returns a response with a CDA package that contains two XML files. We will call this web service, extract the zip file using the library Nehta.VendorLibrary.CDAPackage, and load the XML document into the Browser control using the generic style sheet provided.
1. Download the XSL generic stylesheet available on the Agency’s GitHub, and save the file locally as an XSL document.
2. Install the additional CDA packaging library to extract CDA packages, using following command.
Install-Package Nehta.VendorLibrary.CDAPackage
3. Add the additional namespaces
using System.IO;
using System.Xml;
using System.Xml.Xsl;
using Nehta.VendorLibrary.CDAPackage;
4. Double click on the Open button and add the following code for the on button click event method. Note that the VerifyCertificate method used below is defined further in this guide, you may wish to create a stub method returning true until this method is populated.
private void btnGetDocument_Click(object sender, EventArgs e)
{
var selectedDocument = (PatientDocument)lstDocumentList.SelectedItem;
// Override this value to the current patient's IHI.
header.IhiNumber = txtIHINumber.Text;
// Create the client
// SVT endpoint is "https://services.svt.gw.myhealthrecord.gov.au/getDocument"
// production endpoint is "https://services.ehealth.gov.au/getDocument"
GetDocumentClient getDocumentClient = new GetDocumentClient(new Uri("https://services.svt.gw.myhealthrecord.gov.au/getDocument"), cert, cert);
// Add server certificate validation callback
ServicePointManager.ServerCertificateValidationCallback += ValidateServiceCertificate;
// Create a request
List<RetrieveDocumentSetRequestTypeDocumentRequest> request =
new List<RetrieveDocumentSetRequestTypeDocumentRequest>();
// Set the details of the document to retrieve
request.Add(new RetrieveDocumentSetRequestTypeDocumentRequest()
{
// This should be the value of the ExternalIdentifier "XDSDocumentEntry.uniqueId" in the GetDocumentList response
DocumentUniqueId = selectedDocument.DocumentId, //"document unique id",
// This should be the value of "repositoryUniqueId" in the GetDocumentList response
RepositoryUniqueId = selectedDocument.RepositoryUniqueId //"repository unique id"
});
try
{
// Invoke the service
RetrieveDocumentSetResponseType response = getDocumentClient.GetDocument(header, request.ToArray());
string extractFolderPath = @"C:\adha\cda\extract\" + selectedDocument.Name + "\\";
// Extract the contents of a CDA package file
// Signature is verified on this call, with an exception thrown if the validation fails
var newPackage = CDAPackageUtility.Extract(
response.DocumentResponse[0].Document,
VerifyCertificate
);
// Get CDA document content
byte[] cdaRoodDocumentContent = newPackage.CDADocumentRoot.FileContent;
string rootDocumentFilePath = extractFolderPath + newPackage.CDADocumentRoot.FileName;
// Make sure extractFolderPath exist on file path.
File.WriteAllBytes(rootDocumentFilePath, cdaRoodDocumentContent);
// View the document
if (File.Exists(rootDocumentFilePath))
{
//string xmlFile = extractPath + @"\IHE_XDM\SUBSET01\CDA_ROOT.xml";
string xslFile = @"C:\adha\cda\DH_Generic_CDA_Stylesheet-1.6.0.xsl";
XslCompiledTransform xslDocument = new XslCompiledTransform();
xslDocument.Load(xslFile);
StringWriter stringWriter = new StringWriter();
XmlWriter xmlWriter = new XmlTextWriter(stringWriter);
xslDocument.Transform(rootDocumentFilePath, xmlWriter);
webBrowserView.DocumentText = stringWriter.ToString();
}
}
catch (FaultException ex)
{
// Handle any errors
txtError.Text = ex.Message;
}
catch (Exception ex)
{
txtError.Text = ex.Message;
}
}
Important
The CDA Package may also have an attachment and your final product should expand on the code above to verify whether any attachments exist within the package.
5. Fix the file paths for the XSL file (the generic style sheet downloaded in the previous step), and the file path for the CDA package which you want to load.
6. Build and Run the application and test the getDocumentList and getDocument web service calls.
Step 5: Use case 204 (UC.CIS.204)
When downloading a clinical document from a patient’s My Health Record you will need to meet certain testing criteria. We have listed a few test cases below and you should review the complete list within the Conformance Test Specifications.
Test Case ID | PCEHR_CIS_018721 |
---|---|
Objective | CIS should indicate the software user when viewing the downloaded document: a) That the clinical document being viewed was downloaded from the PCEHR System; and b) The date and time it was downloaded from the PCEHR System. |
This test case suggests that when the CIS software downloads documents from the My Health Record system (previously PCEHR), it should record date and time of the download and display while the user views the document. Generally this would require a mechanism for your software to save the document to the file system and related information into a database.
// Invoke the service
RetrieveDocumentSetResponseType response = getDocumentClient.GetDocument(header, request.ToArray());
// Save the zip file.
File.WriteAllBytes(filePath, response.DocumentResponse[0].Document);
_documentRepo.SaveDocInfo(documentId, filePath, "PCEHR", DateTime.Now);
This screenshot shows the document source as the My Health Record system along with the download date.
Test Case ID | PCEHR_CIS_019041 (Mandatory) |
---|---|
Objective | The Clinical Information System shall provide a capability to save or print a clinical document downloaded from the PCEHR System. |
This test case suggests that the CIS software should have functionality to either save or print the document.
private void btnPrint_Click(object sender, EventArgs e)
{
webDocumentView.Print();
}
Test Case ID | PCEHR_CIS_018634 |
---|---|
Objective | The clinical information system shall verify the CDA package hash value of a clinical document package downloaded from the PCEHR System and it shall indicate if the downloaded clinical document has been modified. |
How to Evaluate | Perform an operation to retrieve a CDA package where the clinical document within the package has been changed to ensure that its hash value will not match the hash value in the package signature: a. Verify that the software indicates to the user that the CDA package hash value comparison has found the CDA package value is invalid. |
This test case requires that the HASH of each clinical document must be verified before saving/printing or rendering for users. The software would then indicate to the CIS user if it is invalid.
Important
Before beginning the following to code, we recommend reading Clarification of conformance requirement 018634 regarding hash value.
The following code downloads a clinical package and uses the CDAPackageUtility to extract the document. When the library extracts the CDA package it verifies the signature and throws an exception if the validation fails.
// Invoke the service
RetrieveDocumentSetResponseType response = getDocumentClient.GetDocument(header, request.ToArray());
CDAPackage newPackage;
string message = "";
// Extract the contents of a CDA package file and validate ths signature
try
{
// Signature is verified on this call, with an exception thrown if the validation fails
// Save as zip file from response or provide file directly.
newPackage = CDAPackageUtility.Extract(response.DocumentResponse[0].Document, VerifyCertificate);
}
catch (Exception ex)
{
// If exception thrown due to failed signature validation, capture the reason.
// We recommend displaying the error to the user and continue to render the document.
// This way the user can make a judgement call on whether or not to trust the
// information contained within the document.
message = ex.Message;
// use message to indicate user as CDA package is not valid.
// No Signature validation checked to allow user to view package still
newPackage = CDAPackageUtility.Extract(response.DocumentResponse[0].Document, null);
}
// Get CDA document content
byte[] cdaRoodDocumentContent = newPackage.CDADocumentRoot.FileContent;
// Verify Certificate Method:
public void VerifyCertificate(X509Certificate2 certificate)
{
// This is an sample certificate check, which does an online revocation check.
// In the future, there may be CDA packages which are signed with certificates
// which are valid at signing time, but have since been revoked or expired.
// In this case, the certificate check should be relaxed. One such way is to
// change the revocation mode to "no check". Eg:
// chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
// Setup the chain
var chain = new X509Chain();
chain.ChainPolicy.RevocationMode = X509RevocationMode.Online;
chain.ChainPolicy.RevocationFlag = X509RevocationFlag.EntireChain;
// Perform the validation
chain.Build(certificate);
// Check the results
if (chain.ChainStatus.Length == 0)
{
// No errors found
}
else
{
// Errors found
}
}
Test Case ID | PCEHR_CIS_019108 |
---|---|
Objective | The Clinical Information System should provide a mechanism to retrieve: • A list of clinical documents associated with a healthcare recipient’s PCEHR (registryStoredQuery service); and • A list of historical versions of a particular clinical document from the PCEHR System (getChangeHistoryView service). |
Note that the above requirement is optional.
In this guide we have already seen how to retrieve a list of clinical documents for a patient. For this test case we retrieve the document change history using the following code and the getChangeHistoryView web service.
private void GetDocumentHistoricalVersions()
{
var selectedDocument = (PatientDocument)lstDocumentList.SelectedItem;
// Instantiate the client
GetChangeHistoryViewClient changeHistoryViewClient = new GetChangeHistoryViewClient(new Uri("https://services.svt.gw.myhealthrecord.gov.au/getChangeHistoryView"), cert, cert);
// Add server certificate validation callback
ServicePointManager.ServerCertificateValidationCallback += ValidateServiceCertificate;
try
{
var changeHistoryView = changeHistoryViewClient.GetChangeHistoryView(
header, new getChangeHistoryView() { documentID = selectedDocument.DocumentId}
);
}
catch (FaultException e)
{
// Handle any errors
}
}
Test Case ID | PCEHR_CIS_019119 (Optional) |
---|---|
Objective | The Clinical Information System should provide a warning to the CIS User if the healthcare recipient's demographic information in a clinical document downloaded from the PCEHR System does not match the demographic information in the local healthcare recipient's record. |
This test case ensures that when a clinical document is downloaded for a patient, the CIS should warn the user if the patient demographic details from the document do not match with the local record. We have already seen in the tutorial how to download the CDA package and extract it. The CDA package contains the XML document (CDA_ROOT.xml) with the patient data.
<recordTarget typeCode="RCT"><patientRole classCode="PAT"><id root="26ab8b1b-82a8-492e-b056-3cae73f9a33d"/>
<addruse="H">
<country>Australia</country><state>NSW</state><city>NEWCASTLE</city><postalCode>2300</postalCode><streetAddressLine>787 CCA HOPE ST</streetAddressLine></addr>
<patient><name><family>MORSE</family><given>ARTHAR</given></name><administrativeGenderCode displayName="Male" codeSystemName="AS 5017-2006 Health Care Client Identifier Sex" codeSystem="2.16.840.1.113883.13.68" code="M"/><birthTime value="20001212"/><ethnicGroupCode displayName="Not stated/inadequately described" codeSystemName="METeOR Indigenous Status" codeSystem="2.16.840.1.113883.3.879.291036" code="9"/><ext:asEntityIdentifier classCode="IDENT"><ext:id root="1.2.36.1.2001.1003.0.8003601240025671" assigningAuthorityName="IHI"/><ext:assigningGeographicArea classCode="PLC"><ext:name>National Identifier</ext:name></ext:assigningGeographicArea></ext:asEntityIdentifier></patient></patientRole></recordTarget>
Let’s assume you have a method which will verify the demographic details of the patient with the clinical document xml.
XElement documentXml = XElement.Load("CDA_ROOT.xml");
if(! IsValidDemographicsDetails(documentXml, patientObj))
{
MessageBox.Show("The patient details in your local system do not match the patient details in the downloaded clinical document.", "Patient demographics notice", MessageBoxButtons.OK, MessageBoxIcon.Warning);
}
Conclusion
In this guide we have implemented the GetDocumentList, GetDocument, and GetChangeHistoryView web services. The next guide will continue from this point and explore more functionality of the My Health Record system.
If you have any feedback about this guide, please contact us at [email protected].
View All | Back | Next: Get Views - My Health Record B2B Developer Guide 4