Zurück zur Übersicht » App - Development: Matterial Core C# Client Library

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):

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

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 (Instanceproperty 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 DefaultContentLanguageof 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 (Clientproperty 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