Micro Focus Content Manager SDK 10.0
|
The .NET SDK has a number of important base classes, from which most SDK objects are derived from. These base classes are:
Most objects in the .NET SDK have their own constructors and can be created directly.
If while programming an SDK-based application you are unsure about how to perform a specific Content Manager function, it is often helpful to see how the function is performed in the Content Manager Client.
The TrimApplication class has methods and properties which describe and adjust the environment which your application is running in. Two important methods are:
TrimApplication.Initialize()
– As previously mentioned.TrimApplication.RuntimeEnvironment
= Environments. This tells Content Manager what the runtime environment of the application is. Content Manager will then make changes to its behavior necessary for that environment.The Database class maintains a session connection to a Content Manager Workgroup Server. You will normally have to construct a Database
object before you can construct any other SDK object.
The Database
object has a default constructor, allowing a new instance to be created by using the .NET new
keyword. Before connecting the Database
object by calling Database.Connect()
, you should specify the dataset ID of the database to use, and the network address of the Workgroup Server to connect to. If these properties are not set, the defaults for the currently logged in user are used.
The DatabaseConnectionPool enables the reuse of database connections, to reduce overhead in multi-user applications, such as web services. Only a single instance should be instantiated in a process, and once this has been done, Database
objects may be instantiated in exactly the same way as before, with the database pool operational in the background.
In the sample code below, the addition of the DatabaseConnectionPool reduces the response time for subsequent connections to less than 3 milliseconds.
Note: In a real application, you would instantiate the database pool at some point during application startup, for example in a static constructor in the class that manages
Database
connections.
Before creating a new Record you need to load a RecordType object which specifies the type of record you are creating. After creating a record or changing an existing record you need to save it.
When creating a new record many of the default settings are obtained from the record type. Because you need to specify the record type in the constructor, if you override these defaults in your code the meaning is obvious and the new value is respected. There are two other key properties that can also have similar side effects in the setting of default values. When you set the Classification or Container property, there are settings on the classification or container record that are meant to override the defaults on the record type. They are not meant to override settings you make specifically in your code, however avoiding that consequence is always reliable. For example, if you set the container before the classification, the defaults on the classification may override the defaults associated with the container, which may not be what you want (or what the records manager wants). To avoid these sort of issues, it is recommended that you set the Classification and Container properties first (and in that order) before setting any other properties when creating a new record. Note that the ServiceAPI does this automatically, this advice applies only to code that is using the Content Manager .NET SDK.
To read information stored on records in an Content Manager database, the SDK programmer must first determine how to access the records required. If a particular record’s internal or external unique identifier is known, the associated record can be accessed directly and efficiently using the Record
constructor. If neither of these unique identifiers is known, it will be necessary to construct a search.
Every Record in Content Manager has a unique record number. This follows a pattern defined by the record type and can be manually entered by the user or set to be automatically generated by Content Manager. Although the commonly used term is ‘number’, it is more correctly an identifier, as it is a string that may contain alphanumeric characters. This string is accessible through the Record.Number
property.
The record number can be used as a string argument when creating Record
objects.
Note: Content Manager stores the record number in two formats, expanded (e.g. "2015/0059") and compressed (e.g. "15/59"). Either can be substituted above.
The Unique Row Identifier or URI of a record is an internal unique number that is transparent to the everyday user of Content Manager. It is the primary key on the TSRECORD
table in the database and provides an internal unique identifier for every record. Similarly to the record number, the record URI can be used as an argument when creating Record
objects.
Once a Record
object has been created, the programmer can access properties and call methods on the object. These are discussed in the following subsections.
Most of the metadata directly associated with a record is exposed through properties on the Record
object. Most properties return primitive data types (strings, numbers or dates) and can be interrogated directly. The meanings of these properties are generally self-evident from their names, but are also given in the object browser.
Property | Example Value |
---|---|
Number | "G1997/0770" |
Title | "Greenhouse Journal of Global Warming - Dugong Habitats" |
DateCreated | 8/20/1997 |
ExternalId | "GJGW 97PB" |
AccessionNumber | 5617 |
Many properties of a Record
represent other objects, such as the Record.RecordType
, Record.Classification
and Record.Container
properties. These are properties where the data type of the property is an object.
A Record
has various properties concerning related location information. These properties of a Record
all return an instantiated Location object:
HomeLocation
– normal location of the recordOwnerLocation
– location of the owner or responsible unit for the recordAuthor
– person who authored the electronic documentCreator
– person who registered the record in Content ManagerAddressee
– person to whom the record is addressedPrimaryContact
– the main contact person (or organization) for the recordSo far we have only considered the methods for reading information from records in Content Manager. The SDK also allows you to update Content Manager records, either by updating the values of properties on a given Record
object, or by calling methods on the Record
.
Updating properties is the simplest way to modify the metadata of a record. You simply assign a new value of the correct data type to the named property of the object. Field-level verification is carried out, and an error will be raised if the property update is invalid (see also the section on ‘Verifying’ below).
For more complicated types of update to a record, you must generally call methods that instruct Content Manager to modify the record, based on arguments passed.
The simplest way to update data in a Content Manager record is to modify the named properties on the Record
object. This can only be done on properties that are not marked as read-only. This includes most of the DateTime
properties, certain Location
properties (Author
, Addressee
and OtherContact
) and miscellaneous properties such as ExternalReference
, Priority
, AccessionNumber
and ForeignBarcode
.
To update other data on a record where read-write properties are not available, you must call a method instead. Update methods often begin with the prefix Set...
and they include a parameter for the new data value you wish to apply.
In many cases other parameters can be specified that control the behavior of the update:
To update a record’s properties where the internal identifier of the property is known, you can use the SetProperty()
method. This requires passing the property identifier and a variant containing the data value.
Content Manager allows user-defined fields to be assigned to records. These user-defined fields cannot be interrogated using normal named properties of the record object. Instead, accessing user defined fields is carried out using a dedicated object for managing these fields - the FieldDefinition object. Each SDK object implementing the ITrimUserFields interface has a pair of methods for manipulating user-defined fields, GetFieldValue()
and SetFieldValue()
. Each method takes a populated FieldDefinition
object as a parameter.
When a Record
object is modified via the SDK, there are two levels of verification that must be carried out before the changes can be committed to the database.
The first is field-level verification, which checks that the change to an individual property is legal. An example would be that the DateRegistered
property is not in the future. If a property update cannot be carried out because of field-level verification, the method call or property assignment will cause a run-time error to be raised and the update will not be carried out.
The second level of verification is object-level verification (sometimes called cross-field verification). This checks that the values of all fields on the object are consistent with each other. An example of object-level verification would be that the DateRegistered
property is not earlier than the DateCreated
property. Object-level verification is performed when the object’s Verify()
or Save()
methods are called.
All main objects (i.e. objects derived from TrimMainObject) have a Verify()
method. This can be called to perform object-level verification prior to saving the object. The method returns false if there are any errors in the state of the object, and the error description will be stored in the object’s ErrorMessage
property. If there are no errors, the method returns true and the Verified
property (see below) is set to true. The method contains an optional parameter failOnWarning
which, if set to true, will cause the Verify()
method to check for warning conditions as well as error conditions, and to fail if a warning is encountered.
If it is not called explicitly in code, the Verify()
method will be automatically called before an object is saved (see below) and if verification fails it will not be saved. This ensures that data cannot become corrupted and that business rules are observed when using the SDK, just as they are for users of the Content Manager Client interface.
All main objects also have a Verified
Boolean read-only property, which is false whenever the object is instantiated. It is set to true when the Verify()
method confirms that it is in a legal state to be saved to the database (regardless of warning conditions).
It is up to the programmer to determine how they wish to deal with possible errors when updating an object. However, they must be aware that error checking takes place even when directly updating properties, so it will be necessary to provide some error-trapping code to prevent run-time errors being displayed to the user if there is a possibility of errors being raised.
All of the update methods and property changes made through the Record
interface are only applied to the object in memory. The changes are not committed on the Content Manager database until the object is saved. Calling the Save()
method on the record object will commit the changes to the database, applying all updates since the object was instantiated (or since it was last saved). Note that if the record has not been verified, Save()
will automatically call the Verify()
method and will only commit the changes if the verification succeeds. Various code examples using this method can be found throughout this document.
This scenario describes the general processes for using the SDK to create a record of a generic record type we are calling a ‘Container File’.
In this and the next scenario (Creating a Document) we are assuming that the reader is familiar with the concept of record types. While it is up to the administrator of each Content Manager implementation to determine the record types to be used, it is typical to follow a standard records management practice of having at least two record types, one representing container files (or folders) and one representing documents (the actual names used for the record types may of course vary).
Container files are usually created and maintained by specialist records managers, as it is generally at this level that classification systems, retention schedules, security, keywords, controlled titling and other records management metadata are applied. Documents, on the other hand, are usually created by end-users, and require little specific metadata, other than the identification of the appropriate container file to which the document belongs, as all other metadata and context is inherited from the Container.
The general steps for creating a new container file record are as follows:
RecordType
objectRecord
object of this typeClassification
or keywords for titling the record (optional)Record
objectWhen creating any record, the RecordType
for the new record must be identified. This is passed to the record object’s constructor along with the Database
which the new record is to belong to. See the Creating new records example.
Note: It is possible to create new Record Types using the SDK; however, this is not recommended as this is generally an administrator’s function only.
Titles for container files are often subject to controlled vocabulary or classification structures such as a thesaurus or file plan, which give records managers’ greater control over file creation, retrieval and retention. Even when such controlled titling is used, each file will typically also have a ‘free text’ title part. The titling method used is determined by the record type, and is usually set by the Records Manager administrator. Thus a record with Classification titling may have a title such as: "Insurance – Property – Storm damage to Mackay information center", where the first two terms are generated from a predefined hierarchical classification structure and the remaining part of the title is ‘free text’ describing the specifics of the file. The generated title terms are determined by the classification codes, usually defined as a numerical sequence, such as "610/600/". The free text title is set via the TypedTitle
property.
Similarly, Thesaurus or Keyword titling allows a file to be titled using either a choice of individual keywords from a controlled list or a specific ‘branch’ of related terms according to a hierarchical structure (similar to a record plan or classification).
The security profile of an individual Content Manager record is governed by three security controls: a Security Level, a set of zero or more Caveats, and Access Control. Security Levels and Caveats determine the access that a Content Manager user has to the metadata of a record. These security specifications are usually applied to Record Types (and inherited by records of each type when they are created) but can be set explicitly on individual records. Every user has a maximum Security Level and zero or more Caveats . In order to access a particular record, the user must have the same or a higher Security Level and must have all the Caveats associated with the record.
There are two ways to assign Security Levels and Caveats to a record via the SDK. If the value and number of Levels and Caveats are fixed then this method can be used.
The more preferred way (although more verbose) is to retrieve the Security Level and Caveats, add them to a Security Profile and assign the Security Profile to the record. Both the SecurityLevel object and the SecurityCaveat objects can be instantiated by full name or by abbreviation. The instantiated SecurityLevel
object can then be assigned to a TrimSecurityProfile’s SecurityLevel
property. Each instantiated SecurityCaveat
object can be added via the TrimSecurityProfile.AddCaveat()
method. The TrimSecurityProfile
can then be assigned to a record’s SecurityProfile
property.
Defining relationships between a container file and location objects (people and places), provides additional and useful context for the record. Unlike record relationships, which can be user-defined, you can only use Content Manager’s predefined standard relationship types for record locations.
Record Locations represent actual (in the case of paper and other physical records) or logical (in the case of electronic records) places where a record resides. Every record in Content Manager has a property representing its Home Location (where the record should normally be). There is also a property for Owner Location – the exact meaning of this can vary according to the practices of each Content Manager implementation, but normally represents the person or body that is responsible for the record.
The Home Location and Owner Location of a record are typically derived from the default values for each Record Type, but all record location properties can be set on creation of a new record, or modified later. The Record
object has methods for setting or changing the value of these location properties, which allow the option of specifying the date and time of the change of location (the default is the current time).
Unlike Record Locations which tend to be internal units, Record Contacts are more commonly people or organizations that have a direct association with the record, and may be internal or external to the organization. Using the AttachContact()
method, Content Manager allows each contact to be specifically identified as an Author, Addressee, Representative or Client. Other contact relationship types must use the generic type of ‘Other’.
This scenario describes the general processes for using the SDK to create a record of a generic Record Type we are calling a ‘Document’.
While container files are usually created and maintained by specialist records managers, documents, on the other hand, are usually created by end-users, and require little specific metadata other than the identification of the appropriate container file to which the document belongs, as most other metadata and context is inherited from the container. A document record usually consists of an electronic object (the source document, image or other file), a unique identifier (which may be automatically generated by Content Manager), a record title and any other metadata required to profile and index the record, and a pointer to the container file from which the document derives its context.
The general steps for creating a new document record are as follows:
RecordType
objectRecord
object of this typeRecord
objectTitling for documents is generally straightforward – free text titling is the norm, and the title simply needs to succinctly describe the document or record. Record numbers may be assigned explicitly or they may be automatically generated – this is configured on the Record Type properties. If the number is explicitly assigned, the number (in expanded format) must be assigned to the Record.LongNumber
property (it must be unique or the record will not be saved).
Although it is not compulsory, it is most common that an electronic record is logically assigned to a container file that represents the subject matter, case, client file or other contextual grouping relevant to the document.
To assign a record to a container, the existing container record must be instantiated (by Id or URI) and then passed as an argument to the (contained) record object’s SetContainer()
method. The method includes a parameter for specifying whether the record is also ‘enclosed in’ the container, i.e. that the current location should reflect that it is with the container.
Document records can represent physical paper documents, but usually they will include an electronic attachment, whether this is a word-processing document, scanned image or other type of file.
To attach an electronic document to a record, the file name and path must be used to instantiate an InputDocument object. This object is then passed as an argument to the record object’s SetDocument()
method. The method includes parameters for specifying whether this should replace any existing document (or be added as a new revision), whether it should be marked as checked out to the current user, and any comments to be added to the record’s Notes field. The title of the document (minus the extension) becomes the title of the record by default.
Record Contacts are Content Manager location objects commonly representing people or organizations that have a direct association with the record. The most common type of Contact to be specified for an electronic document is the Author. Although the AttachContact()
method can be used for this and other contact types, a shortcut is provided through the Author
property. For examples how to do this, see the code examples for:
The Location object is an encapsulation of all properties and methods associated with Persons, Organizations, Positions and Groups. Locations can be identified by name or by URI, and can be selected on other criteria, such as date of birth, nicknames, or membership of a particular organization, role or group.
Although the names of non-persons (Units, Positions and Organizations) must be unique, this is not the case for persons (Staff Names & Contacts). However, Content Manager allows you to store a ‘nickname’ for any person, and this can be used as a substitute for a person’s name when searching. To find a particular person by name, you must pass the person’s combined name and title to the Location object’s constructor.
If the sub-string does not uniquely identify a location (i.e. there are no matches, or there is more than one match) then a null object will be returned.
To create a new staff member, you must instantiate a new location by calling the Location
’s constructor. You then define the type of the location by assigning a value (in this case Person
) to the TypeOfLocation
property. You can then set various properties representing the person’s name, contact details such as telephone numbers and addresses, administrative details such as employee id numbers and so on.
If the new person is to be a Content Manager user, then there are login and security details to be provided. You will need to specify the user’s network login id and optionally an expiry date. For the security profile, you are required to either explicitly state the user’s security level (and optionally any Caveats) and a user category, or if role-based security is used, you can specify that the user takes the profile of a predefined group or user.
Relationships such as membership of units or reporting lines are created using the AddRelationship()
method and passing parameters for the related location and the relationship type.
Addresses (including electronic addresses such as email or URL) are added by calling the New()
method on the location’s ChildAddresses
or ChildEAddresses
collection properties.
One of the most powerful features of Content Manager is the wide range of search criteria that can be applied to select obects from the database. .NET SDK provides the TrimMainObjectSearch class to provide this functionality. This class allows you to combine a number of search criteria together using boolean logic and then iterate through a resulting set of objects. Note that the TrimMainObjectSearch
class can only be used to search for objects of a specified type, for instance, you can create a TrimMainObjectSearch
to retrieve records. As its name implies, this class can be used to search for any SDK object that inherits from TrimMainObject.
The TrimMainObjectSearch
class provides a number of simple "canned" methods for searching that do not involve any boolean logic. These are simple to use if you have a straightforward query, they include:
SelectByPrefix()
SelectFavorites()
SelectByUserLabel()
SelectNone()
SelectAll()
SelectByUris()
SelectTopLevels()
SelectThoseWithin()
To use the string search syntax available in Content Manager (refer to the Content Manager’s Help file for examples). There are three main components to a string search – the primary search string, a set of filters (these work as if they were combined with the primary search string using a boolean ‘and’) and the sort specification. To create a query in this way, you use the SetSearchString()
method to specify the primary search string, and then you can optionally specify any filters using SetFilterString()
and specify a sort using AddSortItemAscending()
/AddSortItemDescending()
. This approach is useful if you want to provide the user with a simple edit control for specifying a search, and is also very useful for web-based applications.
Another option is to work with TrimSearchStackItem objects. The primary search criteria is represented internally as an array of TrimSearchStackItem
objects, arranged as a reverse-polish stack to indicate operator precedence. A TrimSearchStackItem
can be an operator (TrimSearchOperator) or a search clause (TrimSearchClause). You can build up a query using the internal search stack of the TrimMainObjectSearch
, by using such methods as AddSearchClause()
, And()
, Or()
and Not()
.
A read of the Wikipedia article on Reverse Polish notation would be beneficial for developers unfamiliar with this style of expression. Because filters are all automatically "and-ed" together, they are simply represented as an array of TrimSearchClause
items. For sorting, there is a TrimSearchSortItem class and a corresponding array.
These individual classes provide extra features that allow even more elaborate queries to be built.
The TrimMainObjectSearch
class derives from IEnumerable
and thus you can use a standard foreach
loop to retrieve items that match the selection. In addition, there are two methods that will return the search result as an array of unique object identifiers (corresponds to the Uri
property of a TrimMainObject
).
The TrimObjectListEnumerator along with TrimMainObjectSearch
support paged searching, typically used in web applications/services. The code below demonstrates getting the second page containing 20 results.
TrimSearchClause contains the method SetCriteriaFromEnumValueArray()
to support searching and filtering using enum values, as seen below.
You can specify that the items in the selection are eventually intended for a specific pupose. This may apply additional hidden filtering to the selection, although not always. As you are iterating through the results, you can use the IsValidForPurpose()
method to test if the item is suitable for the purpose specified. Each TrimMainObject
has a purpose enumeration associated with it – use this enumeration to specify suitable purpose values for the object you are selecting.
The TrimMainObjectSearch
class has a SearchAsXML
property which will convert the search, filter and sort criteria to an XML string. This string is language-independent and is therefore useful if you wish to reuse the same search at some later stage, perhaps in a different locale.
The TrimMainObjectSearch
class provides an ItemMatches()
method, which allows you to test whether an existing TrimMainObject
matches the search criteria contained within the search.
There are two search result count values, FastCount and Count. FastCount
may, in the case of the use of multiple levels of inherited ACLs, return -1 rather than an actual count. Count
should always return a result accurate at the time it was called. Count
will first try to use FastCount
, if that fails it will fetch all the records and then loop through them to count them. So it will often be as fast as FastCount
but may be a lot slower (especially for large result sets). So, use FastCount
only when you need to avoid a slow response and can accept an invalid response.
Although User Defined Fields are not indexed in the database, and thereby not part of the core sorting engine, it is still possible to sort using them. Always take care when doing this, as the lack of an index requires the entire result set to be fetched and sorted in memory. On large result sets this can be very slow. To sort using a User Defined field use either GetEnumeratorSorted() or GetResultAsUriArraySorted(), as seen in this example:
TrimSearchDataStream provides a simplified search capability that bypasses, among other things, the SDK internal caching to provide a faster response time for simple searches. Stream search is limited to record searching and returns a JSON string rather than an enumeration of Record
objects, not all properties are supported, use IsPropertyOrFieldSupported or the property documentation to check whether a property is supported.
In the code below the search stream is used to find all electronic documents, returning the RecordTitle. Each record is returned as a JSON string which we de-serialize using the .Net DataContract de-serializer.
The primary audience of TrimSearchDataStream is the web service, which is why the response is JSON formatted to match the web service data.
The document store integrity check can now be executed from the SDK. The following class and enum are introduced for the document store checker:
Type | Name |
---|---|
Class | DocumentStoreIntegrityCheckTask |
Enum | DocumentStoreIntegrityCheckCounter |
The TrimObjectChangeNotifier class provides a global notification mechanism for when Content Manager business objects are changed. The notifications specify the object type and the object URI of the changed object, and allows the recipient to take relevant actions, such as refreshing cached objects. A common use case is to notify client-side code when a cached object has been modified by some other process in the system.
The following code:
Database
.TrimObjectChangeNotifier
class to monitor changes to RecordType
objects.RecordType
object changes.The WgsProgressNotifier class provides SDK applications with progress notifications while the SDK is making network calls to a workgroup server. This allows an interactive SDK application to provide ongoing progress feedback to the user, and also allows the current workgroup server operation to be canceled. For more information, please refer to the class documenation for WgsProgressNotifier.