A .Net client for ServiceNow's REST TableAPI in C#
***Updated*
11/29/2016: ResourceLink's now can be updated without having to PUT or POST back as a separate String property. A JsonConverter class has been added that returns the resource links value as a reference string which is what Service Now expects when updating or creating a ResourceLink. I have updated this article; recommend retrieving through the git repo here: https://github.com/merccat/ServiceNowRESTClient
Greetings All,
Our organization had prior to the release of the REST API made fairly extensive use of the SOAP API. Unfortunately (I mean fortunately) those have continued working great so I have not had an opportunity to sit down and work with the REST API. This week the opportunity finally came up so I decided to develop a .Net client to hopefully make working the TableAPI in .Net dead simple. My goal was a client that was not tied to any particular integration project and would be easy to move between projects. Hopefully this can be of some use to someone else.
In this article I plan to provide the following in this order:
- Overview, Constructors, Methods & Example.
- Code for all of the core components with descriptions of what is happening.
- A link to a completed class library project that can be customized if needed compiled and included in your integration solution. I may try putting it on a Git Repo but have not really decided yet how I'm going to make the project available.
What I would like back:
- Suggestions for improvements (this is my first real crack at it and I'm sure it can be improved).
Disclaimer:
- We have tested this in our environment, however, this comes with no guarantees of any kind. Make sure the user you are using has appropriate access levels to be able to accomplish what you want while protecting the integrity of your system. Always test, re-test and test some more in non-production environments first.
Part 1: Overview and Usage
Overview
The ServiceNow TableAPI Client is designed to provide access to your ServiceNow instance's data through ServiceNow's REST TableAPI in a .Net application. It is written in C#, however once compiled and referenced as a DLL, you could certainly use it in a VB project as well if you were so inclined. JSON Serialization / DeSerialization is done with the Newtonsoft.JSON package and I used System.Net.WebClient for the actual communication.
There are only 4 classes including the client itself. Those classes are ServiceNowRecord, ServiceNowLink, TableAPIClient and RESTResponse.
- Record (Abstract) - All record types your implementation passes between the client will need to inherit from this base class. Types derived from ServiceNowRecord will simply contain a list of fields which you want to manipulate with the selected table. The properties much match the field names in ServiceNow. sys_id is already included.
- ResourceLink - A pre-built representation of a standard Link as returned by Service Now that can be included as a property in your record type.
- RESTResponse - All methods that have a response will package the response in a RestReponse where T is your record type. It is packaged this way so that errors returned by service now can be included separately. NOTE: This is actually a base class, from which there are two classes you'll actually use derived:
- RESTQueryResponse - The response property is a List
- RESTSingleResponse - The response property is simply T.
- RESTQueryResponse - The response property is a List
- TableAPIClient (where T is a ServiceNowRecord) - The actual client. On initialization it uses reflection to build a list of fields for the web request query string which is part of why the field names in your Class derived from ServiceNowRecord need to use the actual field names.
Client Constructors
| Signature | Description |
|---|---|
| TableAPIClient(String tableName, String instanceName, NetworkCredential credentails) | Table name is the name of the table in service now to be accessedInstance name is the name of your service now instances. ie instanceName.service-now.comCredentials is a set of network credentials (with username/pwd) for the user to be used. |
| TableAPIClient(String tableName, String instanceName, String userName, String pwd) | Same as above but simple strings for username and password instead of NetworkCredential. |
Public Methods
| Name | Description |
|---|---|
| RestSingleResponse<T> GetById (String id) | Retrieves a single record contained in the RESTSingleResponse of the type T defined for the table client.Any errors will be fully captured and returned in the ErrorMsg property of the response.id is the sys_id of the record to be retrieved. |
| RestQueryResponse<T> GetByQuery (String query) | Retrieves a record set in the response (as a list of T) from the query result (if successful) along with any error messages (if any).query is a standard service-now table query. |
| RestSingleResponse<T> Post(T record) | Creates a new record in the table using the fields provided in the record of type T. A RestResponse containing a single result of T (if successful) for your newly created record, along with any error messages (if any).record is the record of type T to be added to the table in ServiceNow. |
| RestSingleResponse<T> Put(T record) | Updates an existing record in the table. Make sure your record has sys_id populated. All fields of T will be sent in the update so make sure they are ALL populated unless you want the field to be emptied. A good practice is to first retrieve the record using GetById, then only edit the fields you want to change before submitting a Put. A RestResponse containing a single result of T with your updated record (if successfull) along with any error messages (if any).record is the record of type T to be added to the table in ServiceNow. |
| Delete(String id) | Deletes a record in the active table with the specified id.WARNING: Since there is no return type, you will need to catch any errors externally. The exception message will still include all details including any response from Service Now. |
Example
I tried to make the actual usage of the client as simple and straight forward as possible. The following code is of course assuming you have already included the compiled DLL in your project (or are working from the client code project - avoid that). The example will be working with a few fields from the sys_user table.
Step 1:
Create a ServiceNowRecord representing the data structure you want to work with from your table. This can be thought of as our data contract. The example below is a simple example to retrieve some user details. Normally we would of course be working with more fields than this, but lets keep it short and sweet:
using Newtonsoft.Json;
{
public class ServiceNowUser : Record
{
[JsonProperty("employee_number")]
public string employee_number { get; set; }
[JsonProperty("first_name")]
public string first_name { get; set; }
[JsonProperty("last_name")]
public string last_name { get; set; }
}
}
Notice how there is no mention of the table name here, I'm only concerned about the fields. Also, sys_id isn't included because the base ServiceNowRecord already includes that.
Also, note that currently all properties are returned as strings. Why? Well, because everything in a JSON string is a string until you parse it. I'm sure you can add parsers to your record type if needed, but for this case none are needed since everything is a string anyway.
Step 2:
Instantiate my client using the record type created above. Note when instantiating the client I will also need to provide my instance name, the table name I want to work with and a set of credentials (either as username and password or System.Net.NetworkCredential).
TableAPIClient UserClient = new TableAPIClient("sys_user", "intance_name", "api_username", "password");
Step 3:
Invoke a method. Lets get a record (assuming we know it's sys_id):
var response = UserClient.GetById("7648c1904a36231701673929d43fd325");
if(!response.IsError) { ServiceNowUser myUser = response.result; }
or for another example, we can query for all active users in a given department (agency) using a standard service now query:
Users = new List();
string query = "u_agency.sys_id=e2924ee14a3623170084831c4a6b5b9cactive=true";
var result = UserClient.GetByQuery(query);
if (!result.IsError) { Users = result.result; }
Part 2: Core Code
The following code is not an example of usage, it is the code of the client itself along with key supporting classes.
Record
This is an abstract class used to build classes which describe the fields to be retrieved (or updated) from a given table in ServiceNow.
public abstract class Record
{
[JsonProperty("sys_id")]
public string sys_id { get; set; }
// Prevent sys_id from being serialized when sending to ServiceNow while still allowing it to be de-serialized in the response
public bool ShouldSerializesys_id() { return false; }
}
ResourceLink
Related records are returned by link. Since I do frequently retrieve related records, I find this class to be useful, although it's not technically part of the core, it will be included in the posted solution. An example of usage will also be provided.
public class ResourceLink
{
[JsonProperty("link")]
public string link { get; set;} // REST URL for the child record
[JsonProperty("value")]
public string value { get; set; } // sys_id of the child record.
public override string ToString() { return value; }
}
ResourceLinkConverter
Since Service Now requires related objects (returned as a ResourceLink) to be updated as a string reference to the related entity (such as sys_id), this class will on Serialization for transmission back to Service Now, convert objects of type ResourceLink to a simple string value using the ToString method of the ReourceLink.
public class ResourceLinkConverter : JsonConverter
{
public override bool CanRead { get { return false; } }
public override bool CanWrite { get { return base.CanWrite; } }
public override bool CanConvert(Type objectType) { return objectType == typeof(ResourceLink); }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
JToken t = JToken.FromObject(value);
if(t.Type != JTokenType.Object)
{
t.WriteTo(writer);
}
else
{
JToken sys_id = JToken.FromObject(value.ToString());
sys_id.WriteTo(writer);
}
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
// We have set this to not operate on Read
throw new NotImplementedException();
}
}
RESTResponse
This next code block will have the 3 RESTResponse classes. First the abstract class used to build the two response types which are used, followed by RESTQueryResponse and RESTSingleResponse.
public abstract class RestResponse
{
public String RawJSON { get; set; }
public String ErrorMsg { get; set; }
public RestResponse()
{
this.RawJSON = "";
this.ErrorMsg = "";
}
public bool IsError
{
get
{
if(ErrorMsg.Length > 0) { return true; }
return false;
}
}
}
public class RESTQueryResponse : RestResponse
{
public RESTQueryResponse()
{
this.result = new List();
}
public List result { get; set; }
}
public class RESTSingleResponse : RestResponse
{
public RESTSingleResponse() { }
public T result { get; set; }
}
TableAPIClient
Finally, the client itself. Because we make use of an existing web client (System.Net.WebClient) and an existing deserializer (Newtonsoft.Json) the code here is actually fairly small. In fact the bulk of the lines of code are simply error handling.
public class TableAPIClient : ServiceNow.Interface.ITableAPIClient where T : Record
{
private String _TableName;
private String _InstanceName;
private String _FieldList;
private WebClient ServiceNowClient;
public TableAPIClient(String tableName, String instanceName, NetworkCredential credentials)
{
initialize(tableName, instanceName, credentials);
}
public TableAPIClient(String tableName, String instanceName, String userName, string password)
{
NetworkCredential credentials = new NetworkCredential { UserName = userName, Password = password };
initialize(tableName, instanceName, credentials);
}
private void initialize(String tableName, String instanceName, NetworkCredential credentials)
{
_TableName = tableName;
_InstanceName = instanceName;
// Initialize the Web Client
ServiceNowClient = new WebClient();
ServiceNowClient.Credentials = credentials;
// Build the field list from the record type that will be retrieved
_FieldList = "";
Type i = typeof(T);
foreach (var prop in i.GetProperties())
{
// We need to build the field list using the JsonProperty attributes since those strings can contain our dot notation.
var field = prop.CustomAttributes.FirstOrDefault(x => x.AttributeType.Name == "JsonPropertyAttribute");
if (field != null)
{
var fieldName = field.ConstructorArguments.FirstOrDefault(x => x.ArgumentType.Name == "String");
if (fieldName != null)
{
if (_FieldList.Length > 0) { _FieldList += ","; }
_FieldList += fieldName.Value;
}
}
}
}
private String URL { get { return "https://" + _InstanceName + ".service-now.com/api/now/table/" + _TableName; } }
private string ParseWebException(WebException ex)
{
string message = ex.Message + "\n\n";
if (ex.Response != null)
{
var resp = new StreamReader(ex.Response.GetResponseStream()).ReadToEnd();
dynamic obj = JsonConvert.DeserializeObject(resp);
message = "status: " + obj.status + "\n";
message += ex.Message + "\n\n";
message += "message: " + obj.error.message + "\n";
message += "detail: " + obj.error.detail + "\n";
}
return message;
}
public RESTSingleResponse GetById(string id)
{
var response = new RESTSingleResponse();
try
{
response.RawJSON = ServiceNowClient.DownloadString(URL + "/" + id + "?&sysparm_fields=" + _FieldList);
}
catch (WebException ex)
{
response.ErrorMsg = ParseWebException(ex);
}
catch (Exception ex)
{
response.ErrorMsg = "An error occured retrieving the REST response: " + ex.Message;
}
RESTSingleResponse tmp = JsonConvert.DeserializeObject>(response.RawJSON);
if (tmp != null) { response.result = tmp.result; }
return response;
}
public RESTQueryResponse GetByQuery(string query)
{
var response = new RESTQueryResponse();
try
{
response.RawJSON = ServiceNowClient.DownloadString(URL + "?&sysparm_fields=" + _FieldList + "&sysparm_query=" + query);
}
catch (WebException ex)
{
response.ErrorMsg = ParseWebException(ex);
}
catch (Exception ex)
{
response.ErrorMsg = "An error occured retrieving the REST response: " + ex.Message;
}
RESTQueryResponse tmp = JsonConvert.DeserializeObject>(response.RawJSON);
if (tmp != null) { response.result = tmp.result; }
return response;
}
public RESTSingleResponse Put(T record)
{
var response = new RESTSingleResponse();
try
{
string data = JsonConvert.SerializeObject(record, new ResourceLinkConverter());
response.RawJSON = ServiceNowClient.UploadString(URL + "/" + record.sys_id + "?&sysparm_fields=" + _FieldList, "PUT", data);
}
catch (WebException ex)
{
response.ErrorMsg = ParseWebException(ex);
}
catch (Exception ex)
{
response.ErrorMsg = "An error occured retrieving the REST response: " + ex.Message;
}
RESTSingleResponse tmp = JsonConvert.DeserializeObject>(response.RawJSON);
if (tmp != null) { response.result = tmp.result; }
return response;
}
public RESTSingleResponse Post(T record)
{
var response = new RESTSingleResponse();
try
{
string data = JsonConvert.SerializeObject(record, new ResourceLinkConverter());
response.RawJSON = ServiceNowClient.UploadString(URL + "?&sysparm_fields=" + _FieldList, "POST", data);
}
catch (WebException ex)
{
response.ErrorMsg = ParseWebException(ex);
}
catch (Exception ex)
{
response.ErrorMsg = "An error occured retrieving the REST response: " + ex.Message;
}
RESTSingleResponse tmp = JsonConvert.DeserializeObject>(response.RawJSON);
if (tmp != null) { response.result = tmp.result; }
return response;
}
public void Delete(string id)
{
try
{
ServiceNowClient.UploadString(URL + "/" + id, "DELETE", "");
}
catch (WebException ex)
{
string ErrorMsg = ParseWebException(ex);
throw new Exception(ErrorMsg);
}
}
}
Part 3: Completed Solution
I'm still working on a good sample solution, but for now I have put something together here: https://github.com/merccat/ServiceNowRESTClient
GitHub - merccat/ServiceNowRESTClient: REST Client for ServiceNow's TableAPI written in C#
Note that as I was building that repository, I renamed a few of the classes to shorten names and take advantage of the namespaces they are already contained in. For example instead of ServiceNowLink it's ResourceLink in the ServiceNow namespace and instead of ServiceNowRecord is simply Record in the ServiceNow namespace. Once firmed up I will update the above documentation and also post a walk-thru of the solution layout and files here.
In summary, there are 4 projects in the solution:
1. TableAPI - Contains the client and core classes.
2. TableDefinitions - Sample table definitions. Note I have not yet removed all custom fields that would only be applicable to our environment.
3. UnitTests - Feel free to add unit tests for the client for your records, or don't.
4. UsageDemo - Contains a couple examples of usage, including retrieving a more detailed record set which includes ResourceLinks and dot traversed properties.
Thank you for taking the time to look at this. I hope it can be of some use to you and your feedback is appreciated.
https://www.servicenow.com/community/developer-articles/a-net-client-for-servicenow-s-rest-tableapi-in-c/ta-p/2326420