Searching Records
There are three ways to search records, from simplest to most flexible: a text search across every field, a search on specific field values, and an advanced search that gives you sorting and every other option. All three return the same list of Record objects, one page at a time, and set the helper's NumResults to the total match count.
Text search across all fields
SearchRecordsAsync(string) behaves like the search box in the web app. It matches the text against every indexed field and returns records the signed-in user is allowed to see.
var records = await client.SearchRecordsAsync("Acme", pageNum: 1, numPerPage: 25);
foreach (var record in records)
Console.WriteLine($"{record.ID}: {record.GetDataValue("Name", 0)}");
Raw request:
GET /rest/v2/records/search?searchString=Acme&pageNum=1&numPerPage=25
Authorization: Bearer dmapi_...
Search on specific field values
This is the search most integrations need: find the record whose field equals a value. Build a list of SearchValueObj, one per field, and pass the Record Type ID to keep the search inside one Record Type. All the values must match, so two entries mean "field A equals this AND field B equals that".
public async Task<int> FindVendorRecordIDAsync(dmRestHelper client, int vendorRecordTypeID, string vendorNo)
{
var criteria = new List<SearchValueObj>
{
new SearchValueObj { SearchVariable = "VendorNo", SearchValue = vendorNo }
};
var matches = await client.SearchRecordsAsync(criteria, pageNum: 1, numPerPage: 1, recordTypeID: vendorRecordTypeID);
return matches.Count > 0 ? matches[0].ID : 0;
}
Raw request:
POST /rest/v2/records/search?pageNum=1&numPerPage=1&RecordTypeID=5
Authorization: Bearer dmapi_...
Content-Type: application/json
[
{ "SearchVariable": "VendorNo", "SearchValue": "V-1001" }
]
Pass the Record Type by its ID through the recordTypeID argument rather than by adding a RecordType search value. The ID form works on every Record Type; the field form only works when that Record Type happens to have a field named RecordType. To get the ID once at startup, list the Record Types and match on the name:
var recordTypes = await client.GetRecordTypesAsync();
var vendorTypeID = recordTypes.First(rt => rt.Name.Equals("Vendors", StringComparison.OrdinalIgnoreCase)).ID;
A SearchValueObj has a few more options. Set Range to true and fill SearchValueTo to search a span, such as a date or number between two values. Put alternatives in OrSearches when any one of several values should match. Wildcards follow the web app's rules, so ACME* matches anything starting with ACME.
Advanced search
When you need sorting, a search limited to certain record IDs, or full-text matching on document contents, build a RecordSearch and pass it to the third overload. The useful members are SearchString, SearchValues, RecordTypeID, FullText, RecordIDs, PageNum, NumPerPage, and SortField. SortField accepts LASTADDED, FIRSTADDED, CreatedDate, ChangedDate, or one or more field names separated by commas, and any of them takes a trailing DESC to reverse the order.
var search = new RecordSearch
{
RecordTypeID = vendorTypeID,
SearchValues = new List<SearchValueObj>
{
new SearchValueObj { SearchVariable = "Status", SearchValue = "Active" }
},
SortField = "Name",
PageNum = 1,
NumPerPage = 100
};
var active = await client.SearchRecordsAsync(search);
Console.WriteLine($"{active.Count} of {client.NumResults} active vendors on this page");
Raw request:
POST /rest/v2/records/searchadv
Authorization: Bearer dmapi_...
Content-Type: application/json
{
"RecordTypeID": 5,
"SearchValues": [ { "SearchVariable": "Status", "SearchValue": "Active" } ],
"SortField": "Name",
"PageNum": 1,
"NumPerPage": 100
}
Reading the results
A Record carries its field values in a Data list of name and value pairs. Read one with GetDataValue(name, lineNumber), where line number 0 is the record's own fields and 1 and up are line items. The name comparison ignores case, and a field that is not on the record comes back as an empty string rather than an error. ID is the record's permanent identifier and is what you store to come back to it later, and GetRecordAsync(id) fetches a single record by that ID.
var record = await client.GetRecordAsync(recordID);
var name = record.GetDataValue("Name", 0);
var firstLineAmount = record.GetDataValue("Amount", 1);
What "no results" looks like
A search that matches nothing returns an empty list and a 200 status; it does not throw. The exception to plan for is a DocMgtRestException with status 404, which some server versions use to mean the same thing, so a defensive integration treats that one status as an empty result and lets every other status propagate:
List<Record> matches;
try
{
matches = await client.SearchRecordsAsync(criteria, recordTypeID: vendorTypeID);
}
catch (DocMgtRestException ex) when (ex.StatusCode == System.Net.HttpStatusCode.NotFound)
{
matches = new List<Record>();
}
Every search runs with the rights of the user behind the credential. If a record you can see in the web app does not come back over the API, check that the API user has rights to that Record Type and record, because the search is not filtering it out by mistake.
TIPS
- Use recordTypeID to scope a field search; it is faster and works on every Record Type.
- Look up the Record Type ID once at startup, not on every search.
- Ask for one result with numPerPage: 1 when you only need to know whether a record exists.
- Store the record ID, never a field value, as your link back to a DocMgt record.
- Compare Count with NumResults before assuming a page holds everything.