Medidata.RWS.NET Documentation¶
Medidata.RWS.NET is a comprehensive, fluent .NET API library for Medidata RAVE Web Services (RWS). It handles a large portion of the boilerplate C# code you’d normally have to write in order to communicate with RWS, allowing you to get up and running faster.
Introduction¶
Assumptions¶
This documentation assumes that the reader has a familiarity with the Medidata RAVE platform, as well as a basic understanding of RESTful Web Service concepts. While the Medidata.RWS.NET library attempts to abstract away some of the typical HTTP communication mechanisms required when making typical web service requests, the underlying HTTP codes and raw request / response data is still available to developers.
Acknowledgments¶
The creation of this library wouldn’t have been possible without inspiration and examples from some fine folks at Medidata Solutions, specifically Ian Sparks and Geoff Low. In many ways, the library has taken some inspiration from the Python flavored “rwslib”, available on Github: https://github.com/mdsol/rwslib.
Getting Started¶
Installation¶
Install Medidata.RWS.NET via NuGet:
nuget install Medidata.RWS.NET
or, via the Package Manager Console:
Install-Package Medidata.RWS.NET
Basic Example¶
At the most basic level, communicating with Medidata RAVE using Medidata.RWS.NET involves:
- Creating a connection.
using Medidata.RWS.Core.Requests;
var connection = new RwsConnection("innovate", "RAVE username", "RAVE password");
The above code will create a RwsConnection object to the “innovate” RAVE instance (subdomain) - https://innovate.mdsol.com. You’d substitute your RAVE instance subdomain here. The RAVE username and RAVE password parameters should reference a dedicated RAVE account you intend to use for web service activities.
- Creating a request.
using Medidata.RWS.Core.Requests.Datasets;
var datasetRequest = new SubjectDatasetRequest("MediFlex", "PROD", subject_key: "SUBJECT001", formOid: "HEM");
- Sending the request.
var response = connection.SendRequest(datasetRequest) as RWSResponse;
- Dealing with the response/exception.
Console.Write(response.RawXMLString());
- Putting this all together, we have the following.
using Medidata.RWS.Core.Requests.Datasets;
using Medidata.RWS.Core.Requests;
//Create a connection
var connection = new RwsConnection("innovate", "RAVE username", "RAVE password");
//Create a request
var datasetRequest = new SubjectDatasetRequest("MediFlex", "PROD", subject_key: "SUBJECT001", formOid: "HEM");
//Send the request / get a response
var response = connection.SendRequest(datasetRequest) as RWSResponse;
//Write the response XML string to the console
Console.Write(response.RawXMLString());
The above steps outline how to retrieve data for SUBJECT001’s HEM form in the MediFlex study (specifically, the PROD environment).
Miscellaneous Configuration¶
When working with Medidata.RWS.NET, you may wish to configure the connection to RWS beyond the default settings. Options for doing so are described below.
Virtual Directory¶
When you make requests to RAVE Web services, the URL you communicate with follows a specific pattern. The domain name will always be “mdsol.com”, and the protocol will always be “https://”. The sub-domain & virtual directory are configurable. The default virtual directory is “RaveWebServices”. This means that when you create a new connection to innovate using the default virtual directory, you’ll get the following:
using Medidata.RWS.Core.Requests;
//Create a connection
var connection = new RwsConnection("innovate", "RAVE username", "RAVE password");
connection.base_url; // 'https://innovate.mdsol.com/RaveWebServices'
If instead you’d like to change the virtual directory to a custom one, you can pass an additional parameter through the RwsConnection constructor:
using Medidata.RWS.Core.Requests;
//Create a connection
var connection = new RwsConnection("innovate", "RAVE username", "RAVE password", "CustomVirtualDirectory");
connection.base_url; // 'https://innovate.mdsol.com/CustomVirtualDirectory'
Note that this is provided as a convenience, and the default virtual directory will be preferable in most scenarios.
Timeouts¶
If supplied, the timeout of the request in milliseconds. If the request takes longer than the timeout value, an exception will be thrown.
using Medidata.RWS.Core.Requests;
//Create a connection
var connection = new RwsConnection("innovate", "RAVE username", timeout: 1000)
Topics¶
Authentication¶
In order to use Medidata RAVE web services, you must authenticate your web service requests. You can do this by supplying both a RAVE username and RAVE password parameter when you establish a RwsConnection
object. This username/password should reference a dedicated RAVE account you intend to use for web service activities.
For example:
using Medidata.RWS.Core.Requests;
var connection = new RwsConnection("innovate", "rwsUser1", "password1");
The above code will create a RwsConnection
object and point it to the innovate RAVE instance (subdomain) - https://innovate.mdsol.com
. The username and password you provide are concatenated, base64-encoded, and passed in the Authorization HTTP header each time you make a request using the connection object, as follows:
Authorization: Basic cndzVXNlcjE6cGFzc3dvcmQx
Do not share your username / password in publicly accessible areas such GitHub, client-side code, and so forth. Authentication is only required when establishing a new RwsConnection
object. The Medidata.RWS.NET library will then automatically send the appropriate Authorization header for each request made with this connection object.
API requests without authentication will fail.
Errors¶
Medidata RAVE web services uses HTTP response codes to indicate success or failure of an API request. Generally speaking, HTTP codes in the ranges below mean the following:
- 2xx - success
- 4xx - an error that failed given the information provided
- 5xx - an error with Medidata’s servers
Not all errors map cleanly onto HTTP response codes, however. Medidata usually attempts to return a “RWS Reason Code” in addition to the conventional HTTP code to explain in more detail about what went wrong. To see a full listing of these codes, refer to: Rave Web Services Error Responses - Complete List
Handling errors¶
The Medidata.RWS.NET library can raise an exception for a variety reasons, such as invalid parameters, authentication errors, and network unavailability. We recommend writing code that gracefully handles all possible API exceptions.
Basic Requests¶
Medidata.RWS.NET provides some basic diagnostic / health check API requests out of the box.
VersionRequest()¶
Returns the RWS version number. Specifically, this is the textual response returned when calling https://{ subdomain }.mdsol.com/RaveWebServices/version
.
using Medidata.RWS.Core.Requests;
//Create a connection
var connection = new RwsConnection("innovate"); // no authentication required
//Send the request / get a response
var response = connection.SendRequest(new VersionRequest()) as RWSTextResponse;
//Write the response text to the console
Console.Write(response.ResponseText);
//1.15.0
BuildVersionRequest()¶
Returns the internal RWS build number. Specifically, this is the textual response returned when calling https://{ subdomain }.mdsol.com/RaveWebServices/version/build
.
using Medidata.RWS.Core.Requests;
//Create a connection
var connection = new RwsConnection("innovate"); // no authentication required
//Send the request / get a response
var response = connection.SendRequest(new BuildVersionRequest()) as RWSTextResponse;
//Write the response text to the console
Console.Write(response.ResponseText);
//5.6.5.335
TwoHundredRequest()¶
Returns the html document (along with a 200 HTTP response code) that contains information about the MAuth configuration of Rave Web Services with the given configuration.
Specifically, this is the html response returned when calling https://{ subdomain }.mdsol.com/RaveWebServices/twohundred
.
using Medidata.RWS.Core.Requests;
//Create a connection
var connection = new RwsConnection("innovate"); // no authentication required
//Send the request / get a response
var response = connection.SendRequest(new TwoHundredRequest()) as RWSTextResponse;
//Write the response text to the console
Console.Write(response.ResponseText);
//<!DOCTYPE html>\r\n<html>\r\n<head><script..........
CacheFlushRequest()¶
Send a request to flush the RWS cache. Typically, this is used to immediately implement configuration changes in RWS.
Under normal circumstances, this request is unnecessary as RAVE and RWS manage their own caching mechanisms automatically.
Specifically, this is the equivalent of calling https://{ subdomain }.mdsol.com/RaveWebServices/webservice.aspx?CacheFlush
.
using Medidata.RWS.Core.Requests;
//Create a connection
var connection = new RwsConnection("innovate", "username", "password"); // authentication is required
//Send the request / get a response
var response = connection.SendRequest(new CacheFlushRequest()) as RWSResponse;
//Write the response text to the console
Console.Write(response.IsTransactionSuccessful);
//true
Core Resources¶
Clinical Data¶
Often times you’ll want to work with the clinical data of your studies. The Medidata.RWS.NET library provides several requests for extracting clinical data (via RAVE “Clinical Views”) or POSTing clinical data to the RAVE platform.
ClinicalStudiesRequest¶
Returns a list of EDC studies (as a RWSStudies
object). Excludes studies that you (the authenticated user) are not associated with.
Example:
using Medidata.RWS.Core.Requests.Implementations;
//Create a connection
var connection = new RwsConnection("innovate", "username", "password"); // authentication required
//Send the request / get a response
var response = connection.SendRequest(new ClinicalStudiesRequest()) as RWSStudies;
//Write the study list to the console
foreach (var s in response)
{
Console.Write(s.OID + "\r\n");
}
//Mediflex(Prod)
//Mediflex(Dev)
//PlaceboTest(Prod)
//...
StudySubjectsRequest¶
Returns a listing of all the subjects in a study (as a RWSSubjects
object), optionally including those currently inactive or deleted.
Clinical data for the subjects is not included in the response.
This is the equivalent of calling:
https://{subdomain}.mdsol.com/studies/{study-oid}/Subjects[?status=all&include={inactive|inactiveAndDeleted}]
Parameters¶
Parameter | Description | Mandatory? |
---|---|---|
{study-oid} | The study name. | Yes |
status={true | false} | If true, add subject level workflow status to the response (if present). |
No |
include= {inactive | deleted | inactiveAndDeleted} |
Will include active, inactive and/or deleted subjects in the response. |
No |
subjectKeyType= {SubjectName | SubjectUUID} |
Whether RWS should return the unique identifier (UUID) or the subject name. |
No |
links={true | false} | If true, includes “deep link”(s) (e.g. URLs) to the subject page in Rave in the response. |
No |
Example:
using Medidata.RWS.Core.Requests.Implementations;
//Create a connection
var connection = new RwsConnection("innovate", "username", "password"); // authentication required
//Send the request / get a response
var response = connection.SendRequest(new StudySubjectsRequest("Mediflex", "Prod")) as RWSSubjects;
//Write each subject key to the console
foreach (var s in response)
{
Console.Write(s.SubjectKey + "\r\n");
}
// SUBJECT001
// SUBJECT002
// SUBJECT003
// ...
Clinical View Datasets¶
In addition to the above requests, Medidata RAVE Web Services allows for the extraction of clinical data in the form of “Clinical Views” - that is, RAVE database views. There are 3 “Datasets” available that represent different subsets of clinical data for your studies:
StudyDatasetRequest¶
Clinical data in ODM format for the given study / environment. This data can be optionally filtered by a specific Form.
This is the equivalent of calling:
https://{subdomain}.mdsol.com/studies/{project}({environment})/datasets/{ regular|raw }?{options}
or, to filter the data by form:
https://{subdomain}.mdsol.com/studies/{project}({environment})/datasets/{ regular|raw }/{ formoid }?{options}
Example:
using Medidata.RWS.Core.Requests
using Medidata.RWS.Core.Requests.Datasets;
//Create a connection
var connection = new RwsConnection("innovate", "username", "password"); // authentication required
//Send the request / get a response
var response = connection.SendRequest(new StudyDatasetRequest("Mediflex", "Prod", dataset_type: "regular")) as RWSResponse;
//Write the XML response to the console (see XML below)
Console.Write(response.RawXMLString());
<?xml version="1.0" encoding="utf-8"?>
<ODM FileType="Snapshot" FileOID="92747321-c8b3-4a07-a874-0ecb53153f20" CreationDateTime="2017-06-05T13:09:33.202-00:00" ODMVersion="1.3" xmlns:mdsol="http://www.mdsol.com/ns/odm/metadata" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.cdisc.org/ns/odm/v1.3">
<ClinicalData StudyOID="Mediflex(Prod)" MetaDataVersionOID="1">
<SubjectData SubjectKey="1">
<SiteRef LocationOID="1" />
<StudyEventData StudyEventOID="SCREENING" StudyEventRepeatKey="1">
<FormData FormOID="CHEM" FormRepeatKey="1">
<ItemGroupData ItemGroupOID="CHEM_LOG_LINE">
<ItemData ItemOID="CHEM.DATECOLL" Value="2015-04-25T14:09:00" />
</ItemGroupData>
</FormData>
</StudyEventData>
</SubjectData>
</ClinicalData>
<ClinicalData StudyOID="Mediflex(Prod)" MetaDataVersionOID="1">
<SubjectData SubjectKey="2">
<SiteRef LocationOID="1" />
<StudyEventData StudyEventOID="SCREENING" StudyEventRepeatKey="1">
<FormData FormOID="CHEM" FormRepeatKey="1">
<ItemGroupData ItemGroupOID="CHEM_LOG_LINE">
<ItemData ItemOID="CHEM.DATECOLL" Value="2015-04-13T16:34:00" />
</ItemGroupData>
</FormData>
</StudyEventData>
</SubjectData>
</ClinicalData>
<ClinicalData StudyOID="Mediflex(Prod)" MetaDataVersionOID="1">
<SubjectData SubjectKey="3">
<SiteRef LocationOID="1" />
<StudyEventData StudyEventOID="SCREENING" StudyEventRepeatKey="1">
<FormData FormOID="CHEM" FormRepeatKey="1">
<ItemGroupData ItemGroupOID="CHEM_LOG_LINE">
<ItemData ItemOID="CHEM.DATECOLL" Value="2015-05-09T18:52:00" />
</ItemGroupData>
</FormData>
</StudyEventData>
</SubjectData>
</ClinicalData>
...
</ODM>
SubjectDatasetRequest¶
Clinical data in ODM format for the given study / environment for a single subject. Similar to StudyDatasetRequest
,
this data can be optionally filtered by a specific Form.
This is the equivalent of calling:
https://{subdomain}.mdsol.com/studies/{project}({environment})/subjects/{ subjectkey }/datasets/{ regular|raw }?{options}
or, to filter the data by form:
https://{subdomain}.mdsol.com/studies/{project}({environment})/subjects/{ subjectkey }/datasets/{ regular|raw }/{ formoid }?{options}
using Medidata.RWS.Core.Requests
using Medidata.RWS.Core.Requests.Datasets;
//Create a connection
var connection = new RwsConnection("innovate", "username", "password"); // authentication required
//Send the request / get a response
var response = connection.SendRequest(new SubjectDatasetRequest("Mediflex", "Prod", subject_key: "1", dataset_type: "regular")) as RWSResponse;
//Write the XML response to the console (see XML below)
Console.Write(response.RawXMLString());
<?xml version="1.0" encoding="utf-8"?>
<ODM FileType="Snapshot" FileOID="9035596c-f090-4030-860a-0ed27a4e3d03" CreationDateTime="2017-06-05T13:28:39.325-00:00" ODMVersion="1.3" xmlns:mdsol="http://www.mdsol.com/ns/odm/metadata" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.cdisc.org/ns/odm/v1.3">
<ClinicalData StudyOID="Mediflex(Prod)" MetaDataVersionOID="1">
<SubjectData SubjectKey="1">
<SiteRef LocationOID="1" />
<StudyEventData StudyEventOID="SCREENING" StudyEventRepeatKey="1">
<FormData FormOID="CHEM" FormRepeatKey="1">
<ItemGroupData ItemGroupOID="CHEM_LOG_LINE">
<ItemData ItemOID="CHEM.DATECOLL" Value="2015-04-25T16:09:00" />
</ItemGroupData>
</FormData>
</StudyEventData>
</SubjectData>
</ClinicalData>
<ClinicalData StudyOID="Mediflex(Prod)" MetaDataVersionOID="1">
<SubjectData SubjectKey="1">
<SiteRef LocationOID="1" />
<StudyEventData StudyEventOID="SCREENING" StudyEventRepeatKey="1">
<FormData FormOID="ABX" FormRepeatKey="1">
<ItemGroupData ItemGroupOID="ABX_LOG_LINE">
<ItemData ItemOID="ABX.ABXDATE" Value="2017-04-25" />
<ItemData ItemOID="ABX.MODALITY" Value="2" />
</ItemGroupData>
</FormData>
</StudyEventData>
</SubjectData>
</ClinicalData>
<ClinicalData StudyOID="Mediflex(Prod)" MetaDataVersionOID="1">
<SubjectData SubjectKey="1">
<SiteRef LocationOID="1" />
<StudyEventData StudyEventOID="SCREENING" StudyEventRepeatKey="1">
<FormData FormOID="BONEMARROW" FormRepeatKey="1">
<ItemGroupData ItemGroupOID="BONEMARROW_LOG_LINE">
<ItemData ItemOID="BONEMARROW.VISITDAT" Value="2015-04-24" />
<ItemData ItemOID="BONEMARROW.CHEMSAMPLE" Value="1" />
<ItemData ItemOID="BONEMARROW.BMPB_COLLECT" Value="1" />
...
</ItemGroupData>
</FormData>
</StudyEventData>
</SubjectData>
</ClinicalData>
...
</ODM>
VersionDatasetRequest¶
Clinical data in ODM format for the given study / environment for a single RAVE study version for all subjects.
Similar to StudyDatasetRequest
, this data can be optionally filtered by a specific Form.
This is the equivalent of calling:
https://{subdomain}.mdsol.com/studies/{project}({environment})/versions/{ version_id }/datasets/{ regular|raw }?{options}
or, to filter the data by form:
https://{subdomain}.mdsol.com/studies/{project}({environment})/versions/{ version_id }/datasets/{ regular|raw }/{ formoid }?{options}
using Medidata.RWS.Core.Requests
using Medidata.RWS.Core.Requests.Datasets;
//Create a connection
var connection = new RwsConnection("innovate", "username", "password"); // authentication required
//Send the request / get a response
var response = connection.SendRequest(new VersionDatasetRequest(project_name: "Mediflex", environment_name: "Dev", version_oid: "999")) as RWSResponse;
//Write the XML response to the console (see XML below)
Console.Write(response.RawXMLString());
Note the **MetaDataVersionOID* value in the XML response.*
<?xml version="1.0" encoding="utf-8"?>
<ODM FileType="Snapshot" FileOID="9035596c-f090-4030-860a-0ed27a4e3d03" CreationDateTime="2017-06-05T13:28:39.325-00:00" ODMVersion="1.3" xmlns:mdsol="http://www.mdsol.com/ns/odm/metadata" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.cdisc.org/ns/odm/v1.3">
<ClinicalData StudyOID="Mediflex(Dev)" MetaDataVersionOID="999">
<SubjectData SubjectKey="1">
<SiteRef LocationOID="1" />
<StudyEventData StudyEventOID="SCREENING" StudyEventRepeatKey="1">
<FormData FormOID="CHEM" FormRepeatKey="1">
<ItemGroupData ItemGroupOID="CHEM_LOG_LINE">
<ItemData ItemOID="CHEM.DATECOLL" Value="2015-04-25T16:09:00" />
</ItemGroupData>
</FormData>
</StudyEventData>
</SubjectData>
</ClinicalData>
<ClinicalData StudyOID="Mediflex(Dev)" MetaDataVersionOID="999">
<SubjectData SubjectKey="2">
<SiteRef LocationOID="1" />
<StudyEventData StudyEventOID="SCREENING" StudyEventRepeatKey="1">
<FormData FormOID="CHEM" FormRepeatKey="1">
<ItemGroupData ItemGroupOID="CHEM_LOG_LINE">
<ItemData ItemOID="CHEM.DATECOLL" Value="2016-04-25T16:09:00" />
</ItemGroupData>
</FormData>
</StudyEventData>
</SubjectData>
</ClinicalData>
<ClinicalData StudyOID="Mediflex(Dev)" MetaDataVersionOID="999">
<SubjectData SubjectKey="3">
<SiteRef LocationOID="1" />
<StudyEventData StudyEventOID="SCREENING" StudyEventRepeatKey="1">
<FormData FormOID="CHEM" FormRepeatKey="1">
<ItemGroupData ItemGroupOID="CHEM_LOG_LINE">
<ItemData ItemOID="CHEM.DATECOLL" Value="2017-04-25T16:09:00" />
</ItemGroupData>
</FormData>
</StudyEventData>
</SubjectData>
</ClinicalData>
...
</ODM>
Using Builders¶
When communicating with Medidata RAVE Web Services, your data payloads will take the form of ODM XML - or Operational Data Model XML documents.
It’s important to understand that RWS expects the XML data you send to conform to the ODM format - malformed or otherwise improperly formatted XML won’t be processed. Since creating these data structures manually can be time consuming and tedious, Medidata.RWS.NET provides several “Builder” classes to help.
Basic Example - Register a Subject¶
By way of example, let’s say you want to register a subject onto a RAVE study using RWS. In order to do this, you’ll need:
- An authenticated connection to RWS
- An XML document that represents the POST request you intend to make, which will include:
- A Study OID (study)
- A LocationOID (site)
- A SubjectKey (subject)
- A way to deal with the response after the request is sent
The ODMBuilder
class allows developers to build out the ODM XML documents required for transmission using a simple to use, fluent interface.
Using the above example, let’s create a new ODMBuilder
instance to register a subject:
var odmObject = new ODMBuilder().WithClinicalData("MediFlex", cd =>
cd.WithSubjectData("SUBJECT001", "SITE01", sd =>
sd.WithTransactionType(TransactionType.Insert)));
After instantiating the ODMBuilder
class, you’ll notice that you have access to chain-able methods which allow you to construct the object appropriate
for your use case. Since we are registering a subject, we supplied a Study OID (Mediflex
), Subject Key (SUBJECT001
), and Site (SITE01
).
Each of the nested methods used (e.g. WithClinicalData
, WithSubjectData
, and WithTransactionType
) map to the specific XML node we want
to construct.
To see the XML string representation of what we’ve got so far, you can use the AsXMLString()
method, which will convert the ODM object
you constructed into an XML string.
For example:
string registrationXml = new ODMBuilder().WithClinicalData("MediFlex", cd =>
cd.WithSubjectData("SUBJECT001", "SITE01", sd =>
sd.WithTransactionType(TransactionType.Insert))).AsXMLString();
would produce:
<?xml version="1.0" encoding="utf-16"?>
<ODM xmlns:mdsol="http://www.mdsol.com/ns/odm/metadata" FileType="Transactional" Granularity="All" FileOID="1d84fb20-1959-45bf-b9c4-cf2ad7a4273d" CreationDateTime="2017-09-14T15:01:50.8441121-04:00" AsOfDateTime="0001-01-01T00:00:00" ODMVersion="1.3" xmlns="http://www.cdisc.org/ns/odm/v1.3">
<ClinicalData StudyOID="MediFlex" MetaDataVersionOID="1">
<SubjectData SubjectKey="SUBJECT001" TransactionType="Insert">
<SiteRef LocationOID="SITE01" />
</SubjectData>
<AuditRecords />
<Signatures />
<Annotations />
</ClinicalData>
</ODM>
Using this ODM conformant XML, you could now POST it to RAVE by wrapping it in a PostDataRequest
object:
RwsConnection conn = new RwsConnection("innovate", "username", "password");
var registrationRequest = new PostDataRequest(registrationXml);
var response = conn.SendRequest(registrationRequest) as RWSPostResponse;
//If successful, SUBJECT001 should be registered in SITE01 for the Mediflex study.