App - Development: Matterial Core C# Client Library
A client library in C# that encapsulates the Matterial REST API.
Intro
The matterial core client library is a library written in C# 8.0. (.Net Standard 2.1). The library encapsulates the Rest Interface
Library Dependencies
.Net Standard Library 2.1
JSON.Net 13.0.3 (Newtonsoft.Json)
The Nullable Reference Types feature is enabled.
Project home on Gitlab
The source code of the library can be found here.
List of all entities
You can find information on all entities here: Matterial API Objects.
License
The source code is licensed under the MIT License:
Copyright Matterial GmbH (www.matterial.com)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Using the library
The library is available on GiHub and as a package on NuGet. In order to install it, use the Visual Studio NuGet Package manager or execute the following command in the NuGet command prompt:
Install-Package MatterialCore -Version 2.0.3
Async
The library functions are asynchronous functions using the .Net Task class
Intellisense help
When using the library in Visual Studio, you can use the intellisense feature to get help about many functions. You even find a link to the online documentation within the help window (API-Reference link).
ApiSession Concept
The matterial library uses the concept of sessions. A session represents an authenticated state to the server based on user credentials. A session starts with authentication and is valid until log off or time out. The session is represented by a session ID which is generated by the server and stored to a cookie (JSESSIONID). The cookie must be present in each Http request in order to identify the session.
The IApiSession
Interface contains a number of properties each representing an entity in matterial that you can control, e.g. load records, create and update.
Properties of IApiSession
with controllable entities are (alphanumeric order):
- Activity
- Category
- Client
- Comment
- Document
- Instance
- Language
- License
- Task
- Person
- Property
- Role
- TrackingItem
Exceptions
Most of the library functions may throw a System.Net.Http.HttpRequestException
in case an error occurred. The inner exception might contain additional information about the error. You can check the mtrStatusCode
and mtrStatusMsg
information of the inner exception for further details. The related error codes are listed here.
Json Null-Value Handling
When sending json objects to the server, it does not ignore null values of object properties but treats them as properties to update. That means, if you send a json object where only a few properties are set, all other properies will also be updated and set to null.
When updating existing objects like documents, persons etc. in matterial, make sure to load the respective object first, update its properties and then save it.
Example code
Below, you’ll find common example code for typical tasks using the library. The example code uses C# 8.0 language features and is written in the context of a console application calling the functions in a synchronous way.
Create a session
The library is designed to handle multiple sessions. To create a session, invoke the static function Connect
of the Matterial
class. It returns an interface IApiSession
. This interface gives you access to all functions.
The IApiSession
interface supports the IDisposable
interface. We recommend to use the using-statement. That way you can make sure the session will be properly disconnected when disposing.
using MatterialCore;
using MatterialCore.Interfaces;
using System;
using System.Security;
public static void Connect()
{
//Create a session using the static function Connect of the Matterial class
using (IApiSession apiSession = Matterial.Connect("user@mydomain.com", Matterial.SecureString("myPassword")))
{
//Check if we are connected:
if (apiSession.IsLoggedIn)
Console.WriteLine("Right on!");
else
Console.WriteLine("Something went south...");
}
}
Re-using a session
When an instance of an API session is disposed, a disconnect function is automatically called and the session with the server is closed. If you would like to open a session once and then re-use it after the object was disposed, you can set the LogoffOnDispose
property to false and store the SessionId
for later usage. The Connect
function has an overload to create a session using an existing SessionId.
Example
public static void ConnectAndReuse()
{
string sessionId;
//Create a session using the static Connect function of the Matterial class
using (IApiSession apiSession = Matterial.Connect(TestCrendentials.ApiUserName, Matterial.SecureString(TestCrendentials.ApiUserPassword), TestCrendentials.Url))
{
//Check if we are connected:
if (apiSession.IsLoggedIn)
Console.WriteLine("Right on!");
else
Console.WriteLine("Something went south...");
//Set the property to skip log off so we can re-use the session ID
apiSession.LogoffOnDispose = false;
//Save the session ID
sessionId = apiSession.SessionId;
}
//At this point we are disposed but not logged off.
//Create a session with the session ID
using (IApiSession apiSession = Matterial.Connect(sessionId, TestCrendentials.Url))
{
//Check if we are connected:
if (apiSession.IsLoggedIn)
Console.WriteLine("Right on!");
else
Console.WriteLine("Something went south...");
}
}
Change Instance
In case you have multiple instances, you can switch to another instance.
using (IApiSession apiSession = Matterial.Connect("user@mydomain.com", Matterial.SecureString("myPassword")))
{
//Check if we have access to multiple instances.
LoginData loginData = apiSession.LoginData().Result;
if (loginData.AvailableInstances.Count > 1)
{
//Switch to second instance
apiSession.ChangeInstance(loginData.AvailableInstances[1].Id).Wait();
}
}
Disconnect
Simply use the Disconnect()
function of the IApiSession
interface. You can check, if you are connected using the IsLoggedIn
property (a session time out might have occurred).
Note
If you are using the IDisposable interface, the Disconnect()
function will automatically be invoked once the apiSession instance is disposed.
UI language vs. content language
The matterial web user interface is multilingual. A user can go to the Settings page and define the language of the user interface.
Additionally, he or she can specify the default content language. When searching and creating documents, the default content language will be used.
In the library you can find the properties DefaultContentLanguage
and DefaultUiLanguage
of the IApiSession
interface which you can use to get and set each language setting.
Note
Whenever you store or load data, make sure you specify the respective language (in case it can be specified).
Documents
Managing documents is the core feature of matterial. A document in matterial is not only a simple object - it is more than that. A document exists in one or more languages each of them having their own versions. This aspect is very important when programming with document objects, because whenever you want to create, load or update a document, you have to specify which one you refer to. That’s why many functions in the library require more than just the document Id. A system wide unique identifier of a document is the id stored in the languageVersionId
property of the document
object.
For example, when loading a document by its document Id, you can load all versions of a specific language or even all versions of all languages by specifying the respective load behaviour, because they all share the same document Id.
On the contrary: When loading a document by its languageVersionId
, you will only get one specific document having a specific version and language.
Create a document
To create a document, simply use Create()
function of the Document
property of the IApiSession
interface:
private static void CreateDocument()
{
using (IApiSession apiSession = Matterial.Connect(TestCrendentials.ApiUserName, Matterial.SecureString(TestCrendentials.ApiUserPassword), TestCrendentials.Url))
{
//Create a new document object with title, abstract and language key.
Document newDoc = new Document
{
LanguageVersionTitle = "The document's title",
LanguageVersionAbstract = "The abstract (description)",
LanguageVersionLanguageKey = apiSession.DefaultContentLanguage.ToLangString()
};
//In order to unlock it after creation, we need an unlock id.
DocUpdateParams options = new DocUpdateParams
{
UniqueLockId = Guid.NewGuid().ToString()
};
Document myNewDoc = apiSession.Document.Create(newDoc, options, "# This is the document content text", DocumentContentMediaType.Markdown).Result;
//The document is now created and locked. Let's unlock it as we do not want to further update it.
bool unlocked = apiSession.Document.Unlock(options.UniqueLockId).Result;
}
}
Creating multiple languages of a document
As described previously, a document can exist in multiple languages. All the different languages share the same document id - but different language version ids. When creating documents in different languages, all you need to do is to update an existing document and set the Document.LanguageVersionLanguageKey
to the new language you want to create. Make sure, the language you specify is actually active by using the ILanguage
interface (which is the property Language
of the IApiSession
interface). When loading documents, by default the last version of a defined language will be loaded, but by using a different overload of the Load()
function, you can specify the DocLoadByIdParams
object and if you set the AllLanguages
property to true
, all languages will be returned.
Document publishing and versioning
When updating a document, you can specify the PublishRequestType
of the DocUpdateParams
parameter. Using the parameter, you can specify, if the document should be published and how.
Parameter | Decription |
---|---|
None | Do not publish. Document will be saved as draft. The version number will not be incremented. |
Reviewed | Publish the document as reviewed. This requires the permission “immediateReview”. A new version will be created and the version number will be incremented. |
ReviewRequested | The document will be saved and a review will be requested - the version number will not be incremented. Anyone who is member of the Review workgroup can then review the document and publish it. |
Unreviewed | Publish the document unreviewed. The permission “publishUnreviewed” is required. A new version will be created and the version number will be incremented. |
Update an existing document
Please refer to the documentation of the Document Entity class Matterial API Objects to see, which properties of a Document object can be updated.
private static void UpdateDocument()
{
using (IApiSession apiSession = Matterial.Connect(TestCrendentials.ApiUserName, Matterial.SecureString(TestCrendentials.ApiUserPassword), TestCrendentials.Url))
{
//Let's assume we want to update a document with the id 2350
long docId = 2350;
//Set the values we want to update
Document updateValues = new Document
{
LanguageVersionTitle = "The document's new title",
LanguageVersionAbstract = "The new abstract (description)"
};
//In order to unlock it after creation, we need an unlock id.
//First we have to get a lock. Create a unique lock id which we need to lock and unlock later
string uniqueLockId = Guid.NewGuid().ToString();
DocumentLock myLock = apiSession.Document.Lock(docId, uniqueLockId).Result;
//If it worked, keep going
if (!(myLock is null) && myLock.LockedForMe)
{
//Set the lock id
DocUpdateParams options = new DocUpdateParams
{
UniqueLockId = uniqueLockId, //our lock id
PublishRequestType = PublishRequestTypes.ReviewRequested //We want to request review
};
//Update it
Document updatedDoc = apiSession.Document.Update(updateValues, options).Result;
//The document is now updated. Let's unlock it as we do not want to further update it.
bool unlocked = apiSession.Document.Unlock(options.UniqueLockId).Result;
}
}
}
Searching documents
Matterial offers different types of searching / loading documents:
- Search by query string
- Load by properties
Search by query string
When searching by a query string, matterial’s internal search engine (Elastic Search) is used to retrieve documents. The engine indexes many document properties and automatically applies a ranking during the search. The query string can be a simple text string but it can also be combined with search engine specific operators, like “+” and “-” in order to ge a more exact result.
The library’s search function allows you to define a large list of options to control the search behaviour by providing a SearchDocParams
object.
private static void SearchDocuments()
{
using (IApiSession apiSession = Matterial.Connect(TestCrendentials.ApiUserName, Matterial.SecureString(TestCrendentials.ApiUserPassword), TestCrendentials.Url))
{
//Run a search using a query string.
string query = "mouse";
//We can set options of how the search should behave.
SearchDocParams options = new SearchDocParams(Matterial.DefaultLanguage);
//Run the search
SearchResult<Document> results = apiSession.Document.Search(query, options).Result;
//Iterate through the result items
if (results.TotalHits > 0)
{
foreach (var item in results.Results)
{
Console.WriteLine(item.Source.LanguageVersionTitle);
}
}
///Load documents
LoadDocParams loadParams = new LoadDocParams
{
CategoryAndIds = new List<long> { 10, 11, 22 },
Archived = ParamShowValue.Include
};
ListResult<Document> docList = apiSession.Document.Load(loadParams).Result;
}
}
Load by properties
When using the Load function, matterial does not use the search engine but uses database queries to get the results.
//We want to retrieve all documents having one of the three categories with
//the id 10, 11 or 12 assigned to it. Additionally we want to include archived documents.
LoadDocParams loadParams = new LoadDocParams
{
CategoryOrIds = new List<long> { 10, 11, 22 },
Archived = ParamShowValue.Include
};
ListResult<Document> docList = apiSession.Document.Load(loadParams).Result;
Get documents where I am mentioned
“Mentioned in comments” is a property that you can use when loading documents. You simply set the DocLoadParams.MentionedAccountIdInComment
and/or DocLoadParams.MentionedAccountIdInCommentUnread
property to the accountId of interest.
long myCAccountId = 3;
LoadDocParams docParams = new LoadDocParams
{
mentionedAccountIdInComment = myCAccountId
};
ListResult<Document> docs = apiSession.Document.Load(docParams).Result;
Get documents without category
“HasCategoryAssigned” is a property that you can use when searching documents. You simply set the HasCategoryAssigned = CategoryAssignedValue.NoneAssigned
which then will only find documents not having a category assigned.
SearchDocParams options = new SearchDocParams();
options.Limit = 500;
options.HasCategoryAssigned = CategoryAssignedValue.NoneAssigned;
SearchResult<Document> results = apiSession.Document.Search(query, options).Result;
Get “My favourite” documents
A person can mark a document as favourite (the little star icon at the top of a document). Internally, this sets the person’s personal category (also called Quick Category) with the document. Loading all favourites simply is a Load()
on all documents having the personal category.
private static void FafouriteDocuments()
{
using (IApiSession apiSession = Matterial.Connect(TestCrendentials.ApiUserName, Matterial.SecureString(TestCrendentials.ApiUserPassword), TestCrendentials.Url))
{
long personalCategoryId = 0;
//We set the load parameter to Personal Categories = Exclusive.
//The system automatically creates one personal "Quick" category for a person. This is used for favourites.
CategoryParams catParams = new CategoryParams
{
Personal = ParamHideBoolValue.Exclusive
};
//Now load the category to get its Id
List<Category> personalCategories = apiSession.Category.Load(catParams).Result;
if (!(personalCategories is null) && (personalCategories.Count > 0))
{
personalCategoryId = personalCategories[0].Id;
}
//We can set options of how the search should behave. We set it to the category Id
LoadDocParams docParams = new LoadDocParams
{
CategoryAndIds = new List<long> { personalCategoryId }
};
//Load the favourite documents
ListResult<Document> docs = apiSession.Document.Load(docParams).Result;
}
}
Get News, Relevant and Urgent documents
The matterial web UI displays a dashboard after login (by default) and displays certain types of documents that have Additional Properties assigned to them.
The system automatically creates four Additional Properties:
Name | Type | Description |
---|---|---|
Relevant | News | A news that is of interest for everyone |
Urgent | Urgent | A urgent information. Users are notified about urgent documents. |
Let’s go | HelpSectionDashboard | All documents that help getting started with matterial. |
Writing tips | HelpSectionDocumentEditor | All documents that help getting started with the matterial document editor |
InfoCenter | InfoCenter | Documents that to be displayed in the info center. |
Loading documents of each additional property is as simple as setting the LoadDocParams.AdditionalPropertyType
to the type you want to load.
Load Urgent Documents
private static void LoadUrgentDocuments()
{
using (IApiSession apiSession = Matterial.Connect(TestCrendentials.ApiUserName, Matterial.SecureString(TestCrendentials.ApiUserPassword), TestCrendentials.Url))
{
LoadDocParams docParams = new LoadDocParams
{
AdditionalPropertyType = AdditionalPropertyLoadTypes.Urgent
};
ListResult<Document> docs = apiSession.Document.Load(docParams).Result;
}
}
Assign permissions to a document
Document permissions are handled by RoleRights
. The object RoleRight
consists of a Role
and a RoleRightType
(READ or EDIT). Changing the permission of a document means to add or remove a RoleRight to or from the document property RoleRights
.
private void AssignPermissionToDocument()
{
using (IApiSession apiSession = Matterial.Connect(TestCrendentials.ApiUserName, Matterial.SecureString(TestCrendentials.ApiUserPassword), TestCrendentials.Url))
{
//Let's assume, we have a document with the id 222. We want to assign the EDIT permission
//to a group to the english language version of the document.
long docId = 222;
string languageKey = Matterial.DefaultLanguage.ToLangString();
//The role id (id of the work group) is 5
long roleId = 5;
//First, load the document so we get the current information
Document docToUpdate = apiSession.Document.Load(docId, languageKey).Result;
//Lock the document for update
DocUpdateParams updateParams = new DocUpdateParams
{
UniqueLockId = Guid.NewGuid().ToString()
};
DocumentLock docLock = apiSession.Document.Lock(docToUpdate.Id, updateParams.UniqueLockId).Result;
//Check, if it worked out
if (docLock.LockedForMe)
{
Role groupRole = apiSession.Role.LoadById(roleId).Result;
//Now we assign the role by assigning a RoleRight to the document.
RoleRight roleRight = new RoleRight
{
Role = groupRole,
Type = RoleRightTypes.EDIT
};
//Add the role right
docToUpdate.RoleRights.Add(roleRight);
//Update the document using the document object we prepared and the update parameters
//containing the unique lock id
Document updatedDoc = apiSession.Document.Update(docToUpdate, updateParams).Result;
//Unlock the document.
bool b = apiSession.Document.Unlock(updateParams.UniqueLockId).Result;
}
}
}
Archive / Remove / Restore documents
Archiving, removing and restoring document is a simple task. The only particularity is that each of these functions return either a Document
in case of success or a DocumentLock
in case the document is locked.
private void RemoveAndRestoreDocument()
{
using (IApiSession apiSession = Matterial.Connect(TestCrendentials.ApiUserName, Matterial.SecureString(TestCrendentials.ApiUserPassword), TestCrendentials.Url))
{
//Let's assume, we have a document with the id 222.
long docId = 222;
string languageKey = apiSession.DefaultContentLanguage.ToLangString();
//We call the remove function. On success it returns a Document,
//in case the document is locked, it returns a DocumentLock
DocumentBase result = apiSession.Document.Remove(docId, false).Result;
if (result is Document)
{
//It worked. Let's restore
result = apiSession.Document.Restore(docId).Result;
if (result is Document)
{
//Restore OK
}
}
else
{
DocumentLock docLock = (DocumentLock)result;
Console.WriteLine("The document cannot be removed. It is currently locked by " +
docLock.Person.FirstName + " " + docLock.Person.LastName);
}
}
}
Import files from a folder
This example imports files from a folder.
private static void ImportDocuments()
{
using (IApiSession apiSession = Matterial.Connect(TestCrendentials.ApiUserName, Matterial.SecureString(TestCrendentials.ApiUserPassword), TestCrendentials.Url))
{
//Let's assume, we have files in the folder c:\Import including sub folders that we want to import
//In case a file is a PDF or a Text file, we want to extract the text and use it as document text
//Import root folder
string rootFolder = @"C:\Import";
//Language
LangKey languageKey = apiSession.DefaultContentLanguage;
//Tool to extract text from pdf from https://www.xpdfreader.com/pdftotext-man.html
string pdfTextExtractTool = @"..\..\..\Tools\pdftotext.exe";
//Tool arguments
string pdfToTextArgs = "-eol dos -layout -q -enc UTF-8 -nopgbrk";
//Load all files from import folder
List<string> fileList = new List<string>(Directory.EnumerateFiles(rootFolder, string.Empty, SearchOption.AllDirectories));
string textContentFile = "";
//Iterate through the files
foreach (var file in fileList)
{
string docTitle = Path.GetFileNameWithoutExtension(file);
string abstractText = "File form the folder " + String.Join(Path.DirectorySeparatorChar, Path.GetDirectoryName(file).Split(Path.DirectorySeparatorChar)[2..]);
if (Path.GetExtension(file).ToLower() == ".pdf")
{
//Extract text from pdf using a little tool
Process proc = new Process();
proc.StartInfo.FileName = pdfTextExtractTool;
proc.StartInfo.Arguments = "\"" + file + "\" " + pdfToTextArgs;
proc.StartInfo.WorkingDirectory = Path.GetDirectoryName(file);
proc.Start();
proc.WaitForExit(30000);
textContentFile = Path.ChangeExtension(file, ".txt");
}
else
{
//Extract the text from the file
if (Path.GetExtension(file).ToLower() == ".txt")
{
textContentFile = File.ReadAllText(file);
}
}
//Define the contexct token and lock id
//The document should be published without reviewing.
DocUpdateParams updateParams = new DocUpdateParams
{
ContextToken = Guid.NewGuid().ToString(),
PublishRequestType = PublishRequestTypes.Unreviewed,
UniqueLockId = Guid.NewGuid().ToString()
};
//Upload files: One is the main file (the text) and the other is an attachment.
List<UploadFile> uploadFiles = new List<UploadFile>
{
new UploadFile(file, TempFileType.Attachment),
new UploadFile(textContentFile, TempFileType.Document)
};
List<TempFileDescriptor> tmpFiles = apiSession.Document.UploadFile(uploadFiles, updateParams.ContextToken, languageKey).Result;
//Create new document
//Define title, abstract and language
Document newDoc = new Document
{
LanguageVersionTitle = docTitle,
LanguageVersionAbstract = abstractText,
LanguageVersionLanguageKey = languageKey.ToLangString()
};
//Create it
Document createdDoc = apiSession.Document.Create(newDoc, updateParams).Result;
//Unlock it
_ = apiSession.Document.Unlock(updateParams.UniqueLockId).Result;
}
}
}
Export all documents not having categories
This example searches for all documents in all languages not having a category assigned and exports them to a csv file containing documentId, creator and document title.
using (IApiSession apiSession = Matterial.Connect(TestCrendentials.ApiUserName,
Matterial.SecureString(TestCrendentials.ApiUserPassword), TestCrendentials.Url))
{
string CsvContent = "DocumentId\tUser\tDocumentTitle\r\n";
//We need the person controller to load a person by ID
IPerson PersonController = apiSession.Person;
//Run a document search using a query string.
string query = "*";
//We set options of how the search should behave.
SearchDocParams options = new SearchDocParams();
options.Limit = 500;
options.AllLanguages = true;
options.HasCategoryAssigned = CategoryAssignedValue.NoneAssigned;
options.Published = ParamHideValue.Exclusive;
options.Templates = ParamShowValue.Include;
//Run the search
SearchResult<Document> results = apiSession.Document.Search(query, options).Result;
// We need to check if the document was already processed since we might have multiple languages
// of the same document
Dictionary<long, long> ProcessedDocs = new Dictionary<long, long>();
//Iterate through the result items
foreach (var item in results.Results)
{
if (!ProcessedDocs.ContainsKey(item.Source.Id))
{
ProcessedDocs.Add(item.Source.Id, item.Source.Id);
//Load doc including the Author information
var args = new LoadDocByIdParams();
args.LoadOptions.LastAuthorOnly = false;
args.LoadOptions.Authors = true;
var doc = apiSession.Document.Load(item.Source.Id, args).Result;
//Get the first Author's account id (which is the creator of the document)
var accountid = doc.LastWriteTimesInSeconds.Keys.FirstOrDefault();
//Load the person to get all information of the person
var ThePerson = PersonController.LoadByAccountId(accountid).Result;
//create CSV data line
CsvContent += $"{doc.Id}\t{ThePerson.FirstName + " " + ThePerson.LastName}\t{item.Source.LanguageVersionTitle.Replace("\n", "")}\r\n";
}
}
File.WriteAllText("Export.csv", CsvContent);
}
Search docx-attachment and download it
This example searches for documents that have an attachment with a specific file extension (doc, docx, etc.) which can be opened by Microsoft Word (or similar application) and downloads the attachment.
private static void DownloadAttachment()
{
//Attachments supported by MS Word
//see https://docs.microsoft.com/de-de/deployoffice/compat/office-file-format-reference
List<string> SupportedExtensions = new List<string> { "txt", "doc", "docx", "dot", "dotx" };
string DownloadFolder = Path.GetTempPath();
using (IApiSession apiSession = Matterial.Connect(TestCrendentials.ApiUserName, Matterial.SecureString(TestCrendentials.ApiUserPassword), TestCrendentials.Url))
{
//Run a search using a query string.
string query = "My Document";
//we have to add the file types as special query syntax that filters attachmentName
string FileTypeQueryText = "";
if (SupportedExtensions.Count > 0)
{
FileTypeQueryText = $" AND attachmentName:({String.Join(" OR ", SupportedExtensions.ToArray())}))";
}
//We can set options of how the search should behave.
SearchDocParams options = new SearchDocParams
{
AllLanguages = true, //We do not filter out languages but use Aggregation (see belos)
Offset = 0,
Limit = 100
};
//Run the search
Console.WriteLine($"Searching...");
SearchResult<Document> results = apiSession.Document.Search(query + FileTypeQueryText, options).Result;
//Iterate through the result items to show name and description
if (results.Results.Count > 0)
{
Console.WriteLine($"Found {results.TotalHits} documents...");
//We simply use the first one here
Console.WriteLine($"Downloading first document");
Document doc = results.Results[0].Source;
Console.WriteLine($"Doc name:{doc.LanguageVersionTitle}, Description: {doc.LanguageVersionAbstract}");
//Load document and attachment
LoadDocByIdParams loadParams = new LoadDocByIdParams(doc);
loadParams.LoadOptions.Attachments = true;
doc = apiSession.Document.Load(doc.Id, loadParams).Result;
//Does it have attachments
if (doc.Attachments.Count > 0)
{
//Load first attachment ans save it to file
AttachmentLoadParams lp = new AttachmentLoadParams { ForceAttachment = true, Format = AttachmentFormats.Original };
using (var aStream = apiSession.Document.GetAttachment(doc.Id, doc.Attachments[0].Id, lp).Result)
{
string fPath = Path.Combine(DownloadFolder, doc.Attachments[0].Name);
//Save file
if (File.Exists(fPath))
{
File.Delete(fPath);
}
SaveStreamToFile(aStream, fPath);
//Show with Windows Explorer
string cmd = $"/select,\"{fPath}\"";
Process.Start("Explorer.exe", cmd);
}
}
else
{
Console.WriteLine($"No attachments:{doc.Id} - {doc.LanguageVersionTitle}");
}
}
else
{
Console.WriteLine("No documents found.");
}
Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
}
Comments
Comments can be added to documents. Within a comment, you can use the nudging syntax, like @theUser, which can trigger certain other processing in matterial. Comments can also be searched by the mentionedAccountId
.
Create a comment
private static void CreateComment()
{
using (IApiSession apiSession = Matterial.Connect(TestCrendentials.ApiUserName, Matterial.SecureString(TestCrendentials.ApiUserPassword), TestCrendentials.Url))
{
//Let's assume, we have a document with the document language version Id 123
long docLanguageVersionId = 123;
Comment newComment = new Comment
{
Text = "This is the actual comment"
};
//Create it.
long? commentId = apiSession.Comment.Create(docLanguageVersionId, newComment).Result;
}
}
Get all comments where a person is mentioned
private static void GetCommentOfPerson()
{
using (IApiSession apiSession = Matterial.Connect(TestCrendentials.ApiUserName, Matterial.SecureString(TestCrendentials.ApiUserPassword), TestCrendentials.Url))
{
//Let's assume, we have a person with the account Id 4
//We want to load all comments where this person was mentioned
long accountId = 4;
//Create the load parameters
CommentParams options = new CommentParams
{
MentionedAccountId = accountId
};
//Load the comments
ListResult<Comment> comments = apiSession.Comment.Load(options).Result;
}
}
Categories
Categories is a huge feature in matterial. They are used to categorize your documents but users can also follow categories and get notified when documents are added or modified having the followed category.
A category needs a category type assigned to it. Category types are used to group your categories together. You can also load documents based on categories and category types.
Create a category type and a category
When creating a new category, you have to specify the category type it belongs to.
private static void CreateCategory()
{
using (IApiSession apiSession = Matterial.Connect(TestCrendentials.ApiUserName, Matterial.SecureString(TestCrendentials.ApiUserPassword), TestCrendentials.Url))
{
//A category must be assigned to a category type. In this example, we also create a new category type
CategoryType newType = new CategoryType("Comics");
long catTypeId = apiSession.Category.CreateType(newType, Matterial.DefaultLanguage).Result;
//If it worked out, we can go ahead and create the category
if (catTypeId > 0)
{
//create a new category using the TypeId
Category newCat = new Category("Mickey Mouse")
{
TypeId = catTypeId
};
long newCatId = apiSession.Category.Create(newCat, Matterial.DefaultLanguage).Result;
}
}
}
Follow a category
private static void FollowCategory()
{
using (IApiSession apiSession = Matterial.Connect(TestCrendentials.ApiUserName, Matterial.SecureString(TestCrendentials.ApiUserPassword), TestCrendentials.Url))
{
//We want to follow the catogery with the id 33
long categoryId = 33;
bool b = apiSession.Category.Follow(categoryId, Matterial.DefaultLanguage).Result;
}
}
Roles
In matterial, roles are used to manage permissions. There are different types of roles:
Role Type | Description |
---|---|
Functional | A functional role is a role that is used to handle permissions. |
Workgroup | A work-group role is a regular group. Groups are used to manage users. |
Reviewgroup | A review-group role is a special group. Users in that group are the ones reviewing documents. |
Personal | A user as also handled as a role - a personal role. Each user as one personal role. |
Whenever you want to assign a permission to an object - like a document - you assign one or more roles.
Create roles and groups
private static void CreateRole()
{
using (IApiSession apiSession = Matterial.Connect(TestCrendentials.ApiUserName, Matterial.SecureString(TestCrendentials.ApiUserPassword), TestCrendentials.Url))
{
//Create a functional role
Role newRole = new Role("New functional role", RoleEntityType.Functional);
long newRoleId = apiSession.Role.Create(newRole).Result;
//Create a group
newRole = new Role("New group", RoleEntityType.WorkGroup);
long newGroupId = apiSession.Role.Create(newRole).Result;
}
}
Assign roles to a person
private static void AssignRoleToPerson()
{
using (IApiSession apiSession = Matterial.Connect(TestCrendentials.ApiUserName, Matterial.SecureString(TestCrendentials.ApiUserPassword), TestCrendentials.Url))
{
//Let's load all work groups
RoleParams roleParams = new RoleParams
{
EntityTypeIds = new List<RoleEntityType> { RoleEntityType.WorkGroup }
};
ListResult<Role> workGroups = apiSession.Role.Load(roleParams).Result;
//Assign all groups to the current user.
Person currentUser = apiSession.LoggedOnPerson();
foreach (var role in workGroups.Results)
{
_ = apiSession.Role.Assign(role.Id, currentUser.AccountId).Result;
}
}
}
Persons / Contacts / Accounts / Credentials
Matterial uses different terms that might be a bit confusing at the beginning so here is a quick overview of those terms and what they mean.
Person
A person consists of a contact and an account. A person object stores data like names, addresses, phone numbers etc. (which is contact information) and account specific data like login name, account Id, etc.
Credential
Credential is a separate, unique identification item that identifies a physical person having signed up with matterial and which links this person to possibly many instances this person has access to. For each signed-up individual, only one credential exists with the respective login id (mostly the email address) and password - whereas this individual might have multiple instances that he or she has been invited to so there might be many person objects related to the credential in different instances.
Persons can be managed by the IPerson
interface (Person
property of IApiSession
), credentials using the IInstance
(Instance
property of IApiSession
). Persons can basically be created like other objects but before this person can log in and actually use matterial, a new Credential
has to be stored which will send an email invitation to the respective person. Once the invitee accepts the invitation, the server creates the person’s account and the person can log in.
The regular process is to store a new credential using IInstance.StoreCredentials()
function which will then invite the person. Once the person accepts the invite, a person with account and contact information will be created automatically.
Search persons
Searching persons is a simple task: Just use the Search()
function of the IPerson
interface. It returns a SearchResult<Person>
containing the results.
private static void SearchPerson()
{
using (IApiSession apiSession = Matterial.Connect(TestCrendentials.ApiUserName, Matterial.SecureString(TestCrendentials.ApiUserPassword), TestCrendentials.Url))
{
//Let's use default options
SearchPersonParams options = new SearchPersonParams();
//Run the search
SearchResult<Person> results = apiSession.Person.Search("*", options).Result;
//Display results
foreach (var item in results.Results)
{
Console.WriteLine(item.Source.FirstName + " " + item.Source.LastName);
}
}
}
Invite a person
Inviting someone is as simple as this:
private static void InvitePerson()
{
using (IApiSession apiSession = Matterial.Connect(TestCrendentials.ApiUserName, Matterial.SecureString(TestCrendentials.ApiUserPassword), TestCrendentials.Url))
{
//The invite language
LangKey language = apiSession.DefaultContentLanguage;
//Create invite credential object
CredentialWithInvitationText invite = new CredentialWithInvitationText
{
SubscriptionEmail = "ferdinand.stapenhorst@gmail.com",
InvitationText = "Dear user. You have been invited to matterial.com. Please click the link below."
};
//Store it which will send an invite to the email address
long credentialId = apiSession.Instance.StoreCredentials(invite, language).Result;
}
}
You can use the Reinvite()
function to resend the invitation or call RemoveInvitee()
to delete the invite and cancel the whole process. If you want to check which invites are still pending, use the GetInvitees()
function.
Create a person and upload contact image
Creating a person only creates the contact information but will not create an account which means, the person will not be able to log in (see above).
private static void CreatePerson()
{
using (IApiSession apiSession = Matterial.Connect(TestCrendentials.ApiUserName, Matterial.SecureString(TestCrendentials.ApiUserPassword), TestCrendentials.Url))
{
//The person's contact image
string imagePath = @"..\..\..\ContactImage1.png";
//Create new person
Person newPerson = new Person
{
FirstName = "John",
LastName = "Dilbert",
AccountLogin = "john.dilbert@dilbert.com" //This is required when creating a new one.
};
//create the person
Person createdPerson = apiSession.Person.Create(newPerson).Result;
//Upload contact image
bool b = apiSession.Person.UploadContactImage(createdPerson.ContactId, imagePath, true).Result;
}
}
Update person’s personal data
Updating the current logged on user’s personal data, like address, communication data and contact image.
private static void UpdatePersonalData()
{
//Updates data for current logged-in account.
//Personal-data includes Addresses, CommunicationData, and ContactImages
using (IApiSession apiSession = Matterial.Connect(TestCrendentials.ApiUserName, Matterial.SecureString(TestCrendentials.ApiUserPassword), TestCrendentials.Url))
{
//Upload a new contact image and set it active
string imagePath = @"..\..\..\ContactImage1.png";
bool b = apiSession.UploadContactImage(imagePath).Result;
//Load the person object again to get the current data
Person reloadedPerson = apiSession.LoginData(true).Result.Person;
//A an postal address
reloadedPerson.Addresses.Add(new Address { City = "Dallas", Street = "1st street" });
//A an email address
reloadedPerson.CommunicationData.Add(new CommunicationData { Description = "Test email address", Value = "Test@test.de", EntityTypeId = CommunicationEntityType.Email });
//Add mobile number
reloadedPerson.CommunicationData.Add(new CommunicationData { Description = "my private mobile", Value = "212-33445566", EntityTypeId = CommunicationEntityType.Phone });
//Save
_ = apiSession.UpdatePersonalData().Result;
//Update the bio document ( in the web ui it is called Curriculum vitae)
bool b2 = apiSession.UploadBioDocument("This is the text of my CV", Matterial.DefaultLanguage).Result;
}
}
Update logged on person’s account settings
Account settings are all the settings related to the person’s account, like default content language, UI language, etc. You can find all settings in the AccountSettings
property in LoginData
(Function LoginData()
of the IApiSession
interface).
Use the UpdateAccountSettings() function to store the current logged on person’s account settings.
Note
If you want to update the current logged on person’s default UI and content language setting, simply use the properties DefaultContentLanguage
and DefaultContentLanguage
of the IApiSession
interface.
Available account settings
The currently available account settings (version 2.4) are listed below:
Key | Example value |
---|---|
accountSetting.showInvalidInitiallyOnSearch | False |
accountSetting.client | 0 |
accountSetting.desktopNotificationEnabled | True |
accountSetting.notificationEmailSetting.documentReviewRequest | 1 |
accountSetting.notificationEmailSetting.documentComment | 2 |
accountSetting.personalList.new.unreadSinceDays | 7 |
accountSetting.showArchiveInitiallyOnSearch | False |
accountSetting.language.content | de |
accountSetting.createSnap | True |
accountSetting.language.contentAll | False |
accountSetting.notificationEmailSetting.documentCommentAccountMentioned | 1 |
accountSetting.notificationEmailSetting.documentReviewed | 2 |
accountSetting.showHelpSectionDashboard | False |
accountSetting.notificationEmailSetting.documentHelpful | 2 |
accountSetting.language.ui | en |
accountSetting.notificationEmailSetting.documentDeletion | 1 |
accountSetting.sound | False |
accountSetting.notificationEmailSetting.documentReviewDeclined | 1 |
accountSetting.showCategoriesInDocumentList | False |
accountSetting.autoFollowDocuments | True |
accountSetting.notificationEmailSetting.taskRemoved | 0 |
accountSetting.notificationEmailSetting.taskChanged | 0 |
accountSetting.notificationEmailSetting.documentPublished | 0 |
accountSetting.mtrMessagesTimestampInSeconds | 1588626912 |
accountSetting.showHelpSectionDocumentEditor | False |
accountSetting.notificationEmailSetting.taskCreated | 1 |
accountSetting.disableRights | True |
private static void UpdateAccountSettings()
{
//Updates account settings for current logged-in account.
using (IApiSession apiSession = Matterial.Connect(TestCrendentials.ApiUserName, Matterial.SecureString(TestCrendentials.ApiUserPassword), TestCrendentials.Url))
{
//Make sure we have the latest version of account settimngs
LoginData loginData = apiSession.LoginData(true).Result;
//Now update some of the values in the dictionary
const string DesktopNotifyKey = "accountSetting.desktopNotificationEnabled";
const string ShowHelpOnDashboard = "accountSetting.showHelpSectionDashboard";
loginData.AccountSettings[DesktopNotifyKey] = true;
loginData.AccountSettings[ShowHelpOnDashboard] = true;
//Save the settings
apiSession.UpdateAccountSettings(loginData.AccountSettings).Wait();
}
}
Tasks
In matterial you can create document related tasks that help you top keep track of things that need to be done. But tasks are also used to manage document reviews and Knowledge Flashes. If someone not having permissions to publish documents wants to get a document published, he or she needs to “Request a Review” so that somebody who is member of the review group can review the document and publish it. When requesting review, a task can be created for the review group.
Create a task
When creating a task, the following properties must be set:
Property | Note |
---|---|
Description | The task description |
AssignedRole | The role to which the new task will be assigned to |
TaskStatusId | The status of the task. |
DocumentId | The id of the document to which the task will be related to. |
DocumentLanguageVersionId | The document language version id of the above document id. |
private static void CreateTask()
{
using (IApiSession apiSession = Matterial.Connect(TestCrendentials.ApiUserName, Matterial.SecureString(TestCrendentials.ApiUserPassword), TestCrendentials.Url))
{
//Let's assume we have a document with the id 6 and the document language version id 20
//We want to create a task that is assigned to this document
long documentId = 6;
long documentLanguageVersionId = 20;
//The following properties are the ones needed for creation
Person currentPerson = apiSession.LoggedOnPerson();
MtrTask newTask = new MtrTask
{
Description = "The task description", //The description
AssignedRole = currentPerson.RolePersonal, //The role the task will be assigned to
TaskStatusId = TaskStatusId.Open, //Status of the task
DocumentId = documentId, //Document Id
DocumentLanguageVersionId = documentLanguageVersionId //LanguageVersionId
};
//Create the task
MtrTask createdTask = apiSession.Task.Create(newTask).Result;
}
}
Licenses
The license API allows you to query the current license and see how many licenses are still available. Most other functions in the license API require the SystemAccount which is only available for On-Premises installations (private clouds).
Show license info and usage
private static void ShowLicenseInfo()
{
using (IApiSession apiSession = Matterial.Connect(TestCrendentials.ApiUserName, Matterial.SecureString(TestCrendentials.ApiUserPassword), TestCrendentials.Url))
{
License myLicense = apiSession.License.GetLicense().Result;
Console.WriteLine("Storage: " + myLicense.CasSize);
Console.WriteLine("Users: " + myLicense.User);
LicenseUsage usage = apiSession.License.GetUsage().Result;
Console.WriteLine("Used space: " + usage.CasSize);
Console.WriteLine("Licensed users: " + usage.User);
if (!(usage.InstanceActiveUntilInSeconds is null) && usage.InstanceActiveUntilInSeconds > 0)
{
Console.WriteLine("Licensed until: " + Matterial.UnixTimeStampToDateTime(usage.InstanceActiveUntilInSeconds));
}
}
}
Instances and Clients
Please Note
Most requests of this API require the SystemAccount permission which is only available for On-Premises installations (private clouds).
Instances
The Instance Management interface allows you to configure instance settings and manage/create instances. Within an instance, you can create and manage multiple clients (on premise installations only).
To manage instances, you can use the IInstance
interface (Instance
property of IApiSession
) but most of the functions require the SystemAccount which is only available for On-Premises installations.
Clients (On premise only)
A Client represents a separate set of data within the same material instance.
To manage clients, you can use the IClient
interface (Client
property of IApiSession
).
Note
In the current web user interface (version 2.4), clients are not supported.
Miscellaneous
The following other interfaces are available but are probably less interesting:
Interface | Description |
---|---|
IActivity |
Activities like document related actions, task, etc are stored in matterial and can be retrieved by this interface. |
ILanguage |
Manage languages of the system |
ITrackingItem |
Managing statistic data |